From 3d9758887b28ebd59d016b8a3ce8ac925aed2950 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Fri, 9 Jun 2023 17:12:40 +0100 Subject: [PATCH 01/22] feat: flow expansion --- .../incentive/schema/incentive.json | 41 +++ .../incentive/schema/raw/execute.json | 41 +++ .../pool-network/incentive/src/contract.rs | 5 + .../pool-network/incentive/src/error.rs | 6 + .../incentive/src/execute/expand_flow.rs | 103 ++++++ .../pool-network/incentive/src/execute/mod.rs | 2 + .../incentive/src/tests/integration.rs | 315 +++++++++++++++++- .../pool-network/incentive/src/tests/suite.rs | 24 ++ .../white-whale/src/pool_network/incentive.rs | 9 + 9 files changed, 543 insertions(+), 3 deletions(-) create mode 100644 contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs diff --git a/contracts/liquidity_hub/pool-network/incentive/schema/incentive.json b/contracts/liquidity_hub/pool-network/incentive/schema/incentive.json index 1681fb27..d7995b94 100644 --- a/contracts/liquidity_hub/pool-network/incentive/schema/incentive.json +++ b/contracts/liquidity_hub/pool-network/incentive/schema/incentive.json @@ -304,6 +304,47 @@ } }, "additionalProperties": false + }, + { + "description": "Expands an existing flow.", + "type": "object", + "required": [ + "expand_flow" + ], + "properties": { + "expand_flow": { + "type": "object", + "required": [ + "end_epoch", + "flow_asset", + "flow_id" + ], + "properties": { + "end_epoch": { + "description": "The epoch at which the flow should end.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "flow_asset": { + "description": "The asset to be expanded in this flow.", + "allOf": [ + { + "$ref": "#/definitions/Asset" + } + ] + }, + "flow_id": { + "description": "The id of the flow to expand.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { diff --git a/contracts/liquidity_hub/pool-network/incentive/schema/raw/execute.json b/contracts/liquidity_hub/pool-network/incentive/schema/raw/execute.json index 094b99c2..16bcfd3b 100644 --- a/contracts/liquidity_hub/pool-network/incentive/schema/raw/execute.json +++ b/contracts/liquidity_hub/pool-network/incentive/schema/raw/execute.json @@ -227,6 +227,47 @@ } }, "additionalProperties": false + }, + { + "description": "Expands an existing flow.", + "type": "object", + "required": [ + "expand_flow" + ], + "properties": { + "expand_flow": { + "type": "object", + "required": [ + "end_epoch", + "flow_asset", + "flow_id" + ], + "properties": { + "end_epoch": { + "description": "The epoch at which the flow should end.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "flow_asset": { + "description": "The asset to be expanded in this flow.", + "allOf": [ + { + "$ref": "#/definitions/Asset" + } + ] + }, + "flow_id": { + "description": "The id of the flow to expand.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { diff --git a/contracts/liquidity_hub/pool-network/incentive/src/contract.rs b/contracts/liquidity_hub/pool-network/incentive/src/contract.rs index eac9cf96..dff526fd 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/contract.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/contract.rs @@ -100,6 +100,11 @@ pub fn execute( } ExecuteMsg::Withdraw {} => execute::withdraw(deps, env, info), ExecuteMsg::Claim {} => execute::claim(deps, info), + ExecuteMsg::ExpandFlow { + flow_id, + end_epoch, + flow_asset, + } => execute::expand_flow(deps, info, env, flow_id, end_epoch, flow_asset), } } diff --git a/contracts/liquidity_hub/pool-network/incentive/src/error.rs b/contracts/liquidity_hub/pool-network/incentive/src/error.rs index 3282af82..bd28c2af 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/error.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/error.rs @@ -29,6 +29,9 @@ pub enum ContractError { #[error("{0}")] PaymentError(#[from] PaymentError), + #[error("Unauthorized")] + Unauthorized {}, + #[error("Attempt to migrate to version {new_version}, but contract is on a higher version {current_version}")] MigrateInvalidVersion { new_version: Version, @@ -143,6 +146,9 @@ pub enum ContractError { #[error("There're pending rewards to be claimed before you can execute this action")] PendingRewards {}, + + #[error("The end epoch for this flow is invalid")] + InvalidEndEpoch {}, } impl From for ContractError { diff --git a/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs b/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs new file mode 100644 index 00000000..048322d4 --- /dev/null +++ b/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs @@ -0,0 +1,103 @@ +use cosmwasm_std::{ + to_binary, CosmosMsg, DepsMut, Env, MessageInfo, Order, Response, StdResult, WasmMsg, +}; + +use white_whale::pool_network::asset::{Asset, AssetInfo}; +use white_whale::pool_network::incentive::Flow; + +use crate::error::ContractError; +use crate::state::{EpochId, FlowId, FLOWS}; + +pub fn expand_flow( + deps: DepsMut, + info: MessageInfo, + env: Env, + flow_id: u64, + end_epoch: u64, + flow_asset: Asset, +) -> Result { + let flow: Option<((EpochId, FlowId), Flow)> = FLOWS + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()? + .into_iter() + .find(|(_, flow)| flow.flow_id == flow_id); + + if let Some((_, mut flow)) = flow { + // validate that user is allowed to expand the flow + if flow.flow_creator != info.sender { + return Err(ContractError::Unauthorized {}); + } + + //todo check that the incentive has not finished already, otherwise the flow should just be closed. + + if flow.flow_asset.info != flow_asset.info { + return Err(ContractError::FlowAssetNotSent {}); + } + + let mut messages: Vec = vec![]; + + // validate that the flow asset is sent to the contract + match flow_asset.clone().info { + AssetInfo::Token { contract_addr } => { + let allowance: cw20::AllowanceResponse = deps.querier.query_wasm_smart( + contract_addr.clone(), + &cw20::Cw20QueryMsg::Allowance { + owner: info.sender.clone().into_string(), + spender: env.contract.address.clone().into_string(), + }, + )?; + + if allowance.allowance < flow_asset.amount { + return Err(ContractError::FlowAssetNotSent); + } + + // create the transfer message to send the flow asset to the contract + messages.push( + WasmMsg::Execute { + contract_addr, + msg: to_binary(&cw20::Cw20ExecuteMsg::TransferFrom { + owner: info.sender.into_string(), + recipient: env.contract.address.into_string(), + amount: flow_asset.amount, + })?, + funds: vec![], + } + .into(), + ); + } + AssetInfo::NativeToken { denom } => { + let paid_amount = cw_utils::must_pay(&info, &denom)?; + if paid_amount != flow_asset.amount { + return Err(ContractError::MissingPositionDepositNative { + desired_amount: flow_asset.amount, + deposited_amount: paid_amount, + }); + } + // all good, native tokens were sent + } + } + + // if the current end_epoch of this flow is greater than the new end_epoch, return error as + // it wouldn't be expanding but contracting a flow. + if flow.end_epoch > end_epoch { + return Err(ContractError::InvalidEndEpoch {}); + } + + // expand amount and end_epoch for the flow + flow.flow_asset.amount = flow.flow_asset.amount.checked_add(flow_asset.amount)?; + flow.end_epoch = end_epoch; + FLOWS.save(deps.storage, (flow.start_epoch, flow.flow_id), &flow)?; + + Ok(Response::default().add_attributes(vec![ + ("action", "expand_flow".to_string()), + ("flow_id", flow_id.to_string()), + ("end_epoch", end_epoch.to_string()), + ("expanding_flow_asset", flow_asset.to_string()), + ("total_flow_asset", flow.flow_asset.to_string()), + ])) + } else { + Err(ContractError::NonExistentFlow { + invalid_id: flow_id, + }) + } +} diff --git a/contracts/liquidity_hub/pool-network/incentive/src/execute/mod.rs b/contracts/liquidity_hub/pool-network/incentive/src/execute/mod.rs index 452096c9..40985398 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/execute/mod.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/execute/mod.rs @@ -1,6 +1,7 @@ mod claim; mod close_flow; mod close_position; +mod expand_flow; mod expand_position; mod open_flow; mod open_position; @@ -10,6 +11,7 @@ mod withdraw; pub use claim::claim; pub use close_flow::close_flow; pub use close_position::close_position; +pub use expand_flow::expand_flow; pub use expand_position::expand_position; pub use open_flow::open_flow; pub use open_position::open_position; diff --git a/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs b/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs index 1b67c727..6d4bd699 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs @@ -3378,7 +3378,7 @@ fn open_expand_close_flows_positions_and_claim_native_token_incentive() { address_weight: Uint128::new(1_000u128), share: Decimal256::from_ratio( Uint128::new(1_000u128), - Uint128::new(6_000u128) + Uint128::new(6_000u128), ), epoch_id: 16u64, } @@ -3548,7 +3548,7 @@ fn open_expand_close_flows_positions_and_claim_native_token_incentive() { address_weight: Uint128::new(1_000u128), share: Decimal256::from_ratio( Uint128::new(1_000u128), - Uint128::new(6_000u128) + Uint128::new(6_000u128), ), epoch_id: 26u64, } @@ -3783,7 +3783,7 @@ fn open_expand_close_flows_positions_and_claim_native_token_incentive() { address_weight: Uint128::new(2_000u128), share: Decimal256::from_ratio( Uint128::new(2_000u128), - Uint128::new(4_000u128) + Uint128::new(4_000u128), ), epoch_id: 27u64, } @@ -4679,3 +4679,312 @@ fn close_position_if_empty_rewards() { }, ); } + +#[test] +fn open_expand_flow_with_native_token() { + let mut suite = TestingSuite::default_with_balances(vec![ + coin(1_000_000_000u128, "uwhale".to_string()), + coin(1_000_000_000u128, "usdc".to_string()), + ]); + let alice = suite.creator(); + let carol = suite.senders[2].clone(); + + suite.instantiate_default_native_fee().create_lp_tokens(); + + let fee_collector_addr = RefCell::new(Addr::unchecked("")); + + let lp_address_1 = AssetInfo::Token { + contract_addr: suite.cw20_tokens.first().unwrap().to_string(), + }; + + let incentive_addr = RefCell::new(Addr::unchecked("")); + + suite + .create_incentive(alice.clone(), lp_address_1.clone(), |result| { + result.unwrap(); + }) + .query_incentive(lp_address_1.clone(), |result| { + let incentive = result.unwrap(); + assert!(incentive.is_some()); + *incentive_addr.borrow_mut() = incentive.unwrap(); + }); + + let current_epoch = RefCell::new(0u64); + suite + .create_epochs_on_fee_distributor(9u64, vec![]) + .query_current_epoch(|result| { + *current_epoch.borrow_mut() = result.unwrap().epoch.id.u64(); + }); + + // open incentive flow + suite + .open_incentive_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + None, + current_epoch.clone().into_inner() + 9, + Curve::Linear, + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".clone().to_string(), + }, + amount: Uint128::new(2_000u128), + }, + &vec![coin(2_000u128, "uwhale".to_string())], + |result| { + // this should succeed as we sent enough funds to cover for fee + MIN_FLOW_AMOUNT + result.unwrap(); + }, + ) + .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_creator: carol.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string() + }, + amount: Uint128::new(1_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 19u64, + emitted_tokens: Default::default(), + }) + ); + }) + .expand_flow( + alice.clone(), + incentive_addr.clone().into_inner(), + 5u64, // invalid flow id + 19u64, + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(1_000u128), + }, + coins(1_000u128, "uwhale".to_string()), + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::NonExistentFlow { invalid_id } => assert_eq!(invalid_id, 5u64), + _ => panic!("Wrong error type, should return ContractError::NonExistentFlow"), + } + }, + ) + .expand_flow( + alice.clone(), //unauthorized + incentive_addr.clone().into_inner(), + 1u64, + 19u64, + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(1_000u128), + }, + coins(1_000u128, "uwhale".to_string()), + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::Unauthorized {} => {} + _ => panic!("Wrong error type, should return ContractError::Unauthorized"), + } + }, + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 18u64, //invalid epoch + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(1_000u128), + }, + coins(1_000u128, "uwhale".to_string()), + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::InvalidEndEpoch {} => {} + _ => panic!("Wrong error type, should return ContractError::InvalidEndEpoch"), + } + }, + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 19u64, //valid epoch + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(1_000u128), + }, + vec![], //invalid funds + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::PaymentError { .. } => {} + _ => panic!("Wrong error type, should return ContractError::PaymentError"), + } + }, + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 19u64, //valid epoch + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(1_000u128), + }, + coins(500, "uwhale"), //invalid funds + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::MissingPositionDepositNative { .. } => {} + _ => panic!("Wrong error type, should return ContractError::MissingPositionDepositNative"), + } + }, + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 19u64, //valid epoch + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000u128), + }, + coins(1_000, "usdc"), //invalid funds + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::FlowAssetNotSent { .. } => {} + _ => panic!("Wrong error type, should return ContractError::FlowAssetNotSent"), + } + }, + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 19u64, //valid epoch + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(1_000u128), + }, + vec![coin(1_000, "uwhale"), coin(1_000, "usdc")], //invalid funds + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::PaymentError { .. } => {} + _ => panic!("Wrong error type, should return ContractError::PaymentError"), + } + }, + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 19u64, //valid epoch + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(1_000u128), + }, + vec![coin(1_000, "uwhale")], //valid funds + |result| { + result.unwrap(); + }, ) + .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_creator: carol.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string() + }, + amount: Uint128::new(2_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 19u64, + emitted_tokens: Default::default(), + }) + ); + }) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 20u64, //valid epoch + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(0u128), + }, + vec![coin(0, "uwhale")], //valid funds + |result| { + result.unwrap_err(); //can't send 0 coins + }, + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 30u64, //valid epoch + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(1_000u128), + }, + vec![coin(1_000u128, "uwhale")], //valid funds + |result| { + result.unwrap(); + }, + ) + .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_creator: carol.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string() + }, + amount: Uint128::new(3_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 30u64, + emitted_tokens: Default::default(), + }) + ); + }) + ; +} diff --git a/contracts/liquidity_hub/pool-network/incentive/src/tests/suite.rs b/contracts/liquidity_hub/pool-network/incentive/src/tests/suite.rs index 4f973179..63a8921d 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/tests/suite.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/tests/suite.rs @@ -562,6 +562,30 @@ impl TestingSuite { self } + + pub(crate) fn expand_flow( + &mut self, + sender: Addr, + incentive_addr: Addr, + flow_id: u64, + end_epoch: u64, + flow_asset: Asset, + funds: Vec, + result: impl Fn(Result), + ) -> &mut Self { + let msg = white_whale::pool_network::incentive::ExecuteMsg::ExpandFlow { + flow_id, + end_epoch, + flow_asset, + }; + + result( + self.app + .execute_contract(sender, incentive_addr.clone(), &msg, &funds), + ); + + self + } } /// queries diff --git a/packages/white-whale/src/pool_network/incentive.rs b/packages/white-whale/src/pool_network/incentive.rs index bfee1e73..e9b44a8a 100644 --- a/packages/white-whale/src/pool_network/incentive.rs +++ b/packages/white-whale/src/pool_network/incentive.rs @@ -72,6 +72,15 @@ pub enum ExecuteMsg { Withdraw {}, /// Claims the flow rewards. Claim {}, + /// Expands an existing flow. + ExpandFlow { + /// The id of the flow to expand. + flow_id: u64, + /// The epoch at which the flow should end. + end_epoch: u64, + /// The asset to be expanded in this flow. + flow_asset: Asset, + }, } #[cw_serde] From dfb40d782a970aa8f1a608649f313c55f7b3d2d2 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Fri, 9 Jun 2023 18:20:57 +0100 Subject: [PATCH 02/22] feat: add more tests and conditions to expand a flow --- .../pool-network/incentive/src/error.rs | 3 + .../incentive/src/execute/expand_flow.rs | 8 +- .../incentive/src/tests/integration.rs | 366 +++++++++++++++++- 3 files changed, 374 insertions(+), 3 deletions(-) diff --git a/contracts/liquidity_hub/pool-network/incentive/src/error.rs b/contracts/liquidity_hub/pool-network/incentive/src/error.rs index bd28c2af..e31c8a2e 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/error.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/error.rs @@ -149,6 +149,9 @@ pub enum ContractError { #[error("The end epoch for this flow is invalid")] InvalidEndEpoch {}, + + #[error("The flow has already ended, can't be expanded")] + FlowAlreadyEnded {}, } impl From for ContractError { diff --git a/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs b/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs index 048322d4..d864dadd 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs @@ -6,8 +6,10 @@ use white_whale::pool_network::asset::{Asset, AssetInfo}; use white_whale::pool_network::incentive::Flow; use crate::error::ContractError; +use crate::helpers; use crate::state::{EpochId, FlowId, FLOWS}; +/// Expands a flow with the given id. pub fn expand_flow( deps: DepsMut, info: MessageInfo, @@ -28,7 +30,11 @@ pub fn expand_flow( return Err(ContractError::Unauthorized {}); } - //todo check that the incentive has not finished already, otherwise the flow should just be closed. + // check if the flow has already ended + let current_epoch = helpers::get_current_epoch(deps.as_ref())?; + if current_epoch > flow.end_epoch { + return Err(ContractError::FlowAlreadyEnded {}); + } if flow.flow_asset.info != flow_asset.info { return Err(ContractError::FlowAssetNotSent {}); diff --git a/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs b/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs index 6d4bd699..41b188d4 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs @@ -4691,8 +4691,6 @@ fn open_expand_flow_with_native_token() { suite.instantiate_default_native_fee().create_lp_tokens(); - let fee_collector_addr = RefCell::new(Addr::unchecked("")); - let lp_address_1 = AssetInfo::Token { contract_addr: suite.cw20_tokens.first().unwrap().to_string(), }; @@ -4988,3 +4986,367 @@ fn open_expand_flow_with_native_token() { }) ; } + +#[test] +fn open_expand_flow_with_cw20_token() { + let mut suite = TestingSuite::default_with_balances(vec![ + coin(1_000_000_000u128, "uwhale".to_string()), + coin(1_000_000_000u128, "usdc".to_string()), + ]); + let alice = suite.creator(); + let carol = suite.senders[2].clone(); + + suite.instantiate_default_native_fee().create_lp_tokens(); + + let incentive_asset = AssetInfo::Token { + contract_addr: suite.cw20_tokens.first().unwrap().to_string(), + }; + + let incentive_addr = RefCell::new(Addr::unchecked("")); + + let flow_asset = AssetInfo::Token { + contract_addr: suite.cw20_tokens.last().unwrap().to_string(), + }; + + let flow_asset_addr = suite.cw20_tokens.last().unwrap().clone(); + + suite + .create_incentive(alice.clone(), incentive_asset.clone(), |result| { + result.unwrap(); + }) + .query_incentive(incentive_asset.clone(), |result| { + let incentive = result.unwrap(); + assert!(incentive.is_some()); + *incentive_addr.borrow_mut() = incentive.unwrap(); + }); + + let current_epoch = RefCell::new(0u64); + suite + .create_epochs_on_fee_distributor(9u64, vec![]) + .query_current_epoch(|result| { + *current_epoch.borrow_mut() = result.unwrap().epoch.id.u64(); + }); + + // open incentive flow + suite + .increase_allowance( + carol.clone(), + flow_asset_addr.clone(), + Uint128::new(2_000u128), // enough allowance + incentive_addr.clone().into_inner(), + ) + .open_incentive_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + None, + current_epoch.clone().into_inner() + 9, + Curve::Linear, + Asset { + info: flow_asset.clone(), + amount: Uint128::new(2_000u128), + }, + &vec![coin(1_000u128, "uwhale".to_string())], + |result| { + // this should succeed as we sent enough funds to cover for fee + MIN_FLOW_AMOUNT + result.unwrap(); + }, + ) + .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_creator: carol.clone(), + flow_asset: Asset { + info: flow_asset.clone(), + amount: Uint128::new(2_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 19u64, + emitted_tokens: Default::default(), + }) + ); + }) + .expand_flow( + alice.clone(), + incentive_addr.clone().into_inner(), + 5u64, // invalid flow id + 19u64, + Asset { + info: flow_asset.clone(), + amount: Uint128::new(1_000u128), + }, + coins(1_000u128, "uwhale".to_string()), + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::NonExistentFlow { invalid_id } => assert_eq!(invalid_id, 5u64), + _ => panic!("Wrong error type, should return ContractError::NonExistentFlow"), + } + }, + ) + .expand_flow( + alice.clone(), //unauthorized + incentive_addr.clone().into_inner(), + 1u64, + 19u64, + Asset { + info: flow_asset.clone(), + amount: Uint128::new(1_000u128), + }, + coins(1_000u128, "uwhale".to_string()), + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::Unauthorized {} => {} + _ => panic!("Wrong error type, should return ContractError::Unauthorized"), + } + }, + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 19u64, //valid epoch + Asset { + info: flow_asset.clone(), + amount: Uint128::new(1_000u128), + }, + vec![], //invalid funds, no allowance + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::FlowAssetNotSent { .. } => {} + _ => panic!("Wrong error type, should return ContractError::FlowAssetNotSent"), + } + }, + ) + .increase_allowance( + carol.clone(), + flow_asset_addr.clone(), + Uint128::new(500u128), // not enough allowance + incentive_addr.clone().into_inner(), + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 19u64, //valid epoch + Asset { + info: flow_asset.clone(), + amount: Uint128::new(1_000u128), + }, + vec![], //invalid funds, not enough allowance + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::FlowAssetNotSent { .. } => {} + _ => panic!("Wrong error type, should return ContractError::FlowAssetNotSent"), + } + }, + ) + .increase_allowance( + carol.clone(), + flow_asset_addr.clone(), + Uint128::new(1_000u128), // enough allowance + incentive_addr.clone().into_inner(), + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 18u64, //invalid epoch + Asset { + info: flow_asset.clone(), + amount: Uint128::new(1_000u128), + }, + vec![], + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + match err { + ContractError::InvalidEndEpoch {} => {} + _ => panic!("Wrong error type, should return ContractError::InvalidEndEpoch"), + } + }, + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 19u64, //valid epoch + Asset { + info: flow_asset.clone(), + amount: Uint128::new(1_000u128), + }, + vec![], + |result| { + result.unwrap(); + }, + ) + .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_creator: carol.clone(), + flow_asset: Asset { + info: flow_asset.clone(), + amount: Uint128::new(3_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 19u64, + emitted_tokens: Default::default(), + }) + ); + }) + .increase_allowance( + carol.clone(), + flow_asset_addr.clone(), + Uint128::new(1_000u128), // enough allowance + incentive_addr.clone().into_inner(), + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, // valid flow id + 30u64, //valid epoch + Asset { + info: flow_asset.clone(), + amount: Uint128::new(1_000u128), + }, + vec![], + |result| { + result.unwrap(); + }, + ) + .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_creator: carol.clone(), + flow_asset: Asset { + info: flow_asset.clone(), + amount: Uint128::new(4_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 30u64, + emitted_tokens: Default::default(), + }) + ); + }); +} + +#[test] +fn fail_expand_ended_flow() { + let mut suite = TestingSuite::default_with_balances(vec![ + coin(1_000_000_000u128, "uwhale".to_string()), + coin(1_000_000_000u128, "usdc".to_string()), + ]); + let alice = suite.creator(); + let carol = suite.senders[2].clone(); + + suite.instantiate_default_native_fee().create_lp_tokens(); + + let incentive_asset = AssetInfo::Token { + contract_addr: suite.cw20_tokens.first().unwrap().to_string(), + }; + + let incentive_addr = RefCell::new(Addr::unchecked("")); + + let flow_asset = AssetInfo::Token { + contract_addr: suite.cw20_tokens.last().unwrap().to_string(), + }; + + let flow_asset_addr = suite.cw20_tokens.last().unwrap().clone(); + + suite + .create_incentive(alice.clone(), incentive_asset.clone(), |result| { + result.unwrap(); + }) + .query_incentive(incentive_asset.clone(), |result| { + let incentive = result.unwrap(); + assert!(incentive.is_some()); + *incentive_addr.borrow_mut() = incentive.unwrap(); + }); + + let current_epoch = RefCell::new(0u64); + suite + .create_epochs_on_fee_distributor(9u64, vec![]) + .query_current_epoch(|result| { + *current_epoch.borrow_mut() = result.unwrap().epoch.id.u64(); + }); + + // open incentive flow + suite + .increase_allowance( + carol.clone(), + flow_asset_addr.clone(), + Uint128::new(2_000u128), // enough allowance + incentive_addr.clone().into_inner(), + ) + .open_incentive_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + None, + current_epoch.clone().into_inner() + 9, + Curve::Linear, + Asset { + info: flow_asset.clone(), + amount: Uint128::new(2_000u128), + }, + &vec![coin(1_000u128, "uwhale".to_string())], + |result| { + // this should succeed as we sent enough funds to cover for fee + MIN_FLOW_AMOUNT + result.unwrap(); + }, + ) + .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_creator: carol.clone(), + flow_asset: Asset { + info: flow_asset.clone(), + amount: Uint128::new(2_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 19u64, + emitted_tokens: Default::default(), + }) + ); + }) + .create_epochs_on_fee_distributor(20u64, vec![]) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + 1u64, + 50u64, + Asset { + info: flow_asset.clone(), + amount: Uint128::new(1_000u128), + }, + vec![], + |result| { + let err = result.unwrap_err().downcast::().unwrap(); + + match err { + ContractError::FlowAlreadyEnded {} => {} + _ => panic!("Wrong error type, should return ContractError::FlowAlreadyEnded"), + } + }, + ); +} From 33ece71874911f689efab2d4059d75957fe92de0 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Mon, 12 Jun 2023 17:10:03 +0100 Subject: [PATCH 03/22] chore: add expand flow tests --- .../incentive/src/queries/get_rewards.rs | 15 + .../incentive/src/tests/integration.rs | 635 +++++++++++++++++- 2 files changed, 649 insertions(+), 1 deletion(-) diff --git a/contracts/liquidity_hub/pool-network/incentive/src/queries/get_rewards.rs b/contracts/liquidity_hub/pool-network/incentive/src/queries/get_rewards.rs index 01316e8e..0f79a22a 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/queries/get_rewards.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/queries/get_rewards.rs @@ -21,11 +21,15 @@ pub fn get_rewards(deps: Deps, address: String) -> Result flow.end_epoch && flow.claimed_amount == flow.flow_asset.amount { @@ -46,6 +50,11 @@ pub fn get_rewards(deps: Deps, address: String) -> Result Result Result Result {:?}", current_epoch); + + let flow_end_epoch = current_epoch.clone().into_inner() + 10; + suite + .open_incentive_flow( + alice.clone(), + incentive_addr.clone().into_inner(), + None, + flow_end_epoch.clone(), + Curve::Linear, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), + }, + &vec![coin(1_000_000_000u128, "usdc"), coin(1_000u128, "uwhale")], + |result| { + result.unwrap(); + }, + ) + .create_epochs_on_fee_distributor(4, vec![incentive_addr.clone().into_inner()]) // epoch is 15 now, half way of the duration of the flow + .query_rewards( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + println!("result -> {:?}", result); + + assert_eq!( + result.unwrap().rewards, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(500_000_000u128), + },] + ); + }, + ) + .query_funds( + carol.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + *carol_usdc_funds.borrow_mut() = result; + }, + ) + .claim( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + result.unwrap(); + }, + ) + .query_funds( + carol.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + assert_eq!( + result, + carol_usdc_funds + .clone() + .into_inner() + .checked_add(Uint128::new(500_000_000u128)) + .unwrap(), + ); + *carol_usdc_funds.borrow_mut() = result; + }, + ) + .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow.unwrap().claimed_amount, + Uint128::new(500_000_000u128) + ); + }); + + // move 3 more epochs, so carol should have 300 more to claim + suite + .set_time(time.plus_seconds(129600u64)) + .create_epochs_on_fee_distributor(3, vec![incentive_addr.clone().into_inner()]) + .query_rewards( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + assert_eq!( + result.unwrap().rewards, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(300_000_000u128), + },] + ); + }, + ) + // move 2 more epochs, so carol should have an additional 200_000_000usdc to claim. + .create_epochs_on_fee_distributor(2, vec![incentive_addr.clone().into_inner()]) + .query_rewards( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + assert_eq!( + result.unwrap().rewards, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(500_000_000u128), + },] + ); + }, + ) + .claim( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + result.unwrap(); + }, + ) + .query_funds( + carol.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + assert_eq!( + result, + carol_usdc_funds + .clone() + .into_inner() + .checked_add(Uint128::new(500_000_000u128)) + .unwrap(), + ); + *carol_usdc_funds.borrow_mut() = result; + }, + ); + + // expand the flow now + suite + .expand_flow( + alice.clone(), + incentive_addr.clone().into_inner(), + 1u64, + flow_end_epoch.clone() + 20, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(4_000_000_000u128), + }, + vec![coin(4_000_000_000u128, "usdc")], + |result| { + result.unwrap(); + }, + ) + .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + let flow_response = result.unwrap(); + + let mut emitted_tokens: HashMap = HashMap::new(); + + for i in 1..=10 { + emitted_tokens.insert(i + 10, Uint128::from(100_000_000u64 * i)); + } + + let flow_res = flow_response.unwrap().flow.unwrap(); + + assert_eq!(flow_res.flow_id, 1u64); + assert_eq!(flow_res.flow_creator, alice.clone()); + assert_eq!( + flow_res.flow_asset, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(5_000_000_000u128), + } + ); + assert_eq!(flow_res.claimed_amount, Uint128::new(1_000_000_000u128)); + assert_eq!(flow_res.curve, Curve::Linear); + assert_eq!(flow_res.start_epoch, 11u64); + assert_eq!(flow_res.end_epoch, flow_end_epoch + 20); + + println!("flow finishes at: {:?}", flow_end_epoch + 20); + + let mut vec1: Vec<(&u64, &Uint128)> = emitted_tokens.iter().collect(); + let mut vec2: Vec<(&u64, &Uint128)> = flow_res.emitted_tokens.iter().collect(); + vec1.sort(); + vec2.sort(); + + assert_eq!(vec1, vec2); + }); + + suite.query_current_epoch(|result| { + *current_epoch.borrow_mut() = result.unwrap().epoch.id.u64(); + }); + + // move 1 epoch, so carol should have 200_000_000usdc to claim + suite + .create_epochs_on_fee_distributor(1, vec![incentive_addr.clone().into_inner()]) + .query_rewards( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + assert_eq!( + result.unwrap().rewards, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(200_000_000u128), + },] + ); + }, + ) + .create_epochs_on_fee_distributor(9, vec![incentive_addr.clone().into_inner()]) + .query_rewards( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + assert_eq!( + result.unwrap().rewards, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(2_000_000_000u128), + },] + ); + }, + ) + .create_epochs_on_fee_distributor(20, vec![incentive_addr.clone().into_inner()]) + .query_rewards( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + assert_eq!( + result.unwrap().rewards, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(4_000_000_000u128), + },] + ); + }, + ) + .claim( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + result.unwrap(); + }, + ) + .query_funds( + carol.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + assert_eq!( + result, + carol_usdc_funds + .clone() + .into_inner() + .checked_add(Uint128::new(4_000_000_000u128)) + .unwrap(), + ); + *carol_usdc_funds.borrow_mut() = result; + }, + ); + + // let's create a new flow and try to mess up things + suite.query_current_epoch(|result| { + *current_epoch.borrow_mut() = result.unwrap().epoch.id.u64(); + }); + + println!( + "current epoch for new flow: {:?}", + current_epoch.clone().into_inner() + ); + + let incentive_asset = AssetInfo::NativeToken { + denom: "bWHALE".to_string(), + }; + + suite + .create_incentive(alice.clone(), incentive_asset.clone(), |result| { + result.unwrap(); + }) + .query_incentive(incentive_asset.clone(), |result| { + let incentive = result.unwrap(); + assert!(incentive.is_some()); + *incentive_addr.borrow_mut() = incentive.unwrap(); + }) + .query_incentive_config(incentive_addr.clone().into_inner(), |result| { + let config = result.unwrap(); + assert_eq!(config.lp_asset, incentive_asset.clone()); + }); + + // both alice and carol will open positions + + let open_position_alice = incentive::OpenPosition { + amount: Uint128::new(3_000u128), + unbonding_duration: 86400u64, + }; + + let open_position_carol = incentive::OpenPosition { + amount: Uint128::new(1_000u128), + unbonding_duration: 86400u64, + }; + + // alice has 3_000_000_000bWHALE, carol has 1_000_000_000bWHALE + // in %, that is 75% and 25% respectively + + let flow_2_end_epoch = current_epoch.clone().into_inner() + 10; + + suite + .open_incentive_position( + carol.clone(), + incentive_addr.clone().into_inner(), + open_position_carol.amount, + open_position_carol.unbonding_duration, + None, + vec![coin(1_000u128, "bWHALE".to_string())], + |result| { + result.unwrap(); + }, + ) + .open_incentive_position( + alice.clone(), + incentive_addr.clone().into_inner(), + open_position_alice.amount, + open_position_alice.unbonding_duration, + None, + vec![coin(3_000u128, "bWHALE".to_string())], + |result| { + result.unwrap(); + }, + ) + .query_positions( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + assert_eq!( + result.unwrap().positions.first().unwrap(), + &incentive::QueryPosition::OpenPosition { + amount: Uint128::new(1_000u128), + unbonding_duration: open_position_carol.unbonding_duration, + weight: Uint128::new(1_000u128), + } + ); + }, + ) + .query_positions( + incentive_addr.clone().into_inner(), + alice.clone(), + |result| { + assert_eq!( + result.unwrap().positions.first().unwrap(), + &incentive::QueryPosition::OpenPosition { + amount: Uint128::new(3_000u128), + unbonding_duration: open_position_alice.unbonding_duration, + weight: Uint128::new(3_000u128), + } + ); + }, + ) + .open_incentive_flow( + alice.clone(), + incentive_addr.clone().into_inner(), + None, + flow_2_end_epoch.clone(), + Curve::Linear, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), + }, + &vec![coin(1_000_000_000u128, "usdc"), coin(1_000u128, "uwhale")], + |result| { + result.unwrap(); + }, + ) + .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow.unwrap().flow_asset.amount, + Uint128::new(1_000_000_000u128) + ); + }) + // epoch 52 + .create_epochs_on_fee_distributor(2, vec![incentive_addr.clone().into_inner()]) + .query_current_epoch_rewards_share( + incentive_addr.clone().into_inner(), + alice.clone(), + |result| { + let rewards_share = result.unwrap(); + assert_eq!(rewards_share.share, Decimal256::percent(75)); + }, + ) + .query_current_epoch_rewards_share( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + let rewards_share = result.unwrap(); + println!("-----query rewards next"); + assert_eq!(rewards_share.share, Decimal256::percent(25)); + }, + ) + .query_rewards( + incentive_addr.clone().into_inner(), + alice.clone(), + |result| { + assert_eq!( + result.unwrap().rewards, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(150_000_000u128), + },] + ); + }, + ) + .query_funds( + alice.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + *alice_usdc_funds.borrow_mut() = result; + }, + ) + .claim( + incentive_addr.clone().into_inner(), + alice.clone(), + |result| { + result.unwrap(); + }, + ) + .query_funds( + alice.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + assert_eq!( + result, + alice_usdc_funds + .clone() + .into_inner() + .checked_add(Uint128::new(150_000_000u128)) + .unwrap(), + ); + *alice_usdc_funds.borrow_mut() = result; + }, + ) + .query_rewards( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + assert_eq!( + result.unwrap().rewards, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(50_000_000u128), + },] + ); + }, + ); + + // let's expand the flow now + + suite + .expand_flow( + alice.clone(), + incentive_addr.clone().into_inner(), + 1u64, + flow_2_end_epoch.clone() + 10, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(4_000_000_000u128), + }, + vec![coin(4_000_000_000u128, "usdc")], + |result| { + result.unwrap(); + }, + ) + .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + let flow_response = result.unwrap(); + + let mut emitted_tokens: HashMap = HashMap::new(); + + for i in 50..=52 { + emitted_tokens.insert(i, Uint128::from(100_000_000u64 * (i - 49))); + } + + let flow_res = flow_response.unwrap().flow.unwrap(); + + assert_eq!(flow_res.flow_id, 1u64); + assert_eq!(flow_res.flow_creator, alice.clone()); + assert_eq!( + flow_res.flow_asset, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(5_000_000_000u128), + } + ); + assert_eq!(flow_res.claimed_amount, Uint128::new(150_000_000u128)); + assert_eq!(flow_res.curve, Curve::Linear); + assert_eq!(flow_res.start_epoch, 50u64); + assert_eq!(flow_res.end_epoch, flow_2_end_epoch + 10); + + println!("flow finishes at: {:?}", flow_2_end_epoch + 10); + + let mut vec1: Vec<(&u64, &Uint128)> = emitted_tokens.iter().collect(); + let mut vec2: Vec<(&u64, &Uint128)> = flow_res.emitted_tokens.iter().collect(); + + println!("vec1: {:?}", vec1); + vec1.sort(); + vec2.sort(); + + assert_eq!(vec1, vec2); + }); + + //todo we need to fix the rewards calculation for the epochs before the expansion was done + // so maybe we can do something like the expansion applies from a certain epoch onwards and compute the + // new emissions based on the total + expanded amount? + suite.query_rewards( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + assert_eq!( + result.unwrap().rewards, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(50_000_000u128), + },] + ); + }, + ); +} From c30c821e3134c6cb8439b7ad5bfbd3b4bff4a5af Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Tue, 12 Sep 2023 11:56:32 +0100 Subject: [PATCH 04/22] feat: expand flow claiming adjustment --- .../pool-network/incentive/src/claim.rs | 10 +- .../incentive/src/execute/expand_flow.rs | 35 ++++- .../incentive/src/execute/open_flow.rs | 1 + .../pool-network/incentive/src/helpers.rs | 11 ++ .../incentive/src/tests/helpers.rs | 127 ++++++++++++++++++ .../incentive/src/tests/integration.rs | 23 ++++ .../pool-network/incentive/src/tests/mod.rs | 1 + .../white-whale/src/pool_network/incentive.rs | 5 +- 8 files changed, 204 insertions(+), 9 deletions(-) create mode 100644 contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs diff --git a/contracts/liquidity_hub/pool-network/incentive/src/claim.rs b/contracts/liquidity_hub/pool-network/incentive/src/claim.rs index 199a9de1..54ae8148 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/claim.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/claim.rs @@ -5,6 +5,7 @@ use cosmwasm_std::{ use white_whale::pool_network::asset::AssetInfo; +use crate::helpers::get_flow_asset_amount_at_epoch; use crate::state::{EpochId, ADDRESS_WEIGHT_HISTORY, GLOBAL_WEIGHT_SNAPSHOT, LAST_CLAIMED_EPOCH}; use crate::{error::ContractError, helpers, state::FLOWS}; @@ -99,10 +100,11 @@ pub fn claim(deps: &mut DepsMut, info: &MessageInfo) -> Result, C previous_emission }; - // emission = (total_tokens - emitted_tokens_at_epoch) / (flow_start + flow_duration - epoch) = (total_tokens - emitted_tokens_at_epoch) / (flow_end - epoch) - let emission_per_epoch = flow - .flow_asset - .amount + // use the flow asset amount at the current epoch considering flow expansions + let flow_asset_amount = get_flow_asset_amount_at_epoch(flow, epoch_id); + + // emission = (total_tokens_for_epoch_considering_expansion - emitted_tokens_at_epoch) / (flow_start + flow_duration - epoch) = (total_tokens - emitted_tokens_at_epoch) / (flow_end - epoch) + let emission_per_epoch = flow_asset_amount .saturating_sub(emitted_tokens) .checked_div(Uint128::from(flow.end_epoch - epoch_id))?; diff --git a/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs b/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs index d864dadd..577bd952 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{ - to_binary, CosmosMsg, DepsMut, Env, MessageInfo, Order, Response, StdResult, WasmMsg, + to_binary, CosmosMsg, DepsMut, Env, MessageInfo, Order, OverflowError, OverflowOperation, + Response, StdResult, Uint128, WasmMsg, }; use white_whale::pool_network::asset::{Asset, AssetInfo}; @@ -89,17 +90,43 @@ pub fn expand_flow( return Err(ContractError::InvalidEndEpoch {}); } - // expand amount and end_epoch for the flow - flow.flow_asset.amount = flow.flow_asset.amount.checked_add(flow_asset.amount)?; + // expand amount and end_epoch for the flow. The expansion happens from the next epoch. + let next_epoch = current_epoch.checked_add(1u64).map_or_else( + || { + Err(OverflowError { + operation: OverflowOperation::Add, + operand1: current_epoch.to_string(), + operand2: 1u64.to_string(), + }) + }, + Ok, + )?; + + if let Some(existing_amount) = flow.asset_history.get_mut(&next_epoch) { + *existing_amount = existing_amount.checked_add(flow_asset.amount)?; + } else { + flow.asset_history.insert( + next_epoch, + flow.flow_asset.amount.checked_add(flow_asset.amount)?, + ); + } + flow.end_epoch = end_epoch; FLOWS.save(deps.storage, (flow.start_epoch, flow.flow_id), &flow)?; + let total_flow_asset = flow + .asset_history + .values() + .copied() + .sum::() + .checked_add(flow.flow_asset.amount)?; + Ok(Response::default().add_attributes(vec![ ("action", "expand_flow".to_string()), ("flow_id", flow_id.to_string()), ("end_epoch", end_epoch.to_string()), ("expanding_flow_asset", flow_asset.to_string()), - ("total_flow_asset", flow.flow_asset.to_string()), + ("total_flow_asset", total_flow_asset.to_string()), ])) } else { Err(ContractError::NonExistentFlow { diff --git a/contracts/liquidity_hub/pool-network/incentive/src/execute/open_flow.rs b/contracts/liquidity_hub/pool-network/incentive/src/execute/open_flow.rs index bd639b02..509fcfc1 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/execute/open_flow.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/execute/open_flow.rs @@ -356,6 +356,7 @@ pub fn open_flow( start_epoch, end_epoch, emitted_tokens: HashMap::new(), + asset_history: Default::default(), }, )?; diff --git a/contracts/liquidity_hub/pool-network/incentive/src/helpers.rs b/contracts/liquidity_hub/pool-network/incentive/src/helpers.rs index 7493fccc..9b0fcf4a 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/helpers.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/helpers.rs @@ -58,3 +58,14 @@ pub fn delete_weight_history_for_user( }); Ok(()) } + +/// Gets the flow asset amount for a given epoch, taking into account the asset history, i.e. flow expansion. +pub fn get_flow_asset_amount_at_epoch(flow: &Flow, epoch: u64) -> Uint128 { + let mut asset_amount = flow.flow_asset.amount; + + if let Some((_, &change_amount)) = flow.asset_history.range(..=epoch).rev().next() { + asset_amount = change_amount; + } + + asset_amount +} diff --git a/contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs b/contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs new file mode 100644 index 00000000..260650ab --- /dev/null +++ b/contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs @@ -0,0 +1,127 @@ +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers::get_flow_asset_amount_at_epoch; + use cosmwasm_std::{Addr, Uint128}; + use std::collections::{BTreeMap, HashMap}; + use white_whale::pool_network::asset::{Asset, AssetInfo}; + use white_whale::pool_network::incentive::{Curve, Flow}; + + #[test] + fn test_get_flow_asset_amount_at_epoch_with_expansion() { + let mut asset_history = BTreeMap::new(); + asset_history.insert(0, Uint128::from(10000u128)); + asset_history.insert(7, Uint128::from(20000u128)); + asset_history.insert(10, Uint128::from(50000u128)); + + let flow = Flow { + flow_id: 1, + flow_creator: Addr::unchecked("creator"), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::from(10000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 0, + end_epoch: 100, + emitted_tokens: HashMap::new(), + asset_history, + }; + + // Before any change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 0), + Uint128::from(10000u128) + ); + + // After first change but before second change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 6), + Uint128::from(10000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 7), + Uint128::from(20000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 9), + Uint128::from(20000u128) + ); + + // After second change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 10), + Uint128::from(50000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 11), + Uint128::from(50000u128) + ); + + // After the end epoch + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 101), + Uint128::from(50000u128) + ); + } + #[test] + fn test_get_flow_asset_amount_at_epoch_without_expansion() { + let mut asset_history = BTreeMap::new(); + + let flow = Flow { + flow_id: 1, + flow_creator: Addr::unchecked("creator"), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::from(10000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 0, + end_epoch: 100, + emitted_tokens: HashMap::new(), + asset_history, + }; + + // Before any change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 0), + Uint128::from(10000u128) + ); + + // After first change but before second change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 6), + Uint128::from(10000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 7), + Uint128::from(10000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 9), + Uint128::from(10000u128) + ); + + // After second change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 10), + Uint128::from(10000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 11), + Uint128::from(10000u128) + ); + + // After the end epoch + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 101), + Uint128::from(10000u128) + ); + } +} diff --git a/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs b/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs index 04413c64..baa5a32f 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs @@ -327,6 +327,7 @@ fn try_open_more_flows_than_allowed() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), } ); assert_eq!( @@ -345,6 +346,7 @@ fn try_open_more_flows_than_allowed() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), } ); }); @@ -667,6 +669,7 @@ fn open_flow_with_fee_native_token_and_flow_same_native_token() { start_epoch: 10u64, end_epoch: 19u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }) @@ -893,6 +896,7 @@ fn open_flow_with_fee_native_token_and_flow_different_native_token() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }) @@ -1108,6 +1112,7 @@ fn open_flow_with_fee_native_token_and_flow_cw20_token() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }); @@ -1284,6 +1289,7 @@ fn open_flow_with_fee_cw20_token_and_flow_same_cw20_token() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }); @@ -1504,6 +1510,7 @@ fn open_flow_with_fee_cw20_token_and_flow_different_cw20_token() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }) @@ -1755,6 +1762,7 @@ fn open_flow_with_fee_cw20_token_and_flow_native_token() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }) @@ -1856,6 +1864,7 @@ fn close_native_token_flows() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), } ); assert_eq!( @@ -1874,6 +1883,7 @@ fn close_native_token_flows() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), } ); }) @@ -1948,6 +1958,7 @@ fn close_native_token_flows() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), } ); }) @@ -2053,6 +2064,7 @@ fn close_native_token_flows() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), } ); }); @@ -2159,6 +2171,7 @@ fn close_cw20_token_flows() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), } ); assert_eq!( @@ -2175,6 +2188,7 @@ fn close_cw20_token_flows() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), } ); }) @@ -2241,6 +2255,7 @@ fn close_cw20_token_flows() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), } ); }) @@ -2330,6 +2345,7 @@ fn close_cw20_token_flows() { start_epoch: 1u64, end_epoch: 10u64, emitted_tokens: Default::default(), + asset_history: Default::default(), } ); }); @@ -4753,6 +4769,7 @@ fn open_expand_flow_with_native_token() { start_epoch: 10u64, end_epoch: 19u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }) @@ -4929,6 +4946,7 @@ fn open_expand_flow_with_native_token() { start_epoch: 10u64, end_epoch: 19u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }) @@ -4982,6 +5000,7 @@ fn open_expand_flow_with_native_token() { start_epoch: 10u64, end_epoch: 30u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }) @@ -5068,6 +5087,7 @@ fn open_expand_flow_with_cw20_token() { start_epoch: 10u64, end_epoch: 19u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }) @@ -5203,6 +5223,7 @@ fn open_expand_flow_with_cw20_token() { start_epoch: 10u64, end_epoch: 19u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }) @@ -5242,6 +5263,7 @@ fn open_expand_flow_with_cw20_token() { start_epoch: 10u64, end_epoch: 30u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }); @@ -5327,6 +5349,7 @@ fn fail_expand_ended_flow() { start_epoch: 10u64, end_epoch: 19u64, emitted_tokens: Default::default(), + asset_history: Default::default(), }) ); }) diff --git a/contracts/liquidity_hub/pool-network/incentive/src/tests/mod.rs b/contracts/liquidity_hub/pool-network/incentive/src/tests/mod.rs index dbcda28d..f662e117 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/tests/mod.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/tests/mod.rs @@ -1,3 +1,4 @@ +mod helpers; #[allow(non_snake_case)] #[allow(dead_code)] mod integration; diff --git a/packages/white-whale/src/pool_network/incentive.rs b/packages/white-whale/src/pool_network/incentive.rs index e9b44a8a..97763034 100644 --- a/packages/white-whale/src/pool_network/incentive.rs +++ b/packages/white-whale/src/pool_network/incentive.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Decimal256, Uint128}; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fmt; use crate::pool_network::asset::{Asset, AssetInfo}; @@ -105,6 +105,9 @@ pub struct Flow { pub end_epoch: u64, /// emitted tokens pub emitted_tokens: HashMap, + /// A map containing the amount of tokens it was expanded to at a given epoch. This is used + /// to calculate the right amount of tokens to distribute at a given epoch when a flow is expanded. + pub asset_history: BTreeMap, } /// Represents a position that accumulates flow rewards. From 29eff846ffbd481b8242e41246e42a8d222b2d7a Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Wed, 20 Sep 2023 15:42:54 +0100 Subject: [PATCH 05/22] feat: make flow expandable, add tests --- Cargo.lock | 2 +- .../pool-network/incentive/Cargo.toml | 2 +- .../incentive/schema/incentive.json | 231 +- .../incentive/schema/raw/execute.json | 93 +- .../incentive/schema/raw/query.json | 84 +- .../schema/raw/response_to_flow.json | 27 + .../schema/raw/response_to_flows.json | 27 + .../pool-network/incentive/src/claim.rs | 30 +- .../pool-network/incentive/src/contract.rs | 51 +- .../pool-network/incentive/src/error.rs | 9 +- .../incentive/src/execute/close_flow.rs | 16 +- .../incentive/src/execute/expand_flow.rs | 72 +- .../incentive/src/execute/open_flow.rs | 44 +- .../pool-network/incentive/src/helpers.rs | 55 +- .../pool-network/incentive/src/migrations.rs | 21 + .../incentive/src/queries/get_flow.rs | 25 +- .../incentive/src/queries/get_flows.rs | 13 +- .../incentive/src/queries/get_rewards.rs | 39 +- .../incentive/src/tests/helpers.rs | 11 +- .../incentive/src/tests/integration.rs | 2058 ++++++++++------- .../pool-network/incentive/src/tests/suite.rs | 35 +- .../white-whale/src/pool_network/incentive.rs | 78 +- 22 files changed, 1986 insertions(+), 1037 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71a7e425..e11160aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -661,7 +661,7 @@ dependencies = [ [[package]] name = "incentive" -version = "1.0.3" +version = "1.0.6" dependencies = [ "anyhow", "cosmwasm-schema", diff --git a/contracts/liquidity_hub/pool-network/incentive/Cargo.toml b/contracts/liquidity_hub/pool-network/incentive/Cargo.toml index b3d05b9a..37b7d9b9 100644 --- a/contracts/liquidity_hub/pool-network/incentive/Cargo.toml +++ b/contracts/liquidity_hub/pool-network/incentive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "incentive" -version = "1.0.3" +version = "1.0.6" authors = ["kaimen-sano "] edition.workspace = true description = "An incentive manager for an LP token" diff --git a/contracts/liquidity_hub/pool-network/incentive/schema/incentive.json b/contracts/liquidity_hub/pool-network/incentive/schema/incentive.json index d7995b94..87061140 100644 --- a/contracts/liquidity_hub/pool-network/incentive/schema/incentive.json +++ b/contracts/liquidity_hub/pool-network/incentive/schema/incentive.json @@ -103,22 +103,26 @@ "open_flow": { "type": "object", "required": [ - "curve", - "end_epoch", "flow_asset" ], "properties": { "curve": { - "description": "The type of distribution curve.", - "allOf": [ + "description": "The type of distribution curve. If unspecified, the distribution will be linear.", + "anyOf": [ { "$ref": "#/definitions/Curve" + }, + { + "type": "null" } ] }, "end_epoch": { - "description": "The epoch at which the flow should end.", - "type": "integer", + "description": "The epoch at which the flow should end. If unspecified, the flow will default to end at 14 epochs from the current one.", + "type": [ + "integer", + "null" + ], "format": "uint64", "minimum": 0.0 }, @@ -130,8 +134,15 @@ } ] }, + "flow_label": { + "description": "If set, the label will be used to identify the flow, in addition to the flow_id.", + "type": [ + "string", + "null" + ] + }, "start_epoch": { - "description": "The epoch at which the flow should start.\n\nIf unspecified, the flow will start at the current epoch.", + "description": "The epoch at which the flow will start. If unspecified, the flow will start at the current epoch.", "type": [ "integer", "null" @@ -155,14 +166,16 @@ "close_flow": { "type": "object", "required": [ - "flow_id" + "flow_identifier" ], "properties": { - "flow_id": { - "description": "The id of the flow to close.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 + "flow_identifier": { + "description": "The identifier of the flow to close.", + "allOf": [ + { + "$ref": "#/definitions/FlowIdentifier" + } + ] } }, "additionalProperties": false @@ -315,30 +328,34 @@ "expand_flow": { "type": "object", "required": [ - "end_epoch", "flow_asset", - "flow_id" + "flow_identifier" ], "properties": { "end_epoch": { - "description": "The epoch at which the flow should end.", - "type": "integer", + "description": "The epoch at which the flow should end. If not set, the flow will be expanded a default value of 14 epochs.", + "type": [ + "integer", + "null" + ], "format": "uint64", "minimum": 0.0 }, "flow_asset": { - "description": "The asset to be expanded in this flow.", + "description": "The asset to expand this flow with.", "allOf": [ { "$ref": "#/definitions/Asset" } ] }, - "flow_id": { - "description": "The id of the flow to expand.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 + "flow_identifier": { + "description": "The identifier of the flow to expand, whether an id or a label.", + "allOf": [ + { + "$ref": "#/definitions/FlowIdentifier" + } + ] } }, "additionalProperties": false @@ -423,6 +440,36 @@ } ] }, + "FlowIdentifier": { + "oneOf": [ + { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -448,7 +495,7 @@ "additionalProperties": false }, { - "description": "Retrieves a specific flow.", + "description": "Retrieves a specific flow. If start_epoch and end_epoch are set, the asset_history and emitted_tokens will be filtered to only include epochs within the range. The maximum gap between the start_epoch and end_epoch is 100 epochs.", "type": "object", "required": [ "flow" @@ -457,12 +504,32 @@ "flow": { "type": "object", "required": [ - "flow_id" + "flow_identifier" ], "properties": { - "flow_id": { + "end_epoch": { + "description": "If set, filters the asset_history and emitted_tokens to only include epochs until end_epoch.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "flow_identifier": { "description": "The id of the flow to find.", - "type": "integer", + "allOf": [ + { + "$ref": "#/definitions/FlowIdentifier" + } + ] + }, + "start_epoch": { + "description": "If set, filters the asset_history and emitted_tokens to only include epochs from start_epoch.", + "type": [ + "integer", + "null" + ], "format": "uint64", "minimum": 0.0 } @@ -473,7 +540,7 @@ "additionalProperties": false }, { - "description": "Retrieves the current flows.", + "description": "Retrieves the current flows. If start_epoch and end_epoch are set, the asset_history and emitted_tokens will be filtered to only include epochs within the range. The maximum gap between the start_epoch and end_epoch is 100 epochs.", "type": "object", "required": [ "flows" @@ -481,6 +548,26 @@ "properties": { "flows": { "type": "object", + "properties": { + "end_epoch": { + "description": "If set, filters the asset_history and emitted_tokens to only include epochs until end_epoch.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "start_epoch": { + "description": "If set, filters the asset_history and emitted_tokens to only include epochs from start_epoch.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, "additionalProperties": false } }, @@ -580,7 +667,39 @@ }, "additionalProperties": false } - ] + ], + "definitions": { + "FlowIdentifier": { + "oneOf": [ + { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + } }, "migrate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -829,6 +948,7 @@ "description": "Represents a flow.", "type": "object", "required": [ + "asset_history", "claimed_amount", "curve", "emitted_tokens", @@ -839,6 +959,25 @@ "start_epoch" ], "properties": { + "asset_history": { + "description": "A map containing the amount of tokens it was expanded to at a given epoch. This is used to calculate the right amount of tokens to distribute at a given epoch when a flow is expanded.", + "type": "object", + "additionalProperties": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + ], + "maxItems": 2, + "minItems": 2 + } + }, "claimed_amount": { "description": "The amount of the `flow_asset` that has been claimed so far.", "allOf": [ @@ -890,6 +1029,13 @@ "format": "uint64", "minimum": 0.0 }, + "flow_label": { + "description": "An alternative flow label.", + "type": [ + "string", + "null" + ] + }, "start_epoch": { "description": "The epoch at which the flow starts.", "type": "integer", @@ -1006,6 +1152,7 @@ "description": "Represents a flow.", "type": "object", "required": [ + "asset_history", "claimed_amount", "curve", "emitted_tokens", @@ -1016,6 +1163,25 @@ "start_epoch" ], "properties": { + "asset_history": { + "description": "A map containing the amount of tokens it was expanded to at a given epoch. This is used to calculate the right amount of tokens to distribute at a given epoch when a flow is expanded.", + "type": "object", + "additionalProperties": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + ], + "maxItems": 2, + "minItems": 2 + } + }, "claimed_amount": { "description": "The amount of the `flow_asset` that has been claimed so far.", "allOf": [ @@ -1067,6 +1233,13 @@ "format": "uint64", "minimum": 0.0 }, + "flow_label": { + "description": "An alternative flow label.", + "type": [ + "string", + "null" + ] + }, "start_epoch": { "description": "The epoch at which the flow starts.", "type": "integer", diff --git a/contracts/liquidity_hub/pool-network/incentive/schema/raw/execute.json b/contracts/liquidity_hub/pool-network/incentive/schema/raw/execute.json index 16bcfd3b..320b66a1 100644 --- a/contracts/liquidity_hub/pool-network/incentive/schema/raw/execute.json +++ b/contracts/liquidity_hub/pool-network/incentive/schema/raw/execute.json @@ -26,22 +26,26 @@ "open_flow": { "type": "object", "required": [ - "curve", - "end_epoch", "flow_asset" ], "properties": { "curve": { - "description": "The type of distribution curve.", - "allOf": [ + "description": "The type of distribution curve. If unspecified, the distribution will be linear.", + "anyOf": [ { "$ref": "#/definitions/Curve" + }, + { + "type": "null" } ] }, "end_epoch": { - "description": "The epoch at which the flow should end.", - "type": "integer", + "description": "The epoch at which the flow should end. If unspecified, the flow will default to end at 14 epochs from the current one.", + "type": [ + "integer", + "null" + ], "format": "uint64", "minimum": 0.0 }, @@ -53,8 +57,15 @@ } ] }, + "flow_label": { + "description": "If set, the label will be used to identify the flow, in addition to the flow_id.", + "type": [ + "string", + "null" + ] + }, "start_epoch": { - "description": "The epoch at which the flow should start.\n\nIf unspecified, the flow will start at the current epoch.", + "description": "The epoch at which the flow will start. If unspecified, the flow will start at the current epoch.", "type": [ "integer", "null" @@ -78,14 +89,16 @@ "close_flow": { "type": "object", "required": [ - "flow_id" + "flow_identifier" ], "properties": { - "flow_id": { - "description": "The id of the flow to close.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 + "flow_identifier": { + "description": "The identifier of the flow to close.", + "allOf": [ + { + "$ref": "#/definitions/FlowIdentifier" + } + ] } }, "additionalProperties": false @@ -238,30 +251,34 @@ "expand_flow": { "type": "object", "required": [ - "end_epoch", "flow_asset", - "flow_id" + "flow_identifier" ], "properties": { "end_epoch": { - "description": "The epoch at which the flow should end.", - "type": "integer", + "description": "The epoch at which the flow should end. If not set, the flow will be expanded a default value of 14 epochs.", + "type": [ + "integer", + "null" + ], "format": "uint64", "minimum": 0.0 }, "flow_asset": { - "description": "The asset to be expanded in this flow.", + "description": "The asset to expand this flow with.", "allOf": [ { "$ref": "#/definitions/Asset" } ] }, - "flow_id": { - "description": "The id of the flow to expand.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 + "flow_identifier": { + "description": "The identifier of the flow to expand, whether an id or a label.", + "allOf": [ + { + "$ref": "#/definitions/FlowIdentifier" + } + ] } }, "additionalProperties": false @@ -346,6 +363,36 @@ } ] }, + "FlowIdentifier": { + "oneOf": [ + { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/contracts/liquidity_hub/pool-network/incentive/schema/raw/query.json b/contracts/liquidity_hub/pool-network/incentive/schema/raw/query.json index f1bd81e1..bdd4caf0 100644 --- a/contracts/liquidity_hub/pool-network/incentive/schema/raw/query.json +++ b/contracts/liquidity_hub/pool-network/incentive/schema/raw/query.json @@ -17,7 +17,7 @@ "additionalProperties": false }, { - "description": "Retrieves a specific flow.", + "description": "Retrieves a specific flow. If start_epoch and end_epoch are set, the asset_history and emitted_tokens will be filtered to only include epochs within the range. The maximum gap between the start_epoch and end_epoch is 100 epochs.", "type": "object", "required": [ "flow" @@ -26,12 +26,32 @@ "flow": { "type": "object", "required": [ - "flow_id" + "flow_identifier" ], "properties": { - "flow_id": { + "end_epoch": { + "description": "If set, filters the asset_history and emitted_tokens to only include epochs until end_epoch.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "flow_identifier": { "description": "The id of the flow to find.", - "type": "integer", + "allOf": [ + { + "$ref": "#/definitions/FlowIdentifier" + } + ] + }, + "start_epoch": { + "description": "If set, filters the asset_history and emitted_tokens to only include epochs from start_epoch.", + "type": [ + "integer", + "null" + ], "format": "uint64", "minimum": 0.0 } @@ -42,7 +62,7 @@ "additionalProperties": false }, { - "description": "Retrieves the current flows.", + "description": "Retrieves the current flows. If start_epoch and end_epoch are set, the asset_history and emitted_tokens will be filtered to only include epochs within the range. The maximum gap between the start_epoch and end_epoch is 100 epochs.", "type": "object", "required": [ "flows" @@ -50,6 +70,26 @@ "properties": { "flows": { "type": "object", + "properties": { + "end_epoch": { + "description": "If set, filters the asset_history and emitted_tokens to only include epochs until end_epoch.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "start_epoch": { + "description": "If set, filters the asset_history and emitted_tokens to only include epochs from start_epoch.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, "additionalProperties": false } }, @@ -149,5 +189,37 @@ }, "additionalProperties": false } - ] + ], + "definitions": { + "FlowIdentifier": { + "oneOf": [ + { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + } } diff --git a/contracts/liquidity_hub/pool-network/incentive/schema/raw/response_to_flow.json b/contracts/liquidity_hub/pool-network/incentive/schema/raw/response_to_flow.json index 56a433e3..09990c47 100644 --- a/contracts/liquidity_hub/pool-network/incentive/schema/raw/response_to_flow.json +++ b/contracts/liquidity_hub/pool-network/incentive/schema/raw/response_to_flow.json @@ -100,6 +100,7 @@ "description": "Represents a flow.", "type": "object", "required": [ + "asset_history", "claimed_amount", "curve", "emitted_tokens", @@ -110,6 +111,25 @@ "start_epoch" ], "properties": { + "asset_history": { + "description": "A map containing the amount of tokens it was expanded to at a given epoch. This is used to calculate the right amount of tokens to distribute at a given epoch when a flow is expanded.", + "type": "object", + "additionalProperties": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + ], + "maxItems": 2, + "minItems": 2 + } + }, "claimed_amount": { "description": "The amount of the `flow_asset` that has been claimed so far.", "allOf": [ @@ -161,6 +181,13 @@ "format": "uint64", "minimum": 0.0 }, + "flow_label": { + "description": "An alternative flow label.", + "type": [ + "string", + "null" + ] + }, "start_epoch": { "description": "The epoch at which the flow starts.", "type": "integer", diff --git a/contracts/liquidity_hub/pool-network/incentive/schema/raw/response_to_flows.json b/contracts/liquidity_hub/pool-network/incentive/schema/raw/response_to_flows.json index dacb6b25..bf2f9848 100644 --- a/contracts/liquidity_hub/pool-network/incentive/schema/raw/response_to_flows.json +++ b/contracts/liquidity_hub/pool-network/incentive/schema/raw/response_to_flows.json @@ -99,6 +99,7 @@ "description": "Represents a flow.", "type": "object", "required": [ + "asset_history", "claimed_amount", "curve", "emitted_tokens", @@ -109,6 +110,25 @@ "start_epoch" ], "properties": { + "asset_history": { + "description": "A map containing the amount of tokens it was expanded to at a given epoch. This is used to calculate the right amount of tokens to distribute at a given epoch when a flow is expanded.", + "type": "object", + "additionalProperties": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + ], + "maxItems": 2, + "minItems": 2 + } + }, "claimed_amount": { "description": "The amount of the `flow_asset` that has been claimed so far.", "allOf": [ @@ -160,6 +180,13 @@ "format": "uint64", "minimum": 0.0 }, + "flow_label": { + "description": "An alternative flow label.", + "type": [ + "string", + "null" + ] + }, "start_epoch": { "description": "The epoch at which the flow starts.", "type": "integer", diff --git a/contracts/liquidity_hub/pool-network/incentive/src/claim.rs b/contracts/liquidity_hub/pool-network/incentive/src/claim.rs index 54ae8148..9ba23587 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/claim.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/claim.rs @@ -5,12 +5,14 @@ use cosmwasm_std::{ use white_whale::pool_network::asset::AssetInfo; -use crate::helpers::get_flow_asset_amount_at_epoch; +use crate::helpers::{get_flow_asset_amount_at_epoch, get_flow_current_end_epoch}; use crate::state::{EpochId, ADDRESS_WEIGHT_HISTORY, GLOBAL_WEIGHT_SNAPSHOT, LAST_CLAIMED_EPOCH}; use crate::{error::ContractError, helpers, state::FLOWS}; //todo abstract code in this function as most of it is also used in get_rewards.rs +pub const EPOCH_CLAIM_CAP: u64 = 100u64; + #[allow(unused_assignments)] /// Performs the claim function, returning all the [`CosmosMsg`]'s to run. pub fn claim(deps: &mut DepsMut, info: &MessageInfo) -> Result, ContractError> { @@ -35,8 +37,15 @@ pub fn claim(deps: &mut DepsMut, info: &MessageInfo) -> Result, C let mut last_user_weight_seen: Uint128 = Uint128::zero(); //let mut last_user_weight_seen: (EpochId, Uint128) = (064, Uint128::zero()); for flow in flows.iter_mut() { + let expanded_default_values = (flow.flow_asset.amount, flow.end_epoch); + + let (_, (expanded_asset_amount, expanded_end_epoch)) = flow + .asset_history + .last_key_value() + .unwrap_or((&0u64, &expanded_default_values)); + // check if flow already ended and if everything has been claimed for that flow. - if current_epoch > flow.end_epoch && flow.claimed_amount == flow.flow_asset.amount { + if current_epoch > *expanded_end_epoch && flow.claimed_amount == expanded_asset_amount { // if so, skip flow. continue; } @@ -69,13 +78,21 @@ pub fn claim(deps: &mut DepsMut, info: &MessageInfo) -> Result, C } }; + let mut epoch_count = 0; + // calculate the total reward for this flow, from the first claimable epoch to the current epoch for epoch_id in first_claimable_epoch..=current_epoch { + epoch_count += 1; + + if epoch_count > EPOCH_CLAIM_CAP { + break; + } + // check if the flow is active in this epoch if epoch_id < flow.start_epoch { // the flow is not active yet, skip continue; - } else if epoch_id >= flow.end_epoch { + } else if epoch_id >= *expanded_end_epoch { // this flow has finished // todo maybe we should make end_epoch inclusive? break; @@ -94,7 +111,7 @@ pub fn claim(deps: &mut DepsMut, info: &MessageInfo) -> Result, C // statement above is true. let previous_emission = *flow .emitted_tokens - .get(&(epoch_id - 1u64)) + .get(&(epoch_id.saturating_sub(1u64))) .unwrap_or(&Uint128::zero()); previous_emission @@ -102,11 +119,12 @@ pub fn claim(deps: &mut DepsMut, info: &MessageInfo) -> Result, C // use the flow asset amount at the current epoch considering flow expansions let flow_asset_amount = get_flow_asset_amount_at_epoch(flow, epoch_id); + let flow_expanded_end_epoch = get_flow_current_end_epoch(flow, epoch_id); // emission = (total_tokens_for_epoch_considering_expansion - emitted_tokens_at_epoch) / (flow_start + flow_duration - epoch) = (total_tokens - emitted_tokens_at_epoch) / (flow_end - epoch) let emission_per_epoch = flow_asset_amount .saturating_sub(emitted_tokens) - .checked_div(Uint128::from(flow.end_epoch - epoch_id))?; + .checked_div(Uint128::from(flow_expanded_end_epoch - epoch_id))?; // record the emitted tokens for this epoch if it hasn't been recorded before. // emitted tokens for this epoch is the total emitted tokens in previous epoch + the ones @@ -157,7 +175,7 @@ pub fn claim(deps: &mut DepsMut, info: &MessageInfo) -> Result, C // sanity check for user_reward_at_epoch if user_reward_at_epoch > emission_per_epoch - || user_reward_at_epoch.checked_add(flow.claimed_amount)? > flow.flow_asset.amount + || user_reward_at_epoch.checked_add(flow.claimed_amount)? > *expanded_asset_amount { return Err(ContractError::InvalidReward {}); } diff --git a/contracts/liquidity_hub/pool-network/incentive/src/contract.rs b/contracts/liquidity_hub/pool-network/incentive/src/contract.rs index dff526fd..8dde6792 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/contract.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/contract.rs @@ -9,12 +9,13 @@ use white_whale::pool_network::incentive::{ }; use semver::Version; +use white_whale::migrate_guards::check_contract_name; use white_whale::pool_network::asset::AssetInfo; use crate::error::ContractError; use crate::error::ContractError::MigrateInvalidVersion; use crate::state::{CONFIG, FLOW_COUNTER, GLOBAL_WEIGHT}; -use crate::{execute, queries}; +use crate::{execute, migrations, queries}; // version info for migration info const CONTRACT_NAME: &str = "white_whale-incentive"; @@ -83,8 +84,20 @@ pub fn execute( end_epoch, curve, flow_asset, - } => execute::open_flow(deps, env, info, start_epoch, end_epoch, curve, flow_asset), - ExecuteMsg::CloseFlow { flow_id } => execute::close_flow(deps, info, flow_id), + flow_label, + } => execute::open_flow( + deps, + env, + info, + start_epoch, + end_epoch, + curve, + flow_asset, + flow_label, + ), + ExecuteMsg::CloseFlow { flow_identifier } => { + execute::close_flow(deps, info, flow_identifier) + } ExecuteMsg::OpenPosition { amount, unbonding_duration, @@ -101,10 +114,10 @@ pub fn execute( ExecuteMsg::Withdraw {} => execute::withdraw(deps, env, info), ExecuteMsg::Claim {} => execute::claim(deps, info), ExecuteMsg::ExpandFlow { - flow_id, + flow_identifier, end_epoch, flow_asset, - } => execute::expand_flow(deps, info, env, flow_id, end_epoch, flow_asset), + } => execute::expand_flow(deps, info, env, flow_identifier, end_epoch, flow_asset), } } @@ -113,8 +126,24 @@ pub fn execute( pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { match msg { QueryMsg::Config {} => Ok(to_binary(&queries::get_config(deps)?)?), - QueryMsg::Flow { flow_id } => Ok(to_binary(&queries::get_flow(deps, flow_id)?)?), - QueryMsg::Flows {} => Ok(to_binary(&queries::get_flows(deps)?)?), + QueryMsg::Flow { + flow_identifier, + start_epoch, + end_epoch, + } => Ok(to_binary(&queries::get_flow( + deps, + flow_identifier, + start_epoch, + end_epoch, + )?)?), + QueryMsg::Flows { + start_epoch, + end_epoch, + } => Ok(to_binary(&queries::get_flows( + deps, + start_epoch, + end_epoch, + )?)?), QueryMsg::Positions { address } => { Ok(to_binary(&queries::get_positions(deps, env, address)?)?) } @@ -130,7 +159,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result Result { +pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + check_contract_name(deps.storage, CONTRACT_NAME.to_string())?; + let version: Version = CONTRACT_VERSION.parse()?; let storage_version: Version = get_contract_version(deps.storage)?.version.parse()?; @@ -141,6 +172,10 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result Result { // validate that user is allowed to close the flow let config = CONFIG.load(deps.storage)?; @@ -27,14 +28,17 @@ pub fn close_flow( .range(deps.storage, None, None, Order::Ascending) .collect::>>()? .into_iter() - .find(|(_, flow)| flow.flow_id == flow_id) + .find(|(_, flow)| match &flow_identifier.clone() { + FlowIdentifier::Id(id) => flow.flow_id == *id, + FlowIdentifier::Label(label) => flow.flow_label.as_ref() == Some(label), + }) .ok_or(ContractError::NonExistentFlow { - invalid_id: flow_id, + invalid_identifier: flow_identifier.clone(), }) .map(|(_, flow)| flow)?; if !(flow.flow_creator == info.sender || info.sender == factory_config.owner) { - return Err(ContractError::UnauthorizedFlowClose { flow_id }); + return Err(ContractError::UnauthorizedFlowClose { flow_identifier }); } let amount_to_return = flow.flow_asset.amount.saturating_sub(flow.claimed_amount); @@ -63,7 +67,7 @@ pub fn close_flow( Ok(Response::default() .add_attributes(vec![ ("action", "close_flow".to_string()), - ("flow_id", flow_id.to_string()), + ("flow_identifier", flow_identifier.to_string()), ]) .add_messages(messages)) } diff --git a/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs b/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs index 577bd952..ca16abdd 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/execute/expand_flow.rs @@ -4,36 +4,42 @@ use cosmwasm_std::{ }; use white_whale::pool_network::asset::{Asset, AssetInfo}; -use white_whale::pool_network::incentive::Flow; +use white_whale::pool_network::incentive::{Flow, FlowIdentifier}; use crate::error::ContractError; +use crate::execute::open_flow::DEFAULT_FLOW_DURATION; use crate::helpers; +use crate::helpers::{get_flow_asset_amount_at_epoch, get_flow_end_epoch}; use crate::state::{EpochId, FlowId, FLOWS}; -/// Expands a flow with the given id. +// If the end_epoch is not specified, the flow will be expanded by DEFAULT_FLOW_DURATION when +// the current epoch is within FLOW_EXPANSION_BUFFER epochs from the end_epoch. +const FLOW_EXPANSION_BUFFER: u64 = 5u64; + +/// Expands a flow with the given id. Can be done by anyone. pub fn expand_flow( deps: DepsMut, info: MessageInfo, env: Env, - flow_id: u64, - end_epoch: u64, + flow_identifier: FlowIdentifier, + end_epoch: Option, flow_asset: Asset, ) -> Result { let flow: Option<((EpochId, FlowId), Flow)> = FLOWS .range(deps.storage, None, None, Order::Ascending) .collect::>>()? .into_iter() - .find(|(_, flow)| flow.flow_id == flow_id); + .find(|(_, flow)| match &flow_identifier.clone() { + FlowIdentifier::Id(id) => flow.flow_id == *id, + FlowIdentifier::Label(label) => flow.flow_label.as_ref() == Some(label), + }); if let Some((_, mut flow)) = flow { - // validate that user is allowed to expand the flow - if flow.flow_creator != info.sender { - return Err(ContractError::Unauthorized {}); - } - // check if the flow has already ended let current_epoch = helpers::get_current_epoch(deps.as_ref())?; - if current_epoch > flow.end_epoch { + let expanded_end_epoch = get_flow_end_epoch(&flow); + + if current_epoch > expanded_end_epoch { return Err(ContractError::FlowAlreadyEnded {}); } @@ -84,9 +90,21 @@ pub fn expand_flow( } } + // expand the flow only if the the epoch is within the expansion buffer. + let expand_until = + if expanded_end_epoch.saturating_sub(current_epoch) < FLOW_EXPANSION_BUFFER { + expanded_end_epoch + .checked_add(DEFAULT_FLOW_DURATION) + .ok_or(ContractError::InvalidEndEpoch {})? + } else { + expanded_end_epoch + }; + + let end_epoch = end_epoch.unwrap_or(expand_until); + // if the current end_epoch of this flow is greater than the new end_epoch, return error as // it wouldn't be expanding but contracting a flow. - if flow.end_epoch > end_epoch { + if expanded_end_epoch > end_epoch { return Err(ContractError::InvalidEndEpoch {}); } @@ -102,35 +120,53 @@ pub fn expand_flow( Ok, )?; - if let Some(existing_amount) = flow.asset_history.get_mut(&next_epoch) { + if let Some((existing_amount, expanded_end_epoch)) = flow.asset_history.get_mut(&next_epoch) + { *existing_amount = existing_amount.checked_add(flow_asset.amount)?; + *expanded_end_epoch = end_epoch; } else { + // if there's no entry for the previous epoch, i.e. it is the first time the flow is expanded, + // default to the original flow asset amount + + let expanded_amount = get_flow_asset_amount_at_epoch(&flow, current_epoch); + // + // let expanded_amount = if flow.asset_history.is_empty() { + // flow.flow_asset.amount + // } else { + // flow.asset_history.range(..=current_epoch).rev().next() + // }; + // + // let expanded_amount = flow + // .asset_history + // + // .get(¤t_epoch) + // .unwrap_or(&flow.flow_asset.amount); + flow.asset_history.insert( next_epoch, - flow.flow_asset.amount.checked_add(flow_asset.amount)?, + (expanded_amount.checked_add(flow_asset.amount)?, end_epoch), ); } - flow.end_epoch = end_epoch; FLOWS.save(deps.storage, (flow.start_epoch, flow.flow_id), &flow)?; let total_flow_asset = flow .asset_history .values() - .copied() + .map(|&(expanded_amount, _)| expanded_amount) .sum::() .checked_add(flow.flow_asset.amount)?; Ok(Response::default().add_attributes(vec![ ("action", "expand_flow".to_string()), - ("flow_id", flow_id.to_string()), + ("flow_id", flow_identifier.to_string()), ("end_epoch", end_epoch.to_string()), ("expanding_flow_asset", flow_asset.to_string()), ("total_flow_asset", total_flow_asset.to_string()), ])) } else { Err(ContractError::NonExistentFlow { - invalid_id: flow_id, + invalid_identifier: flow_identifier, }) } } diff --git a/contracts/liquidity_hub/pool-network/incentive/src/execute/open_flow.rs b/contracts/liquidity_hub/pool-network/incentive/src/execute/open_flow.rs index 509fcfc1..b4d566f0 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/execute/open_flow.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/execute/open_flow.rs @@ -19,16 +19,19 @@ use crate::{ }; const MIN_FLOW_AMOUNT: Uint128 = Uint128::new(1_000u128); +pub const DEFAULT_FLOW_DURATION: u64 = 14u64; /// Opens a flow to incentivize liquidity providers +#[allow(clippy::too_many_arguments)] pub fn open_flow( deps: DepsMut, env: Env, info: MessageInfo, start_epoch: Option, - end_epoch: u64, - curve: Curve, + end_epoch: Option, + curve: Option, mut flow_asset: Asset, + flow_label: Option, ) -> Result { // check the user is not trying to create an empty flow if flow_asset.amount < MIN_FLOW_AMOUNT { @@ -319,6 +322,11 @@ pub fn open_flow( } let current_epoch = helpers::get_current_epoch(deps.as_ref())?; + let end_epoch = end_epoch.unwrap_or( + current_epoch + .checked_add(DEFAULT_FLOW_DURATION) + .ok_or(ContractError::InvalidEndEpoch {})?, + ); // ensure the flow is set for a expire date in the future if current_epoch > end_epoch { @@ -344,12 +352,15 @@ pub fn open_flow( let flow_id = FLOW_COUNTER.update::<_, StdError>(deps.storage, |current_id| Ok(current_id + 1u64))?; + let curve = curve.unwrap_or(Curve::Linear); + FLOWS.save( deps.storage, (start_epoch, flow_id), &Flow { flow_creator: info.sender.clone(), flow_id, + flow_label: flow_label.clone(), curve: curve.clone(), flow_asset: flow_asset.clone(), claimed_amount: Uint128::zero(), @@ -360,17 +371,24 @@ pub fn open_flow( }, )?; + let mut attributes = vec![("action", "open_flow".to_string())]; + + if let Some(flow_label) = flow_label { + attributes.push(("flow_label", flow_label)); + } + + attributes.extend(vec![ + ("flow_id", flow_id.to_string()), + ("flow_creator", info.sender.into_string()), + ("flow_asset", flow_asset.info.to_string()), + ("flow_asset_amount", flow_asset.amount.to_string()), + ("start_epoch", start_epoch.to_string()), + ("end_epoch", end_epoch.to_string()), + ("emissions_per_epoch", emissions_per_epoch.to_string()), + ("curve", curve.to_string()), + ]); + Ok(Response::default() - .add_attributes(vec![ - ("action", "open_flow".to_string()), - ("flow_id", flow_id.to_string()), - ("flow_creator", info.sender.into_string()), - ("flow_asset", flow_asset.info.to_string()), - ("flow_asset_amount", flow_asset.amount.to_string()), - ("start_epoch", start_epoch.to_string()), - ("end_epoch", end_epoch.to_string()), - ("emissions_per_epoch", emissions_per_epoch.to_string()), - ("curve", curve.to_string()), - ]) + .add_attributes(attributes) .add_messages(messages)) } diff --git a/contracts/liquidity_hub/pool-network/incentive/src/helpers.rs b/contracts/liquidity_hub/pool-network/incentive/src/helpers.rs index 9b0fcf4a..297185e7 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/helpers.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/helpers.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Deps, DepsMut, Order, StdResult, Uint128}; +use cosmwasm_std::{Addr, Deps, DepsMut, Order, StdError, StdResult, Uint128}; use white_whale::pool_network::incentive::Flow; @@ -63,9 +63,60 @@ pub fn delete_weight_history_for_user( pub fn get_flow_asset_amount_at_epoch(flow: &Flow, epoch: u64) -> Uint128 { let mut asset_amount = flow.flow_asset.amount; - if let Some((_, &change_amount)) = flow.asset_history.range(..=epoch).rev().next() { + if let Some((_, &(change_amount, _))) = flow.asset_history.range(..=epoch).rev().next() { asset_amount = change_amount; } asset_amount } + +/// Gets the flow end_epoch, taking into account flow expansion. +pub fn get_flow_end_epoch(flow: &Flow) -> u64 { + let mut end_epoch = flow.end_epoch; + + if let Some((_, &(_, expanded_end_epoch))) = flow.asset_history.last_key_value() { + end_epoch = expanded_end_epoch; + } + + end_epoch +} + +/// Gets the flow end_epoch, taking into account flow expansion. +pub fn get_flow_current_end_epoch(flow: &Flow, epoch: u64) -> u64 { + let mut end_epoch = flow.end_epoch; + + if let Some((_, &(_, current_end_epoch))) = flow.asset_history.range(..=epoch).rev().next() { + end_epoch = current_end_epoch; + } + + end_epoch +} + +pub const MAX_EPOCH_LIMIT: u64 = 100; + +/// Gets a [Flow] filtering the asset history and emitted tokens to the given range of epochs. +pub fn get_filtered_flow( + mut flow: Flow, + start_epoch: Option, + end_epoch: Option, +) -> StdResult { + let start_range = start_epoch.unwrap_or(flow.start_epoch); + let mut end_range = end_epoch.unwrap_or( + start_range + .checked_add(MAX_EPOCH_LIMIT) + .ok_or_else(|| StdError::generic_err("Overflow"))?, + ); + + if end_range.saturating_sub(start_range) > MAX_EPOCH_LIMIT { + end_range = start_range + .checked_add(MAX_EPOCH_LIMIT) + .ok_or_else(|| StdError::generic_err("Overflow"))?; + } + + flow.asset_history + .retain(|&k, _| k >= start_range && k <= end_range); + flow.emitted_tokens + .retain(|k, _| *k >= start_range && *k <= end_range); + + Ok(flow) +} diff --git a/contracts/liquidity_hub/pool-network/incentive/src/migrations.rs b/contracts/liquidity_hub/pool-network/incentive/src/migrations.rs index a5da0d4f..9824b828 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/migrations.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/migrations.rs @@ -1,2 +1,23 @@ +use std::collections::BTreeMap; + // currently a stub file // until migrations are needed in the future +use cosmwasm_std::{DepsMut, StdError}; + +use crate::queries::get_flows; +use crate::state::FLOWS; + +/// Migrates to version 1.0.5, which introduces the [Flow] field asset_history. +pub(crate) fn migrate_to_v105(deps: DepsMut) -> Result<(), StdError> { + let mut flows = get_flows(deps.as_ref(), None, None)?; + + // add the asset_history field to all available flows + for flow in flows.iter_mut() { + flow.asset_history = BTreeMap::new(); + flow.flow_label = None; + + FLOWS.save(deps.storage, (flow.start_epoch, flow.flow_id), flow)?; + } + + Ok(()) +} diff --git a/contracts/liquidity_hub/pool-network/incentive/src/queries/get_flow.rs b/contracts/liquidity_hub/pool-network/incentive/src/queries/get_flow.rs index deca02ac..b36c0d05 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/queries/get_flow.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/queries/get_flow.rs @@ -1,14 +1,29 @@ use cosmwasm_std::{Deps, Order, StdError, StdResult}; -use white_whale::pool_network::incentive::{Flow, FlowResponse}; +use white_whale::pool_network::incentive::{Flow, FlowIdentifier, FlowResponse}; +use crate::helpers::get_filtered_flow; use crate::state::FLOWS; -pub fn get_flow(deps: Deps, flow_id: u64) -> Result, StdError> { - Ok(FLOWS +/// Gets a flow given the [FlowIdentifier]. +pub fn get_flow( + deps: Deps, + flow_identifier: FlowIdentifier, + start_epoch: Option, + end_epoch: Option, +) -> Result, StdError> { + FLOWS .range(deps.storage, None, None, Order::Ascending) .collect::>>()? .into_iter() - .find(|(_, flow)| flow.flow_id == flow_id) - .map(|(_, flow)| FlowResponse { flow: Some(flow) })) + .find(|(_, flow)| match &flow_identifier { + FlowIdentifier::Id(id) => flow.flow_id == *id, + FlowIdentifier::Label(label) => flow.flow_label.as_ref() == Some(label), + }) + .map(|(_, flow)| { + get_filtered_flow(flow, start_epoch, end_epoch).map(|filtered_flow| FlowResponse { + flow: Some(filtered_flow), + }) + }) + .transpose() } diff --git a/contracts/liquidity_hub/pool-network/incentive/src/queries/get_flows.rs b/contracts/liquidity_hub/pool-network/incentive/src/queries/get_flows.rs index ec735950..bf05d81f 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/queries/get_flows.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/queries/get_flows.rs @@ -2,14 +2,19 @@ use cosmwasm_std::{Deps, Order, StdError, StdResult}; use white_whale::pool_network::incentive::Flow; +use crate::helpers::get_filtered_flow; use crate::state::FLOWS; /// Retrieves all the current flows that exist. -pub fn get_flows(deps: Deps) -> Result, StdError> { - Ok(FLOWS +pub fn get_flows( + deps: Deps, + start_epoch: Option, + end_epoch: Option, +) -> Result, StdError> { + FLOWS .range(deps.storage, None, None, Order::Ascending) .collect::>>()? .into_iter() - .map(|(_, flow)| flow) - .collect::>()) + .map(|(_, flow)| get_filtered_flow(flow, start_epoch, end_epoch)) + .collect::>>() } diff --git a/contracts/liquidity_hub/pool-network/incentive/src/queries/get_rewards.rs b/contracts/liquidity_hub/pool-network/incentive/src/queries/get_rewards.rs index 0f79a22a..62a457a0 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/queries/get_rewards.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/queries/get_rewards.rs @@ -4,6 +4,7 @@ use white_whale::pool_network::{asset::Asset, incentive::RewardsResponse}; use crate::error::ContractError; use crate::helpers; +use crate::helpers::{get_flow_asset_amount_at_epoch, get_flow_current_end_epoch}; use crate::state::{EpochId, ADDRESS_WEIGHT_HISTORY, GLOBAL_WEIGHT_SNAPSHOT, LAST_CLAIMED_EPOCH}; #[allow(unused_assignments)] @@ -21,18 +22,21 @@ pub fn get_rewards(deps: Deps, address: String) -> Result flow.end_epoch && flow.claimed_amount == flow.flow_asset.amount { + if current_epoch > *expanded_end_epoch && flow.claimed_amount == expanded_asset_amount { // if so, skip flow. continue; } @@ -50,11 +54,6 @@ pub fn get_rewards(deps: Deps, address: String) -> Result Result Result= flow.end_epoch { + } else if epoch_id >= *expanded_end_epoch { // this flow has finished // todo maybe we should make end_epoch inclusive? break; @@ -98,22 +95,20 @@ pub fn get_rewards(deps: Deps, address: String) -> Result Result emission_per_epoch - || user_reward_at_epoch.checked_add(flow.claimed_amount)? > flow.flow_asset.amount + || user_reward_at_epoch.checked_add(flow.claimed_amount)? > *expanded_asset_amount { return Err(ContractError::InvalidReward {}); } diff --git a/contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs b/contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs index 260650ab..7b05bbe9 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs @@ -10,12 +10,12 @@ mod tests { #[test] fn test_get_flow_asset_amount_at_epoch_with_expansion() { let mut asset_history = BTreeMap::new(); - asset_history.insert(0, Uint128::from(10000u128)); - asset_history.insert(7, Uint128::from(20000u128)); - asset_history.insert(10, Uint128::from(50000u128)); - + asset_history.insert(0, (Uint128::from(10000u128), 105u64)); + asset_history.insert(7, (Uint128::from(20000u128), 110u64)); + asset_history.insert(10, (Uint128::from(50000u128), 115u64)); let flow = Flow { flow_id: 1, + flow_label: None, flow_creator: Addr::unchecked("creator"), flow_asset: Asset { info: AssetInfo::NativeToken { @@ -69,10 +69,11 @@ mod tests { } #[test] fn test_get_flow_asset_amount_at_epoch_without_expansion() { - let mut asset_history = BTreeMap::new(); + let asset_history = BTreeMap::new(); let flow = Flow { flow_id: 1, + flow_label: None, flow_creator: Addr::unchecked("creator"), flow_asset: Asset { info: AssetInfo::NativeToken { diff --git a/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs b/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs index baa5a32f..5a897349 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs @@ -1,11 +1,11 @@ use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; -use cosmwasm_std::{coin, coins, Addr, Decimal, Decimal256, Timestamp, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Decimal256, Timestamp, Uint128}; use white_whale::pool_network::asset::{Asset, AssetInfo}; use white_whale::pool_network::incentive; -use white_whale::pool_network::incentive::{Curve, Flow, RewardsShareResponse}; +use white_whale::pool_network::incentive::{Curve, Flow, FlowIdentifier, RewardsShareResponse}; use white_whale::pool_network::incentive_factory::IncentivesContract; use crate::error::ContractError; @@ -279,14 +279,15 @@ fn try_open_more_flows_than_allowed() { alice.clone(), incentive_addr.clone().into_inner(), None, - 10u64, - Curve::Linear, + Some(10u64), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".to_string(), }, amount: Uint128::new(i * 2_000u128), }, + None, &vec![coin(i * 2_000u128, "uwhale".to_string())], |result| { if i > 7 { @@ -305,7 +306,7 @@ fn try_open_more_flows_than_allowed() { } let incentive_flows = RefCell::new(vec![]); - suite.query_flows(incentive_addr.clone().into_inner(), |result| { + suite.query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); *incentive_flows.borrow_mut() = flows.clone(); @@ -315,6 +316,7 @@ fn try_open_more_flows_than_allowed() { flows.first().unwrap(), &Flow { flow_id: 1, + flow_label: None, flow_creator: alice.clone(), flow_asset: Asset { info: AssetInfo::NativeToken { @@ -334,6 +336,7 @@ fn try_open_more_flows_than_allowed() { flows.last().unwrap(), &Flow { flow_id: 7, + flow_label: None, flow_creator: alice.clone(), flow_asset: Asset { info: AssetInfo::NativeToken { @@ -398,14 +401,15 @@ fn try_open_flows_with_wrong_epochs() { alice.clone(), incentive_addr.clone().into_inner(), None, - past_epoch.clone(), - Curve::Linear, + Some(past_epoch.clone()), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(2_000u128), }, + None, &vec![coin(2_000u128, "uwhale".to_string())], |result| { let err = result.unwrap_err().downcast::().unwrap(); @@ -422,14 +426,15 @@ fn try_open_flows_with_wrong_epochs() { alice.clone(), incentive_addr.clone().into_inner(), Some(future_future_epoch.clone()), - future_epoch.clone(), - Curve::Linear, + Some(future_epoch.clone()), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(2_000u128), }, + None, &vec![coin(2_000u128, "uwhale".to_string())], |result| { let err = result.unwrap_err().downcast::().unwrap(); @@ -448,14 +453,15 @@ fn try_open_flows_with_wrong_epochs() { Some( current_epoch.clone().into_inner() + max_flow_epoch_buffer.clone().into_inner() + 1, ), - current_epoch.clone().into_inner() + 100, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 100), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(2_000u128), }, + None, &vec![coin(2_000u128, "uwhale".to_string())], |result| { let err = result.unwrap_err().downcast::().unwrap(); @@ -470,14 +476,15 @@ fn try_open_flows_with_wrong_epochs() { alice.clone(), incentive_addr.clone().into_inner(), None, - future_epoch.clone(), - Curve::Linear, + Some(future_epoch.clone()), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(2_000u128), }, + None, &vec![coin(2_000u128, "uwhale".to_string())], |result| { result.unwrap(); @@ -525,14 +532,15 @@ fn open_flow_with_fee_native_token_and_flow_same_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(0u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { // this should fail as not enough funds were sent @@ -548,14 +556,15 @@ fn open_flow_with_fee_native_token_and_flow_same_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { // this should fail as not enough funds were sent to cover for fee + MIN_FLOW_AMOUNT @@ -570,14 +579,15 @@ fn open_flow_with_fee_native_token_and_flow_same_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![coin(100u128, "uwhale".to_string())], |result| { // this should fail as not enough funds were sent to cover for fee + MIN_FLOW_AMOUNT @@ -592,14 +602,15 @@ fn open_flow_with_fee_native_token_and_flow_same_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(2_000u128), }, + None, &vec![coin(500u128, "uwhale".to_string())], |result| { // this should fail as we didn't send enough funds to cover for the fee @@ -614,14 +625,15 @@ fn open_flow_with_fee_native_token_and_flow_same_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(2_000u128), }, + None, &vec![coin(2_000u128, "uwhale".to_string())], |result| { // this should succeed as we sent enough funds to cover for fee + MIN_FLOW_AMOUNT @@ -651,33 +663,42 @@ fn open_flow_with_fee_native_token_and_flow_same_native_token() { assert_eq!(funds, Uint128::new(1_000u128)); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow, - Some(Flow { - flow_id: 1, - flow_creator: carol.clone(), - flow_asset: Asset { - info: AssetInfo::NativeToken { - denom: "uwhale".to_string() + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_label: None, + flow_creator: carol.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string() + }, + amount: Uint128::new(1_000u128), }, - amount: Uint128::new(1_000u128), - }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 10u64, - end_epoch: 19u64, - emitted_tokens: Default::default(), - asset_history: Default::default(), - }) - ); - }) - .query_flow(incentive_addr.clone().into_inner(), 5u64, |result| { - // this should not work as there is no flow with id 5 - let flow_response = result.unwrap(); - assert_eq!(flow_response, None); - }); + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 19u64, + emitted_tokens: Default::default(), + asset_history: Default::default(), + }) + ); + }, + ) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(5u64), + |result| { + // this should not work as there is no flow with id 5 + let flow_response = result.unwrap(); + assert_eq!(flow_response, None); + }, + ); } #[test] @@ -722,14 +743,15 @@ fn open_flow_with_fee_native_token_and_flow_different_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "ampWHALE".clone().to_string(), }, amount: Uint128::new(500u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { // this should fail as MIN_FLOW_AMOUNT is not met @@ -745,14 +767,15 @@ fn open_flow_with_fee_native_token_and_flow_different_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "ampWHALE".clone().to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { // this should fail as the flow asset was not sent @@ -767,14 +790,15 @@ fn open_flow_with_fee_native_token_and_flow_different_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "ampWHALE".clone().to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![ coin(1_000u128, "uwhale".to_string()), coin(500u128, "ampWHALE".to_string()), @@ -792,14 +816,15 @@ fn open_flow_with_fee_native_token_and_flow_different_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "ampWHALE".clone().to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![ coin(100u128, "uwhale".to_string()), coin(1_00u128, "ampWHALE".to_string()), @@ -817,14 +842,15 @@ fn open_flow_with_fee_native_token_and_flow_different_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "ampWHALE".clone().to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![ coin(1_000u128, "uwhale".to_string()), coin(1_000u128, "ampWHALE".to_string()), @@ -878,33 +904,42 @@ fn open_flow_with_fee_native_token_and_flow_different_native_token() { assert_eq!(funds, Uint128::zero()); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow, - Some(Flow { - flow_id: 1, - flow_creator: carol.clone(), - flow_asset: Asset { - info: AssetInfo::NativeToken { - denom: "ampWHALE".to_string() + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_label: None, + flow_creator: carol.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "ampWHALE".to_string() + }, + amount: Uint128::new(1_000u128), }, - amount: Uint128::new(1_000u128), - }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 1u64, - end_epoch: 10u64, - emitted_tokens: Default::default(), - asset_history: Default::default(), - }) - ); - }) - .query_flow(incentive_addr.clone().into_inner(), 5u64, |result| { - // this should not work as there is no flow with id 5 - let flow_response = result.unwrap(); - assert_eq!(flow_response, None); - }) + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 1u64, + end_epoch: 10u64, + emitted_tokens: Default::default(), + asset_history: Default::default(), + }) + ); + }, + ) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(5u64), + |result| { + // this should not work as there is no flow with id 5 + let flow_response = result.unwrap(); + assert_eq!(flow_response, None); + }, + ) .query_funds( carol.clone(), AssetInfo::NativeToken { @@ -919,14 +954,15 @@ fn open_flow_with_fee_native_token_and_flow_different_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "ampWHALE".clone().to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![ coin(50_000u128, "uwhale".to_string()), coin(1_000u128, "ampWHALE".to_string()), @@ -997,12 +1033,13 @@ fn open_flow_with_fee_native_token_and_flow_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_incentive.clone(), amount: Uint128::new(500u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { // this should fail as MIN_FLOW_AMOUNT is not met @@ -1018,12 +1055,13 @@ fn open_flow_with_fee_native_token_and_flow_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_incentive.clone(), amount: Uint128::new(1_000u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { // this should fail as the flow asset was not sent, i.e. Allowance was not increased @@ -1045,12 +1083,13 @@ fn open_flow_with_fee_native_token_and_flow_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_incentive.clone(), amount: Uint128::new(1_000u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { // this should succeed as the allowance was increased @@ -1096,26 +1135,31 @@ fn open_flow_with_fee_native_token_and_flow_cw20_token() { assert_eq!(funds, Uint128::zero()); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow, - Some(Flow { - flow_id: 1, - flow_creator: carol.clone(), - flow_asset: Asset { - info: cw20_incentive.clone(), - amount: Uint128::new(1_000u128), - }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 1u64, - end_epoch: 10u64, - emitted_tokens: Default::default(), - asset_history: Default::default(), - }) - ); - }); + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_label: None, + flow_creator: carol.clone(), + flow_asset: Asset { + info: cw20_incentive.clone(), + amount: Uint128::new(1_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 1u64, + end_epoch: 10u64, + emitted_tokens: Default::default(), + asset_history: Default::default(), + }) + ); + }, + ); } #[test] @@ -1164,12 +1208,13 @@ fn open_flow_with_fee_cw20_token_and_flow_same_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(500u128), }, + None, &vec![], |result| { // this should fail as not enough funds were sent @@ -1185,12 +1230,13 @@ fn open_flow_with_fee_cw20_token_and_flow_same_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(1_000u128), }, + None, &vec![], |result| { // this should fail as not enough funds were sent to cover for fee @@ -1213,12 +1259,13 @@ fn open_flow_with_fee_cw20_token_and_flow_same_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(1_500u128), }, + None, &vec![], |result| { // this should fail as not enough funds were sent to cover for fee and MIN_FLOW_AMOUNT @@ -1240,12 +1287,13 @@ fn open_flow_with_fee_cw20_token_and_flow_same_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(2_000u128), }, + None, &vec![], |result| { // this should succeed as enough funds were sent to cover for fee and MIN_FLOW_AMOUNT @@ -1273,26 +1321,31 @@ fn open_flow_with_fee_cw20_token_and_flow_same_cw20_token() { assert_eq!(funds, Uint128::new(1_000u128)); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow, - Some(Flow { - flow_id: 1, - flow_creator: carol.clone(), - flow_asset: Asset { - info: cw20_asset.clone(), - amount: Uint128::new(1_000u128), - }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 1u64, - end_epoch: 10u64, - emitted_tokens: Default::default(), - asset_history: Default::default(), - }) - ); - }); + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_label: None, + flow_creator: carol.clone(), + flow_asset: Asset { + info: cw20_asset.clone(), + amount: Uint128::new(1_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 1u64, + end_epoch: 10u64, + emitted_tokens: Default::default(), + asset_history: Default::default(), + }) + ); + }, + ); } #[test] @@ -1341,12 +1394,13 @@ fn open_flow_with_fee_cw20_token_and_flow_different_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(500u128), }, + None, &vec![], |result| { // this should fail as not enough funds were sent @@ -1362,12 +1416,13 @@ fn open_flow_with_fee_cw20_token_and_flow_different_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(1_000u128), }, + None, &vec![], |result| { // this should fail as the asset to pay for the fee was not transferred @@ -1390,12 +1445,13 @@ fn open_flow_with_fee_cw20_token_and_flow_different_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(1_000u128), }, + None, &vec![], |result| { // this should fail as not enough funds were sent to cover for fee @@ -1418,12 +1474,13 @@ fn open_flow_with_fee_cw20_token_and_flow_different_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(1_000u128), }, + None, &vec![], |result| { // this should fail as not enough funds were sent to cover the flow asset @@ -1446,12 +1503,13 @@ fn open_flow_with_fee_cw20_token_and_flow_different_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(1_000u128), }, + None, &vec![], |result| { // this should succeed as both the fee was paid in full and the flow asset amount @@ -1494,31 +1552,40 @@ fn open_flow_with_fee_cw20_token_and_flow_different_cw20_token() { assert_eq!(funds, Uint128::new(1_000u128)); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow, - Some(Flow { - flow_id: 1, - flow_creator: carol.clone(), - flow_asset: Asset { - info: cw20_asset.clone(), - amount: Uint128::new(1_000u128), - }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 1u64, - end_epoch: 10u64, - emitted_tokens: Default::default(), - asset_history: Default::default(), - }) - ); - }) - .query_flow(incentive_addr.clone().into_inner(), 5u64, |result| { - // this should not work as there is no flow with id 5 - let flow_response = result.unwrap(); - assert_eq!(flow_response, None); - }); + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_label: None, + flow_creator: carol.clone(), + flow_asset: Asset { + info: cw20_asset.clone(), + amount: Uint128::new(1_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 1u64, + end_epoch: 10u64, + emitted_tokens: Default::default(), + asset_history: Default::default(), + }) + ); + }, + ) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(5u64), + |result| { + // this should not work as there is no flow with id 5 + let flow_response = result.unwrap(); + assert_eq!(flow_response, None); + }, + ); } #[test] @@ -1562,14 +1629,15 @@ fn open_flow_with_fee_cw20_token_and_flow_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, amount: Uint128::new(500u128), }, + None, &vec![], |result| { // this should fail as not enough funds were sent @@ -1585,14 +1653,15 @@ fn open_flow_with_fee_cw20_token_and_flow_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![], |result| { // this should fail as the asset to pay for the fee was not transferred @@ -1615,14 +1684,15 @@ fn open_flow_with_fee_cw20_token_and_flow_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![], |result| { // this should fail as not enough funds were sent to cover for fee @@ -1645,14 +1715,15 @@ fn open_flow_with_fee_cw20_token_and_flow_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![], |result| { // this should fail as the flow asset was not sent to the contract @@ -1668,14 +1739,15 @@ fn open_flow_with_fee_cw20_token_and_flow_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![coin(900u128, "usdc".to_string())], |result| { // this should fail as the flow asset was not sent to the contract @@ -1691,14 +1763,15 @@ fn open_flow_with_fee_cw20_token_and_flow_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, amount: Uint128::new(1_000u128), }, + None, &vec![coin(1_000u128, "usdc".to_string())], |result| { // this should succeed as the flow asset was sent to the contract @@ -1744,33 +1817,42 @@ fn open_flow_with_fee_cw20_token_and_flow_native_token() { assert_eq!(funds, Uint128::new(1_000u128)); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow, - Some(Flow { - flow_id: 1, - flow_creator: carol.clone(), - flow_asset: Asset { - info: AssetInfo::NativeToken { - denom: "usdc".to_string(), + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_label: None, + flow_creator: carol.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000u128), }, - amount: Uint128::new(1_000u128), - }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 1u64, - end_epoch: 10u64, - emitted_tokens: Default::default(), - asset_history: Default::default(), - }) - ); - }) - .query_flow(incentive_addr.clone().into_inner(), 5u64, |result| { - // this should not work as there is no flow with id 5 - let flow_response = result.unwrap(); - assert_eq!(flow_response, None); - }); + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 1u64, + end_epoch: 10u64, + emitted_tokens: Default::default(), + asset_history: Default::default(), + }) + ); + }, + ) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(5u64), + |result| { + // this should not work as there is no flow with id 5 + let flow_response = result.unwrap(); + assert_eq!(flow_response, None); + }, + ); } #[test] @@ -1814,14 +1896,15 @@ fn close_native_token_flows() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(2_000u128), }, + None, &vec![coin(2_000u128, "uwhale".to_string())], |result| { result.unwrap(); @@ -1831,20 +1914,21 @@ fn close_native_token_flows() { alice.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(11_000u128), }, + None, &vec![coin(11_000u128, "uwhale".to_string())], |result| { result.unwrap(); }, ) - .query_flows(incentive_addr.clone().into_inner(), |result| { + .query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); assert_eq!(flows.len(), 2usize); @@ -1852,6 +1936,7 @@ fn close_native_token_flows() { flows.first().unwrap(), &Flow { flow_id: 1, + flow_label: None, flow_creator: carol.clone(), flow_asset: Asset { info: AssetInfo::NativeToken { @@ -1871,6 +1956,7 @@ fn close_native_token_flows() { flows.last().unwrap(), &Flow { flow_id: 2, + flow_label: None, flow_creator: alice.clone(), flow_asset: Asset { info: AssetInfo::NativeToken { @@ -1890,7 +1976,7 @@ fn close_native_token_flows() { .close_incentive_flow( bob.clone(), incentive_addr.clone().into_inner(), - 1u64, + FlowIdentifier::Id(1u64), |result| { // this should error because bob didn't open the flow, nor he is the owner of the incentive let err = result.unwrap_err().downcast::().unwrap(); @@ -1906,7 +1992,7 @@ fn close_native_token_flows() { .close_incentive_flow( carol.clone(), incentive_addr.clone().into_inner(), - 2u64, + FlowIdentifier::Id(2u64), |result| { // this should error because carol didn't open the flow, nor he is the owner of the incentive let err = result.unwrap_err().downcast::().unwrap(); @@ -1932,13 +2018,13 @@ fn close_native_token_flows() { .close_incentive_flow( alice.clone(), incentive_addr.clone().into_inner(), - 2u64, + FlowIdentifier::Id(2u64), |result| { // this should be fine because carol opened the flow result.unwrap(); }, ) - .query_flows(incentive_addr.clone().into_inner(), |result| { + .query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); assert_eq!(flows.len(), 1usize); @@ -1946,6 +2032,7 @@ fn close_native_token_flows() { flows.last().unwrap(), &Flow { flow_id: 1, + flow_label: None, flow_creator: carol.clone(), flow_asset: Asset { info: AssetInfo::NativeToken { @@ -1989,7 +2076,7 @@ fn close_native_token_flows() { .close_incentive_flow( alice.clone(), incentive_addr.clone().into_inner(), - 1u64, + FlowIdentifier::Id(1u64), |result| { result.unwrap(); }, @@ -2007,7 +2094,7 @@ fn close_native_token_flows() { ); }, ) - .query_flows(incentive_addr.clone().into_inner(), |result| { + .query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); assert!(flows.is_empty()); }) @@ -2015,13 +2102,13 @@ fn close_native_token_flows() { .close_incentive_flow( bob.clone(), incentive_addr.clone().into_inner(), - 3u64, + FlowIdentifier::Id(3u64), |result| { let err = result.unwrap_err().downcast::().unwrap(); match err { - ContractError::NonExistentFlow { invalid_id } => { - assert_eq!(invalid_id, 3u64) + ContractError::NonExistentFlow { invalid_identifier } => { + assert_eq!(invalid_identifier, FlowIdentifier::Id(3u64)) } _ => panic!("Wrong error type, should return ContractError::NonExistentFlow"), } @@ -2031,20 +2118,21 @@ fn close_native_token_flows() { alice.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(5_000u128), }, + None, &vec![coin(5_000u128, "uwhale".to_string())], |result| { result.unwrap(); }, ) - .query_flows(incentive_addr.clone().into_inner(), |result| { + .query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); assert_eq!(flows.len(), 1usize); @@ -2052,6 +2140,7 @@ fn close_native_token_flows() { flows.last().unwrap(), &Flow { flow_id: 3, + flow_label: None, flow_creator: alice.clone(), flow_asset: Asset { info: AssetInfo::NativeToken { @@ -2121,12 +2210,13 @@ fn close_cw20_token_flows() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(1_000u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { result.unwrap(); @@ -2142,18 +2232,19 @@ fn close_cw20_token_flows() { alice.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(10_000u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { result.unwrap(); }, ) - .query_flows(incentive_addr.clone().into_inner(), |result| { + .query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); assert_eq!(flows.len(), 2usize); @@ -2161,6 +2252,7 @@ fn close_cw20_token_flows() { flows.first().unwrap(), &Flow { flow_id: 1, + flow_label: None, flow_creator: carol.clone(), flow_asset: Asset { info: cw20_asset.clone(), @@ -2178,6 +2270,7 @@ fn close_cw20_token_flows() { flows.last().unwrap(), &Flow { flow_id: 2, + flow_label: None, flow_creator: alice.clone(), flow_asset: Asset { info: cw20_asset.clone(), @@ -2195,7 +2288,7 @@ fn close_cw20_token_flows() { .close_incentive_flow( bob.clone(), incentive_addr.clone().into_inner(), - 1u64, + FlowIdentifier::Id(1u64), |result| { // this should error because bob didn't open the flow, nor he is the owner of the incentive let err = result.unwrap_err().downcast::().unwrap(); @@ -2211,7 +2304,7 @@ fn close_cw20_token_flows() { .close_incentive_flow( carol.clone(), incentive_addr.clone().into_inner(), - 2u64, + FlowIdentifier::Id(2u64), |result| { // this should error because carol didn't open the flow, nor he is the owner of the incentive let err = result.unwrap_err().downcast::().unwrap(); @@ -2231,13 +2324,13 @@ fn close_cw20_token_flows() { .close_incentive_flow( alice.clone(), incentive_addr.clone().into_inner(), - 2u64, + FlowIdentifier::Id(2u64), |result| { // this should be fine because carol opened the flow result.unwrap(); }, ) - .query_flows(incentive_addr.clone().into_inner(), |result| { + .query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); assert_eq!(flows.len(), 1usize); @@ -2245,6 +2338,7 @@ fn close_cw20_token_flows() { flows.last().unwrap(), &Flow { flow_id: 1, + flow_label: None, flow_creator: carol.clone(), flow_asset: Asset { info: cw20_asset.clone(), @@ -2274,7 +2368,7 @@ fn close_cw20_token_flows() { .close_incentive_flow( alice.clone(), incentive_addr.clone().into_inner(), - 1u64, + FlowIdentifier::Id(1u64), |result| { result.unwrap(); }, @@ -2286,7 +2380,7 @@ fn close_cw20_token_flows() { Uint128::new(1_000u128) ); }) - .query_flows(incentive_addr.clone().into_inner(), |result| { + .query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); assert!(flows.is_empty()); }) @@ -2294,13 +2388,13 @@ fn close_cw20_token_flows() { .close_incentive_flow( bob.clone(), incentive_addr.clone().into_inner(), - 3u64, + FlowIdentifier::Id(3u64), |result| { let err = result.unwrap_err().downcast::().unwrap(); match err { - ContractError::NonExistentFlow { invalid_id } => { - assert_eq!(invalid_id, 3u64) + ContractError::NonExistentFlow { invalid_identifier } => { + assert_eq!(invalid_identifier, FlowIdentifier::Id(3u64)) } _ => panic!("Wrong error type, should return ContractError::NonExistentFlow"), } @@ -2316,18 +2410,19 @@ fn close_cw20_token_flows() { alice.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: cw20_asset.clone(), amount: Uint128::new(5_000u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { result.unwrap(); }, ) - .query_flows(incentive_addr.clone().into_inner(), |result| { + .query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); assert_eq!(flows.len(), 1usize); @@ -2335,6 +2430,7 @@ fn close_cw20_token_flows() { flows.last().unwrap(), &Flow { flow_id: 3, + flow_label: None, flow_creator: alice.clone(), flow_asset: Asset { info: cw20_asset.clone(), @@ -2589,14 +2685,15 @@ fn open_flow_positions_and_claim_native_token_incentive() { alice.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 10, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 10), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, amount: Uint128::new(1_000_000_000u128), }, + None, &vec![coin(1_000_000_000u128, "usdc"), coin(1_000u128, "uwhale")], |result| { result.unwrap(); @@ -2655,13 +2752,17 @@ fn open_flow_positions_and_claim_native_token_incentive() { ); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow.unwrap().claimed_amount, - Uint128::new(500_000_000u128) - ); - }); + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow.unwrap().claimed_amount, + Uint128::new(500_000_000u128) + ); + }, + ); // move 3 more epochs, so carol should have 300 more to claim suite @@ -2729,13 +2830,17 @@ fn open_flow_positions_and_claim_native_token_incentive() { result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow.unwrap().claimed_amount, - Uint128::new(1_000_000_000u128) - ); - }) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow.unwrap().claimed_amount, + Uint128::new(1_000_000_000u128) + ); + }, + ) .query_funds( carol.clone(), AssetInfo::NativeToken { @@ -2991,12 +3096,13 @@ fn open_flow_positions_claim_cw20_token_incentive() { alice.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 10, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 10), + Some(Curve::Linear), Asset { info: flow_asset.clone(), amount: Uint128::new(1_000_000_000u128), }, + None, &vec![coin(1_000u128, "uwhale")], |result| { result.unwrap(); @@ -3039,13 +3145,17 @@ fn open_flow_positions_claim_cw20_token_incentive() { .unwrap(), ); }) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow.unwrap().claimed_amount, - Uint128::new(500_000_000u128) - ); - }); + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow.unwrap().claimed_amount, + Uint128::new(500_000_000u128) + ); + }, + ); // move 3 more epochs, so carol should have 300 more to claim suite @@ -3064,13 +3174,17 @@ fn open_flow_positions_claim_cw20_token_incentive() { ); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow.unwrap().claimed_amount, - Uint128::new(500_000_000u128) - ); - }) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow.unwrap().claimed_amount, + Uint128::new(500_000_000u128) + ); + }, + ) // move 2 more epochs, so carol should have an additional 200_000_000usdc to claim. .set_time(time.plus_seconds(172800u64)) .create_epochs_on_fee_distributor(2, vec![incentive_addr.clone().into_inner()]) @@ -3110,13 +3224,17 @@ fn open_flow_positions_claim_cw20_token_incentive() { result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow.unwrap().claimed_amount, - Uint128::new(1_000_000_000u128) - ); - }) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow.unwrap().claimed_amount, + Uint128::new(1_000_000_000u128) + ); + }, + ) .query_funds(carol.clone(), flow_asset.clone(), |result| { assert_eq!( result, @@ -3199,13 +3317,14 @@ fn open_expand_close_flows_positions_and_claim_native_token_incentive() { .open_incentive_flow( alice.clone(), incentive_addr.clone().into_inner(), - None, //epoch 11 - current_epoch.clone().into_inner() + 10, // epoch 21 - Curve::Linear, + None, //epoch 11 + Some(current_epoch.clone().into_inner() + 10), // epoch 21 + Some(Curve::Linear), Asset { info: flow_asset_1.clone(), amount: Uint128::new(1_000_000_000u128), }, + None, &vec![ coin(1_000_000_000u128, "ampWHALE"), coin(1_000u128, "uwhale"), @@ -3218,18 +3337,19 @@ fn open_expand_close_flows_positions_and_claim_native_token_incentive() { alice.clone(), incentive_addr.clone().into_inner(), Some(current_epoch.clone().into_inner() + 10), // epoch 21 - current_epoch.clone().into_inner() + 30, //epoch 41 , ends in 30 epochs from the start, i.e. has a duration of 20 epochs - Curve::Linear, + Some(current_epoch.clone().into_inner() + 30), //epoch 41 , ends in 30 epochs from the start, i.e. has a duration of 20 epochs + Some(Curve::Linear), Asset { info: flow_asset_2.clone(), amount: Uint128::new(10_000_000_000u128), }, + None, &vec![coin(10_000_000_000u128, "usdc"), coin(1_000u128, "uwhale")], |result| { result.unwrap(); }, ) - .query_flows(incentive_addr.clone().into_inner(), |result| { + .query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); println!("flows created:: {:?}", flows); assert_eq!(flows.len(), 2); @@ -3492,14 +3612,18 @@ fn open_expand_close_flows_positions_and_claim_native_token_incentive() { }); println!("all good"); - suite.query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow.unwrap().claimed_amount, - // Uint128::new(250_000_000u128) - Uint128::new(249_999_995u128) - ); - }); + suite.query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow.unwrap().claimed_amount, + // Uint128::new(250_000_000u128) + Uint128::new(249_999_995u128) + ); + }, + ); // move 10 epochs let time = suite.get_time(); @@ -3827,26 +3951,30 @@ fn open_expand_close_flows_positions_and_claim_native_token_incentive() { // let's close flow 1 suite - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - let total_rewards = flow_response - .clone() - .unwrap() - .flow - .unwrap() - .flow_asset - .amount; - let claimed = flow_response.clone().unwrap().flow.unwrap().claimed_amount; - let expected_claimed = total_rewards - Uint128::new(100_000_000u128); - assert!(total_rewards > claimed); - assert!(expected_claimed >= claimed); - - assert!((expected_claimed.u128() as i128 - claimed.u128() as i128).abs() < 10i128); - }) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + let total_rewards = flow_response + .clone() + .unwrap() + .flow + .unwrap() + .flow_asset + .amount; + let claimed = flow_response.clone().unwrap().flow.unwrap().claimed_amount; + let expected_claimed = total_rewards - Uint128::new(100_000_000u128); + assert!(total_rewards > claimed); + assert!(expected_claimed >= claimed); + + assert!((expected_claimed.u128() as i128 - claimed.u128() as i128).abs() < 10i128); + }, + ) .close_incentive_flow( bob.clone(), incentive_addr.clone().into_inner(), - 1u64, + FlowIdentifier::Id(1u64), |result| { let err = result.unwrap_err().downcast::().unwrap(); @@ -3861,7 +3989,7 @@ fn open_expand_close_flows_positions_and_claim_native_token_incentive() { .close_incentive_flow( bob.clone(), incentive_addr.clone().into_inner(), - 5u64, + FlowIdentifier::Id(5u64), |result| { let err = result.unwrap_err().downcast::().unwrap(); @@ -3874,7 +4002,7 @@ fn open_expand_close_flows_positions_and_claim_native_token_incentive() { .close_incentive_flow( alice.clone(), incentive_addr.clone().into_inner(), - 1u64, + FlowIdentifier::Id(1u64), |result| { result.unwrap(); }, @@ -4092,22 +4220,26 @@ fn open_expand_close_flows_positions_and_claim_native_token_incentive() { ); *alice_usdc_funds.borrow_mut() = result; }) - .query_flow(incentive_addr.clone().into_inner(), 2u64, |result| { - let flow_response = result.unwrap(); - let total_rewards = flow_response - .clone() - .unwrap() - .flow - .unwrap() - .flow_asset - .amount; - let claimed = flow_response.clone().unwrap().flow.unwrap().claimed_amount; - let expected_claimed = total_rewards.clone(); - - assert!(total_rewards > claimed); - assert!(expected_claimed >= claimed); - assert!((expected_claimed.u128() as i128 - claimed.u128() as i128).abs() < 10i128); - }); + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(2u64), + |result| { + let flow_response = result.unwrap(); + let total_rewards = flow_response + .clone() + .unwrap() + .flow + .unwrap() + .flow_asset + .amount; + let claimed = flow_response.clone().unwrap().flow.unwrap().claimed_amount; + let expected_claimed = total_rewards.clone(); + + assert!(total_rewards > claimed); + assert!(expected_claimed >= claimed); + assert!((expected_claimed.u128() as i128 - claimed.u128() as i128).abs() < 10i128); + }, + ); // carol should be able to withdraw, as many epochs has passed let carol_incentive_asset_funds = RefCell::new(Uint128::zero()); @@ -4251,13 +4383,14 @@ fn open_expand_position_with_optional_receiver() { .open_incentive_flow( alice.clone(), incentive_addr.clone().into_inner(), - None, //epoch 11 - current_epoch.clone().into_inner() + 10, // epoch 21 - Curve::Linear, + None, //epoch 11 + Some(current_epoch.clone().into_inner() + 10), // epoch 21 + Some(Curve::Linear), Asset { info: flow_asset_1.clone(), amount: Uint128::new(1_000_000_000u128), }, + None, &vec![ coin(1_000_000_000u128, "ampWHALE"), coin(1_000u128, "uwhale"), @@ -4266,7 +4399,7 @@ fn open_expand_position_with_optional_receiver() { result.unwrap(); }, ) - .query_flows(incentive_addr.clone().into_inner(), |result| { + .query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); assert_eq!(flows.len(), 1); assert_eq!( @@ -4393,19 +4526,20 @@ fn close_position_if_empty_rewards() { .open_incentive_flow( alice.clone(), incentive_addr.clone().into_inner(), - None, //epoch 11 - current_epoch.clone().into_inner() + 10, // epoch 21 - Curve::Linear, + None, //epoch 11 + Some(current_epoch.clone().into_inner() + 10), // epoch 21 + Some(Curve::Linear), Asset { info: flow_asset_1.clone(), amount: Uint128::new(1_000u128), }, + None, &vec![coin(1_000u128, "ampWHALE"), coin(1_000u128, "uwhale")], |result| { result.unwrap(); }, ) - .query_flows(incentive_addr.clone().into_inner(), |result| { + .query_flows(incentive_addr.clone().into_inner(), None, None, |result| { let flows = result.unwrap(); println!("flows created:: {:?}", flows); assert_eq!(flows.len(), 1); @@ -4635,13 +4769,17 @@ fn close_position_if_empty_rewards() { ); }); - suite.query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow.unwrap().claimed_amount, - Uint128::new(351u128) - ); - }); + suite.query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow.unwrap().claimed_amount, + Uint128::new(351u128) + ); + }, + ); suite .query_rewards(incentive_addr.clone().into_inner(), bob.clone(), |result| { @@ -4737,26 +4875,28 @@ fn open_expand_flow_with_native_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: AssetInfo::NativeToken { denom: "uwhale".clone().to_string(), }, amount: Uint128::new(2_000u128), }, + None, &vec![coin(2_000u128, "uwhale".to_string())], |result| { // this should succeed as we sent enough funds to cover for fee + MIN_FLOW_AMOUNT result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + .query_flow(incentive_addr.clone().into_inner(), FlowIdentifier::Id(1u64), |result| { let flow_response = result.unwrap(); assert_eq!( flow_response.unwrap().flow, Some(Flow { flow_id: 1, + flow_label: None, flow_creator: carol.clone(), flow_asset: Asset { info: AssetInfo::NativeToken { @@ -4776,8 +4916,8 @@ fn open_expand_flow_with_native_token() { .expand_flow( alice.clone(), incentive_addr.clone().into_inner(), - 5u64, // invalid flow id - 19u64, + FlowIdentifier::Id(5u64), // invalid flow id + Some(19u64), Asset { info: AssetInfo::NativeToken { denom: "uwhale".to_string(), @@ -4788,36 +4928,16 @@ fn open_expand_flow_with_native_token() { |result| { let err = result.unwrap_err().downcast::().unwrap(); match err { - ContractError::NonExistentFlow { invalid_id } => assert_eq!(invalid_id, 5u64), + ContractError::NonExistentFlow { invalid_identifier } => assert_eq!(invalid_identifier, FlowIdentifier::Id(5u64)), _ => panic!("Wrong error type, should return ContractError::NonExistentFlow"), } }, ) - .expand_flow( - alice.clone(), //unauthorized - incentive_addr.clone().into_inner(), - 1u64, - 19u64, - Asset { - info: AssetInfo::NativeToken { - denom: "uwhale".to_string(), - }, - amount: Uint128::new(1_000u128), - }, - coins(1_000u128, "uwhale".to_string()), - |result| { - let err = result.unwrap_err().downcast::().unwrap(); - match err { - ContractError::Unauthorized {} => {} - _ => panic!("Wrong error type, should return ContractError::Unauthorized"), - } - }, - ) .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 18u64, //invalid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(18u64), //invalid epoch Asset { info: AssetInfo::NativeToken { denom: "uwhale".to_string(), @@ -4836,8 +4956,8 @@ fn open_expand_flow_with_native_token() { .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 19u64, //valid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(19u64), //valid epoch Asset { info: AssetInfo::NativeToken { denom: "uwhale".to_string(), @@ -4856,8 +4976,8 @@ fn open_expand_flow_with_native_token() { .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 19u64, //valid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(19u64), //valid epoch Asset { info: AssetInfo::NativeToken { denom: "uwhale".to_string(), @@ -4876,8 +4996,8 @@ fn open_expand_flow_with_native_token() { .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 19u64, //valid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(19u64), //valid epoch Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), @@ -4896,8 +5016,8 @@ fn open_expand_flow_with_native_token() { .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 19u64, //valid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(19u64), //valid epoch Asset { info: AssetInfo::NativeToken { denom: "uwhale".to_string(), @@ -4916,8 +5036,8 @@ fn open_expand_flow_with_native_token() { .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 19u64, //valid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(19u64), //valid epoch Asset { info: AssetInfo::NativeToken { denom: "uwhale".to_string(), @@ -4928,33 +5048,34 @@ fn open_expand_flow_with_native_token() { |result| { result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + .query_flow(incentive_addr.clone().into_inner(), FlowIdentifier::Id(1u64), |result| { let flow_response = result.unwrap(); assert_eq!( flow_response.unwrap().flow, Some(Flow { flow_id: 1, + flow_label: None, flow_creator: carol.clone(), flow_asset: Asset { info: AssetInfo::NativeToken { denom: "uwhale".to_string() }, - amount: Uint128::new(2_000u128), + amount: Uint128::new(1_000u128), }, claimed_amount: Uint128::zero(), curve: Curve::Linear, start_epoch: 10u64, end_epoch: 19u64, emitted_tokens: Default::default(), - asset_history: Default::default(), + asset_history: BTreeMap::from_iter(vec![(11, (Uint128::new(2_000u128), 19u64))]), }) ); }) .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 20u64, //valid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(20u64), //valid epoch Asset { info: AssetInfo::NativeToken { denom: "uwhale".to_string(), @@ -4969,8 +5090,8 @@ fn open_expand_flow_with_native_token() { .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 30u64, //valid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(30u64), //valid epoch Asset { info: AssetInfo::NativeToken { denom: "uwhale".to_string(), @@ -4982,29 +5103,29 @@ fn open_expand_flow_with_native_token() { result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { + .query_flow(incentive_addr.clone().into_inner(), FlowIdentifier::Id(1u64), |result| { let flow_response = result.unwrap(); assert_eq!( flow_response.unwrap().flow, Some(Flow { flow_id: 1, + flow_label: None, flow_creator: carol.clone(), flow_asset: Asset { info: AssetInfo::NativeToken { denom: "uwhale".to_string() }, - amount: Uint128::new(3_000u128), + amount: Uint128::new(1_000u128), }, claimed_amount: Uint128::zero(), curve: Curve::Linear, start_epoch: 10u64, - end_epoch: 30u64, + end_epoch: 19u64, emitted_tokens: Default::default(), - asset_history: Default::default(), + asset_history: BTreeMap::from_iter(vec![(11, (Uint128::new(3_000u128), 30u64))]), }) ); - }) - ; + }); } #[test] @@ -5059,61 +5180,49 @@ fn open_expand_flow_with_cw20_token() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: flow_asset.clone(), amount: Uint128::new(2_000u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { // this should succeed as we sent enough funds to cover for fee + MIN_FLOW_AMOUNT result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow, - Some(Flow { - flow_id: 1, - flow_creator: carol.clone(), - flow_asset: Asset { - info: flow_asset.clone(), - amount: Uint128::new(2_000u128), - }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 10u64, - end_epoch: 19u64, - emitted_tokens: Default::default(), - asset_history: Default::default(), - }) - ); - }) - .expand_flow( - alice.clone(), + .query_flow( incentive_addr.clone().into_inner(), - 5u64, // invalid flow id - 19u64, - Asset { - info: flow_asset.clone(), - amount: Uint128::new(1_000u128), - }, - coins(1_000u128, "uwhale".to_string()), + FlowIdentifier::Id(1u64), |result| { - let err = result.unwrap_err().downcast::().unwrap(); - match err { - ContractError::NonExistentFlow { invalid_id } => assert_eq!(invalid_id, 5u64), - _ => panic!("Wrong error type, should return ContractError::NonExistentFlow"), - } + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_label: None, + flow_creator: carol.clone(), + flow_asset: Asset { + info: flow_asset.clone(), + amount: Uint128::new(2_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 19u64, + emitted_tokens: Default::default(), + asset_history: Default::default(), + }) + ); }, ) .expand_flow( - alice.clone(), //unauthorized + alice.clone(), incentive_addr.clone().into_inner(), - 1u64, - 19u64, + FlowIdentifier::Id(5u64), // invalid flow id + Some(19u64), Asset { info: flow_asset.clone(), amount: Uint128::new(1_000u128), @@ -5122,16 +5231,18 @@ fn open_expand_flow_with_cw20_token() { |result| { let err = result.unwrap_err().downcast::().unwrap(); match err { - ContractError::Unauthorized {} => {} - _ => panic!("Wrong error type, should return ContractError::Unauthorized"), + ContractError::NonExistentFlow { invalid_identifier } => { + assert_eq!(invalid_identifier, FlowIdentifier::Id(5u64)) + } + _ => panic!("Wrong error type, should return ContractError::NonExistentFlow"), } }, ) .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 19u64, //valid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(19u64), //valid epoch Asset { info: flow_asset.clone(), amount: Uint128::new(1_000u128), @@ -5154,8 +5265,8 @@ fn open_expand_flow_with_cw20_token() { .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 19u64, //valid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(19u64), //valid epoch Asset { info: flow_asset.clone(), amount: Uint128::new(1_000u128), @@ -5178,8 +5289,8 @@ fn open_expand_flow_with_cw20_token() { .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 18u64, //invalid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(18u64), //invalid epoch Asset { info: flow_asset.clone(), amount: Uint128::new(1_000u128), @@ -5196,8 +5307,8 @@ fn open_expand_flow_with_cw20_token() { .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 19u64, //valid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(19u64), //valid epoch Asset { info: flow_asset.clone(), amount: Uint128::new(1_000u128), @@ -5207,26 +5318,34 @@ fn open_expand_flow_with_cw20_token() { result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow, - Some(Flow { - flow_id: 1, - flow_creator: carol.clone(), - flow_asset: Asset { - info: flow_asset.clone(), - amount: Uint128::new(3_000u128), - }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 10u64, - end_epoch: 19u64, - emitted_tokens: Default::default(), - asset_history: Default::default(), - }) - ); - }) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_label: None, + flow_creator: carol.clone(), + flow_asset: Asset { + info: flow_asset.clone(), + amount: Uint128::new(2_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 19u64, + emitted_tokens: Default::default(), + asset_history: BTreeMap::from_iter(vec![( + 11, + (Uint128::new(3_000u128), 19u64) + )]), + }) + ); + }, + ) .increase_allowance( carol.clone(), flow_asset_addr.clone(), @@ -5236,8 +5355,8 @@ fn open_expand_flow_with_cw20_token() { .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, // valid flow id - 30u64, //valid epoch + FlowIdentifier::Id(1u64), // valid flow id + Some(30u64), //valid epoch Asset { info: flow_asset.clone(), amount: Uint128::new(1_000u128), @@ -5247,26 +5366,34 @@ fn open_expand_flow_with_cw20_token() { result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow, - Some(Flow { - flow_id: 1, - flow_creator: carol.clone(), - flow_asset: Asset { - info: flow_asset.clone(), - amount: Uint128::new(4_000u128), - }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 10u64, - end_epoch: 30u64, - emitted_tokens: Default::default(), - asset_history: Default::default(), - }) - ); - }); + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_label: None, + flow_creator: carol.clone(), + flow_asset: Asset { + info: flow_asset.clone(), + amount: Uint128::new(2_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 19u64, + emitted_tokens: Default::default(), + asset_history: BTreeMap::from_iter(vec![( + 11, + (Uint128::new(4_000u128), 30u64) + )]), + }) + ); + }, + ); } #[test] @@ -5321,44 +5448,50 @@ fn fail_expand_ended_flow() { carol.clone(), incentive_addr.clone().into_inner(), None, - current_epoch.clone().into_inner() + 9, - Curve::Linear, + Some(current_epoch.clone().into_inner() + 9), + Some(Curve::Linear), Asset { info: flow_asset.clone(), amount: Uint128::new(2_000u128), }, + None, &vec![coin(1_000u128, "uwhale".to_string())], |result| { // this should succeed as we sent enough funds to cover for fee + MIN_FLOW_AMOUNT result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow, - Some(Flow { - flow_id: 1, - flow_creator: carol.clone(), - flow_asset: Asset { - info: flow_asset.clone(), - amount: Uint128::new(2_000u128), - }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 10u64, - end_epoch: 19u64, - emitted_tokens: Default::default(), - asset_history: Default::default(), - }) - ); - }) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow, + Some(Flow { + flow_id: 1, + flow_label: None, + flow_creator: carol.clone(), + flow_asset: Asset { + info: flow_asset.clone(), + amount: Uint128::new(2_000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 10u64, + end_epoch: 19u64, + emitted_tokens: Default::default(), + asset_history: Default::default(), + }) + ); + }, + ) .create_epochs_on_fee_distributor(20u64, vec![]) .expand_flow( carol.clone(), incentive_addr.clone().into_inner(), - 1u64, - 50u64, + FlowIdentifier::Id(1u64), + Some(50u64), Asset { info: flow_asset.clone(), amount: Uint128::new(1_000u128), @@ -5376,7 +5509,7 @@ fn fail_expand_ended_flow() { } #[test] -fn expand_flow_verify_rewards() { +fn open_expand_flow_with_default_values() { let mut suite = TestingSuite::default_with_balances(vec![ coin(5_000_000_000u128, "uwhale".to_string()), coin(50_000_000_000u128, "usdc".to_string()), @@ -5459,287 +5592,278 @@ fn expand_flow_verify_rewards() { alice.clone(), incentive_addr.clone().into_inner(), None, - flow_end_epoch.clone(), - Curve::Linear, + None, + None, Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, amount: Uint128::new(1_000_000_000u128), }, + Some("alias".to_string()), &vec![coin(1_000_000_000u128, "usdc"), coin(1_000u128, "uwhale")], |result| { result.unwrap(); }, ) - .create_epochs_on_fee_distributor(4, vec![incentive_addr.clone().into_inner()]) // epoch is 15 now, half way of the duration of the flow - .query_rewards( + .query_flow( incentive_addr.clone().into_inner(), - carol.clone(), + FlowIdentifier::Id(1u64), |result| { - println!("result -> {:?}", result); - + let flow = result.unwrap().unwrap().flow.unwrap(); assert_eq!( - result.unwrap().rewards, - vec![Asset { - info: AssetInfo::NativeToken { - denom: "usdc".to_string(), + flow, + Flow { + flow_id: 1u64, + flow_label: Some("alias".to_string()), + flow_creator: alice.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), }, - amount: Uint128::new(500_000_000u128), - },] + claimed_amount: Default::default(), + curve: Curve::Linear, + start_epoch: 11u64, + end_epoch: 25u64, + emitted_tokens: Default::default(), + asset_history: Default::default(), + } ); }, ) - .query_funds( - carol.clone(), - AssetInfo::NativeToken { - denom: "usdc".to_string(), + .expand_flow( + alice.clone(), + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + None, + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), }, + vec![coin(1_000_000_000u128, "uwhale")], |result| { - *carol_usdc_funds.borrow_mut() = result; + let err = result.unwrap_err().downcast::().unwrap(); + + match err { + ContractError::FlowAssetNotSent {} => {} + _ => panic!("Wrong error type, should return ContractError::FlowAssetNotSent"), + } }, ) - .claim( - incentive_addr.clone().into_inner(), + .expand_flow( carol.clone(), + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + None, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), + }, + vec![coin(1_000_000_000u128, "usdc")], |result| { result.unwrap(); }, ) - .query_funds( - carol.clone(), - AssetInfo::NativeToken { - denom: "usdc".to_string(), - }, - |result| { - assert_eq!( - result, - carol_usdc_funds - .clone() - .into_inner() - .checked_add(Uint128::new(500_000_000u128)) - .unwrap(), - ); - *carol_usdc_funds.borrow_mut() = result; - }, - ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow.unwrap().claimed_amount, - Uint128::new(500_000_000u128) - ); - }); - - // move 3 more epochs, so carol should have 300 more to claim - suite - .set_time(time.plus_seconds(129600u64)) - .create_epochs_on_fee_distributor(3, vec![incentive_addr.clone().into_inner()]) - .query_rewards( - incentive_addr.clone().into_inner(), - carol.clone(), - |result| { - assert_eq!( - result.unwrap().rewards, - vec![Asset { - info: AssetInfo::NativeToken { - denom: "usdc".to_string(), - }, - amount: Uint128::new(300_000_000u128), - },] - ); - }, - ) - // move 2 more epochs, so carol should have an additional 200_000_000usdc to claim. - .create_epochs_on_fee_distributor(2, vec![incentive_addr.clone().into_inner()]) - .query_rewards( + .query_flow( incentive_addr.clone().into_inner(), - carol.clone(), + FlowIdentifier::Id(1u64), |result| { + let flow = result.unwrap().unwrap().flow.unwrap(); assert_eq!( - result.unwrap().rewards, - vec![Asset { - info: AssetInfo::NativeToken { - denom: "usdc".to_string(), + flow, + Flow { + flow_id: 1u64, + flow_label: Some("alias".to_string()), + flow_creator: alice.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), }, - amount: Uint128::new(500_000_000u128), - },] + claimed_amount: Default::default(), + curve: Curve::Linear, + start_epoch: 11u64, + end_epoch: 25u64, + emitted_tokens: Default::default(), + asset_history: vec![(12, (Uint128::new(2_000_000_000u128), 25u64))] + .into_iter() + .collect(), + } ); }, ) - .claim( - incentive_addr.clone().into_inner(), - carol.clone(), - |result| { - result.unwrap(); - }, - ) - .query_funds( - carol.clone(), - AssetInfo::NativeToken { - denom: "usdc".to_string(), - }, - |result| { - assert_eq!( - result, - carol_usdc_funds - .clone() - .into_inner() - .checked_add(Uint128::new(500_000_000u128)) - .unwrap(), - ); - *carol_usdc_funds.borrow_mut() = result; - }, - ); - - // expand the flow now - suite .expand_flow( alice.clone(), incentive_addr.clone().into_inner(), - 1u64, - flow_end_epoch.clone() + 20, + FlowIdentifier::Id(1u64), + None, Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(4_000_000_000u128), + amount: Uint128::new(1_000_000_000u128), }, - vec![coin(4_000_000_000u128, "usdc")], + vec![coin(1_000_000_000u128, "usdc")], |result| { result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - - let mut emitted_tokens: HashMap = HashMap::new(); - - for i in 1..=10 { - emitted_tokens.insert(i + 10, Uint128::from(100_000_000u64 * i)); - } - - let flow_res = flow_response.unwrap().flow.unwrap(); - - assert_eq!(flow_res.flow_id, 1u64); - assert_eq!(flow_res.flow_creator, alice.clone()); - assert_eq!( - flow_res.flow_asset, - Asset { - info: AssetInfo::NativeToken { - denom: "usdc".to_string(), - }, - amount: Uint128::new(5_000_000_000u128), - } - ); - assert_eq!(flow_res.claimed_amount, Uint128::new(1_000_000_000u128)); - assert_eq!(flow_res.curve, Curve::Linear); - assert_eq!(flow_res.start_epoch, 11u64); - assert_eq!(flow_res.end_epoch, flow_end_epoch + 20); - - println!("flow finishes at: {:?}", flow_end_epoch + 20); - - let mut vec1: Vec<(&u64, &Uint128)> = emitted_tokens.iter().collect(); - let mut vec2: Vec<(&u64, &Uint128)> = flow_res.emitted_tokens.iter().collect(); - vec1.sort(); - vec2.sort(); - - assert_eq!(vec1, vec2); - }); - - suite.query_current_epoch(|result| { - *current_epoch.borrow_mut() = result.unwrap().epoch.id.u64(); - }); - - // move 1 epoch, so carol should have 200_000_000usdc to claim - suite - .create_epochs_on_fee_distributor(1, vec![incentive_addr.clone().into_inner()]) - .query_rewards( + .query_flow( incentive_addr.clone().into_inner(), - carol.clone(), + FlowIdentifier::Id(1u64), |result| { + let flow = result.unwrap().unwrap().flow.unwrap(); assert_eq!( - result.unwrap().rewards, - vec![Asset { - info: AssetInfo::NativeToken { - denom: "usdc".to_string(), + flow, + Flow { + flow_id: 1u64, + flow_label: Some("alias".to_string()), + flow_creator: alice.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), }, - amount: Uint128::new(200_000_000u128), - },] + claimed_amount: Default::default(), + curve: Curve::Linear, + start_epoch: 11u64, + end_epoch: 25u64, + emitted_tokens: Default::default(), + asset_history: vec![(12, (Uint128::new(3_000_000_000u128), 25u64))] + .into_iter() + .collect(), + } ); }, ) .create_epochs_on_fee_distributor(9, vec![incentive_addr.clone().into_inner()]) - .query_rewards( + .expand_flow( + alice.clone(), incentive_addr.clone().into_inner(), - carol.clone(), + FlowIdentifier::Label("alias".to_string()), + None, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), + }, + vec![coin(1_000_000_000u128, "usdc")], |result| { - assert_eq!( - result.unwrap().rewards, - vec![Asset { - info: AssetInfo::NativeToken { - denom: "usdc".to_string(), - }, - amount: Uint128::new(2_000_000_000u128), - },] - ); + result.unwrap(); }, ) - .create_epochs_on_fee_distributor(20, vec![incentive_addr.clone().into_inner()]) - .query_rewards( + .query_flow( incentive_addr.clone().into_inner(), - carol.clone(), + FlowIdentifier::Id(1u64), |result| { + let flow = result.unwrap().unwrap().flow.unwrap(); assert_eq!( - result.unwrap().rewards, - vec![Asset { - info: AssetInfo::NativeToken { - denom: "usdc".to_string(), + flow, + Flow { + flow_id: 1u64, + flow_label: Some("alias".to_string()), + flow_creator: alice.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), }, - amount: Uint128::new(4_000_000_000u128), - },] + claimed_amount: Default::default(), + curve: Curve::Linear, + start_epoch: 11u64, + end_epoch: 25u64, + emitted_tokens: Default::default(), + asset_history: vec![ + (12, (Uint128::new(3_000_000_000u128), 25u64)), + (21, (Uint128::new(4_000_000_000u128), 25u64)), + ] + .into_iter() + .collect(), + } ); }, ) - .claim( + .create_epochs_on_fee_distributor(1, vec![incentive_addr.clone().into_inner()]) + .expand_flow( + alice.clone(), incentive_addr.clone().into_inner(), - carol.clone(), + FlowIdentifier::Label("alias".to_string()), + None, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), + }, + vec![coin(1_000_000_000u128, "usdc")], |result| { result.unwrap(); }, ) - .query_funds( - carol.clone(), - AssetInfo::NativeToken { - denom: "usdc".to_string(), - }, + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), |result| { + let flow = result.unwrap().unwrap().flow.unwrap(); assert_eq!( - result, - carol_usdc_funds - .clone() - .into_inner() - .checked_add(Uint128::new(4_000_000_000u128)) - .unwrap(), + flow, + Flow { + flow_id: 1u64, + flow_label: Some("alias".to_string()), + flow_creator: alice.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), + }, + claimed_amount: Default::default(), + curve: Curve::Linear, + start_epoch: 11u64, + end_epoch: 25u64, //expanded as it goes beyond the epoch expansion BUFFER. + emitted_tokens: Default::default(), + asset_history: vec![ + (12, (Uint128::new(3_000_000_000u128), 25u64)), + (21, (Uint128::new(4_000_000_000u128), 25u64)), + (22, (Uint128::new(5_000_000_000u128), 39u64)), + ] + .into_iter() + .collect(), + } ); - *carol_usdc_funds.borrow_mut() = result; }, ); +} - // let's create a new flow and try to mess up things - suite.query_current_epoch(|result| { - *current_epoch.borrow_mut() = result.unwrap().epoch.id.u64(); - }); +#[test] +fn open_expand_flow_verify_rewards() { + let mut suite = TestingSuite::default_with_balances(vec![ + coin(5_000_000_000u128, "uwhale".to_string()), + coin(50_000_000_000u128, "usdc".to_string()), + coin(5_000_000_000u128, "ampWHALE".to_string()), + coin(5_000_000_000u128, "bWHALE".to_string()), + ]); + let alice = suite.creator(); + let carol = suite.senders[2].clone(); - println!( - "current epoch for new flow: {:?}", - current_epoch.clone().into_inner() - ); + suite.instantiate_default_native_fee().create_lp_tokens(); let incentive_asset = AssetInfo::NativeToken { - denom: "bWHALE".to_string(), + denom: "ampWHALE".to_string(), }; + let incentive_addr = RefCell::new(Addr::unchecked("")); + suite .create_incentive(alice.clone(), incentive_asset.clone(), |result| { result.unwrap(); @@ -5754,42 +5878,18 @@ fn expand_flow_verify_rewards() { assert_eq!(config.lp_asset, incentive_asset.clone()); }); - // both alice and carol will open positions - - let open_position_alice = incentive::OpenPosition { - amount: Uint128::new(3_000u128), - unbonding_duration: 86400u64, - }; - - let open_position_carol = incentive::OpenPosition { + let open_position = incentive::OpenPosition { amount: Uint128::new(1_000u128), unbonding_duration: 86400u64, }; - - // alice has 3_000_000_000bWHALE, carol has 1_000_000_000bWHALE - // in %, that is 75% and 25% respectively - - let flow_2_end_epoch = current_epoch.clone().into_inner() + 10; - suite .open_incentive_position( carol.clone(), incentive_addr.clone().into_inner(), - open_position_carol.amount, - open_position_carol.unbonding_duration, - None, - vec![coin(1_000u128, "bWHALE".to_string())], - |result| { - result.unwrap(); - }, - ) - .open_incentive_position( - alice.clone(), - incentive_addr.clone().into_inner(), - open_position_alice.amount, - open_position_alice.unbonding_duration, + open_position.amount, + open_position.unbonding_duration, None, - vec![coin(3_000u128, "bWHALE".to_string())], + vec![coin(1_000u128, "ampWHALE".to_string())], |result| { result.unwrap(); }, @@ -5802,84 +5902,219 @@ fn expand_flow_verify_rewards() { result.unwrap().positions.first().unwrap(), &incentive::QueryPosition::OpenPosition { amount: Uint128::new(1_000u128), - unbonding_duration: open_position_carol.unbonding_duration, + unbonding_duration: open_position.unbonding_duration, weight: Uint128::new(1_000u128), } ); }, + ); + + suite + .open_incentive_position( + alice.clone(), + incentive_addr.clone().into_inner(), + open_position.amount, + open_position.unbonding_duration, + None, + vec![coin(1_000u128, "ampWHALE".to_string())], + |result| { + result.unwrap(); + }, ) .query_positions( incentive_addr.clone().into_inner(), - alice.clone(), + carol.clone(), |result| { assert_eq!( result.unwrap().positions.first().unwrap(), &incentive::QueryPosition::OpenPosition { - amount: Uint128::new(3_000u128), - unbonding_duration: open_position_alice.unbonding_duration, - weight: Uint128::new(3_000u128), + amount: Uint128::new(1_000u128), + unbonding_duration: open_position.unbonding_duration, + weight: Uint128::new(1_000u128), } ); }, - ) + ); + + let time = Timestamp::from_seconds(1684766796u64); + suite.set_time(time); + + let current_epoch = RefCell::new(0u64); + suite + .create_epochs_on_fee_distributor(10, vec![incentive_addr.clone().into_inner()]) + .query_current_epoch(|result| { + *current_epoch.borrow_mut() = result.unwrap().epoch.id.u64(); + }); + + let carol_usdc_funds = RefCell::new(Uint128::zero()); + let alice_usdc_funds = RefCell::new(Uint128::zero()); + println!("CURRENT_EPOCH -> {:?}", current_epoch); + + suite .open_incentive_flow( alice.clone(), incentive_addr.clone().into_inner(), None, - flow_2_end_epoch.clone(), - Curve::Linear, + Some(21u64), + None, Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(1_000_000_000u128), + amount: Uint128::new(10_000u128), }, - &vec![coin(1_000_000_000u128, "usdc"), coin(1_000u128, "uwhale")], + Some("alias".to_string()), + &vec![coin(10_000u128, "usdc"), coin(1_000u128, "uwhale")], |result| { result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - assert_eq!( - flow_response.unwrap().flow.unwrap().flow_asset.amount, - Uint128::new(1_000_000_000u128) - ); - }) - // epoch 52 - .create_epochs_on_fee_distributor(2, vec![incentive_addr.clone().into_inner()]) - .query_current_epoch_rewards_share( + .query_flow( incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow = result.unwrap().unwrap().flow.unwrap(); + assert_eq!( + flow, + Flow { + flow_id: 1u64, + flow_label: Some("alias".to_string()), + flow_creator: alice.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(10_000u128), + }, + claimed_amount: Default::default(), + curve: Curve::Linear, + start_epoch: 11u64, + end_epoch: 21u64, + emitted_tokens: Default::default(), + asset_history: Default::default(), + } + ); + }, + ) + .create_epochs_on_fee_distributor(6, vec![incentive_addr.clone().into_inner()]) + .query_funds( alice.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, |result| { - let rewards_share = result.unwrap(); - assert_eq!(rewards_share.share, Decimal256::percent(75)); + *alice_usdc_funds.borrow_mut() = result; }, ) - .query_current_epoch_rewards_share( + .claim( incentive_addr.clone().into_inner(), - carol.clone(), + alice.clone(), |result| { - let rewards_share = result.unwrap(); - println!("-----query rewards next"); - assert_eq!(rewards_share.share, Decimal256::percent(25)); + result.unwrap(); }, ) - .query_rewards( + .query_funds( + alice.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + assert_eq!( + result, + alice_usdc_funds + .clone() + .into_inner() + .checked_add(Uint128::new(3_500u128)) + .unwrap(), + ); + }, + ) + .query_flow( incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow.unwrap().claimed_amount, + Uint128::new(3_500u128) + ); + }, + ) + .expand_flow( alice.clone(), + incentive_addr.clone().into_inner(), + FlowIdentifier::Label("alias".to_string()), + None, + Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::new(1_000_000_000u128), + }, + vec![coin(1_000_000_000u128, "uwhale")], |result| { + let err = result.unwrap_err().downcast::().unwrap(); + + match err { + ContractError::FlowAssetNotSent {} => {} + _ => panic!("Wrong error type, should return ContractError::FlowAssetNotSent"), + } + }, + ) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + None, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(31_000u128), + }, + vec![coin(31_000u128, "usdc")], + |result| { + result.unwrap(); + }, + ) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow = result.unwrap().unwrap().flow.unwrap(); assert_eq!( - result.unwrap().rewards, - vec![Asset { - info: AssetInfo::NativeToken { - denom: "usdc".to_string(), + flow, + Flow { + flow_id: 1u64, + flow_label: Some("alias".to_string()), + flow_creator: alice.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(10_000u128), }, - amount: Uint128::new(150_000_000u128), - },] + claimed_amount: Uint128::new(3_500u128), + curve: Curve::Linear, + start_epoch: 11u64, + end_epoch: 21u64, + emitted_tokens: HashMap::from_iter(vec![ + (11, Uint128::new(1_000u128)), + (12, Uint128::new(2_000u128)), + (13, Uint128::new(3_000u128)), + (14, Uint128::new(4_000u128)), + (15, Uint128::new(5_000u128)), + (16, Uint128::new(6_000u128)), + (17, Uint128::new(7_000u128)), + ]), + asset_history: vec![(18, (Uint128::new(41_000u128), 35u64))] + .into_iter() + .collect(), + } ); }, ) + .create_epochs_on_fee_distributor(10, vec![incentive_addr.clone().into_inner()]) .query_funds( alice.clone(), AssetInfo::NativeToken { @@ -5907,102 +6142,225 @@ fn expand_flow_verify_rewards() { alice_usdc_funds .clone() .into_inner() - .checked_add(Uint128::new(150_000_000u128)) + .checked_add(Uint128::new(10_000u128)) .unwrap(), ); - *alice_usdc_funds.borrow_mut() = result; }, ) .query_rewards( incentive_addr.clone().into_inner(), carol.clone(), |result| { + println!("carol rewards: {:?}", result); assert_eq!( result.unwrap().rewards, vec![Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(50_000_000u128), + amount: Uint128::new(13_500u128), },] ); }, - ); - - // let's expand the flow now - - suite + ) + //claim with carol + .query_funds( + carol.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + *carol_usdc_funds.borrow_mut() = result; + }, + ) + .claim( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + println!("carol claim result: {:?}", result); + result.unwrap(); + }, + ) + .query_funds( + carol.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + println!("carol funds: {:?}", result); + assert_eq!( + result, + carol_usdc_funds + .clone() + .into_inner() + .checked_add(Uint128::new(13_500u128)) + .unwrap(), + ); + }, + ) .expand_flow( alice.clone(), incentive_addr.clone().into_inner(), - 1u64, - flow_2_end_epoch.clone() + 10, + FlowIdentifier::Id(1u64), + Some(40u64), Asset { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(4_000_000_000u128), + amount: Uint128::new(46_000u128), }, - vec![coin(4_000_000_000u128, "usdc")], + vec![coin(46_000u128, "usdc")], |result| { result.unwrap(); }, ) - .query_flow(incentive_addr.clone().into_inner(), 1u64, |result| { - let flow_response = result.unwrap(); - - let mut emitted_tokens: HashMap = HashMap::new(); - - for i in 50..=52 { - emitted_tokens.insert(i, Uint128::from(100_000_000u64 * (i - 49))); - } - - let flow_res = flow_response.unwrap().flow.unwrap(); - - assert_eq!(flow_res.flow_id, 1u64); - assert_eq!(flow_res.flow_creator, alice.clone()); - assert_eq!( - flow_res.flow_asset, - Asset { - info: AssetInfo::NativeToken { - denom: "usdc".to_string(), - }, - amount: Uint128::new(5_000_000_000u128), - } - ); - assert_eq!(flow_res.claimed_amount, Uint128::new(150_000_000u128)); - assert_eq!(flow_res.curve, Curve::Linear); - assert_eq!(flow_res.start_epoch, 50u64); - assert_eq!(flow_res.end_epoch, flow_2_end_epoch + 10); - - println!("flow finishes at: {:?}", flow_2_end_epoch + 10); - - let mut vec1: Vec<(&u64, &Uint128)> = emitted_tokens.iter().collect(); - let mut vec2: Vec<(&u64, &Uint128)> = flow_res.emitted_tokens.iter().collect(); - - println!("vec1: {:?}", vec1); - vec1.sort(); - vec2.sort(); - - assert_eq!(vec1, vec2); - }); - - //todo we need to fix the rewards calculation for the epochs before the expansion was done - // so maybe we can do something like the expansion applies from a certain epoch onwards and compute the - // new emissions based on the total + expanded amount? - suite.query_rewards( - incentive_addr.clone().into_inner(), - carol.clone(), - |result| { - assert_eq!( - result.unwrap().rewards, - vec![Asset { - info: AssetInfo::NativeToken { - denom: "usdc".to_string(), - }, - amount: Uint128::new(50_000_000u128), - },] - ); - }, - ); + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow = result.unwrap().unwrap().flow.unwrap(); + assert_eq!( + flow, + Flow { + flow_id: 1u64, + flow_label: Some("alias".to_string()), + flow_creator: alice.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(10_000u128), + }, + claimed_amount: Uint128::new(27_000u128), + curve: Curve::Linear, + start_epoch: 11u64, + end_epoch: 21u64, + emitted_tokens: HashMap::from_iter(vec![ + (11, Uint128::new(1_000u128)), + (12, Uint128::new(2_000u128)), + (13, Uint128::new(3_000u128)), + (14, Uint128::new(4_000u128)), + (15, Uint128::new(5_000u128)), + (16, Uint128::new(6_000u128)), + (17, Uint128::new(7_000u128)), + (18, Uint128::new(9_000u128)), + (19, Uint128::new(11_000u128)), + (20, Uint128::new(13_000u128)), + (21, Uint128::new(15_000u128)), + (22, Uint128::new(17_000u128)), + (23, Uint128::new(19_000u128)), + (24, Uint128::new(21_000u128)), + (25, Uint128::new(23_000u128)), + (26, Uint128::new(25_000u128)), + (27, Uint128::new(27_000u128)), + ]), + asset_history: vec![ + (18, (Uint128::new(41_000u128), 35u64)), + (28, (Uint128::new(87_000u128), 40u64)), + ] + .into_iter() + .collect(), + } + ); + }, + ) + .create_epochs_on_fee_distributor(13, vec![incentive_addr.clone().into_inner()]) + .query_rewards( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + println!("carol rewards: {:?}", result); + assert_eq!( + result.unwrap().rewards, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(30_000u128), + },] + ); + }, + ) + .query_rewards( + incentive_addr.clone().into_inner(), + alice.clone(), + |result| { + println!("carol rewards: {:?}", result); + assert_eq!( + result.unwrap().rewards, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(30_000u128), + },] + ); + }, + ) + //claim with carol + .query_funds( + carol.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + *carol_usdc_funds.borrow_mut() = result; + }, + ) + .claim( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + result.unwrap(); + }, + ) + .query_funds( + carol.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + assert_eq!( + result, + carol_usdc_funds + .clone() + .into_inner() + .checked_add(Uint128::new(30_000u128)) + .unwrap(), + ); + }, + ) + //claim with alice + .query_funds( + alice.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + *alice_usdc_funds.borrow_mut() = result; + }, + ) + .claim( + incentive_addr.clone().into_inner(), + alice.clone(), + |result| { + result.unwrap(); + }, + ) + .query_funds( + alice.clone(), + AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + |result| { + assert_eq!( + result, + alice_usdc_funds + .clone() + .into_inner() + .checked_add(Uint128::new(30_000u128)) + .unwrap(), + ); + }, + ); } diff --git a/contracts/liquidity_hub/pool-network/incentive/src/tests/suite.rs b/contracts/liquidity_hub/pool-network/incentive/src/tests/suite.rs index 63a8921d..77ef069a 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/tests/suite.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/tests/suite.rs @@ -5,8 +5,8 @@ use cw_multi_test::{App, AppBuilder, AppResponse, BankKeeper, Executor}; use white_whale::fee_distributor::EpochResponse; use white_whale::pool_network::asset::{Asset, AssetInfo}; use white_whale::pool_network::incentive::{ - Curve, Flow, FlowResponse, GlobalWeightResponse, PositionsResponse, RewardsResponse, - RewardsShareResponse, + Curve, Flow, FlowIdentifier, FlowResponse, GlobalWeightResponse, PositionsResponse, + RewardsResponse, RewardsShareResponse, }; use white_whale::pool_network::incentive_factory::{ IncentiveResponse, IncentivesResponse, InstantiateMsg, @@ -362,9 +362,10 @@ impl TestingSuite { sender: Addr, incentive_addr: Addr, start_epoch: Option, - end_epoch: u64, - curve: Curve, + end_epoch: Option, + curve: Option, flow_asset: Asset, + flow_label: Option, funds: &Vec, result: impl Fn(Result), ) -> &mut Self { @@ -373,6 +374,7 @@ impl TestingSuite { end_epoch, curve, flow_asset, + flow_label, }; result( @@ -387,10 +389,10 @@ impl TestingSuite { &mut self, sender: Addr, incentive_addr: Addr, - flow_id: u64, + flow_identifier: FlowIdentifier, result: impl Fn(Result), ) -> &mut Self { - let msg = white_whale::pool_network::incentive::ExecuteMsg::CloseFlow { flow_id }; + let msg = white_whale::pool_network::incentive::ExecuteMsg::CloseFlow { flow_identifier }; result( self.app @@ -567,14 +569,14 @@ impl TestingSuite { &mut self, sender: Addr, incentive_addr: Addr, - flow_id: u64, - end_epoch: u64, + flow_identifier: FlowIdentifier, + end_epoch: Option, flow_asset: Asset, funds: Vec, result: impl Fn(Result), ) -> &mut Self { let msg = white_whale::pool_network::incentive::ExecuteMsg::ExpandFlow { - flow_id, + flow_identifier, end_epoch, flow_asset, }; @@ -679,12 +681,16 @@ impl TestingSuite { pub(crate) fn query_flow( &mut self, incentive_addr: Addr, - flow_id: u64, + flow_identifier: FlowIdentifier, result: impl Fn(StdResult>), ) -> &mut Self { let flow_response: StdResult> = self.app.wrap().query_wasm_smart( incentive_addr, - &white_whale::pool_network::incentive::QueryMsg::Flow { flow_id }, + &white_whale::pool_network::incentive::QueryMsg::Flow { + flow_identifier, + start_epoch: None, + end_epoch: None, + }, ); result(flow_response); @@ -695,11 +701,16 @@ impl TestingSuite { pub(crate) fn query_flows( &mut self, incentive_addr: Addr, + start_epoch: Option, + end_epoch: Option, result: impl Fn(StdResult>), ) -> &mut Self { let flows_response: StdResult> = self.app.wrap().query_wasm_smart( incentive_addr, - &white_whale::pool_network::incentive::QueryMsg::Flows {}, + &white_whale::pool_network::incentive::QueryMsg::Flows { + start_epoch, + end_epoch, + }, ); result(flows_response); diff --git a/packages/white-whale/src/pool_network/incentive.rs b/packages/white-whale/src/pool_network/incentive.rs index 97763034..532a318f 100644 --- a/packages/white-whale/src/pool_network/incentive.rs +++ b/packages/white-whale/src/pool_network/incentive.rs @@ -1,8 +1,9 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Decimal256, Uint128}; use std::collections::{BTreeMap, HashMap}; use std::fmt; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Decimal256, Uint128}; + use crate::pool_network::asset::{Asset, AssetInfo}; #[cw_serde] @@ -19,23 +20,25 @@ pub enum ExecuteMsg { TakeGlobalWeightSnapshot {}, /// Opens a new liquidity flow OpenFlow { - /// The epoch at which the flow should start. - /// - /// If unspecified, the flow will start at the current epoch. + /// The epoch at which the flow will start. If unspecified, the flow will start at the + /// current epoch. start_epoch: Option, - /// The epoch at which the flow should end. - end_epoch: u64, - /// The type of distribution curve. - curve: Curve, + /// The epoch at which the flow should end. If unspecified, the flow will default to end at + /// 14 epochs from the current one. + end_epoch: Option, + /// The type of distribution curve. If unspecified, the distribution will be linear. + curve: Option, /// The asset to be distributed in this flow. flow_asset: Asset, + /// If set, the label will be used to identify the flow, in addition to the flow_id. + flow_label: Option, }, /// Closes an existing liquidity flow. /// /// Sender of the message must either be the contract admin or the creator of the flow. CloseFlow { - /// The id of the flow to close. - flow_id: u64, + /// The identifier of the flow to close. + flow_identifier: FlowIdentifier, }, /// Creates a new position to earn flow rewards. OpenPosition { @@ -74,11 +77,11 @@ pub enum ExecuteMsg { Claim {}, /// Expands an existing flow. ExpandFlow { - /// The id of the flow to expand. - flow_id: u64, - /// The epoch at which the flow should end. - end_epoch: u64, - /// The asset to be expanded in this flow. + /// The identifier of the flow to expand, whether an id or a label. + flow_identifier: FlowIdentifier, + /// The epoch at which the flow should end. If not set, the flow will be expanded a default value of 14 epochs. + end_epoch: Option, + /// The asset to expand this flow with. flow_asset: Asset, }, } @@ -91,6 +94,8 @@ pub struct MigrateMsg {} pub struct Flow { /// A unique identifier of the flow. pub flow_id: u64, + /// An alternative flow label. + pub flow_label: Option, /// The account which opened the flow and can manage it. pub flow_creator: Addr, /// The asset the flow was created to distribute. @@ -98,7 +103,8 @@ pub struct Flow { /// The amount of the `flow_asset` that has been claimed so far. pub claimed_amount: Uint128, /// The type of curve the flow has. - pub curve: Curve, //todo not doing anything for now + pub curve: Curve, + //todo not doing anything for now /// The epoch at which the flow starts. pub start_epoch: u64, /// The epoch at which the flow ends. @@ -107,7 +113,7 @@ pub struct Flow { pub emitted_tokens: HashMap, /// A map containing the amount of tokens it was expanded to at a given epoch. This is used /// to calculate the right amount of tokens to distribute at a given epoch when a flow is expanded. - pub asset_history: BTreeMap, + pub asset_history: BTreeMap, } /// Represents a position that accumulates flow rewards. @@ -148,15 +154,28 @@ pub enum QueryMsg { /// Retrieves the current contract configuration. #[returns(ConfigResponse)] Config {}, - /// Retrieves a specific flow. + /// Retrieves a specific flow. If start_epoch and end_epoch are set, the asset_history and + /// emitted_tokens will be filtered to only include epochs within the range. The maximum gap between + /// the start_epoch and end_epoch is 100 epochs. #[returns(FlowResponse)] Flow { /// The id of the flow to find. - flow_id: u64, + flow_identifier: FlowIdentifier, + /// If set, filters the asset_history and emitted_tokens to only include epochs from start_epoch. + start_epoch: Option, + /// If set, filters the asset_history and emitted_tokens to only include epochs until end_epoch. + end_epoch: Option, }, - /// Retrieves the current flows. + /// Retrieves the current flows. If start_epoch and end_epoch are set, the asset_history and + /// emitted_tokens will be filtered to only include epochs within the range. The maximum gap between + /// the start_epoch and end_epoch is 100 epochs. #[returns(FlowsResponse)] - Flows {}, + Flows { + /// If set, filters the asset_history and emitted_tokens to only include epochs from start_epoch. + start_epoch: Option, + /// If set, filters the asset_history and emitted_tokens to only include epochs until end_epoch. + end_epoch: Option, + }, /// Retrieves the positions for an address. #[returns(PositionsResponse)] Positions { @@ -285,3 +304,18 @@ pub struct RewardsShareResponse { pub share: Decimal256, pub epoch_id: u64, } + +#[cw_serde] +pub enum FlowIdentifier { + Id(u64), + Label(String), +} + +impl fmt::Display for FlowIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FlowIdentifier::Id(flow_id) => write!(f, "flow_id: {}", flow_id), + FlowIdentifier::Label(flow_label) => write!(f, "flow_label: {}", flow_label), + } + } +} From a8a92e59c5871023bc43c6dc5518d07d3fad7be5 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Thu, 21 Sep 2023 11:12:28 +0100 Subject: [PATCH 06/22] chore: add tests to cover new flow expansion reset logic --- .../pool-network/incentive/src/contract.rs | 2 +- .../incentive/src/execute/expand_flow.rs | 55 ++- .../pool-network/incentive/src/migrations.rs | 6 +- .../incentive/src/tests/helpers.rs | 315 +++++++++++------- .../incentive/src/tests/integration.rs | 253 +++++++++++++- 5 files changed, 483 insertions(+), 148 deletions(-) diff --git a/contracts/liquidity_hub/pool-network/incentive/src/contract.rs b/contracts/liquidity_hub/pool-network/incentive/src/contract.rs index 8dde6792..9a78b23b 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/contract.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/contract.rs @@ -173,7 +173,7 @@ pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result FLOW_EXPANSION_LIMIT { + // if the flow is being reset, shift the start_epoch to the current epoch, clear the map histories, + // and make the flow_asset the remaining amount that has not been claimed. + + FLOWS.remove(deps.storage, (flow.start_epoch, flow.flow_id)); + + let flow_amount_default_value = (flow_asset.amount, 0u64); + + let (_, (flow_amount, _)) = flow + .asset_history + .last_key_value() + .unwrap_or((&0u64, &flow_amount_default_value)); + + flow.flow_asset = Asset { + info: flow_asset.info.clone(), + amount: flow_amount.saturating_sub(flow.claimed_amount), + }; + + flow.start_epoch = current_epoch; + flow.end_epoch = expanded_end_epoch; + flow.claimed_amount = Uint128::zero(); + flow.asset_history.clear(); + flow.emitted_tokens.clear(); + + attributes.push(("flow reset", "true".to_string())); + } + // expand amount and end_epoch for the flow. The expansion happens from the next epoch. let next_epoch = current_epoch.checked_add(1u64).map_or_else( || { @@ -129,18 +163,6 @@ pub fn expand_flow( // default to the original flow asset amount let expanded_amount = get_flow_asset_amount_at_epoch(&flow, current_epoch); - // - // let expanded_amount = if flow.asset_history.is_empty() { - // flow.flow_asset.amount - // } else { - // flow.asset_history.range(..=current_epoch).rev().next() - // }; - // - // let expanded_amount = flow - // .asset_history - // - // .get(¤t_epoch) - // .unwrap_or(&flow.flow_asset.amount); flow.asset_history.insert( next_epoch, @@ -157,13 +179,14 @@ pub fn expand_flow( .sum::() .checked_add(flow.flow_asset.amount)?; - Ok(Response::default().add_attributes(vec![ - ("action", "expand_flow".to_string()), - ("flow_id", flow_identifier.to_string()), + attributes.append(&mut vec![ + ("flow_id", flow.flow_id.to_string()), ("end_epoch", end_epoch.to_string()), ("expanding_flow_asset", flow_asset.to_string()), ("total_flow_asset", total_flow_asset.to_string()), - ])) + ]); + + Ok(Response::default().add_attributes(attributes)) } else { Err(ContractError::NonExistentFlow { invalid_identifier: flow_identifier, diff --git a/contracts/liquidity_hub/pool-network/incentive/src/migrations.rs b/contracts/liquidity_hub/pool-network/incentive/src/migrations.rs index 9824b828..1a4c4f82 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/migrations.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/migrations.rs @@ -1,3 +1,5 @@ +#![cfg(not(tarpaulin_include))] + use std::collections::BTreeMap; // currently a stub file @@ -7,8 +9,8 @@ use cosmwasm_std::{DepsMut, StdError}; use crate::queries::get_flows; use crate::state::FLOWS; -/// Migrates to version 1.0.5, which introduces the [Flow] field asset_history. -pub(crate) fn migrate_to_v105(deps: DepsMut) -> Result<(), StdError> { +/// Migrates to version 1.0.6, which introduces the [Flow] field asset_history. +pub(crate) fn migrate_to_v106(deps: DepsMut) -> Result<(), StdError> { let mut flows = get_flows(deps.as_ref(), None, None)?; // add the asset_history field to all available flows diff --git a/contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs b/contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs index 7b05bbe9..b6ef9f2c 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/tests/helpers.rs @@ -1,128 +1,193 @@ -#[cfg(test)] -mod tests { - use super::*; - use crate::helpers::get_flow_asset_amount_at_epoch; - use cosmwasm_std::{Addr, Uint128}; - use std::collections::{BTreeMap, HashMap}; - use white_whale::pool_network::asset::{Asset, AssetInfo}; - use white_whale::pool_network::incentive::{Curve, Flow}; - - #[test] - fn test_get_flow_asset_amount_at_epoch_with_expansion() { - let mut asset_history = BTreeMap::new(); - asset_history.insert(0, (Uint128::from(10000u128), 105u64)); - asset_history.insert(7, (Uint128::from(20000u128), 110u64)); - asset_history.insert(10, (Uint128::from(50000u128), 115u64)); - let flow = Flow { - flow_id: 1, - flow_label: None, - flow_creator: Addr::unchecked("creator"), - flow_asset: Asset { - info: AssetInfo::NativeToken { - denom: "uwhale".to_string(), - }, - amount: Uint128::from(10000u128), +use std::collections::{BTreeMap, HashMap}; + +use cosmwasm_std::{Addr, Uint128}; + +use white_whale::pool_network::asset::{Asset, AssetInfo}; +use white_whale::pool_network::incentive::{Curve, Flow}; + +use crate::helpers::{get_filtered_flow, get_flow_asset_amount_at_epoch}; + +#[test] +fn test_get_flow_asset_amount_at_epoch_with_expansion() { + let mut asset_history = BTreeMap::new(); + asset_history.insert(0, (Uint128::from(10000u128), 105u64)); + asset_history.insert(7, (Uint128::from(20000u128), 110u64)); + asset_history.insert(10, (Uint128::from(50000u128), 115u64)); + let flow = Flow { + flow_id: 1, + flow_label: None, + flow_creator: Addr::unchecked("creator"), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + amount: Uint128::from(10000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 0, + end_epoch: 100, + emitted_tokens: HashMap::new(), + asset_history, + }; + + // Before any change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 0), + Uint128::from(10000u128) + ); + + // After first change but before second change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 6), + Uint128::from(10000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 7), + Uint128::from(20000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 9), + Uint128::from(20000u128) + ); + + // After second change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 10), + Uint128::from(50000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 11), + Uint128::from(50000u128) + ); + + // After the end epoch + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 101), + Uint128::from(50000u128) + ); +} + +#[test] +fn test_get_flow_asset_amount_at_epoch_without_expansion() { + let asset_history = BTreeMap::new(); + + let flow = Flow { + flow_id: 1, + flow_label: None, + flow_creator: Addr::unchecked("creator"), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 0, - end_epoch: 100, - emitted_tokens: HashMap::new(), - asset_history, - }; - - // Before any change - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 0), - Uint128::from(10000u128) - ); - - // After first change but before second change - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 6), - Uint128::from(10000u128) - ); - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 7), - Uint128::from(20000u128) - ); - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 9), - Uint128::from(20000u128) - ); - - // After second change - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 10), - Uint128::from(50000u128) - ); - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 11), - Uint128::from(50000u128) - ); - - // After the end epoch - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 101), - Uint128::from(50000u128) - ); - } - #[test] - fn test_get_flow_asset_amount_at_epoch_without_expansion() { - let asset_history = BTreeMap::new(); - - let flow = Flow { - flow_id: 1, - flow_label: None, - flow_creator: Addr::unchecked("creator"), - flow_asset: Asset { - info: AssetInfo::NativeToken { - denom: "uwhale".to_string(), - }, - amount: Uint128::from(10000u128), + amount: Uint128::from(10000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 0, + end_epoch: 100, + emitted_tokens: HashMap::new(), + asset_history, + }; + + // Before any change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 0), + Uint128::from(10000u128) + ); + + // After first change but before second change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 6), + Uint128::from(10000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 7), + Uint128::from(10000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 9), + Uint128::from(10000u128) + ); + + // After second change + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 10), + Uint128::from(10000u128) + ); + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 11), + Uint128::from(10000u128) + ); + + // After the end epoch + assert_eq!( + get_flow_asset_amount_at_epoch(&flow, 101), + Uint128::from(10000u128) + ); +} + +#[test] +fn get_filtered_flow_cases() { + let flow = Flow { + flow_id: 1, + flow_label: None, + flow_creator: Addr::unchecked("creator"), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "uwhale".to_string(), }, - claimed_amount: Uint128::zero(), - curve: Curve::Linear, - start_epoch: 0, - end_epoch: 100, - emitted_tokens: HashMap::new(), - asset_history, - }; - - // Before any change - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 0), - Uint128::from(10000u128) - ); - - // After first change but before second change - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 6), - Uint128::from(10000u128) - ); - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 7), - Uint128::from(10000u128) - ); - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 9), - Uint128::from(10000u128) - ); - - // After second change - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 10), - Uint128::from(10000u128) - ); - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 11), - Uint128::from(10000u128) - ); - - // After the end epoch - assert_eq!( - get_flow_asset_amount_at_epoch(&flow, 101), - Uint128::from(10000u128) - ); - } + amount: Uint128::from(10000u128), + }, + claimed_amount: Uint128::zero(), + curve: Curve::Linear, + start_epoch: 1, + end_epoch: 100, + emitted_tokens: HashMap::from_iter((1..105).map(|i| { + ( + i, + (Uint128::from(10000u128) + .checked_add(Uint128::from(i)) + .unwrap()), + ) + })), + asset_history: BTreeMap::from_iter((1..105).map(|i| { + ( + i, + ( + Uint128::from(10000u128) + .checked_add(Uint128::from(i)) + .unwrap(), + 105u64, + ), + ) + })), + }; + + assert!(flow.asset_history.get(&104).is_some()); + + let filtered_flow = get_filtered_flow(flow.clone(), None, None).unwrap(); + assert!(filtered_flow.asset_history.get(&104).is_none()); + assert_eq!(filtered_flow.emitted_tokens.len(), 101usize); + assert_eq!(filtered_flow.asset_history.len(), 101usize); + + let filtered_flow = get_filtered_flow(flow.clone(), Some(55u64), None).unwrap(); + assert!(filtered_flow.asset_history.get(&54).is_none()); + assert_eq!(filtered_flow.emitted_tokens.len(), 50usize); + assert_eq!(filtered_flow.asset_history.len(), 50usize); + + let filtered_flow = get_filtered_flow(flow.clone(), Some(110), None).unwrap(); + assert!(filtered_flow.asset_history.is_empty()); + assert!(filtered_flow.emitted_tokens.is_empty()); + + let filtered_flow = get_filtered_flow(flow.clone(), Some(11u64), Some(30u64)).unwrap(); + assert!(filtered_flow.asset_history.get(&10).is_none()); + assert!(filtered_flow.emitted_tokens.get(&35).is_none()); + assert_eq!(filtered_flow.emitted_tokens.len(), 20usize); + assert_eq!(filtered_flow.asset_history.len(), 20usize); + + let filtered_flow = get_filtered_flow(flow.clone(), None, Some(50u64)).unwrap(); + assert!(filtered_flow.asset_history.get(&1).is_some()); + assert_eq!(filtered_flow.emitted_tokens.len(), 50usize); + assert_eq!(filtered_flow.asset_history.len(), 50usize); } diff --git a/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs b/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs index 5a897349..6e2aa1aa 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/tests/integration.rs @@ -5582,11 +5582,8 @@ fn open_expand_flow_with_default_values() { *current_epoch.borrow_mut() = result.unwrap().epoch.id.u64(); }); - let carol_usdc_funds = RefCell::new(Uint128::zero()); - let alice_usdc_funds = RefCell::new(Uint128::zero()); println!("CURRENT_EPOCH -> {:?}", current_epoch); - let flow_end_epoch = current_epoch.clone().into_inner() + 10; suite .open_incentive_flow( alice.clone(), @@ -5672,7 +5669,7 @@ fn open_expand_flow_with_default_values() { ) .query_flow( incentive_addr.clone().into_inner(), - FlowIdentifier::Id(1u64), + FlowIdentifier::Label("alias".to_string()), |result| { let flow = result.unwrap().unwrap().flow.unwrap(); assert_eq!( @@ -6364,3 +6361,251 @@ fn open_expand_flow_verify_rewards() { }, ); } + +#[test] +fn open_expand_flow_over_expand_limit() { + let mut suite = TestingSuite::default_with_balances(vec![ + coin(5_000_000_000u128, "uwhale".to_string()), + coin(50_000_000_000u128, "usdc".to_string()), + coin(5_000_000_000u128, "ampWHALE".to_string()), + coin(5_000_000_000u128, "bWHALE".to_string()), + ]); + let alice = suite.creator(); + let carol = suite.senders[2].clone(); + + suite.instantiate_default_native_fee().create_lp_tokens(); + + let incentive_asset = AssetInfo::NativeToken { + denom: "ampWHALE".to_string(), + }; + + let incentive_addr = RefCell::new(Addr::unchecked("")); + let flow_ref = RefCell::new(Flow { + flow_id: 0, + flow_label: None, + flow_creator: alice.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "".to_string(), + }, + amount: Default::default(), + }, + claimed_amount: Default::default(), + curve: Curve::Linear, + start_epoch: 0, + end_epoch: 0, + emitted_tokens: Default::default(), + asset_history: Default::default(), + }); + + suite + .create_incentive(alice.clone(), incentive_asset.clone(), |result| { + result.unwrap(); + }) + .query_incentive(incentive_asset.clone(), |result| { + let incentive = result.unwrap(); + assert!(incentive.is_some()); + *incentive_addr.borrow_mut() = incentive.unwrap(); + }) + .query_incentive_config(incentive_addr.clone().into_inner(), |result| { + let config = result.unwrap(); + assert_eq!(config.lp_asset, incentive_asset.clone()); + }); + + let open_position = incentive::OpenPosition { + amount: Uint128::new(1_000u128), + unbonding_duration: 86400u64, + }; + suite + .open_incentive_position( + carol.clone(), + incentive_addr.clone().into_inner(), + open_position.amount, + open_position.unbonding_duration, + None, + vec![coin(1_000u128, "ampWHALE".to_string())], + |result| { + result.unwrap(); + }, + ) + .query_positions( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + assert_eq!( + result.unwrap().positions.first().unwrap(), + &incentive::QueryPosition::OpenPosition { + amount: Uint128::new(1_000u128), + unbonding_duration: open_position.unbonding_duration, + weight: Uint128::new(1_000u128), + } + ); + }, + ); + + let current_epoch = RefCell::new(0u64); + suite + .create_epochs_on_fee_distributor(10, vec![incentive_addr.clone().into_inner()]) + .query_current_epoch(|result| { + *current_epoch.borrow_mut() = result.unwrap().epoch.id.u64(); + }); + + println!("CURRENT_EPOCH -> {:?}", current_epoch); + + suite + .open_incentive_flow( + alice.clone(), + incentive_addr.clone().into_inner(), + None, + Some(21u64), + None, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(10_000u128), + }, + Some("alias".to_string()), + &vec![coin(10_000u128, "usdc"), coin(1_000u128, "uwhale")], + |result| { + result.unwrap(); + }, + ) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow = result.unwrap().unwrap().flow.unwrap(); + *flow_ref.borrow_mut() = flow.clone(); + assert_eq!( + flow, + Flow { + flow_id: 1u64, + flow_label: Some("alias".to_string()), + flow_creator: alice.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(10_000u128), + }, + claimed_amount: Default::default(), + curve: Curve::Linear, + start_epoch: 11u64, + end_epoch: 21u64, + emitted_tokens: Default::default(), + asset_history: Default::default(), + } + ); + }, + ); + + let claimed_rewards = RefCell::new(Uint128::zero()); + + let mut i = 0; + + // expand the flow until it gets reset + while flow_ref.clone().into_inner().start_epoch == 11u64 { + suite + .create_epochs_on_fee_distributor(1, vec![incentive_addr.clone().into_inner()]) + .expand_flow( + carol.clone(), + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + None, + Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(1_000u128), + }, + vec![coin(1_000u128, "usdc")], + |result| { + result.unwrap(); + }, + ) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow = result.unwrap().unwrap().flow.unwrap(); + *flow_ref.borrow_mut() = flow.clone(); + }, + ); + + if i <= 170 { + suite + .claim( + incentive_addr.clone().into_inner(), + carol.clone(), + |result| { + result.unwrap(); + }, + ) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + println!("flow_response -> {:?}", flow_response); + *claimed_rewards.borrow_mut() = + flow_response.unwrap().flow.unwrap().claimed_amount; + }, + ); + } + + i += 1; + } + + suite.query_current_epoch(|result| { + *current_epoch.borrow_mut() = result.unwrap().epoch.id.u64(); + }); + + suite + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Id(1u64), + |result| { + let flow_response = result.unwrap(); + assert_eq!( + flow_response.unwrap().flow.unwrap(), + Flow { + flow_id: 1u64, + flow_label: Some("alias".to_string()), + flow_creator: alice.clone(), + flow_asset: Asset { + info: AssetInfo::NativeToken { + denom: "usdc".to_string(), + }, + amount: Uint128::new(12_005u128), // 184k - ~173k claimed + }, + claimed_amount: Default::default(), + curve: Curve::Linear, + start_epoch: 186u64, + end_epoch: 203u64, + emitted_tokens: Default::default(), + asset_history: BTreeMap::from_iter(vec![( + 187, + (Uint128::new(13_005), 203u64) + )]), + } + ); + }, + ) + .close_incentive_flow( + alice.clone(), + incentive_addr.clone().into_inner(), + FlowIdentifier::Label("alias".to_string()), + |result| { + result.unwrap(); + }, + ) + .query_flow( + incentive_addr.clone().into_inner(), + FlowIdentifier::Label("alias".to_string()), + |result| { + let flow_response = result.unwrap(); + assert!(flow_response.is_none()); + }, + ); +} From 5972d459fdd381574d8c7889450b5649d14e2351 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Thu, 21 Sep 2023 14:37:55 +0100 Subject: [PATCH 07/22] fix: migration fix --- .../pool-network/incentive/src/contract.rs | 2 +- .../pool-network/incentive/src/migrations.rs | 60 ++++++++++++++++--- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/contracts/liquidity_hub/pool-network/incentive/src/contract.rs b/contracts/liquidity_hub/pool-network/incentive/src/contract.rs index 9a78b23b..bd525433 100644 --- a/contracts/liquidity_hub/pool-network/incentive/src/contract.rs +++ b/contracts/liquidity_hub/pool-network/incentive/src/contract.rs @@ -172,7 +172,7 @@ pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result Result<(), StdError> { - let mut flows = get_flows(deps.as_ref(), None, None)?; + #[cw_serde] + pub struct FlowV104 { + /// A unique identifier of the flow. + pub flow_id: u64, + /// The account which opened the flow and can manage it. + pub flow_creator: Addr, + /// The asset the flow was created to distribute. + pub flow_asset: Asset, + /// The amount of the `flow_asset` that has been claimed so far. + pub claimed_amount: Uint128, + /// The type of curve the flow has. + pub curve: Curve, //todo not doing anything for now + /// The epoch at which the flow starts. + pub start_epoch: u64, + /// The epoch at which the flow ends. + pub end_epoch: u64, + /// emitted tokens + pub emitted_tokens: HashMap, + } + + // load old flows map + pub const FLOWS_V104: Map<(EpochId, FlowId), FlowV104> = Map::new("flows"); + + let flows = FLOWS_V104 + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()? + .into_iter() + .map(|(_, flow)| flow) + .collect::>(); // add the asset_history field to all available flows - for flow in flows.iter_mut() { - flow.asset_history = BTreeMap::new(); - flow.flow_label = None; + for f in flows.iter() { + let flow = Flow { + flow_id: f.clone().flow_id, + flow_label: None, //new field + flow_creator: f.clone().flow_creator, + flow_asset: f.clone().flow_asset, + claimed_amount: f.clone().claimed_amount, + curve: f.clone().curve, + start_epoch: f.clone().start_epoch, + end_epoch: f.clone().end_epoch, + emitted_tokens: f.clone().emitted_tokens, + asset_history: BTreeMap::new(), //new field + }; - FLOWS.save(deps.storage, (flow.start_epoch, flow.flow_id), flow)?; + FLOWS.save(deps.storage, (f.start_epoch, f.flow_id), &flow)?; } Ok(()) From e56f1cb39508c4620b3ece58a44f179d9d67c5b4 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Wed, 18 Oct 2023 12:12:46 +0100 Subject: [PATCH 08/22] fix: max spread assertion --- .../terraswap_pair/src/commands.rs | 11 +- .../terraswap_pair/src/helpers.rs | 78 +- .../terraswap_pair/src/tests/testing.rs | 197 +- .../vault-manager/schema/raw/execute.json | 1310 +++++++++ .../vault-manager/schema/raw/instantiate.json | 133 + .../vault-manager/schema/raw/query.json | 205 ++ .../schema/raw/response_to_config.json | 151 ++ .../schema/raw/response_to_ownership.json | 95 + .../raw/response_to_payback_amount.json | 100 + .../schema/raw/response_to_share.json | 89 + .../schema/raw/response_to_vault.json | 135 + .../schema/raw/response_to_vaults.json | 135 + .../vault-manager/schema/vault-manager.json | 2362 +++++++++++++++++ 13 files changed, 4818 insertions(+), 183 deletions(-) create mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/execute.json create mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/instantiate.json create mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/query.json create mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_config.json create mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_ownership.json create mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_payback_amount.json create mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_share.json create mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_vault.json create mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_vaults.json create mode 100644 contracts/liquidity_hub/vault-manager/schema/vault-manager.json diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs index 5ebb6e9c..806c8cbf 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs @@ -403,15 +403,18 @@ pub fn swap( amount: swap_computation.return_amount, }; + let fees = swap_computation + .swap_fee_amount + .checked_add(swap_computation.protocol_fee_amount)? + .checked_add(swap_computation.burn_fee_amount)?; // check max spread limit if exist + helpers::assert_max_spread( belief_price, max_spread, - offer_asset.clone(), - return_asset.clone(), + offer_asset.amount, + return_asset.amount.checked_add(fees)?, swap_computation.spread_amount, - offer_decimal, - ask_decimal, )?; let receiver = to.unwrap_or_else(|| sender.clone()); diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs index cebfd1a2..480a1376 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs @@ -1,16 +1,17 @@ use std::cmp::Ordering; use std::ops::Mul; +use std::str::FromStr; use cosmwasm_schema::cw_serde; +#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +use cosmwasm_std::CosmosMsg; use cosmwasm_std::{ - to_binary, Decimal, Decimal256, DepsMut, Env, ReplyOn, Response, StdError, StdResult, Storage, - SubMsg, Uint128, Uint256, WasmMsg, + to_binary, Decimal, Decimal256, DepsMut, Env, Fraction, ReplyOn, Response, StdError, StdResult, + Storage, SubMsg, Uint128, Uint256, WasmMsg, }; use cw20::MinterResponse; use cw_storage_plus::Item; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] -use cosmwasm_std::CosmosMsg; use white_whale::pool_network::asset::{Asset, AssetInfo, AssetInfoRaw, PairType}; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::MsgCreateDenom; @@ -322,58 +323,32 @@ pub struct OfferAmountComputation { pub burn_fee_amount: Uint128, } +/// Default swap slippage in case max_spread is not specified +pub const DEFAULT_SLIPPAGE: &str = "0.005"; +/// Cap on the maximum swap slippage that is allowed. If max_spread goes over this limit, it will +/// be capped to this value. +pub const MAX_ALLOWED_SLIPPAGE: &str = "0.5"; + /// If `belief_price` and `max_spread` both are given, /// we compute new spread else we just use pool network /// spread to check `max_spread` pub fn assert_max_spread( belief_price: Option, max_spread: Option, - offer_asset: Asset, - return_asset: Asset, + offer_amount: Uint128, + return_amount: Uint128, spread_amount: Uint128, - offer_decimal: u8, - return_decimal: u8, ) -> Result<(), ContractError> { - let (offer_amount, return_amount, spread_amount): (Uint256, Uint256, Uint256) = - match offer_decimal.cmp(&return_decimal) { - Ordering::Greater => { - let diff_decimal = 10u64.pow((offer_decimal - return_decimal).into()); - - ( - offer_asset.amount.into(), - return_asset - .amount - .checked_mul(Uint128::from(diff_decimal))? - .into(), - spread_amount - .checked_mul(Uint128::from(diff_decimal))? - .into(), - ) - } - Ordering::Less => { - let diff_decimal = 10u64.pow((return_decimal - offer_decimal).into()); - - ( - offer_asset - .amount - .checked_mul(Uint128::from(diff_decimal))? - .into(), - return_asset.amount.into(), - spread_amount.into(), - ) - } - Ordering::Equal => ( - offer_asset.amount.into(), - return_asset.amount.into(), - spread_amount.into(), - ), - }; - - if let (Some(max_spread), Some(belief_price)) = (max_spread, belief_price) { - let belief_price: Decimal256 = belief_price.into(); - let max_spread: Decimal256 = max_spread.into(); - - let expected_return = offer_amount * (Decimal256::one() / belief_price); + let max_spread: Decimal256 = max_spread + .unwrap_or(Decimal::from_str(DEFAULT_SLIPPAGE)?) + .min(Decimal::from_str(MAX_ALLOWED_SLIPPAGE)?) + .into(); + + if let Some(belief_price) = belief_price { + let expected_return = offer_amount + * belief_price + .inv() + .ok_or_else(|| StdError::generic_err("Belief price can't be zero"))?; let spread_amount = expected_return.saturating_sub(return_amount); if return_amount < expected_return @@ -381,11 +356,8 @@ pub fn assert_max_spread( { return Err(ContractError::MaxSpreadAssertion {}); } - } else if let Some(max_spread) = max_spread { - let max_spread: Decimal256 = max_spread.into(); - if Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread { - return Err(ContractError::MaxSpreadAssertion {}); - } + } else if Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread { + return Err(ContractError::MaxSpreadAssertion {}); } Ok(()) diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs index c22e73bd..1d25ff70 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs @@ -348,175 +348,120 @@ fn test_initialization_invalid_fees() { #[test] fn test_max_spread() { - let offer_asset_info = AssetInfo::NativeToken { - denom: "offer_asset".to_string(), - }; - let ask_asset_info = AssetInfo::NativeToken { - denom: "ask_asset_info".to_string(), - }; - assert_max_spread( - Some(Decimal::from_ratio(1_200u128, 1u128)), + Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), Some(Decimal::percent(1)), - Asset { - info: offer_asset_info.clone(), - amount: Uint128::from(1_200_000_000u128), - }, - Asset { - info: ask_asset_info.clone(), - amount: Uint128::from(989_999u128), - }, + Uint128::from(1200_000_000u128), + Uint128::from(989_999u128), Uint128::zero(), - 6u8, - 6u8, ) .unwrap_err(); + // same example as above but using 6 and 18 decimal places assert_max_spread( - Some(Decimal::from_ratio(1_200u128, 1u128)), + Some(Decimal::from_ratio( + 1200_000_000u128, + 1_000_000_000_000_000_000u128, + )), Some(Decimal::percent(1)), - Asset { - info: offer_asset_info.clone(), - amount: Uint128::from(1_200_000_000u128), - }, - Asset { - info: ask_asset_info.clone(), - amount: Uint128::from(990_000u128), - }, + Uint128::from(1200_000_000u128), + Uint128::from(989_999_900_000_000_000u128), + Uint128::zero(), + ) + .unwrap_err(); + + assert_max_spread( + Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), + None, // defaults to 0.5% + Uint128::from(1200_000_000u128), + Uint128::from(995_000u128), // all good Uint128::zero(), - 6u8, - 6u8, ) .unwrap(); assert_max_spread( - None, - Some(Decimal::percent(1)), - Asset { - info: offer_asset_info.clone(), - amount: Uint128::zero(), - }, - Asset { - info: ask_asset_info.clone(), - amount: Uint128::from(989_999u128), - }, - Uint128::from(1_0001u128), - 6u8, - 6u8, + Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), + None, // defaults to 0.5% + Uint128::from(1200_000_000u128), + Uint128::from(990_000u128), // fails + Uint128::zero(), ) .unwrap_err(); assert_max_spread( - None, + Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), Some(Decimal::percent(1)), - Asset { - info: offer_asset_info, - amount: Uint128::zero(), - }, - Asset { - info: ask_asset_info, - amount: Uint128::from(990_000u128), - }, - Uint128::from(10_000u128), - 6u8, - 6u8, + Uint128::from(1200_000_000u128), + Uint128::from(990_000u128), + Uint128::zero(), ) .unwrap(); -} - -#[test] -fn test_max_spread_with_diff_decimal() { - let token_addr = "ask_asset_info".to_string(); - - let mut deps = mock_dependencies(&[]); - deps.querier.with_token_balances(&[( - &token_addr, - &[( - &MOCK_CONTRACT_ADDR.to_string(), - &Uint128::from(10000000000u64), - )], - )]); - let offer_asset_info = AssetInfo::NativeToken { - denom: "offer_asset".to_string(), - }; - let ask_asset_info = AssetInfo::Token { - contract_addr: token_addr.to_string(), - }; + // same example as above but using 6 and 18 decimal place assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), + Some(Decimal::from_ratio( + 1200_000_000u128, + 1_000_000_000_000_000_000u128, + )), Some(Decimal::percent(1)), - Asset { - info: offer_asset_info.clone(), - amount: Uint128::from(1200000000u128), - }, - Asset { - info: ask_asset_info.clone(), - amount: Uint128::from(100000000u128), - }, + Uint128::from(1200_000_000u128), + Uint128::from(990_000__000_000_000_000u128), + Uint128::zero(), + ) + .unwrap(); + + // similar example with 18 and 6 decimal places + assert_max_spread( + Some(Decimal::from_ratio( + 1_000_000_000_000_000_000u128, + 10_000_000u128, + )), + Some(Decimal::percent(2)), + Uint128::from(1_000_000_000_000_000_000u128), + Uint128::from(9_800_000u128), Uint128::zero(), - 6u8, - 8u8, ) .unwrap(); + // same as before but error because spread is 1% assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), + Some(Decimal::from_ratio( + 1_000_000_000_000_000_000u128, + 10_000_000u128, + )), Some(Decimal::percent(1)), - Asset { - info: offer_asset_info, - amount: Uint128::from(1200000000u128), - }, - Asset { - info: ask_asset_info, - amount: Uint128::from(98999999u128), - }, + Uint128::from(1_000_000_000_000_000_000u128), + Uint128::from(9_800_000u128), Uint128::zero(), - 6u8, - 8u8, ) .unwrap_err(); - let offer_asset_info = AssetInfo::Token { - contract_addr: token_addr, - }; - let ask_asset_info = AssetInfo::NativeToken { - denom: "offer_asset".to_string(), - }; + assert_max_spread( + None, + Some(Decimal::percent(1)), + Uint128::zero(), + Uint128::from(989_999u128), + Uint128::from(10001u128), + ) + .unwrap_err(); assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), + None, Some(Decimal::percent(1)), - Asset { - info: offer_asset_info.clone(), - amount: Uint128::from(120000000000u128), - }, - Asset { - info: ask_asset_info.clone(), - amount: Uint128::from(1000000u128), - }, Uint128::zero(), - 8u8, - 6u8, + Uint128::from(990_000u128), + Uint128::from(10000u128), ) .unwrap(); assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), - Some(Decimal::percent(1)), - Asset { - info: offer_asset_info, - amount: Uint128::from(120000000000u128), - }, - Asset { - info: ask_asset_info, - amount: Uint128::from(989999u128), - }, + Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), + Some(Decimal::percent(60)), // this will default to 50% + Uint128::from(1200_000_000u128), + Uint128::from(989_999u128), Uint128::zero(), - 8u8, - 6u8, ) - .unwrap_err(); + .unwrap(); } #[test] diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/execute.json b/contracts/liquidity_hub/vault-manager/schema/raw/execute.json new file mode 100644 index 00000000..e83e325d --- /dev/null +++ b/contracts/liquidity_hub/vault-manager/schema/raw/execute.json @@ -0,0 +1,1310 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "The execution messages", + "oneOf": [ + { + "description": "Creates a new vault given the asset info the vault should manage deposits and withdrawals for and the fees", + "type": "object", + "required": [ + "create_vault" + ], + "properties": { + "create_vault": { + "type": "object", + "required": [ + "asset_info", + "fees" + ], + "properties": { + "asset_info": { + "$ref": "#/definitions/AssetInfo" + }, + "fees": { + "$ref": "#/definitions/VaultFee" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes a vault given its [AssetInfo]", + "type": "object", + "required": [ + "remove_vault" + ], + "properties": { + "remove_vault": { + "type": "object", + "required": [ + "asset_info" + ], + "properties": { + "asset_info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Updates a vault config", + "type": "object", + "required": [ + "update_vault_fees" + ], + "properties": { + "update_vault_fees": { + "type": "object", + "required": [ + "vault_asset_info", + "vault_fee" + ], + "properties": { + "vault_asset_info": { + "$ref": "#/definitions/AssetInfo" + }, + "vault_fee": { + "$ref": "#/definitions/VaultFee" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Updates the configuration of the vault manager. If a field is not specified, it will not be modified.", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "cw20_lp_code_id": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "deposit_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "flash_loan_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "vault_creation_fee": { + "anyOf": [ + { + "$ref": "#/definitions/Asset" + }, + { + "type": "null" + } + ] + }, + "whale_lair_addr": { + "type": [ + "string", + "null" + ] + }, + "withdraw_enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Deposits a given asset into the vault manager.", + "type": "object", + "required": [ + "deposit" + ], + "properties": { + "deposit": { + "type": "object", + "required": [ + "asset" + ], + "properties": { + "asset": { + "$ref": "#/definitions/Asset" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraws from the vault manager. Used when the LP token is a token manager token.", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "description": "Retrieves the desired `asset` and runs the `payload`, paying the required amount back to the vault after running the messages in the payload, and returning the profit to the sender.", + "type": "object", + "required": [ + "flash_loan" + ], + "properties": { + "flash_loan": { + "type": "object", + "required": [ + "asset", + "payload" + ], + "properties": { + "asset": { + "$ref": "#/definitions/Asset" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Callback message for post-processing flash-loans.", + "type": "object", + "required": [ + "callback" + ], + "properties": { + "callback": { + "$ref": "#/definitions/CallbackMsg" + } + }, + "additionalProperties": false + }, + { + "description": "Update the contract's ownership. The `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.", + "type": "object", + "required": [ + "update_ownership" + ], + "properties": { + "update_ownership": { + "$ref": "#/definitions/Action" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Action": { + "description": "Actions that can be taken to alter the contract's ownership", + "oneOf": [ + { + "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", + "type": "object", + "required": [ + "transfer_ownership" + ], + "properties": { + "transfer_ownership": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "expiry": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", + "type": "string", + "enum": [ + "accept_ownership" + ] + }, + { + "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", + "type": "string", + "enum": [ + "renounce_ownership" + ] + } + ] + }, + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "CallbackMsg": { + "description": "The callback messages available. Only callable by the vault contract itself.", + "oneOf": [ + { + "type": "object", + "required": [ + "after_flashloan" + ], + "properties": { + "after_flashloan": { + "type": "object", + "required": [ + "loan_asset", + "old_asset_balance", + "sender" + ], + "properties": { + "loan_asset": { + "$ref": "#/definitions/Asset" + }, + "old_asset_balance": { + "$ref": "#/definitions/Uint128" + }, + "sender": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "Cw20ReceiveMsg": { + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Fee": { + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This maps directly to [MsgVoteWeighted](https://github.com/cosmos/cosmos-sdk/blob/v0.45.8/proto/cosmos/gov/v1beta1/tx.proto#L66-L78) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote_weighted" + ], + "properties": { + "vote_weighted": { + "type": "object", + "required": [ + "options", + "proposal_id" + ], + "properties": { + "options": { + "type": "array", + "items": { + "$ref": "#/definitions/WeightedVoteOption" + } + }, + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VaultFee": { + "type": "object", + "required": [ + "flash_loan_fee", + "protocol_fee" + ], + "properties": { + "flash_loan_fee": { + "$ref": "#/definitions/Fee" + }, + "protocol_fee": { + "$ref": "#/definitions/Fee" + } + }, + "additionalProperties": false + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code using a predictable address derivation algorithm implemented in [`cosmwasm_std::instantiate2_address`].\n\nThis is translated to a [MsgInstantiateContract2](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L73-L96). `sender` is automatically filled with the current contract's address. `fix_msg` is automatically set to false.", + "type": "object", + "required": [ + "instantiate2" + ], + "properties": { + "instantiate2": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg", + "salt" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "salt": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "WeightedVoteOption": { + "type": "object", + "required": [ + "option", + "weight" + ], + "properties": { + "option": { + "$ref": "#/definitions/VoteOption" + }, + "weight": { + "$ref": "#/definitions/Decimal" + } + } + } + } +} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/instantiate.json b/contracts/liquidity_hub/vault-manager/schema/raw/instantiate.json new file mode 100644 index 00000000..1edb0e51 --- /dev/null +++ b/contracts/liquidity_hub/vault-manager/schema/raw/instantiate.json @@ -0,0 +1,133 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "The instantiation message", + "type": "object", + "required": [ + "lp_token_type", + "owner", + "vault_creation_fee", + "whale_lair_addr" + ], + "properties": { + "lp_token_type": { + "description": "The type of LP token to use, whether a cw20 token or a token factory token", + "allOf": [ + { + "$ref": "#/definitions/LpTokenType" + } + ] + }, + "owner": { + "description": "The owner of the contract", + "type": "string" + }, + "vault_creation_fee": { + "description": "The fee to create a vault", + "allOf": [ + { + "$ref": "#/definitions/Asset" + } + ] + }, + "whale_lair_addr": { + "description": "The whale lair address, where protocol fees are distributed", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Asset": { + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "LpTokenType": { + "description": "The type of LP token to use, whether a cw20 token or a token factory token", + "oneOf": [ + { + "type": "string", + "enum": [ + "token_factory" + ] + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/query.json b/contracts/liquidity_hub/vault-manager/schema/raw/query.json new file mode 100644 index 00000000..092d439c --- /dev/null +++ b/contracts/liquidity_hub/vault-manager/schema/raw/query.json @@ -0,0 +1,205 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "The query messages", + "oneOf": [ + { + "description": "Retrieves the configuration of the manager.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Retrieves a vault given the asset_info.", + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "required": [ + "asset_info" + ], + "properties": { + "asset_info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Retrieves the addresses for all the vaults.", + "type": "object", + "required": [ + "vaults" + ], + "properties": { + "vaults": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Retrieves the share of the assets stored in the vault that a given `lp_share` is entitled to.", + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "type": "object", + "required": [ + "lp_share" + ], + "properties": { + "lp_share": { + "$ref": "#/definitions/Asset" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Retrieves the [`Uint128`] amount that must be sent back to the contract to pay off a loan taken out.", + "type": "object", + "required": [ + "payback_amount" + ], + "properties": { + "payback_amount": { + "type": "object", + "required": [ + "asset" + ], + "properties": { + "asset": { + "$ref": "#/definitions/Asset" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Query the contract's ownership information", + "type": "object", + "required": [ + "ownership" + ], + "properties": { + "ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Asset": { + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_config.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_config.json new file mode 100644 index 00000000..dcdbe103 --- /dev/null +++ b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_config.json @@ -0,0 +1,151 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "Configuration for the contract (manager)", + "type": "object", + "required": [ + "deposit_enabled", + "flash_loan_enabled", + "lp_token_type", + "vault_creation_fee", + "whale_lair_addr", + "withdraw_enabled" + ], + "properties": { + "deposit_enabled": { + "description": "If deposits are enabled", + "type": "boolean" + }, + "flash_loan_enabled": { + "description": "If flash-loans are enabled", + "type": "boolean" + }, + "lp_token_type": { + "description": "The type of LP token to use, whether a cw20 token or a token factory token", + "allOf": [ + { + "$ref": "#/definitions/LpTokenType" + } + ] + }, + "vault_creation_fee": { + "description": "The fee to create a new vault", + "allOf": [ + { + "$ref": "#/definitions/Asset" + } + ] + }, + "whale_lair_addr": { + "description": "The whale lair contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "withdraw_enabled": { + "description": "If withdrawals are enabled", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "LpTokenType": { + "description": "The type of LP token to use, whether a cw20 token or a token factory token", + "oneOf": [ + { + "type": "string", + "enum": [ + "token_factory" + ] + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_ownership.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_ownership.json new file mode 100644 index 00000000..afe1713f --- /dev/null +++ b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_ownership.json @@ -0,0 +1,95 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Ownership_for_String", + "description": "The contract's ownership info", + "type": "object", + "properties": { + "owner": { + "description": "The contract's current owner. `None` if the ownership has been renounced.", + "type": [ + "string", + "null" + ] + }, + "pending_expiry": { + "description": "The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline.", + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "pending_owner": { + "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_payback_amount.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_payback_amount.json new file mode 100644 index 00000000..fc7261f2 --- /dev/null +++ b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_payback_amount.json @@ -0,0 +1,100 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PaybackAssetResponse", + "description": "Response for the PaybackAmount query. Contains the amount that must be paid back to the contract if taken a flashloan.", + "type": "object", + "required": [ + "asset_info", + "flash_loan_fee", + "payback_amount", + "protocol_fee" + ], + "properties": { + "asset_info": { + "description": "The asset info of the asset that must be paid back", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + }, + "flash_loan_fee": { + "description": "The amount of fee paid to vault holders", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "payback_amount": { + "description": "The total amount that must be returned. Equivalent to `amount` + `protocol_fee` + `flash_loan_fee`.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "protocol_fee": { + "description": "The amount of fee paid to the protocol", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_share.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_share.json new file mode 100644 index 00000000..3a5e1183 --- /dev/null +++ b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_share.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ShareResponse", + "description": "Response for the Share query. Contains the amount of assets that the given `lp_share` is entitled to.", + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "description": "The amount of assets that the given `lp_share` is entitled to.", + "allOf": [ + { + "$ref": "#/definitions/Asset" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Asset": { + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vault.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vault.json new file mode 100644 index 00000000..b1b0014e --- /dev/null +++ b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vault.json @@ -0,0 +1,135 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultsResponse", + "description": "Response for the vaults query", + "type": "object", + "required": [ + "vaults" + ], + "properties": { + "vaults": { + "type": "array", + "items": { + "$ref": "#/definitions/Vault" + } + } + }, + "additionalProperties": false, + "definitions": { + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Fee": { + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "Vault": { + "description": "Vault representation", + "type": "object", + "required": [ + "asset_info", + "fees", + "lp_asset" + ], + "properties": { + "asset_info": { + "description": "The asset info the vault manages", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + }, + "fees": { + "description": "The fees associated with the vault", + "allOf": [ + { + "$ref": "#/definitions/VaultFee" + } + ] + }, + "lp_asset": { + "description": "The LP asset", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "VaultFee": { + "type": "object", + "required": [ + "flash_loan_fee", + "protocol_fee" + ], + "properties": { + "flash_loan_fee": { + "$ref": "#/definitions/Fee" + }, + "protocol_fee": { + "$ref": "#/definitions/Fee" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vaults.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vaults.json new file mode 100644 index 00000000..b1b0014e --- /dev/null +++ b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vaults.json @@ -0,0 +1,135 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultsResponse", + "description": "Response for the vaults query", + "type": "object", + "required": [ + "vaults" + ], + "properties": { + "vaults": { + "type": "array", + "items": { + "$ref": "#/definitions/Vault" + } + } + }, + "additionalProperties": false, + "definitions": { + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Fee": { + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "Vault": { + "description": "Vault representation", + "type": "object", + "required": [ + "asset_info", + "fees", + "lp_asset" + ], + "properties": { + "asset_info": { + "description": "The asset info the vault manages", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + }, + "fees": { + "description": "The fees associated with the vault", + "allOf": [ + { + "$ref": "#/definitions/VaultFee" + } + ] + }, + "lp_asset": { + "description": "The LP asset", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "VaultFee": { + "type": "object", + "required": [ + "flash_loan_fee", + "protocol_fee" + ], + "properties": { + "flash_loan_fee": { + "$ref": "#/definitions/Fee" + }, + "protocol_fee": { + "$ref": "#/definitions/Fee" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/liquidity_hub/vault-manager/schema/vault-manager.json b/contracts/liquidity_hub/vault-manager/schema/vault-manager.json new file mode 100644 index 00000000..af50149a --- /dev/null +++ b/contracts/liquidity_hub/vault-manager/schema/vault-manager.json @@ -0,0 +1,2362 @@ +{ + "contract_name": "vault-manager", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "The instantiation message", + "type": "object", + "required": [ + "lp_token_type", + "owner", + "vault_creation_fee", + "whale_lair_addr" + ], + "properties": { + "lp_token_type": { + "description": "The type of LP token to use, whether a cw20 token or a token factory token", + "allOf": [ + { + "$ref": "#/definitions/LpTokenType" + } + ] + }, + "owner": { + "description": "The owner of the contract", + "type": "string" + }, + "vault_creation_fee": { + "description": "The fee to create a vault", + "allOf": [ + { + "$ref": "#/definitions/Asset" + } + ] + }, + "whale_lair_addr": { + "description": "The whale lair address, where protocol fees are distributed", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Asset": { + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "LpTokenType": { + "description": "The type of LP token to use, whether a cw20 token or a token factory token", + "oneOf": [ + { + "type": "string", + "enum": [ + "token_factory" + ] + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "The execution messages", + "oneOf": [ + { + "description": "Creates a new vault given the asset info the vault should manage deposits and withdrawals for and the fees", + "type": "object", + "required": [ + "create_vault" + ], + "properties": { + "create_vault": { + "type": "object", + "required": [ + "asset_info", + "fees" + ], + "properties": { + "asset_info": { + "$ref": "#/definitions/AssetInfo" + }, + "fees": { + "$ref": "#/definitions/VaultFee" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes a vault given its [AssetInfo]", + "type": "object", + "required": [ + "remove_vault" + ], + "properties": { + "remove_vault": { + "type": "object", + "required": [ + "asset_info" + ], + "properties": { + "asset_info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Updates a vault config", + "type": "object", + "required": [ + "update_vault_fees" + ], + "properties": { + "update_vault_fees": { + "type": "object", + "required": [ + "vault_asset_info", + "vault_fee" + ], + "properties": { + "vault_asset_info": { + "$ref": "#/definitions/AssetInfo" + }, + "vault_fee": { + "$ref": "#/definitions/VaultFee" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Updates the configuration of the vault manager. If a field is not specified, it will not be modified.", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "cw20_lp_code_id": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "deposit_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "flash_loan_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "vault_creation_fee": { + "anyOf": [ + { + "$ref": "#/definitions/Asset" + }, + { + "type": "null" + } + ] + }, + "whale_lair_addr": { + "type": [ + "string", + "null" + ] + }, + "withdraw_enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Deposits a given asset into the vault manager.", + "type": "object", + "required": [ + "deposit" + ], + "properties": { + "deposit": { + "type": "object", + "required": [ + "asset" + ], + "properties": { + "asset": { + "$ref": "#/definitions/Asset" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraws from the vault manager. Used when the LP token is a token manager token.", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "description": "Retrieves the desired `asset` and runs the `payload`, paying the required amount back to the vault after running the messages in the payload, and returning the profit to the sender.", + "type": "object", + "required": [ + "flash_loan" + ], + "properties": { + "flash_loan": { + "type": "object", + "required": [ + "asset", + "payload" + ], + "properties": { + "asset": { + "$ref": "#/definitions/Asset" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Callback message for post-processing flash-loans.", + "type": "object", + "required": [ + "callback" + ], + "properties": { + "callback": { + "$ref": "#/definitions/CallbackMsg" + } + }, + "additionalProperties": false + }, + { + "description": "Update the contract's ownership. The `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.", + "type": "object", + "required": [ + "update_ownership" + ], + "properties": { + "update_ownership": { + "$ref": "#/definitions/Action" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Action": { + "description": "Actions that can be taken to alter the contract's ownership", + "oneOf": [ + { + "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", + "type": "object", + "required": [ + "transfer_ownership" + ], + "properties": { + "transfer_ownership": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "expiry": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", + "type": "string", + "enum": [ + "accept_ownership" + ] + }, + { + "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", + "type": "string", + "enum": [ + "renounce_ownership" + ] + } + ] + }, + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "CallbackMsg": { + "description": "The callback messages available. Only callable by the vault contract itself.", + "oneOf": [ + { + "type": "object", + "required": [ + "after_flashloan" + ], + "properties": { + "after_flashloan": { + "type": "object", + "required": [ + "loan_asset", + "old_asset_balance", + "sender" + ], + "properties": { + "loan_asset": { + "$ref": "#/definitions/Asset" + }, + "old_asset_balance": { + "$ref": "#/definitions/Uint128" + }, + "sender": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "Cw20ReceiveMsg": { + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Fee": { + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This maps directly to [MsgVoteWeighted](https://github.com/cosmos/cosmos-sdk/blob/v0.45.8/proto/cosmos/gov/v1beta1/tx.proto#L66-L78) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote_weighted" + ], + "properties": { + "vote_weighted": { + "type": "object", + "required": [ + "options", + "proposal_id" + ], + "properties": { + "options": { + "type": "array", + "items": { + "$ref": "#/definitions/WeightedVoteOption" + } + }, + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VaultFee": { + "type": "object", + "required": [ + "flash_loan_fee", + "protocol_fee" + ], + "properties": { + "flash_loan_fee": { + "$ref": "#/definitions/Fee" + }, + "protocol_fee": { + "$ref": "#/definitions/Fee" + } + }, + "additionalProperties": false + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code using a predictable address derivation algorithm implemented in [`cosmwasm_std::instantiate2_address`].\n\nThis is translated to a [MsgInstantiateContract2](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L73-L96). `sender` is automatically filled with the current contract's address. `fix_msg` is automatically set to false.", + "type": "object", + "required": [ + "instantiate2" + ], + "properties": { + "instantiate2": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg", + "salt" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "salt": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "WeightedVoteOption": { + "type": "object", + "required": [ + "option", + "weight" + ], + "properties": { + "option": { + "$ref": "#/definitions/VoteOption" + }, + "weight": { + "$ref": "#/definitions/Decimal" + } + } + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "The query messages", + "oneOf": [ + { + "description": "Retrieves the configuration of the manager.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Retrieves a vault given the asset_info.", + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "required": [ + "asset_info" + ], + "properties": { + "asset_info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Retrieves the addresses for all the vaults.", + "type": "object", + "required": [ + "vaults" + ], + "properties": { + "vaults": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Retrieves the share of the assets stored in the vault that a given `lp_share` is entitled to.", + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "type": "object", + "required": [ + "lp_share" + ], + "properties": { + "lp_share": { + "$ref": "#/definitions/Asset" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Retrieves the [`Uint128`] amount that must be sent back to the contract to pay off a loan taken out.", + "type": "object", + "required": [ + "payback_amount" + ], + "properties": { + "payback_amount": { + "type": "object", + "required": [ + "asset" + ], + "properties": { + "asset": { + "$ref": "#/definitions/Asset" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Query the contract's ownership information", + "type": "object", + "required": [ + "ownership" + ], + "properties": { + "ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Asset": { + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "Configuration for the contract (manager)", + "type": "object", + "required": [ + "deposit_enabled", + "flash_loan_enabled", + "lp_token_type", + "vault_creation_fee", + "whale_lair_addr", + "withdraw_enabled" + ], + "properties": { + "deposit_enabled": { + "description": "If deposits are enabled", + "type": "boolean" + }, + "flash_loan_enabled": { + "description": "If flash-loans are enabled", + "type": "boolean" + }, + "lp_token_type": { + "description": "The type of LP token to use, whether a cw20 token or a token factory token", + "allOf": [ + { + "$ref": "#/definitions/LpTokenType" + } + ] + }, + "vault_creation_fee": { + "description": "The fee to create a new vault", + "allOf": [ + { + "$ref": "#/definitions/Asset" + } + ] + }, + "whale_lair_addr": { + "description": "The whale lair contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "withdraw_enabled": { + "description": "If withdrawals are enabled", + "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "LpTokenType": { + "description": "The type of LP token to use, whether a cw20 token or a token factory token", + "oneOf": [ + { + "type": "string", + "enum": [ + "token_factory" + ] + }, + { + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "ownership": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Ownership_for_String", + "description": "The contract's ownership info", + "type": "object", + "properties": { + "owner": { + "description": "The contract's current owner. `None` if the ownership has been renounced.", + "type": [ + "string", + "null" + ] + }, + "pending_expiry": { + "description": "The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline.", + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "pending_owner": { + "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "payback_amount": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PaybackAssetResponse", + "description": "Response for the PaybackAmount query. Contains the amount that must be paid back to the contract if taken a flashloan.", + "type": "object", + "required": [ + "asset_info", + "flash_loan_fee", + "payback_amount", + "protocol_fee" + ], + "properties": { + "asset_info": { + "description": "The asset info of the asset that must be paid back", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + }, + "flash_loan_fee": { + "description": "The amount of fee paid to vault holders", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "payback_amount": { + "description": "The total amount that must be returned. Equivalent to `amount` + `protocol_fee` + `flash_loan_fee`.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "protocol_fee": { + "description": "The amount of fee paid to the protocol", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "share": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ShareResponse", + "description": "Response for the Share query. Contains the amount of assets that the given `lp_share` is entitled to.", + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "description": "The amount of assets that the given `lp_share` is entitled to.", + "allOf": [ + { + "$ref": "#/definitions/Asset" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Asset": { + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "info": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "vault": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultsResponse", + "description": "Response for the vaults query", + "type": "object", + "required": [ + "vaults" + ], + "properties": { + "vaults": { + "type": "array", + "items": { + "$ref": "#/definitions/Vault" + } + } + }, + "additionalProperties": false, + "definitions": { + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Fee": { + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "Vault": { + "description": "Vault representation", + "type": "object", + "required": [ + "asset_info", + "fees", + "lp_asset" + ], + "properties": { + "asset_info": { + "description": "The asset info the vault manages", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + }, + "fees": { + "description": "The fees associated with the vault", + "allOf": [ + { + "$ref": "#/definitions/VaultFee" + } + ] + }, + "lp_asset": { + "description": "The LP asset", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "VaultFee": { + "type": "object", + "required": [ + "flash_loan_fee", + "protocol_fee" + ], + "properties": { + "flash_loan_fee": { + "$ref": "#/definitions/Fee" + }, + "protocol_fee": { + "$ref": "#/definitions/Fee" + } + }, + "additionalProperties": false + } + } + }, + "vaults": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultsResponse", + "description": "Response for the vaults query", + "type": "object", + "required": [ + "vaults" + ], + "properties": { + "vaults": { + "type": "array", + "items": { + "$ref": "#/definitions/Vault" + } + } + }, + "additionalProperties": false, + "definitions": { + "AssetInfo": { + "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", + "oneOf": [ + { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Fee": { + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "Vault": { + "description": "Vault representation", + "type": "object", + "required": [ + "asset_info", + "fees", + "lp_asset" + ], + "properties": { + "asset_info": { + "description": "The asset info the vault manages", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + }, + "fees": { + "description": "The fees associated with the vault", + "allOf": [ + { + "$ref": "#/definitions/VaultFee" + } + ] + }, + "lp_asset": { + "description": "The LP asset", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "VaultFee": { + "type": "object", + "required": [ + "flash_loan_fee", + "protocol_fee" + ], + "properties": { + "flash_loan_fee": { + "$ref": "#/definitions/Fee" + }, + "protocol_fee": { + "$ref": "#/definitions/Fee" + } + }, + "additionalProperties": false + } + } + } + } +} From a9d31b6df5c7d966a73dbfc9cc55c4459c445d9b Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Thu, 19 Oct 2023 16:58:51 +0100 Subject: [PATCH 09/22] feat: add max_spread property on pool router --- Cargo.lock | 8 +- .../liquidity_hub/fee_collector/Cargo.toml | 2 +- .../fee_collector/src/commands.rs | 5 +- .../src/tests/common_integration.rs | 2 +- .../fee_collector/src/tests/integration.rs | 177 ++++++++-------- .../pool-network/stableswap_3pool/Cargo.toml | 2 +- .../stableswap_3pool/src/commands.rs | 11 +- .../stableswap_3pool/src/helpers.rs | 97 ++++----- .../stableswap_3pool/src/tests/testing.rs | 197 +++++++----------- .../pool-network/terraswap_pair/Cargo.toml | 2 +- .../terraswap_pair/src/commands.rs | 2 +- .../terraswap_pair/src/helpers.rs | 15 +- .../terraswap_pair/src/tests/protocol_fees.rs | 40 ++-- .../terraswap_pair/src/tests/swap.rs | 6 +- .../terraswap_pair/src/tests/testing.rs | 6 +- .../pool-network/terraswap_router/Cargo.toml | 2 +- .../terraswap_router/src/contract.rs | 17 +- .../terraswap_router/src/operations.rs | 3 +- .../terraswap_router/src/testing/tests.rs | 19 +- .../white-whale/src/pool_network/router.rs | 5 +- 20 files changed, 295 insertions(+), 323 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e11160aa..85364201 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,7 +509,7 @@ dependencies = [ [[package]] name = "fee_collector" -version = "1.1.1" +version = "1.1.2" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1342,7 +1342,7 @@ dependencies = [ [[package]] name = "stableswap-3pool" -version = "1.2.1" +version = "1.2.2" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1434,7 +1434,7 @@ dependencies = [ [[package]] name = "terraswap-pair" -version = "1.3.1" +version = "1.3.2" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1452,7 +1452,7 @@ dependencies = [ [[package]] name = "terraswap-router" -version = "1.1.0" +version = "1.1.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/liquidity_hub/fee_collector/Cargo.toml b/contracts/liquidity_hub/fee_collector/Cargo.toml index ec4702af..50b87a89 100644 --- a/contracts/liquidity_hub/fee_collector/Cargo.toml +++ b/contracts/liquidity_hub/fee_collector/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fee_collector" -version = "1.1.1" +version = "1.1.2" authors = ["Kerber0x "] edition.workspace = true description = "Contract to collect the fees accrued by the pools and vaults in the liquidity hub" diff --git a/contracts/liquidity_hub/fee_collector/src/commands.rs b/contracts/liquidity_hub/fee_collector/src/commands.rs index 3dd3d62d..bfa14771 100644 --- a/contracts/liquidity_hub/fee_collector/src/commands.rs +++ b/contracts/liquidity_hub/fee_collector/src/commands.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - to_binary, Addr, BalanceResponse, BankQuery, Coin, CosmosMsg, DepsMut, Env, MessageInfo, - QueryRequest, ReplyOn, Response, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, + to_binary, Addr, BalanceResponse, BankQuery, Coin, CosmosMsg, Decimal, DepsMut, Env, + MessageInfo, QueryRequest, ReplyOn, Response, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, }; use cw20::{Cw20ExecuteMsg, Cw20QueryMsg}; @@ -289,6 +289,7 @@ pub fn aggregate_fees( operations, minimum_receive: None, to: None, + max_spread: Some(Decimal::percent(50u64)), })?; match offer_asset_info.clone() { diff --git a/contracts/liquidity_hub/fee_collector/src/tests/common_integration.rs b/contracts/liquidity_hub/fee_collector/src/tests/common_integration.rs index 160bb030..0d39078b 100644 --- a/contracts/liquidity_hub/fee_collector/src/tests/common_integration.rs +++ b/contracts/liquidity_hub/fee_collector/src/tests/common_integration.rs @@ -181,7 +181,7 @@ pub fn increase_allowance(app: &mut App, sender: Addr, contract_addr: Addr, spen contract_addr, &cw20::Cw20ExecuteMsg::IncreaseAllowance { spender: spender.to_string(), - amount: Uint128::new(500_000_000_000u128), + amount: Uint128::new(5_000_000_000_000u128), expires: None, }, &[], diff --git a/contracts/liquidity_hub/fee_collector/src/tests/integration.rs b/contracts/liquidity_hub/fee_collector/src/tests/integration.rs index 63ee67b1..030e1bb8 100644 --- a/contracts/liquidity_hub/fee_collector/src/tests/integration.rs +++ b/contracts/liquidity_hub/fee_collector/src/tests/integration.rs @@ -118,7 +118,7 @@ fn collect_all_factories_cw20_fees_successfully() { decimals: 6, initial_balances: vec![Cw20Coin { address: creator.clone().sender.to_string(), - amount: Uint128::new(1_000_000_000_000u128), + amount: Uint128::new(10_000_000_000_000u128), }], mint: Some(MinterResponse { minter: creator.clone().sender.to_string(), @@ -228,13 +228,13 @@ fn collect_all_factories_cw20_fees_successfully() { info: AssetInfo::Token { contract_addr: cw20_tokens[i as usize].to_string(), }, - amount: Uint128::new(500_000u128), + amount: Uint128::new(1_000_000_000_000u128), }, Asset { info: AssetInfo::Token { contract_addr: cw20_tokens[i as usize + 1].to_string(), }, - amount: Uint128::new(500_000u128), + amount: Uint128::new(1_000_000_000_000u128), }, ], slippage_tolerance: None, @@ -274,7 +274,7 @@ fn collect_all_factories_cw20_fees_successfully() { amount: Uint128::new(200_000_000_000u128), msg: to_binary(&pool_network::pair::Cw20HookMsg::Swap { belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(20u64)), to: None, }) .unwrap(), @@ -711,10 +711,10 @@ fn collect_cw20_fees_for_specific_contracts_successfully() { cw20_tokens[i].clone(), &Cw20ExecuteMsg::Send { contract: pair_tokens[i - 1].to_string(), - amount: Uint128::new(100_000_000u128), + amount: Uint128::new(100_000u128), msg: to_binary(&pool_network::pair::Cw20HookMsg::Swap { belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(30u64)), to: None, }) .unwrap(), @@ -728,10 +728,10 @@ fn collect_cw20_fees_for_specific_contracts_successfully() { cw20_tokens[i].clone(), &Cw20ExecuteMsg::Send { contract: pair_tokens[i].to_string(), - amount: Uint128::new(200_000_000_000u128), + amount: Uint128::new(200_000u128), msg: to_binary(&pool_network::pair::Cw20HookMsg::Swap { belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(30u64)), to: None, }) .unwrap(), @@ -935,7 +935,7 @@ fn collect_pools_native_fees_successfully() { let creator = mock_creator(); let balances = vec![( creator.clone().sender, - coins(1_000_000_000u128, "native".to_string()), + coins(50_000_000_000_000u128, "native".to_string()), )]; let mut app = mock_app_with_balance(balances); @@ -1031,7 +1031,7 @@ fn collect_pools_native_fees_successfully() { decimals: 6, initial_balances: vec![Cw20Coin { address: creator.clone().sender.to_string(), - amount: Uint128::new(1_000_000_000_000u128), + amount: Uint128::new(10_000_000_000_000u128), }], mint: Some(MinterResponse { minter: creator.clone().sender.to_string(), @@ -1116,13 +1116,13 @@ fn collect_pools_native_fees_successfully() { info: AssetInfo::NativeToken { denom: "native".to_string(), }, - amount: Uint128::new(500_000u128), + amount: Uint128::new(5_000_000_000_000u128), }, Asset { info: AssetInfo::Token { contract_addr: cw20_token.to_string(), }, - amount: Uint128::new(500_000u128), + amount: Uint128::new(5_000_000_000_000u128), }, ], slippage_tolerance: None, @@ -1130,7 +1130,7 @@ fn collect_pools_native_fees_successfully() { }, &[Coin { denom: "native".to_string(), - amount: Uint128::new(500_000u128), + amount: Uint128::new(5_000_000_000_000u128), }], ) .unwrap(); @@ -1676,15 +1676,15 @@ fn collect_fees_with_pagination_successfully() { info: AssetInfo::NativeToken { denom: "native".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "native".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -1695,10 +1695,10 @@ fn collect_fees_with_pagination_successfully() { cw20_token.clone(), &Cw20ExecuteMsg::Send { contract: pair_tokens[i].to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), msg: to_binary(&pool_network::pair::Cw20HookMsg::Swap { belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }) .unwrap(), @@ -3047,15 +3047,15 @@ fn collect_and_distribute_fees_successfully() { info: AssetInfo::NativeToken { denom: "uwhale".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(30u64)), to: None, }, &[Coin { denom: "uwhale".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -3069,15 +3069,15 @@ fn collect_and_distribute_fees_successfully() { info: AssetInfo::NativeToken { denom: native_token.clone().to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: native_token.clone().to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -4419,15 +4419,15 @@ fn collect_and_distribute_fees_with_expiring_epoch_successfully() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(30u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -4473,15 +4473,15 @@ fn collect_and_distribute_fees_with_expiring_epoch_successfully() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(30u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -5670,15 +5670,15 @@ fn create_epoch_unsuccessfully() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -5713,15 +5713,15 @@ fn create_epoch_unsuccessfully() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -6116,15 +6116,15 @@ fn decrease_grace_period_fee_distributor() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -6155,15 +6155,15 @@ fn decrease_grace_period_fee_distributor() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -6545,15 +6545,15 @@ fn users_cannot_claim_rewards_from_past_epochs() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -6598,15 +6598,15 @@ fn users_cannot_claim_rewards_from_past_epochs() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -6680,15 +6680,15 @@ fn users_cannot_claim_rewards_from_past_epochs() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -7105,15 +7105,15 @@ fn user_can_claim_even_when_his_weight_increases_for_past_epochs() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -7158,15 +7158,15 @@ fn user_can_claim_even_when_his_weight_increases_for_past_epochs() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -7306,15 +7306,15 @@ fn user_can_claim_even_when_his_weight_increases_for_past_epochs() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -7759,15 +7759,15 @@ fn user_weight_accounts_for_unbondings() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -7812,15 +7812,15 @@ fn user_weight_accounts_for_unbondings() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -7960,15 +7960,15 @@ fn user_weight_accounts_for_unbondings() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -8048,7 +8048,7 @@ fn user_weight_accounts_for_unbondings() { info: NativeToken { denom: "uwhale".to_string() }, - amount: Uint128::new(161u128) + amount: Uint128::new(1324u128) }] ); assert_eq!( @@ -8057,7 +8057,7 @@ fn user_weight_accounts_for_unbondings() { info: NativeToken { denom: "uwhale".to_string() }, - amount: Uint128::new(161u128) + amount: Uint128::new(1323u128) }] ); @@ -8070,7 +8070,7 @@ fn user_weight_accounts_for_unbondings() { ) .unwrap(); - let epoch_res: EpochResponse = app + let mut epoch_res: EpochResponse = app .wrap() .query_wasm_smart( fee_distributor_address.clone(), @@ -8087,7 +8087,7 @@ fn user_weight_accounts_for_unbondings() { info: NativeToken { denom: "uwhale".to_string() }, - amount: Uint128::zero() + amount: Uint128::one() }] ); assert_eq!( @@ -8096,10 +8096,15 @@ fn user_weight_accounts_for_unbondings() { info: NativeToken { denom: "uwhale".to_string() }, - amount: Uint128::new(322u128) + amount: Uint128::new(2646u128) }] ); - assert_eq!(epoch_res.epoch.claimed, epoch_res.epoch.total); + assert!( + (epoch_res.epoch.claimed.first().unwrap().amount.u128() as i128 + - epoch_res.epoch.total.first().unwrap().amount.u128() as i128) + .abs() + < 2i128 + ); //now unbond partially, check if weight is computed correctly app.execute_contract( @@ -8129,15 +8134,15 @@ fn user_weight_accounts_for_unbondings() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -8196,7 +8201,7 @@ fn user_weight_accounts_for_unbondings() { info: NativeToken { denom: "uwhale".to_string() }, - amount: Uint128::new(112u128) + amount: Uint128::new(1242u128) }] ); assert_eq!( @@ -8205,7 +8210,7 @@ fn user_weight_accounts_for_unbondings() { info: NativeToken { denom: "uwhale".to_string() }, - amount: Uint128::new(55u128) + amount: Uint128::new(620u128) }] ); } @@ -8555,15 +8560,15 @@ fn users_can_claim_even_when_global_index_was_taken_after_epoch_was_created() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); @@ -8614,15 +8619,15 @@ fn users_can_claim_even_when_global_index_was_taken_after_epoch_was_created() { info: AssetInfo::NativeToken { denom: "usdc".to_string(), }, - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(40u64)), to: None, }, &[Coin { denom: "usdc".to_string(), - amount: Uint128::new(200_000_000u128), + amount: Uint128::new(200_000u128), }], ) .unwrap(); diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/Cargo.toml b/contracts/liquidity_hub/pool-network/stableswap_3pool/Cargo.toml index 981c8641..85a07dee 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/Cargo.toml +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stableswap-3pool" -version = "1.2.1" +version = "1.2.2" authors = [ "Adam J. Weigold ", ] diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs index 709f294c..d2feb158 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs @@ -472,15 +472,18 @@ pub fn swap( amount: swap_computation.return_amount, }; + let fees = swap_computation + .swap_fee_amount + .checked_add(swap_computation.protocol_fee_amount)? + .checked_add(swap_computation.burn_fee_amount)?; + // check max spread limit if exist helpers::assert_max_spread( belief_price, max_spread, - offer_asset.clone(), - return_asset.clone(), + offer_asset.amount, + return_asset.amount.checked_add(fees)?, swap_computation.spread_amount, - offer_decimal, - ask_decimal, )?; let receiver = to.unwrap_or_else(|| sender.clone()); diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs index 7dd3cc24..14643e31 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs @@ -1,9 +1,10 @@ use std::cmp::Ordering; +use std::str::FromStr; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_binary, Decimal, Decimal256, Deps, DepsMut, Env, ReplyOn, Response, StdError, StdResult, - Storage, SubMsg, Uint128, Uint256, WasmMsg, + to_binary, Decimal, Decimal256, Deps, DepsMut, Env, Fraction, ReplyOn, Response, StdError, + StdResult, Storage, SubMsg, Uint128, Uint256, WasmMsg, }; use cw20::MinterResponse; use cw_storage_plus::Item; @@ -127,77 +128,53 @@ pub struct OfferAmountComputation { pub burn_fee_amount: Uint128, } +/// Default swap slippage in case max_spread is not specified +pub const DEFAULT_SLIPPAGE: &str = "0.01"; +/// Cap on the maximum swap slippage that is allowed. If max_spread goes over this limit, it will +/// be capped to this value. +pub const MAX_ALLOWED_SLIPPAGE: &str = "0.5"; + /// If `belief_price` and `max_spread` both are given, -/// we compute new spread else we just use terraswap +/// we compute new spread else we just use pool network /// spread to check `max_spread` pub fn assert_max_spread( belief_price: Option, max_spread: Option, - offer_asset: Asset, - return_asset: Asset, + offer_amount: Uint128, + return_amount: Uint128, spread_amount: Uint128, - offer_decimal: u8, - return_decimal: u8, ) -> Result<(), ContractError> { - let (offer_amount, return_amount, spread_amount): (Uint256, Uint256, Uint256) = - match offer_decimal.cmp(&return_decimal) { - Ordering::Greater => { - let diff_decimal = 10u64.pow((offer_decimal - return_decimal).into()); - - ( - offer_asset.amount.into(), - return_asset - .amount - .checked_mul(Uint128::from(diff_decimal))? - .into(), - spread_amount - .checked_mul(Uint128::from(diff_decimal))? - .into(), - ) - } - Ordering::Less => { - let diff_decimal = 10u64.pow((return_decimal - offer_decimal).into()); - - ( - offer_asset - .amount - .checked_mul(Uint128::from(diff_decimal))? - .into(), - return_asset.amount.into(), - spread_amount.into(), - ) - } - Ordering::Equal => ( - offer_asset.amount.into(), - return_asset.amount.into(), - spread_amount.into(), - ), - }; - - if let (Some(max_spread), Some(belief_price)) = (max_spread, belief_price) { - let belief_price: Decimal256 = belief_price.into(); - let max_spread: Decimal256 = max_spread.into(); - if max_spread > Decimal256::one() { - return Err(StdError::generic_err("max spread cannot bigger than 1").into()); - } - - let expected_return = offer_amount * (Decimal256::one() / belief_price); - let spread_amount = if expected_return > return_amount { - expected_return - return_amount - } else { - Uint256::zero() - }; + println!("assert_max_spread: belief_price: {:?}, max_spread: {:?}, offer_amount: {:?}, return_amount: {:?}, spread_amount: {:?}", belief_price, max_spread, offer_amount, return_amount, spread_amount); + + let max_spread: Decimal256 = max_spread + .unwrap_or(Decimal::from_str(DEFAULT_SLIPPAGE)?) + .min(Decimal::from_str(MAX_ALLOWED_SLIPPAGE)?) + .into(); + + println!("max_spread: {:?}", max_spread); + println!( + "Decimal256::from_ratio(spread_amount, return_amount + spread_amount) {:?}", + Decimal256::from_ratio(spread_amount, return_amount + spread_amount) + ); + println!( + "Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread: {:?}", + Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread + ); + + if let Some(belief_price) = belief_price { + let expected_return = offer_amount + * belief_price + .inv() + .ok_or_else(|| StdError::generic_err("Belief price can't be zero"))?; + let spread_amount = expected_return.saturating_sub(return_amount); if return_amount < expected_return && Decimal256::from_ratio(spread_amount, expected_return) > max_spread { return Err(ContractError::MaxSpreadAssertion {}); } - } else if let Some(max_spread) = max_spread { - let max_spread: Decimal256 = max_spread.into(); - if Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread { - return Err(ContractError::MaxSpreadAssertion {}); - } + } else if Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread { + return Err(ContractError::MaxSpreadAssertion {}); } Ok(()) diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/tests/testing.rs b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/tests/testing.rs index 58c1786c..334fe0ee 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/tests/testing.rs +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/tests/testing.rs @@ -495,175 +495,120 @@ fn can_migrate_contract() { #[test] fn test_max_spread() { - let offer_asset_info = AssetInfo::NativeToken { - denom: "offer_asset".to_string(), - }; - let ask_asset_info = AssetInfo::NativeToken { - denom: "ask_asset_info".to_string(), - }; - assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), + Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), Some(Decimal::percent(1)), - Asset { - info: offer_asset_info.clone(), - amount: Uint128::from(1200000000u128), - }, - Asset { - info: ask_asset_info.clone(), - amount: Uint128::from(989999u128), - }, + Uint128::from(1200_000_000u128), + Uint128::from(989_999u128), Uint128::zero(), - 6u8, - 6u8, ) .unwrap_err(); + // same example as above but using 6 and 18 decimal places assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), + Some(Decimal::from_ratio( + 1200_000_000u128, + 1_000_000_000_000_000_000u128, + )), Some(Decimal::percent(1)), - Asset { - info: offer_asset_info.clone(), - amount: Uint128::from(1200000000u128), - }, - Asset { - info: ask_asset_info.clone(), - amount: Uint128::from(990000u128), - }, + Uint128::from(1200_000_000u128), + Uint128::from(989_999_900_000_000_000u128), + Uint128::zero(), + ) + .unwrap_err(); + + assert_max_spread( + Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), + None, // defaults to 0.5% + Uint128::from(1200_000_000u128), + Uint128::from(995_000u128), // all good Uint128::zero(), - 6u8, - 6u8, ) .unwrap(); assert_max_spread( - None, - Some(Decimal::percent(1)), - Asset { - info: offer_asset_info.clone(), - amount: Uint128::zero(), - }, - Asset { - info: ask_asset_info.clone(), - amount: Uint128::from(989999u128), - }, - Uint128::from(10001u128), - 6u8, - 6u8, + Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), + None, // defaults to 0.1% + Uint128::from(1200_000_000u128), + Uint128::from(989_000u128), // fails + Uint128::zero(), ) .unwrap_err(); assert_max_spread( - None, + Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), Some(Decimal::percent(1)), - Asset { - info: offer_asset_info, - amount: Uint128::zero(), - }, - Asset { - info: ask_asset_info, - amount: Uint128::from(990000u128), - }, - Uint128::from(10000u128), - 6u8, - 6u8, + Uint128::from(1200_000_000u128), + Uint128::from(990_000u128), + Uint128::zero(), ) .unwrap(); -} - -#[test] -fn test_max_spread_with_diff_decimal() { - let token_addr = "ask_asset_info".to_string(); - - let mut deps = mock_dependencies(&[]); - deps.querier.with_token_balances(&[( - &token_addr, - &[( - &MOCK_CONTRACT_ADDR.to_string(), - &Uint128::from(10000000000u64), - )], - )]); - let offer_asset_info = AssetInfo::NativeToken { - denom: "offer_asset".to_string(), - }; - let ask_asset_info = AssetInfo::Token { - contract_addr: token_addr.to_string(), - }; + // same example as above but using 6 and 18 decimal place assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), + Some(Decimal::from_ratio( + 1200_000_000u128, + 1_000_000_000_000_000_000u128, + )), Some(Decimal::percent(1)), - Asset { - info: offer_asset_info.clone(), - amount: Uint128::from(1200000000u128), - }, - Asset { - info: ask_asset_info.clone(), - amount: Uint128::from(100000000u128), - }, + Uint128::from(1200_000_000u128), + Uint128::from(990_000__000_000_000_000u128), Uint128::zero(), - 6u8, - 8u8, ) .unwrap(); + // similar example with 18 and 6 decimal places assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), + Some(Decimal::from_ratio( + 1_000_000_000_000_000_000u128, + 10_000_000u128, + )), + Some(Decimal::percent(2)), + Uint128::from(1_000_000_000_000_000_000u128), + Uint128::from(9_800_000u128), + Uint128::zero(), + ) + .unwrap(); + + // same as before but error because spread is 1% + assert_max_spread( + Some(Decimal::from_ratio( + 1_000_000_000_000_000_000u128, + 10_000_000u128, + )), Some(Decimal::percent(1)), - Asset { - info: offer_asset_info, - amount: Uint128::from(1200000000u128), - }, - Asset { - info: ask_asset_info, - amount: Uint128::from(98999999u128), - }, + Uint128::from(1_000_000_000_000_000_000u128), + Uint128::from(9_800_000u128), Uint128::zero(), - 6u8, - 8u8, ) .unwrap_err(); - let offer_asset_info = AssetInfo::Token { - contract_addr: token_addr, - }; - let ask_asset_info = AssetInfo::NativeToken { - denom: "offer_asset".to_string(), - }; + assert_max_spread( + None, + Some(Decimal::percent(1)), + Uint128::zero(), + Uint128::from(989_999u128), + Uint128::from(10001u128), + ) + .unwrap_err(); assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), + None, Some(Decimal::percent(1)), - Asset { - info: offer_asset_info.clone(), - amount: Uint128::from(120000000000u128), - }, - Asset { - info: ask_asset_info.clone(), - amount: Uint128::from(1000000u128), - }, Uint128::zero(), - 8u8, - 6u8, + Uint128::from(990_000u128), + Uint128::from(10000u128), ) .unwrap(); assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), - Some(Decimal::percent(1)), - Asset { - info: offer_asset_info, - amount: Uint128::from(120000000000u128), - }, - Asset { - info: ask_asset_info, - amount: Uint128::from(989999u128), - }, + Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), + Some(Decimal::percent(60)), // this will default to 50% + Uint128::from(1200_000_000u128), + Uint128::from(989_999u128), Uint128::zero(), - 8u8, - 6u8, ) - .unwrap_err(); + .unwrap(); } #[test] diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml b/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml index c498ea4a..4f1f403d 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "terraswap-pair" -version = "1.3.1" +version = "1.3.2" authors = [ "Terraform Labs, PTE.", "DELIGHT LABS", diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs index 806c8cbf..b637ed7d 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs @@ -407,8 +407,8 @@ pub fn swap( .swap_fee_amount .checked_add(swap_computation.protocol_fee_amount)? .checked_add(swap_computation.burn_fee_amount)?; - // check max spread limit if exist + // check max spread limit if exist helpers::assert_max_spread( belief_price, max_spread, diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs index 480a1376..6092e0d9 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs @@ -1,4 +1,3 @@ -use std::cmp::Ordering; use std::ops::Mul; use std::str::FromStr; @@ -324,7 +323,7 @@ pub struct OfferAmountComputation { } /// Default swap slippage in case max_spread is not specified -pub const DEFAULT_SLIPPAGE: &str = "0.005"; +pub const DEFAULT_SLIPPAGE: &str = "0.01"; /// Cap on the maximum swap slippage that is allowed. If max_spread goes over this limit, it will /// be capped to this value. pub const MAX_ALLOWED_SLIPPAGE: &str = "0.5"; @@ -339,11 +338,23 @@ pub fn assert_max_spread( return_amount: Uint128, spread_amount: Uint128, ) -> Result<(), ContractError> { + println!("assert_max_spread: belief_price: {:?}, max_spread: {:?}, offer_amount: {:?}, return_amount: {:?}, spread_amount: {:?}", belief_price, max_spread, offer_amount, return_amount, spread_amount); + let max_spread: Decimal256 = max_spread .unwrap_or(Decimal::from_str(DEFAULT_SLIPPAGE)?) .min(Decimal::from_str(MAX_ALLOWED_SLIPPAGE)?) .into(); + println!("max_spread: {:?}", max_spread); + println!( + "Decimal256::from_ratio(spread_amount, return_amount + spread_amount) {:?}", + Decimal256::from_ratio(spread_amount, return_amount + spread_amount) + ); + println!( + "Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread: {:?}", + Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread + ); + if let Some(belief_price) = belief_price { let expected_return = offer_amount * belief_price diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/protocol_fees.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/protocol_fees.rs index 35e0c121..a979de45 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/protocol_fees.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/protocol_fees.rs @@ -14,9 +14,9 @@ use white_whale::pool_network::pair::{Cw20HookMsg, ExecuteMsg, InstantiateMsg, P #[test] fn test_protocol_fees() { - let total_share = Uint128::from(30_000_000_000u128); - let asset_pool_amount = Uint128::from(20_000_000_000u128); - let collateral_pool_amount = Uint128::from(30_000_000_000u128); + let total_share = Uint128::from(300_000_000_000u128); + let asset_pool_amount = Uint128::from(200_000_000_000u128); + let collateral_pool_amount = Uint128::from(300_000_000_000u128); let offer_amount = Uint128::from(1_500_000_000u128); let mut deps = mock_dependencies(&[Coin { @@ -107,8 +107,8 @@ fn test_protocol_fees() { execute(deps.as_mut(), env, info, msg).unwrap(); // ask_amount = ((ask_pool - accrued protocol fees) * offer_amount / (offer_pool - accrued protocol fees + offer_amount)) - // 952.380952 = (20000 - 0) * 1500 / (30000 - 0 + 1500) - swap_fee - protocol_fee - let expected_ret_amount = Uint128::from(952_380_952u128); + // 995.024875 = (200000 - 0) * 1500 / (300000 - 0 + 1500) - swap_fee - protocol_fee + let expected_ret_amount = Uint128::from(995_024_875u128); let expected_protocol_fee_amount = expected_ret_amount.multiply_ratio(1u128, 1000u128); // 0.1% // as we swapped native to token, we accumulate the protocol fees in token. Native token fees should be 0 @@ -162,8 +162,8 @@ fn test_protocol_fees() { execute(deps.as_mut(), env, info, msg).unwrap(); // ask_amount = ((ask_pool - accrued protocol fees) * offer_amount / (offer_pool - accrued protocol fees + offer_amount)) - // 952.335600 = (20000 - 0.952380 ) * 1500 / (30000 - 0 + 1500) - swap_fee - protocol_fee - let expected_ret_amount = Uint128::from(952_335_600u128); + // 995.019925 = (200000 - 0.995024 ) * 1500 / (300000 - 0 + 1500) - swap_fee - protocol_fee + let expected_ret_amount = Uint128::from(995_019_925u128); let new_expected_protocol_fee_amount = expected_ret_amount.multiply_ratio(1u128, 1000u128); // 0.1% // the new protocol fees should have increased from the previous time @@ -198,9 +198,9 @@ fn test_protocol_fees() { #[test] fn test_collect_protocol_fees_successful() { - let total_share = Uint128::from(30_000_000_000u128); - let asset_pool_amount = Uint128::from(20_000_000_000u128); - let collateral_pool_amount = Uint128::from(30_000_000_000u128); + let total_share = Uint128::from(300_000_000_000u128); + let asset_pool_amount = Uint128::from(200_000_000_000u128); + let collateral_pool_amount = Uint128::from(300_000_000_000u128); let offer_amount = Uint128::from(1_500_000_000u128); let mut deps = mock_dependencies(&[Coin { @@ -291,8 +291,8 @@ fn test_collect_protocol_fees_successful() { execute(deps.as_mut(), env.clone(), info, msg).unwrap(); // ask_amount = ((ask_pool - accrued protocol fees) * offer_amount / (offer_pool - accrued protocol fees + offer_amount)) - // 952.380952 = (20000 - 0) * 1500 / (30000 - 0 + 1500) - swap_fee - protocol_fee - let expected_ret_amount = Uint128::from(952_380_952u128); + // 995.024875 = (200000 - 0) * 1500 / (300000 - 0 + 1500) - swap_fee - protocol_fee + let expected_ret_amount = Uint128::from(995_024_875u128); let expected_protocol_fee_token_amount = expected_ret_amount.multiply_ratio(1u128, 1000u128); // 0.001% // second swap, token -> native @@ -310,8 +310,8 @@ fn test_collect_protocol_fees_successful() { execute(deps.as_mut(), env.clone(), info, msg).unwrap(); // ask_amount = ((ask_pool - accrued protocol fees) * offer_amount / (offer_pool - accrued protocol fees + offer_amount)) - // 2362.612505 = (31500 - 0) * 1500 / (18500 - 0.952380 + 1500) - swap_fee - protocol_fee - let expected_ret_amount = Uint128::from(2_362_612_505u128); + // 2261.261505 = (301500 - 0) * 1500 / (18500 - 0.995024 + 1500) - swap_fee - protocol_fee + let expected_ret_amount = Uint128::from(2_261_261_505u128); let expected_protocol_fee_native_amount = expected_ret_amount.multiply_ratio(1u128, 1000u128); // 0.001% // as we swapped both native and token, we should have collected fees in both of them @@ -426,10 +426,10 @@ fn test_collect_protocol_fees_successful() { #[test] fn test_collect_protocol_fees_successful_1_fee_only() { - let total_share = Uint128::from(30_000_000_000u128); - let asset_pool_amount = Uint128::from(20_000_000_000u128); - let collateral_pool_amount = Uint128::from(30_000_000_000u128); - let offer_amount = Uint128::from(1_500_000_000u128); + let total_share = Uint128::from(300_000_000_000u128); + let asset_pool_amount = Uint128::from(200_000_000_000u128); + let collateral_pool_amount = Uint128::from(300_000_000_000u128); + let offer_amount = Uint128::from(1_500_000u128); let mut deps = mock_dependencies(&[Coin { denom: "uusd".to_string(), @@ -519,8 +519,8 @@ fn test_collect_protocol_fees_successful_1_fee_only() { execute(deps.as_mut(), env.clone(), info, msg).unwrap(); // ask_amount = (ask_pool * offer_amount / (offer_pool + offer_amount)) - // 952.380952 = 20000 * 1500 / (30000 + 1500) - swap_fee - protocol_fee - let expected_ret_amount = Uint128::from(952_380_952u128); + // 9.995002 = 20000 * 15 / (30000 + 15) - swap_fee - protocol_fee + let expected_ret_amount = Uint128::from(999000u128); let expected_protocol_fee_token_amount = expected_ret_amount.multiply_ratio(1u128, 1000u128); // 0.1% // as did only one swap from native -> token, we should have collected fees in token diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/swap.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/swap.rs index f78f6d2d..4fa0a0ef 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/swap.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/swap.rs @@ -133,7 +133,7 @@ fn try_native_to_token() { amount: offer_amount, }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(5u64)), to: None, }; let env = mock_env(); @@ -597,7 +597,7 @@ fn try_token_to_native() { amount: offer_amount, msg: to_binary(&Cw20HookMsg::Swap { belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(5u64)), to: Some("third_party".to_string()), }) .unwrap(), @@ -926,7 +926,7 @@ fn test_swap_to_third_party() { amount: offer_amount, }, belief_price: None, - max_spread: None, + max_spread: Some(Decimal::percent(5u64)), to: Some("third_party".to_string()), }; let env = mock_env(); diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs index 1d25ff70..71fe8723 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs @@ -345,7 +345,6 @@ fn test_initialization_invalid_fees() { _ => panic!("should return StdError::generic_err(Invalid fee)"), } } - #[test] fn test_max_spread() { assert_max_spread( @@ -381,9 +380,9 @@ fn test_max_spread() { assert_max_spread( Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), - None, // defaults to 0.5% + None, // defaults to 0.1% Uint128::from(1200_000_000u128), - Uint128::from(990_000u128), // fails + Uint128::from(989_000u128), // fails Uint128::zero(), ) .unwrap_err(); @@ -463,7 +462,6 @@ fn test_max_spread() { ) .unwrap(); } - #[test] fn test_update_config_unsuccessful() { let mut deps = mock_dependencies(&[]); diff --git a/contracts/liquidity_hub/pool-network/terraswap_router/Cargo.toml b/contracts/liquidity_hub/pool-network/terraswap_router/Cargo.toml index 27aa00f9..042bd53c 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_router/Cargo.toml +++ b/contracts/liquidity_hub/pool-network/terraswap_router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "terraswap-router" -version = "1.1.0" +version = "1.1.1" authors = [ "Terraform Labs, PTE.", "DELIGHT LABS", diff --git a/contracts/liquidity_hub/pool-network/terraswap_router/src/contract.rs b/contracts/liquidity_hub/pool-network/terraswap_router/src/contract.rs index 404dd0e2..27724aaf 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_router/src/contract.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_router/src/contract.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, from_binary, to_binary, Addr, Api, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, - Order, Response, StdError, StdResult, Uint128, WasmMsg, + attr, from_binary, to_binary, Addr, Api, Binary, CosmosMsg, Decimal, Deps, DepsMut, Env, + MessageInfo, Order, Response, StdError, StdResult, Uint128, WasmMsg, }; use cw2::{get_contract_version, set_contract_version}; use cw20::Cw20ReceiveMsg; @@ -59,6 +59,7 @@ pub fn execute( operations, minimum_receive, to, + max_spread, } => { let api = deps.api; execute_swap_operations( @@ -68,9 +69,14 @@ pub fn execute( operations, minimum_receive, optional_addr_validate(api, to)?, + max_spread, ) } - ExecuteMsg::ExecuteSwapOperation { operation, to } => { + ExecuteMsg::ExecuteSwapOperation { + operation, + to, + max_spread, + } => { let api = deps.api; execute_swap_operation( deps, @@ -78,6 +84,7 @@ pub fn execute( info, operation, optional_addr_validate(api, to)?.map(|v| v.to_string()), + max_spread, ) } ExecuteMsg::AssertMinimumReceive { @@ -123,6 +130,7 @@ pub fn receive_cw20( operations, minimum_receive, to, + max_spread, } => { let api = deps.api; execute_swap_operations( @@ -132,6 +140,7 @@ pub fn receive_cw20( operations, minimum_receive, optional_addr_validate(api, to)?, + max_spread, ) } } @@ -144,6 +153,7 @@ pub fn execute_swap_operations( operations: Vec, minimum_receive: Option, to: Option, + max_spread: Option, ) -> Result { let operations_len = operations.len(); if operations_len == 0 { @@ -174,6 +184,7 @@ pub fn execute_swap_operations( } else { None }, + max_spread, })?, })) }) diff --git a/contracts/liquidity_hub/pool-network/terraswap_router/src/operations.rs b/contracts/liquidity_hub/pool-network/terraswap_router/src/operations.rs index c64b835f..804b8df0 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_router/src/operations.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_router/src/operations.rs @@ -20,6 +20,7 @@ pub fn execute_swap_operation( info: MessageInfo, operation: SwapOperation, to: Option, + max_spread: Option, ) -> Result { if env.contract.address != info.sender { return Err(ContractError::Unauthorized {}); @@ -57,7 +58,7 @@ pub fn execute_swap_operation( deps.as_ref(), Addr::unchecked(pair_info.contract_addr), offer_asset, - None, + max_spread, to, )?] } diff --git a/contracts/liquidity_hub/pool-network/terraswap_router/src/testing/tests.rs b/contracts/liquidity_hub/pool-network/terraswap_router/src/testing/tests.rs index ecc84858..41728fec 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_router/src/testing/tests.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_router/src/testing/tests.rs @@ -1,6 +1,7 @@ use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - attr, coin, from_binary, to_binary, Addr, Coin, CosmosMsg, StdError, SubMsg, Uint128, WasmMsg, + attr, coin, from_binary, to_binary, Addr, Coin, CosmosMsg, Decimal, StdError, SubMsg, Uint128, + WasmMsg, }; use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use white_whale::pool_network; @@ -58,6 +59,7 @@ fn execute_swap_operations() { operations: vec![], minimum_receive: None, to: None, + max_spread: None, }; let info = mock_info("addr0000", &[]); @@ -99,6 +101,7 @@ fn execute_swap_operations() { ], minimum_receive: Some(Uint128::from(1000000u128)), to: None, + max_spread: None, }; let info = mock_info("addr0000", &[]); @@ -119,6 +122,7 @@ fn execute_swap_operations() { }, }, to: None, + max_spread: None, }) .unwrap(), })), @@ -135,6 +139,7 @@ fn execute_swap_operations() { }, }, to: None, + max_spread: None, }) .unwrap(), })), @@ -151,6 +156,7 @@ fn execute_swap_operations() { }, }, to: Some("addr0000".to_string()), + max_spread: None, }) .unwrap(), })), @@ -202,6 +208,7 @@ fn execute_swap_operations() { ], minimum_receive: None, to: Some("addr0002".to_string()), + max_spread: None, }) .unwrap(), }); @@ -224,6 +231,7 @@ fn execute_swap_operations() { }, }, to: None, + max_spread: None, }) .unwrap(), })), @@ -240,6 +248,7 @@ fn execute_swap_operations() { }, }, to: None, + max_spread: None, }) .unwrap(), })), @@ -256,6 +265,7 @@ fn execute_swap_operations() { }, }, to: Some("addr0002".to_string()), + max_spread: None, }) .unwrap(), })), @@ -316,6 +326,7 @@ fn execute_swap_operation() { }, }, to: None, + max_spread: None, }; let info = mock_info("addr0000", &[]); let res = execute(deps.as_mut(), mock_env(), info, msg.clone()); @@ -357,6 +368,7 @@ fn execute_swap_operation() { }, }, to: Some("addr0000".to_string()), + max_spread: None, }; let info = mock_info(MOCK_CONTRACT_ADDR, &[]); let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); @@ -415,6 +427,7 @@ fn execute_swap_operation() { }, }, to: Some("addr0000".to_string()), + max_spread: None, }; let info = mock_info(MOCK_CONTRACT_ADDR, &[]); @@ -661,6 +674,7 @@ fn query_reverse_routes_with_from_native() { }, }, to: None, + max_spread: None, }; let info = mock_info("addr0", &[coin(offer_amount.u128(), "ukrw")]); let res = execute(deps.as_mut(), mock_env(), info, msg.clone()); @@ -801,6 +815,7 @@ fn query_reverse_routes_with_to_native() { }], minimum_receive: None, to: None, + max_spread: None, }) .unwrap(), }); @@ -822,6 +837,7 @@ fn query_reverse_routes_with_to_native() { }, }, to: Some("addr0".to_string()), + max_spread: None, }) .unwrap(), })),], @@ -837,6 +853,7 @@ fn query_reverse_routes_with_to_native() { }, }, to: None, + max_spread: None, }; let info = mock_info(MOCK_CONTRACT_ADDR, &[]); diff --git a/packages/white-whale/src/pool_network/router.rs b/packages/white-whale/src/pool_network/router.rs index 49572f2d..20ab68e6 100644 --- a/packages/white-whale/src/pool_network/router.rs +++ b/packages/white-whale/src/pool_network/router.rs @@ -1,7 +1,7 @@ use std::fmt; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Uint128; +use cosmwasm_std::{Decimal, Uint128}; use cw20::Cw20ReceiveMsg; use crate::pool_network::asset::AssetInfo; @@ -74,11 +74,13 @@ pub enum ExecuteMsg { operations: Vec, minimum_receive: Option, to: Option, + max_spread: Option, }, /// Swap the offer to ask token. This message can only be called internally by the router contract. ExecuteSwapOperation { operation: SwapOperation, to: Option, + max_spread: Option, }, /// Checks if the swap amount exceeds the minimum_receive. This message can only be called /// internally by the router contract. @@ -100,6 +102,7 @@ pub enum Cw20HookMsg { operations: Vec, minimum_receive: Option, to: Option, + max_spread: Option, }, } From 50b8c5d1e66a6c342913fd7211a4ef59102f32b0 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Fri, 20 Oct 2023 10:32:29 +0100 Subject: [PATCH 10/22] chore: cleanup --- .../fee_collector/src/tests/integration.rs | 2 +- .../stableswap_3pool/src/commands.rs | 23 +- .../stableswap_3pool/src/error.rs | 3 - .../stableswap_3pool/src/helpers.rs | 60 +- .../stableswap_3pool/src/tests/testing.rs | 3 +- .../terraswap_pair/src/commands.rs | 4 +- .../pool-network/terraswap_pair/src/error.rs | 3 - .../terraswap_pair/src/helpers.rs | 57 +- .../terraswap_pair/src/tests/testing.rs | 3 +- .../terraswap_router/schema/raw/execute.json | 24 + .../schema/terraswap-router.json | 24 + .../terraswap_router/src/testing/tests.rs | 3 +- .../vault-manager/schema/raw/execute.json | 1310 --------- .../vault-manager/schema/raw/instantiate.json | 133 - .../vault-manager/schema/raw/query.json | 205 -- .../schema/raw/response_to_config.json | 151 -- .../schema/raw/response_to_ownership.json | 95 - .../raw/response_to_payback_amount.json | 100 - .../schema/raw/response_to_share.json | 89 - .../schema/raw/response_to_vault.json | 135 - .../schema/raw/response_to_vaults.json | 135 - .../vault-manager/schema/vault-manager.json | 2362 ----------------- packages/white-whale/src/pool_network/mod.rs | 1 + packages/white-whale/src/pool_network/swap.rs | 42 + 24 files changed, 105 insertions(+), 4862 deletions(-) delete mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/execute.json delete mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/instantiate.json delete mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/query.json delete mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_config.json delete mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_ownership.json delete mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_payback_amount.json delete mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_share.json delete mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_vault.json delete mode 100644 contracts/liquidity_hub/vault-manager/schema/raw/response_to_vaults.json delete mode 100644 contracts/liquidity_hub/vault-manager/schema/vault-manager.json create mode 100644 packages/white-whale/src/pool_network/swap.rs diff --git a/contracts/liquidity_hub/fee_collector/src/tests/integration.rs b/contracts/liquidity_hub/fee_collector/src/tests/integration.rs index 030e1bb8..fdb11861 100644 --- a/contracts/liquidity_hub/fee_collector/src/tests/integration.rs +++ b/contracts/liquidity_hub/fee_collector/src/tests/integration.rs @@ -8070,7 +8070,7 @@ fn user_weight_accounts_for_unbondings() { ) .unwrap(); - let mut epoch_res: EpochResponse = app + let epoch_res: EpochResponse = app .wrap() .query_wasm_smart( fee_distributor_address.clone(), diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs index d2feb158..bced40b2 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs @@ -16,6 +16,7 @@ use white_whale::pool_network::asset::{ use white_whale::pool_network::denom::{Coin, MsgBurn, MsgMint}; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::{Coin, MsgBurn, MsgMint}; +use white_whale::pool_network::swap; use white_whale::pool_network::trio::{Config, Cw20HookMsg, FeatureToggle, PoolFee, RampAmp}; use crate::error::ContractError; @@ -387,24 +388,16 @@ pub fn swap( let ask_pool: Asset; let offer_pool: Asset; let unswapped_pool: Asset; - let ask_decimal: u8; - let offer_decimal: u8; if ask_asset.equal(&pools[0].info) { if offer_asset.info.equal(&pools[1].info) { ask_pool = pools[0].clone(); offer_pool = pools[1].clone(); unswapped_pool = pools[2].clone(); - - ask_decimal = trio_info.asset_decimals[0]; - offer_decimal = trio_info.asset_decimals[1]; } else if offer_asset.info.equal(&pools[2].info) { ask_pool = pools[0].clone(); offer_pool = pools[2].clone(); unswapped_pool = pools[1].clone(); - - ask_decimal = trio_info.asset_decimals[0]; - offer_decimal = trio_info.asset_decimals[2]; } else { return Err(ContractError::AssetMismatch {}); } @@ -413,16 +406,10 @@ pub fn swap( ask_pool = pools[1].clone(); offer_pool = pools[0].clone(); unswapped_pool = pools[2].clone(); - - ask_decimal = trio_info.asset_decimals[1]; - offer_decimal = trio_info.asset_decimals[0]; } else if offer_asset.info.equal(&pools[2].info) { ask_pool = pools[1].clone(); offer_pool = pools[2].clone(); unswapped_pool = pools[0].clone(); - - ask_decimal = trio_info.asset_decimals[1]; - offer_decimal = trio_info.asset_decimals[2]; } else { return Err(ContractError::AssetMismatch {}); } @@ -431,16 +418,10 @@ pub fn swap( ask_pool = pools[2].clone(); offer_pool = pools[0].clone(); unswapped_pool = pools[1].clone(); - - ask_decimal = trio_info.asset_decimals[2]; - offer_decimal = trio_info.asset_decimals[0]; } else if offer_asset.info.equal(&pools[1].info) { ask_pool = pools[2].clone(); offer_pool = pools[1].clone(); unswapped_pool = pools[0].clone(); - - ask_decimal = trio_info.asset_decimals[2]; - offer_decimal = trio_info.asset_decimals[1]; } else { return Err(ContractError::AssetMismatch {}); } @@ -478,7 +459,7 @@ pub fn swap( .checked_add(swap_computation.burn_fee_amount)?; // check max spread limit if exist - helpers::assert_max_spread( + swap::assert_max_spread( belief_price, max_spread, offer_asset.amount, diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/error.rs b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/error.rs index 6b6e4926..7a93ab0e 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/error.rs +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/error.rs @@ -19,9 +19,6 @@ pub enum ContractError { #[error("Invalid zero amount")] InvalidZeroAmount {}, - #[error("Spread limit exceeded")] - MaxSpreadAssertion {}, - #[error("Slippage tolerance exceeded")] MaxSlippageAssertion {}, diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs index 14643e31..9c4674fa 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs @@ -1,10 +1,7 @@ -use std::cmp::Ordering; -use std::str::FromStr; - use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_binary, Decimal, Decimal256, Deps, DepsMut, Env, Fraction, ReplyOn, Response, StdError, - StdResult, Storage, SubMsg, Uint128, Uint256, WasmMsg, + to_binary, Decimal, Decimal256, Deps, DepsMut, Env, ReplyOn, Response, StdError, StdResult, + Storage, SubMsg, Uint128, Uint256, WasmMsg, }; use cw20::MinterResponse; use cw_storage_plus::Item; @@ -127,59 +124,6 @@ pub struct OfferAmountComputation { pub protocol_fee_amount: Uint128, pub burn_fee_amount: Uint128, } - -/// Default swap slippage in case max_spread is not specified -pub const DEFAULT_SLIPPAGE: &str = "0.01"; -/// Cap on the maximum swap slippage that is allowed. If max_spread goes over this limit, it will -/// be capped to this value. -pub const MAX_ALLOWED_SLIPPAGE: &str = "0.5"; - -/// If `belief_price` and `max_spread` both are given, -/// we compute new spread else we just use pool network -/// spread to check `max_spread` -pub fn assert_max_spread( - belief_price: Option, - max_spread: Option, - offer_amount: Uint128, - return_amount: Uint128, - spread_amount: Uint128, -) -> Result<(), ContractError> { - println!("assert_max_spread: belief_price: {:?}, max_spread: {:?}, offer_amount: {:?}, return_amount: {:?}, spread_amount: {:?}", belief_price, max_spread, offer_amount, return_amount, spread_amount); - - let max_spread: Decimal256 = max_spread - .unwrap_or(Decimal::from_str(DEFAULT_SLIPPAGE)?) - .min(Decimal::from_str(MAX_ALLOWED_SLIPPAGE)?) - .into(); - - println!("max_spread: {:?}", max_spread); - println!( - "Decimal256::from_ratio(spread_amount, return_amount + spread_amount) {:?}", - Decimal256::from_ratio(spread_amount, return_amount + spread_amount) - ); - println!( - "Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread: {:?}", - Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread - ); - - if let Some(belief_price) = belief_price { - let expected_return = offer_amount - * belief_price - .inv() - .ok_or_else(|| StdError::generic_err("Belief price can't be zero"))?; - let spread_amount = expected_return.saturating_sub(return_amount); - - if return_amount < expected_return - && Decimal256::from_ratio(spread_amount, expected_return) > max_spread - { - return Err(ContractError::MaxSpreadAssertion {}); - } - } else if Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread { - return Err(ContractError::MaxSpreadAssertion {}); - } - - Ok(()) -} - pub fn assert_slippage_tolerance( slippage_tolerance: &Option, deposits: &[Uint128; 3], diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/tests/testing.rs b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/tests/testing.rs index 334fe0ee..6b718104 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/tests/testing.rs +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/tests/testing.rs @@ -14,13 +14,14 @@ use white_whale::pool_network::asset::{Asset, AssetInfo, TrioInfo}; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::MsgCreateDenom; use white_whale::pool_network::mock_querier::mock_dependencies; +use white_whale::pool_network::swap::assert_max_spread; use white_whale::pool_network::token::InstantiateMsg as TokenInstantiateMsg; use white_whale::pool_network::trio::ExecuteMsg::UpdateConfig; use white_whale::pool_network::trio::{Config, InstantiateMsg, MigrateMsg, PoolFee, QueryMsg}; use crate::contract::{execute, instantiate, migrate, query, reply}; use crate::error::ContractError; -use crate::helpers::{assert_max_spread, assert_slippage_tolerance}; +use crate::helpers::assert_slippage_tolerance; use crate::queries::query_trio_info; #[test] diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs index b637ed7d..afb99946 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs @@ -17,7 +17,7 @@ use white_whale::pool_network::denom::{Coin, MsgBurn, MsgMint}; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::{Coin, MsgBurn, MsgMint}; use white_whale::pool_network::pair::{Config, Cw20HookMsg, FeatureToggle, PoolFee}; -use white_whale::pool_network::U256; +use white_whale::pool_network::{swap, U256}; use crate::error::ContractError; use crate::helpers; @@ -409,7 +409,7 @@ pub fn swap( .checked_add(swap_computation.burn_fee_amount)?; // check max spread limit if exist - helpers::assert_max_spread( + swap::assert_max_spread( belief_price, max_spread, offer_asset.amount, diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/error.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/error.rs index d6a7ae01..22a97034 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/error.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/error.rs @@ -34,9 +34,6 @@ pub enum ContractError { #[error("Invalid zero amount")] InvalidZeroAmount {}, - #[error("Spread limit exceeded")] - MaxSpreadAssertion {}, - #[error("Slippage tolerance exceeded")] MaxSlippageAssertion {}, diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs index 6092e0d9..81e49aef 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs @@ -1,12 +1,11 @@ use std::ops::Mul; -use std::str::FromStr; use cosmwasm_schema::cw_serde; #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] use cosmwasm_std::CosmosMsg; use cosmwasm_std::{ - to_binary, Decimal, Decimal256, DepsMut, Env, Fraction, ReplyOn, Response, StdError, StdResult, - Storage, SubMsg, Uint128, Uint256, WasmMsg, + to_binary, Decimal, Decimal256, DepsMut, Env, ReplyOn, Response, StdError, StdResult, Storage, + SubMsg, Uint128, Uint256, WasmMsg, }; use cw20::MinterResponse; use cw_storage_plus::Item; @@ -322,58 +321,6 @@ pub struct OfferAmountComputation { pub burn_fee_amount: Uint128, } -/// Default swap slippage in case max_spread is not specified -pub const DEFAULT_SLIPPAGE: &str = "0.01"; -/// Cap on the maximum swap slippage that is allowed. If max_spread goes over this limit, it will -/// be capped to this value. -pub const MAX_ALLOWED_SLIPPAGE: &str = "0.5"; - -/// If `belief_price` and `max_spread` both are given, -/// we compute new spread else we just use pool network -/// spread to check `max_spread` -pub fn assert_max_spread( - belief_price: Option, - max_spread: Option, - offer_amount: Uint128, - return_amount: Uint128, - spread_amount: Uint128, -) -> Result<(), ContractError> { - println!("assert_max_spread: belief_price: {:?}, max_spread: {:?}, offer_amount: {:?}, return_amount: {:?}, spread_amount: {:?}", belief_price, max_spread, offer_amount, return_amount, spread_amount); - - let max_spread: Decimal256 = max_spread - .unwrap_or(Decimal::from_str(DEFAULT_SLIPPAGE)?) - .min(Decimal::from_str(MAX_ALLOWED_SLIPPAGE)?) - .into(); - - println!("max_spread: {:?}", max_spread); - println!( - "Decimal256::from_ratio(spread_amount, return_amount + spread_amount) {:?}", - Decimal256::from_ratio(spread_amount, return_amount + spread_amount) - ); - println!( - "Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread: {:?}", - Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread - ); - - if let Some(belief_price) = belief_price { - let expected_return = offer_amount - * belief_price - .inv() - .ok_or_else(|| StdError::generic_err("Belief price can't be zero"))?; - let spread_amount = expected_return.saturating_sub(return_amount); - - if return_amount < expected_return - && Decimal256::from_ratio(spread_amount, expected_return) > max_spread - { - return Err(ContractError::MaxSpreadAssertion {}); - } - } else if Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread { - return Err(ContractError::MaxSpreadAssertion {}); - } - - Ok(()) -} - pub fn assert_slippage_tolerance( slippage_tolerance: &Option, deposits: &[Uint128; 2], diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs index 71fe8723..8efa2aaa 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs @@ -16,11 +16,12 @@ use white_whale::pool_network::denom::MsgCreateDenom; use white_whale::pool_network::mock_querier::mock_dependencies; use white_whale::pool_network::pair::ExecuteMsg::UpdateConfig; use white_whale::pool_network::pair::{Config, InstantiateMsg, PoolFee, QueryMsg}; +use white_whale::pool_network::swap::assert_max_spread; use white_whale::pool_network::token::InstantiateMsg as TokenInstantiateMsg; use crate::contract::{execute, instantiate, query, reply}; use crate::error::ContractError; -use crate::helpers::{assert_max_spread, assert_slippage_tolerance}; +use crate::helpers::assert_slippage_tolerance; use crate::queries::query_pair_info; #[test] diff --git a/contracts/liquidity_hub/pool-network/terraswap_router/schema/raw/execute.json b/contracts/liquidity_hub/pool-network/terraswap_router/schema/raw/execute.json index 591bb572..12765ff3 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_router/schema/raw/execute.json +++ b/contracts/liquidity_hub/pool-network/terraswap_router/schema/raw/execute.json @@ -27,6 +27,16 @@ "operations" ], "properties": { + "max_spread": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, "minimum_receive": { "anyOf": [ { @@ -68,6 +78,16 @@ "operation" ], "properties": { + "max_spread": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, "operation": { "$ref": "#/definitions/SwapOperation" }, @@ -216,6 +236,10 @@ }, "additionalProperties": false }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "SwapOperation": { "oneOf": [ { diff --git a/contracts/liquidity_hub/pool-network/terraswap_router/schema/terraswap-router.json b/contracts/liquidity_hub/pool-network/terraswap_router/schema/terraswap-router.json index 6c8967b8..6e948653 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_router/schema/terraswap-router.json +++ b/contracts/liquidity_hub/pool-network/terraswap_router/schema/terraswap-router.json @@ -45,6 +45,16 @@ "operations" ], "properties": { + "max_spread": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, "minimum_receive": { "anyOf": [ { @@ -86,6 +96,16 @@ "operation" ], "properties": { + "max_spread": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, "operation": { "$ref": "#/definitions/SwapOperation" }, @@ -234,6 +254,10 @@ }, "additionalProperties": false }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "SwapOperation": { "oneOf": [ { diff --git a/contracts/liquidity_hub/pool-network/terraswap_router/src/testing/tests.rs b/contracts/liquidity_hub/pool-network/terraswap_router/src/testing/tests.rs index 41728fec..8222d7eb 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_router/src/testing/tests.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_router/src/testing/tests.rs @@ -1,7 +1,6 @@ use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - attr, coin, from_binary, to_binary, Addr, Coin, CosmosMsg, Decimal, StdError, SubMsg, Uint128, - WasmMsg, + attr, coin, from_binary, to_binary, Addr, Coin, CosmosMsg, StdError, SubMsg, Uint128, WasmMsg, }; use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use white_whale::pool_network; diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/execute.json b/contracts/liquidity_hub/vault-manager/schema/raw/execute.json deleted file mode 100644 index e83e325d..00000000 --- a/contracts/liquidity_hub/vault-manager/schema/raw/execute.json +++ /dev/null @@ -1,1310 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "description": "The execution messages", - "oneOf": [ - { - "description": "Creates a new vault given the asset info the vault should manage deposits and withdrawals for and the fees", - "type": "object", - "required": [ - "create_vault" - ], - "properties": { - "create_vault": { - "type": "object", - "required": [ - "asset_info", - "fees" - ], - "properties": { - "asset_info": { - "$ref": "#/definitions/AssetInfo" - }, - "fees": { - "$ref": "#/definitions/VaultFee" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Removes a vault given its [AssetInfo]", - "type": "object", - "required": [ - "remove_vault" - ], - "properties": { - "remove_vault": { - "type": "object", - "required": [ - "asset_info" - ], - "properties": { - "asset_info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Updates a vault config", - "type": "object", - "required": [ - "update_vault_fees" - ], - "properties": { - "update_vault_fees": { - "type": "object", - "required": [ - "vault_asset_info", - "vault_fee" - ], - "properties": { - "vault_asset_info": { - "$ref": "#/definitions/AssetInfo" - }, - "vault_fee": { - "$ref": "#/definitions/VaultFee" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Updates the configuration of the vault manager. If a field is not specified, it will not be modified.", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "cw20_lp_code_id": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "deposit_enabled": { - "type": [ - "boolean", - "null" - ] - }, - "flash_loan_enabled": { - "type": [ - "boolean", - "null" - ] - }, - "vault_creation_fee": { - "anyOf": [ - { - "$ref": "#/definitions/Asset" - }, - { - "type": "null" - } - ] - }, - "whale_lair_addr": { - "type": [ - "string", - "null" - ] - }, - "withdraw_enabled": { - "type": [ - "boolean", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Deposits a given asset into the vault manager.", - "type": "object", - "required": [ - "deposit" - ], - "properties": { - "deposit": { - "type": "object", - "required": [ - "asset" - ], - "properties": { - "asset": { - "$ref": "#/definitions/Asset" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Withdraws from the vault manager. Used when the LP token is a token manager token.", - "type": "object", - "required": [ - "withdraw" - ], - "properties": { - "withdraw": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "description": "Retrieves the desired `asset` and runs the `payload`, paying the required amount back to the vault after running the messages in the payload, and returning the profit to the sender.", - "type": "object", - "required": [ - "flash_loan" - ], - "properties": { - "flash_loan": { - "type": "object", - "required": [ - "asset", - "payload" - ], - "properties": { - "asset": { - "$ref": "#/definitions/Asset" - }, - "payload": { - "type": "array", - "items": { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Callback message for post-processing flash-loans.", - "type": "object", - "required": [ - "callback" - ], - "properties": { - "callback": { - "$ref": "#/definitions/CallbackMsg" - } - }, - "additionalProperties": false - }, - { - "description": "Update the contract's ownership. The `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.", - "type": "object", - "required": [ - "update_ownership" - ], - "properties": { - "update_ownership": { - "$ref": "#/definitions/Action" - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Action": { - "description": "Actions that can be taken to alter the contract's ownership", - "oneOf": [ - { - "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", - "type": "object", - "required": [ - "transfer_ownership" - ], - "properties": { - "transfer_ownership": { - "type": "object", - "required": [ - "new_owner" - ], - "properties": { - "expiry": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "new_owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", - "type": "string", - "enum": [ - "accept_ownership" - ] - }, - { - "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", - "type": "string", - "enum": [ - "renounce_ownership" - ] - } - ] - }, - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "BankMsg": { - "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", - "oneOf": [ - { - "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "to_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "CallbackMsg": { - "description": "The callback messages available. Only callable by the vault contract itself.", - "oneOf": [ - { - "type": "object", - "required": [ - "after_flashloan" - ], - "properties": { - "after_flashloan": { - "type": "object", - "required": [ - "loan_asset", - "old_asset_balance", - "sender" - ], - "properties": { - "loan_asset": { - "$ref": "#/definitions/Asset" - }, - "old_asset_balance": { - "$ref": "#/definitions/Uint128" - }, - "sender": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "CosmosMsg_for_Empty": { - "oneOf": [ - { - "type": "object", - "required": [ - "bank" - ], - "properties": { - "bank": { - "$ref": "#/definitions/BankMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "custom" - ], - "properties": { - "custom": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "staking" - ], - "properties": { - "staking": { - "$ref": "#/definitions/StakingMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "distribution" - ], - "properties": { - "distribution": { - "$ref": "#/definitions/DistributionMsg" - } - }, - "additionalProperties": false - }, - { - "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", - "type": "object", - "required": [ - "stargate" - ], - "properties": { - "stargate": { - "type": "object", - "required": [ - "type_url", - "value" - ], - "properties": { - "type_url": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Binary" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "ibc" - ], - "properties": { - "ibc": { - "$ref": "#/definitions/IbcMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "wasm" - ], - "properties": { - "wasm": { - "$ref": "#/definitions/WasmMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "gov" - ], - "properties": { - "gov": { - "$ref": "#/definitions/GovMsg" - } - }, - "additionalProperties": false - } - ] - }, - "Cw20ReceiveMsg": { - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - }, - "additionalProperties": false - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "DistributionMsg": { - "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "set_withdraw_address" - ], - "properties": { - "set_withdraw_address": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "description": "The `withdraw_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "withdraw_delegator_reward" - ], - "properties": { - "withdraw_delegator_reward": { - "type": "object", - "required": [ - "validator" - ], - "properties": { - "validator": { - "description": "The `validator_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Fee": { - "type": "object", - "required": [ - "share" - ], - "properties": { - "share": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - }, - "GovMsg": { - "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", - "oneOf": [ - { - "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", - "type": "object", - "required": [ - "vote" - ], - "properties": { - "vote": { - "type": "object", - "required": [ - "proposal_id", - "vote" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "vote": { - "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", - "allOf": [ - { - "$ref": "#/definitions/VoteOption" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This maps directly to [MsgVoteWeighted](https://github.com/cosmos/cosmos-sdk/blob/v0.45.8/proto/cosmos/gov/v1beta1/tx.proto#L66-L78) in the Cosmos SDK with voter set to the contract address.", - "type": "object", - "required": [ - "vote_weighted" - ], - "properties": { - "vote_weighted": { - "type": "object", - "required": [ - "options", - "proposal_id" - ], - "properties": { - "options": { - "type": "array", - "items": { - "$ref": "#/definitions/WeightedVoteOption" - } - }, - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcMsg": { - "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", - "oneOf": [ - { - "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", - "type": "object", - "required": [ - "transfer" - ], - "properties": { - "transfer": { - "type": "object", - "required": [ - "amount", - "channel_id", - "timeout", - "to_address" - ], - "properties": { - "amount": { - "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", - "allOf": [ - { - "$ref": "#/definitions/Coin" - } - ] - }, - "channel_id": { - "description": "exisiting channel to send the tokens over", - "type": "string" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - }, - "to_address": { - "description": "address on the remote chain to receive these tokens", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", - "type": "object", - "required": [ - "send_packet" - ], - "properties": { - "send_packet": { - "type": "object", - "required": [ - "channel_id", - "data", - "timeout" - ], - "properties": { - "channel_id": { - "type": "string" - }, - "data": { - "$ref": "#/definitions/Binary" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", - "type": "object", - "required": [ - "close_channel" - ], - "properties": { - "close_channel": { - "type": "object", - "required": [ - "channel_id" - ], - "properties": { - "channel_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcTimeout": { - "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", - "type": "object", - "properties": { - "block": { - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, - { - "type": "null" - } - ] - }, - "timestamp": { - "anyOf": [ - { - "$ref": "#/definitions/Timestamp" - }, - { - "type": "null" - } - ] - } - } - }, - "IbcTimeoutBlock": { - "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", - "type": "object", - "required": [ - "height", - "revision" - ], - "properties": { - "height": { - "description": "block height after which the packet times out. the height within the given revision", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "StakingMsg": { - "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "delegate" - ], - "properties": { - "delegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "undelegate" - ], - "properties": { - "undelegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "redelegate" - ], - "properties": { - "redelegate": { - "type": "object", - "required": [ - "amount", - "dst_validator", - "src_validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "dst_validator": { - "type": "string" - }, - "src_validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - }, - "VaultFee": { - "type": "object", - "required": [ - "flash_loan_fee", - "protocol_fee" - ], - "properties": { - "flash_loan_fee": { - "$ref": "#/definitions/Fee" - }, - "protocol_fee": { - "$ref": "#/definitions/Fee" - } - }, - "additionalProperties": false - }, - "VoteOption": { - "type": "string", - "enum": [ - "yes", - "no", - "abstain", - "no_with_veto" - ] - }, - "WasmMsg": { - "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", - "oneOf": [ - { - "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "execute" - ], - "properties": { - "execute": { - "type": "object", - "required": [ - "contract_addr", - "funds", - "msg" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "msg": { - "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "instantiate" - ], - "properties": { - "instantiate": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readbale label for the contract", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code using a predictable address derivation algorithm implemented in [`cosmwasm_std::instantiate2_address`].\n\nThis is translated to a [MsgInstantiateContract2](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L73-L96). `sender` is automatically filled with the current contract's address. `fix_msg` is automatically set to false.", - "type": "object", - "required": [ - "instantiate2" - ], - "properties": { - "instantiate2": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg", - "salt" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readbale label for the contract", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "salt": { - "$ref": "#/definitions/Binary" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "migrate" - ], - "properties": { - "migrate": { - "type": "object", - "required": [ - "contract_addr", - "msg", - "new_code_id" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "msg": { - "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "new_code_id": { - "description": "the code_id of the new logic to place in the given contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "admin", - "contract_addr" - ], - "properties": { - "admin": { - "type": "string" - }, - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "clear_admin" - ], - "properties": { - "clear_admin": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "WeightedVoteOption": { - "type": "object", - "required": [ - "option", - "weight" - ], - "properties": { - "option": { - "$ref": "#/definitions/VoteOption" - }, - "weight": { - "$ref": "#/definitions/Decimal" - } - } - } - } -} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/instantiate.json b/contracts/liquidity_hub/vault-manager/schema/raw/instantiate.json deleted file mode 100644 index 1edb0e51..00000000 --- a/contracts/liquidity_hub/vault-manager/schema/raw/instantiate.json +++ /dev/null @@ -1,133 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "description": "The instantiation message", - "type": "object", - "required": [ - "lp_token_type", - "owner", - "vault_creation_fee", - "whale_lair_addr" - ], - "properties": { - "lp_token_type": { - "description": "The type of LP token to use, whether a cw20 token or a token factory token", - "allOf": [ - { - "$ref": "#/definitions/LpTokenType" - } - ] - }, - "owner": { - "description": "The owner of the contract", - "type": "string" - }, - "vault_creation_fee": { - "description": "The fee to create a vault", - "allOf": [ - { - "$ref": "#/definitions/Asset" - } - ] - }, - "whale_lair_addr": { - "description": "The whale lair address, where protocol fees are distributed", - "type": "string" - } - }, - "additionalProperties": false, - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "LpTokenType": { - "description": "The type of LP token to use, whether a cw20 token or a token factory token", - "oneOf": [ - { - "type": "string", - "enum": [ - "token_factory" - ] - }, - { - "type": "object", - "required": [ - "cw20" - ], - "properties": { - "cw20": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/query.json b/contracts/liquidity_hub/vault-manager/schema/raw/query.json deleted file mode 100644 index 092d439c..00000000 --- a/contracts/liquidity_hub/vault-manager/schema/raw/query.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "description": "The query messages", - "oneOf": [ - { - "description": "Retrieves the configuration of the manager.", - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Retrieves a vault given the asset_info.", - "type": "object", - "required": [ - "vault" - ], - "properties": { - "vault": { - "type": "object", - "required": [ - "asset_info" - ], - "properties": { - "asset_info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Retrieves the addresses for all the vaults.", - "type": "object", - "required": [ - "vaults" - ], - "properties": { - "vaults": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "array", - "null" - ], - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Retrieves the share of the assets stored in the vault that a given `lp_share` is entitled to.", - "type": "object", - "required": [ - "share" - ], - "properties": { - "share": { - "type": "object", - "required": [ - "lp_share" - ], - "properties": { - "lp_share": { - "$ref": "#/definitions/Asset" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Retrieves the [`Uint128`] amount that must be sent back to the contract to pay off a loan taken out.", - "type": "object", - "required": [ - "payback_amount" - ], - "properties": { - "payback_amount": { - "type": "object", - "required": [ - "asset" - ], - "properties": { - "asset": { - "$ref": "#/definitions/Asset" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Query the contract's ownership information", - "type": "object", - "required": [ - "ownership" - ], - "properties": { - "ownership": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_config.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_config.json deleted file mode 100644 index dcdbe103..00000000 --- a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_config.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config", - "description": "Configuration for the contract (manager)", - "type": "object", - "required": [ - "deposit_enabled", - "flash_loan_enabled", - "lp_token_type", - "vault_creation_fee", - "whale_lair_addr", - "withdraw_enabled" - ], - "properties": { - "deposit_enabled": { - "description": "If deposits are enabled", - "type": "boolean" - }, - "flash_loan_enabled": { - "description": "If flash-loans are enabled", - "type": "boolean" - }, - "lp_token_type": { - "description": "The type of LP token to use, whether a cw20 token or a token factory token", - "allOf": [ - { - "$ref": "#/definitions/LpTokenType" - } - ] - }, - "vault_creation_fee": { - "description": "The fee to create a new vault", - "allOf": [ - { - "$ref": "#/definitions/Asset" - } - ] - }, - "whale_lair_addr": { - "description": "The whale lair contract address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "withdraw_enabled": { - "description": "If withdrawals are enabled", - "type": "boolean" - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "LpTokenType": { - "description": "The type of LP token to use, whether a cw20 token or a token factory token", - "oneOf": [ - { - "type": "string", - "enum": [ - "token_factory" - ] - }, - { - "type": "object", - "required": [ - "cw20" - ], - "properties": { - "cw20": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_ownership.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_ownership.json deleted file mode 100644 index afe1713f..00000000 --- a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_ownership.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Ownership_for_String", - "description": "The contract's ownership info", - "type": "object", - "properties": { - "owner": { - "description": "The contract's current owner. `None` if the ownership has been renounced.", - "type": [ - "string", - "null" - ] - }, - "pending_expiry": { - "description": "The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline.", - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "pending_owner": { - "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "definitions": { - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_payback_amount.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_payback_amount.json deleted file mode 100644 index fc7261f2..00000000 --- a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_payback_amount.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PaybackAssetResponse", - "description": "Response for the PaybackAmount query. Contains the amount that must be paid back to the contract if taken a flashloan.", - "type": "object", - "required": [ - "asset_info", - "flash_loan_fee", - "payback_amount", - "protocol_fee" - ], - "properties": { - "asset_info": { - "description": "The asset info of the asset that must be paid back", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - }, - "flash_loan_fee": { - "description": "The amount of fee paid to vault holders", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "payback_amount": { - "description": "The total amount that must be returned. Equivalent to `amount` + `protocol_fee` + `flash_loan_fee`.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "protocol_fee": { - "description": "The amount of fee paid to the protocol", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_share.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_share.json deleted file mode 100644 index 3a5e1183..00000000 --- a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_share.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ShareResponse", - "description": "Response for the Share query. Contains the amount of assets that the given `lp_share` is entitled to.", - "type": "object", - "required": [ - "share" - ], - "properties": { - "share": { - "description": "The amount of assets that the given `lp_share` is entitled to.", - "allOf": [ - { - "$ref": "#/definitions/Asset" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vault.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vault.json deleted file mode 100644 index b1b0014e..00000000 --- a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vault.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultsResponse", - "description": "Response for the vaults query", - "type": "object", - "required": [ - "vaults" - ], - "properties": { - "vaults": { - "type": "array", - "items": { - "$ref": "#/definitions/Vault" - } - } - }, - "additionalProperties": false, - "definitions": { - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Fee": { - "type": "object", - "required": [ - "share" - ], - "properties": { - "share": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - }, - "Vault": { - "description": "Vault representation", - "type": "object", - "required": [ - "asset_info", - "fees", - "lp_asset" - ], - "properties": { - "asset_info": { - "description": "The asset info the vault manages", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - }, - "fees": { - "description": "The fees associated with the vault", - "allOf": [ - { - "$ref": "#/definitions/VaultFee" - } - ] - }, - "lp_asset": { - "description": "The LP asset", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - } - }, - "additionalProperties": false - }, - "VaultFee": { - "type": "object", - "required": [ - "flash_loan_fee", - "protocol_fee" - ], - "properties": { - "flash_loan_fee": { - "$ref": "#/definitions/Fee" - }, - "protocol_fee": { - "$ref": "#/definitions/Fee" - } - }, - "additionalProperties": false - } - } -} diff --git a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vaults.json b/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vaults.json deleted file mode 100644 index b1b0014e..00000000 --- a/contracts/liquidity_hub/vault-manager/schema/raw/response_to_vaults.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultsResponse", - "description": "Response for the vaults query", - "type": "object", - "required": [ - "vaults" - ], - "properties": { - "vaults": { - "type": "array", - "items": { - "$ref": "#/definitions/Vault" - } - } - }, - "additionalProperties": false, - "definitions": { - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Fee": { - "type": "object", - "required": [ - "share" - ], - "properties": { - "share": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - }, - "Vault": { - "description": "Vault representation", - "type": "object", - "required": [ - "asset_info", - "fees", - "lp_asset" - ], - "properties": { - "asset_info": { - "description": "The asset info the vault manages", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - }, - "fees": { - "description": "The fees associated with the vault", - "allOf": [ - { - "$ref": "#/definitions/VaultFee" - } - ] - }, - "lp_asset": { - "description": "The LP asset", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - } - }, - "additionalProperties": false - }, - "VaultFee": { - "type": "object", - "required": [ - "flash_loan_fee", - "protocol_fee" - ], - "properties": { - "flash_loan_fee": { - "$ref": "#/definitions/Fee" - }, - "protocol_fee": { - "$ref": "#/definitions/Fee" - } - }, - "additionalProperties": false - } - } -} diff --git a/contracts/liquidity_hub/vault-manager/schema/vault-manager.json b/contracts/liquidity_hub/vault-manager/schema/vault-manager.json deleted file mode 100644 index af50149a..00000000 --- a/contracts/liquidity_hub/vault-manager/schema/vault-manager.json +++ /dev/null @@ -1,2362 +0,0 @@ -{ - "contract_name": "vault-manager", - "contract_version": "0.1.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "description": "The instantiation message", - "type": "object", - "required": [ - "lp_token_type", - "owner", - "vault_creation_fee", - "whale_lair_addr" - ], - "properties": { - "lp_token_type": { - "description": "The type of LP token to use, whether a cw20 token or a token factory token", - "allOf": [ - { - "$ref": "#/definitions/LpTokenType" - } - ] - }, - "owner": { - "description": "The owner of the contract", - "type": "string" - }, - "vault_creation_fee": { - "description": "The fee to create a vault", - "allOf": [ - { - "$ref": "#/definitions/Asset" - } - ] - }, - "whale_lair_addr": { - "description": "The whale lair address, where protocol fees are distributed", - "type": "string" - } - }, - "additionalProperties": false, - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "LpTokenType": { - "description": "The type of LP token to use, whether a cw20 token or a token factory token", - "oneOf": [ - { - "type": "string", - "enum": [ - "token_factory" - ] - }, - { - "type": "object", - "required": [ - "cw20" - ], - "properties": { - "cw20": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "description": "The execution messages", - "oneOf": [ - { - "description": "Creates a new vault given the asset info the vault should manage deposits and withdrawals for and the fees", - "type": "object", - "required": [ - "create_vault" - ], - "properties": { - "create_vault": { - "type": "object", - "required": [ - "asset_info", - "fees" - ], - "properties": { - "asset_info": { - "$ref": "#/definitions/AssetInfo" - }, - "fees": { - "$ref": "#/definitions/VaultFee" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Removes a vault given its [AssetInfo]", - "type": "object", - "required": [ - "remove_vault" - ], - "properties": { - "remove_vault": { - "type": "object", - "required": [ - "asset_info" - ], - "properties": { - "asset_info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Updates a vault config", - "type": "object", - "required": [ - "update_vault_fees" - ], - "properties": { - "update_vault_fees": { - "type": "object", - "required": [ - "vault_asset_info", - "vault_fee" - ], - "properties": { - "vault_asset_info": { - "$ref": "#/definitions/AssetInfo" - }, - "vault_fee": { - "$ref": "#/definitions/VaultFee" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Updates the configuration of the vault manager. If a field is not specified, it will not be modified.", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "cw20_lp_code_id": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "deposit_enabled": { - "type": [ - "boolean", - "null" - ] - }, - "flash_loan_enabled": { - "type": [ - "boolean", - "null" - ] - }, - "vault_creation_fee": { - "anyOf": [ - { - "$ref": "#/definitions/Asset" - }, - { - "type": "null" - } - ] - }, - "whale_lair_addr": { - "type": [ - "string", - "null" - ] - }, - "withdraw_enabled": { - "type": [ - "boolean", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Deposits a given asset into the vault manager.", - "type": "object", - "required": [ - "deposit" - ], - "properties": { - "deposit": { - "type": "object", - "required": [ - "asset" - ], - "properties": { - "asset": { - "$ref": "#/definitions/Asset" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Withdraws from the vault manager. Used when the LP token is a token manager token.", - "type": "object", - "required": [ - "withdraw" - ], - "properties": { - "withdraw": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "description": "Retrieves the desired `asset` and runs the `payload`, paying the required amount back to the vault after running the messages in the payload, and returning the profit to the sender.", - "type": "object", - "required": [ - "flash_loan" - ], - "properties": { - "flash_loan": { - "type": "object", - "required": [ - "asset", - "payload" - ], - "properties": { - "asset": { - "$ref": "#/definitions/Asset" - }, - "payload": { - "type": "array", - "items": { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Callback message for post-processing flash-loans.", - "type": "object", - "required": [ - "callback" - ], - "properties": { - "callback": { - "$ref": "#/definitions/CallbackMsg" - } - }, - "additionalProperties": false - }, - { - "description": "Update the contract's ownership. The `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.", - "type": "object", - "required": [ - "update_ownership" - ], - "properties": { - "update_ownership": { - "$ref": "#/definitions/Action" - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Action": { - "description": "Actions that can be taken to alter the contract's ownership", - "oneOf": [ - { - "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", - "type": "object", - "required": [ - "transfer_ownership" - ], - "properties": { - "transfer_ownership": { - "type": "object", - "required": [ - "new_owner" - ], - "properties": { - "expiry": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "new_owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", - "type": "string", - "enum": [ - "accept_ownership" - ] - }, - { - "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", - "type": "string", - "enum": [ - "renounce_ownership" - ] - } - ] - }, - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "BankMsg": { - "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", - "oneOf": [ - { - "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "to_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "CallbackMsg": { - "description": "The callback messages available. Only callable by the vault contract itself.", - "oneOf": [ - { - "type": "object", - "required": [ - "after_flashloan" - ], - "properties": { - "after_flashloan": { - "type": "object", - "required": [ - "loan_asset", - "old_asset_balance", - "sender" - ], - "properties": { - "loan_asset": { - "$ref": "#/definitions/Asset" - }, - "old_asset_balance": { - "$ref": "#/definitions/Uint128" - }, - "sender": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "CosmosMsg_for_Empty": { - "oneOf": [ - { - "type": "object", - "required": [ - "bank" - ], - "properties": { - "bank": { - "$ref": "#/definitions/BankMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "custom" - ], - "properties": { - "custom": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "staking" - ], - "properties": { - "staking": { - "$ref": "#/definitions/StakingMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "distribution" - ], - "properties": { - "distribution": { - "$ref": "#/definitions/DistributionMsg" - } - }, - "additionalProperties": false - }, - { - "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", - "type": "object", - "required": [ - "stargate" - ], - "properties": { - "stargate": { - "type": "object", - "required": [ - "type_url", - "value" - ], - "properties": { - "type_url": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Binary" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "ibc" - ], - "properties": { - "ibc": { - "$ref": "#/definitions/IbcMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "wasm" - ], - "properties": { - "wasm": { - "$ref": "#/definitions/WasmMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "gov" - ], - "properties": { - "gov": { - "$ref": "#/definitions/GovMsg" - } - }, - "additionalProperties": false - } - ] - }, - "Cw20ReceiveMsg": { - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - }, - "additionalProperties": false - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "DistributionMsg": { - "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "set_withdraw_address" - ], - "properties": { - "set_withdraw_address": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "description": "The `withdraw_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "withdraw_delegator_reward" - ], - "properties": { - "withdraw_delegator_reward": { - "type": "object", - "required": [ - "validator" - ], - "properties": { - "validator": { - "description": "The `validator_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Fee": { - "type": "object", - "required": [ - "share" - ], - "properties": { - "share": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - }, - "GovMsg": { - "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", - "oneOf": [ - { - "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", - "type": "object", - "required": [ - "vote" - ], - "properties": { - "vote": { - "type": "object", - "required": [ - "proposal_id", - "vote" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "vote": { - "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", - "allOf": [ - { - "$ref": "#/definitions/VoteOption" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This maps directly to [MsgVoteWeighted](https://github.com/cosmos/cosmos-sdk/blob/v0.45.8/proto/cosmos/gov/v1beta1/tx.proto#L66-L78) in the Cosmos SDK with voter set to the contract address.", - "type": "object", - "required": [ - "vote_weighted" - ], - "properties": { - "vote_weighted": { - "type": "object", - "required": [ - "options", - "proposal_id" - ], - "properties": { - "options": { - "type": "array", - "items": { - "$ref": "#/definitions/WeightedVoteOption" - } - }, - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcMsg": { - "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", - "oneOf": [ - { - "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", - "type": "object", - "required": [ - "transfer" - ], - "properties": { - "transfer": { - "type": "object", - "required": [ - "amount", - "channel_id", - "timeout", - "to_address" - ], - "properties": { - "amount": { - "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", - "allOf": [ - { - "$ref": "#/definitions/Coin" - } - ] - }, - "channel_id": { - "description": "exisiting channel to send the tokens over", - "type": "string" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - }, - "to_address": { - "description": "address on the remote chain to receive these tokens", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", - "type": "object", - "required": [ - "send_packet" - ], - "properties": { - "send_packet": { - "type": "object", - "required": [ - "channel_id", - "data", - "timeout" - ], - "properties": { - "channel_id": { - "type": "string" - }, - "data": { - "$ref": "#/definitions/Binary" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", - "type": "object", - "required": [ - "close_channel" - ], - "properties": { - "close_channel": { - "type": "object", - "required": [ - "channel_id" - ], - "properties": { - "channel_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcTimeout": { - "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", - "type": "object", - "properties": { - "block": { - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, - { - "type": "null" - } - ] - }, - "timestamp": { - "anyOf": [ - { - "$ref": "#/definitions/Timestamp" - }, - { - "type": "null" - } - ] - } - } - }, - "IbcTimeoutBlock": { - "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", - "type": "object", - "required": [ - "height", - "revision" - ], - "properties": { - "height": { - "description": "block height after which the packet times out. the height within the given revision", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "revision": { - "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "StakingMsg": { - "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "delegate" - ], - "properties": { - "delegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "undelegate" - ], - "properties": { - "undelegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "redelegate" - ], - "properties": { - "redelegate": { - "type": "object", - "required": [ - "amount", - "dst_validator", - "src_validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "dst_validator": { - "type": "string" - }, - "src_validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - }, - "VaultFee": { - "type": "object", - "required": [ - "flash_loan_fee", - "protocol_fee" - ], - "properties": { - "flash_loan_fee": { - "$ref": "#/definitions/Fee" - }, - "protocol_fee": { - "$ref": "#/definitions/Fee" - } - }, - "additionalProperties": false - }, - "VoteOption": { - "type": "string", - "enum": [ - "yes", - "no", - "abstain", - "no_with_veto" - ] - }, - "WasmMsg": { - "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", - "oneOf": [ - { - "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "execute" - ], - "properties": { - "execute": { - "type": "object", - "required": [ - "contract_addr", - "funds", - "msg" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "msg": { - "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "instantiate" - ], - "properties": { - "instantiate": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readbale label for the contract", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code using a predictable address derivation algorithm implemented in [`cosmwasm_std::instantiate2_address`].\n\nThis is translated to a [MsgInstantiateContract2](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L73-L96). `sender` is automatically filled with the current contract's address. `fix_msg` is automatically set to false.", - "type": "object", - "required": [ - "instantiate2" - ], - "properties": { - "instantiate2": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg", - "salt" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readbale label for the contract", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "salt": { - "$ref": "#/definitions/Binary" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "migrate" - ], - "properties": { - "migrate": { - "type": "object", - "required": [ - "contract_addr", - "msg", - "new_code_id" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "msg": { - "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "new_code_id": { - "description": "the code_id of the new logic to place in the given contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "admin", - "contract_addr" - ], - "properties": { - "admin": { - "type": "string" - }, - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "clear_admin" - ], - "properties": { - "clear_admin": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "WeightedVoteOption": { - "type": "object", - "required": [ - "option", - "weight" - ], - "properties": { - "option": { - "$ref": "#/definitions/VoteOption" - }, - "weight": { - "$ref": "#/definitions/Decimal" - } - } - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "description": "The query messages", - "oneOf": [ - { - "description": "Retrieves the configuration of the manager.", - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Retrieves a vault given the asset_info.", - "type": "object", - "required": [ - "vault" - ], - "properties": { - "vault": { - "type": "object", - "required": [ - "asset_info" - ], - "properties": { - "asset_info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Retrieves the addresses for all the vaults.", - "type": "object", - "required": [ - "vaults" - ], - "properties": { - "vaults": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "array", - "null" - ], - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Retrieves the share of the assets stored in the vault that a given `lp_share` is entitled to.", - "type": "object", - "required": [ - "share" - ], - "properties": { - "share": { - "type": "object", - "required": [ - "lp_share" - ], - "properties": { - "lp_share": { - "$ref": "#/definitions/Asset" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Retrieves the [`Uint128`] amount that must be sent back to the contract to pay off a loan taken out.", - "type": "object", - "required": [ - "payback_amount" - ], - "properties": { - "payback_amount": { - "type": "object", - "required": [ - "asset" - ], - "properties": { - "asset": { - "$ref": "#/definitions/Asset" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Query the contract's ownership information", - "type": "object", - "required": [ - "ownership" - ], - "properties": { - "ownership": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "migrate": null, - "sudo": null, - "responses": { - "config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config", - "description": "Configuration for the contract (manager)", - "type": "object", - "required": [ - "deposit_enabled", - "flash_loan_enabled", - "lp_token_type", - "vault_creation_fee", - "whale_lair_addr", - "withdraw_enabled" - ], - "properties": { - "deposit_enabled": { - "description": "If deposits are enabled", - "type": "boolean" - }, - "flash_loan_enabled": { - "description": "If flash-loans are enabled", - "type": "boolean" - }, - "lp_token_type": { - "description": "The type of LP token to use, whether a cw20 token or a token factory token", - "allOf": [ - { - "$ref": "#/definitions/LpTokenType" - } - ] - }, - "vault_creation_fee": { - "description": "The fee to create a new vault", - "allOf": [ - { - "$ref": "#/definitions/Asset" - } - ] - }, - "whale_lair_addr": { - "description": "The whale lair contract address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "withdraw_enabled": { - "description": "If withdrawals are enabled", - "type": "boolean" - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "LpTokenType": { - "description": "The type of LP token to use, whether a cw20 token or a token factory token", - "oneOf": [ - { - "type": "string", - "enum": [ - "token_factory" - ] - }, - { - "type": "object", - "required": [ - "cw20" - ], - "properties": { - "cw20": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "ownership": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Ownership_for_String", - "description": "The contract's ownership info", - "type": "object", - "properties": { - "owner": { - "description": "The contract's current owner. `None` if the ownership has been renounced.", - "type": [ - "string", - "null" - ] - }, - "pending_expiry": { - "description": "The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline.", - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "pending_owner": { - "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "definitions": { - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "payback_amount": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PaybackAssetResponse", - "description": "Response for the PaybackAmount query. Contains the amount that must be paid back to the contract if taken a flashloan.", - "type": "object", - "required": [ - "asset_info", - "flash_loan_fee", - "payback_amount", - "protocol_fee" - ], - "properties": { - "asset_info": { - "description": "The asset info of the asset that must be paid back", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - }, - "flash_loan_fee": { - "description": "The amount of fee paid to vault holders", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "payback_amount": { - "description": "The total amount that must be returned. Equivalent to `amount` + `protocol_fee` + `flash_loan_fee`.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "protocol_fee": { - "description": "The amount of fee paid to the protocol", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "share": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ShareResponse", - "description": "Response for the Share query. Contains the amount of assets that the given `lp_share` is entitled to.", - "type": "object", - "required": [ - "share" - ], - "properties": { - "share": { - "description": "The amount of assets that the given `lp_share` is entitled to.", - "allOf": [ - { - "$ref": "#/definitions/Asset" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "vault": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultsResponse", - "description": "Response for the vaults query", - "type": "object", - "required": [ - "vaults" - ], - "properties": { - "vaults": { - "type": "array", - "items": { - "$ref": "#/definitions/Vault" - } - } - }, - "additionalProperties": false, - "definitions": { - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Fee": { - "type": "object", - "required": [ - "share" - ], - "properties": { - "share": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - }, - "Vault": { - "description": "Vault representation", - "type": "object", - "required": [ - "asset_info", - "fees", - "lp_asset" - ], - "properties": { - "asset_info": { - "description": "The asset info the vault manages", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - }, - "fees": { - "description": "The fees associated with the vault", - "allOf": [ - { - "$ref": "#/definitions/VaultFee" - } - ] - }, - "lp_asset": { - "description": "The LP asset", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - } - }, - "additionalProperties": false - }, - "VaultFee": { - "type": "object", - "required": [ - "flash_loan_fee", - "protocol_fee" - ], - "properties": { - "flash_loan_fee": { - "$ref": "#/definitions/Fee" - }, - "protocol_fee": { - "$ref": "#/definitions/Fee" - } - }, - "additionalProperties": false - } - } - }, - "vaults": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultsResponse", - "description": "Response for the vaults query", - "type": "object", - "required": [ - "vaults" - ], - "properties": { - "vaults": { - "type": "array", - "items": { - "$ref": "#/definitions/Vault" - } - } - }, - "additionalProperties": false, - "definitions": { - "AssetInfo": { - "description": "AssetInfo contract_addr is usually passed from the cw20 hook so we can trust the contract_addr is properly validated.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Fee": { - "type": "object", - "required": [ - "share" - ], - "properties": { - "share": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - }, - "Vault": { - "description": "Vault representation", - "type": "object", - "required": [ - "asset_info", - "fees", - "lp_asset" - ], - "properties": { - "asset_info": { - "description": "The asset info the vault manages", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - }, - "fees": { - "description": "The fees associated with the vault", - "allOf": [ - { - "$ref": "#/definitions/VaultFee" - } - ] - }, - "lp_asset": { - "description": "The LP asset", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - } - }, - "additionalProperties": false - }, - "VaultFee": { - "type": "object", - "required": [ - "flash_loan_fee", - "protocol_fee" - ], - "properties": { - "flash_loan_fee": { - "$ref": "#/definitions/Fee" - }, - "protocol_fee": { - "$ref": "#/definitions/Fee" - } - }, - "additionalProperties": false - } - } - } - } -} diff --git a/packages/white-whale/src/pool_network/mod.rs b/packages/white-whale/src/pool_network/mod.rs index 91f88096..52c2d7fc 100644 --- a/packages/white-whale/src/pool_network/mod.rs +++ b/packages/white-whale/src/pool_network/mod.rs @@ -10,6 +10,7 @@ pub mod incentive_factory; pub mod pair; pub mod querier; pub mod router; +pub mod swap; pub mod token; pub mod trio; diff --git a/packages/white-whale/src/pool_network/swap.rs b/packages/white-whale/src/pool_network/swap.rs new file mode 100644 index 00000000..ef58ce18 --- /dev/null +++ b/packages/white-whale/src/pool_network/swap.rs @@ -0,0 +1,42 @@ +use cosmwasm_std::{Decimal, Decimal256, Fraction, StdError, StdResult, Uint128}; +use std::str::FromStr; + +/// Default swap slippage in case max_spread is not specified +pub const DEFAULT_SLIPPAGE: &str = "0.01"; +/// Cap on the maximum swap slippage that is allowed. If max_spread goes over this limit, it will +/// be capped to this value. +pub const MAX_ALLOWED_SLIPPAGE: &str = "0.5"; + +/// If `belief_price` and `max_spread` both are given, +/// we compute new spread else we just use pool network +/// spread to check `max_spread` +pub fn assert_max_spread( + belief_price: Option, + max_spread: Option, + offer_amount: Uint128, + return_amount: Uint128, + spread_amount: Uint128, +) -> StdResult<()> { + let max_spread: Decimal256 = max_spread + .unwrap_or(Decimal::from_str(DEFAULT_SLIPPAGE)?) + .min(Decimal::from_str(MAX_ALLOWED_SLIPPAGE)?) + .into(); + + if let Some(belief_price) = belief_price { + let expected_return = offer_amount + * belief_price + .inv() + .ok_or_else(|| StdError::generic_err("Belief price can't be zero"))?; + let spread_amount = expected_return.saturating_sub(return_amount); + + if return_amount < expected_return + && Decimal256::from_ratio(spread_amount, expected_return) > max_spread + { + return Err(StdError::generic_err("Spread limit exceeded")); + } + } else if Decimal256::from_ratio(spread_amount, return_amount + spread_amount) > max_spread { + return Err(StdError::generic_err("Spread limit exceeded")); + } + + Ok(()) +} From 5e57ffb2f8cbe8a5201bef1c2d4ad102c59ed26e Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Fri, 20 Oct 2023 12:18:13 +0100 Subject: [PATCH 11/22] test: add missing test to cover for assert_max_spread error --- .../terraswap_pair/src/tests/testing.rs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs index 8efa2aaa..4105cb3d 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/testing.rs @@ -1,14 +1,12 @@ -#[cfg(feature = "token_factory")] -use crate::state::LP_SYMBOL; +use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; #[cfg(feature = "token_factory")] use cosmwasm_std::CosmosMsg; - -use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ from_binary, to_binary, Addr, Decimal, Reply, ReplyOn, StdError, SubMsg, SubMsgResponse, SubMsgResult, Uint128, WasmMsg, }; use cw20::MinterResponse; + use white_whale::fee::Fee; use white_whale::pool_network::asset::{Asset, AssetInfo, PairInfo, PairType}; #[cfg(feature = "token_factory")] @@ -23,6 +21,8 @@ use crate::contract::{execute, instantiate, query, reply}; use crate::error::ContractError; use crate::helpers::assert_slippage_tolerance; use crate::queries::query_pair_info; +#[cfg(feature = "token_factory")] +use crate::state::LP_SYMBOL; #[test] fn proper_initialization_cw20_lp() { @@ -346,6 +346,7 @@ fn test_initialization_invalid_fees() { _ => panic!("should return StdError::generic_err(Invalid fee)"), } } + #[test] fn test_max_spread() { assert_max_spread( @@ -462,7 +463,26 @@ fn test_max_spread() { Uint128::zero(), ) .unwrap(); + + assert_max_spread( + Some(Decimal::from_ratio(1200_000_000u128, 1_000_000u128)), + Some(Decimal::percent(60)), // this will default to 50% + Uint128::from(1200_000_000u128), + Uint128::from(989_999u128), + Uint128::zero(), + ) + .unwrap(); + + assert_max_spread( + Some(Decimal::zero()), + None, + Uint128::new(100), + Uint128::new(90), + Uint128::new(10), + ) + .unwrap_err(); } + #[test] fn test_update_config_unsuccessful() { let mut deps = mock_dependencies(&[]); From 9a38f3718e7639957bd5ca039b62ac220792de07 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Fri, 20 Oct 2023 14:01:35 +0100 Subject: [PATCH 12/22] chore: bump vault contract version before migration on terra --- Cargo.lock | 2 +- .../pool-network/terraswap_pair/src/contract.rs | 17 +++++++---------- .../vault-network/vault/Cargo.toml | 2 +- scripts/build_release.sh | 4 ++-- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85364201..91b1f7a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1542,7 +1542,7 @@ checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" [[package]] name = "vault" -version = "1.2.3" +version = "1.2.4" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs index c5a205ee..7d2719b5 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs @@ -238,18 +238,15 @@ pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result= version { - // return Err(ContractError::MigrateInvalidVersion { - // current_version: storage_version, - // new_version: version, - // }); - // } + if storage_version >= version { + return Err(ContractError::MigrateInvalidVersion { + current_version: storage_version, + new_version: version, + }); + } if storage_version <= Version::parse("1.0.4")? { migrations::migrate_to_v110(deps.branch())?; diff --git a/contracts/liquidity_hub/vault-network/vault/Cargo.toml b/contracts/liquidity_hub/vault-network/vault/Cargo.toml index fe7a2885..6c8b24e8 100644 --- a/contracts/liquidity_hub/vault-network/vault/Cargo.toml +++ b/contracts/liquidity_hub/vault-network/vault/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vault" -version = "1.2.3" +version = "1.2.4" authors = ["kaimen-sano "] edition.workspace = true description = "Contract to handle a single vault that controls an asset" diff --git a/scripts/build_release.sh b/scripts/build_release.sh index 8643f25b..ecc5dd62 100755 --- a/scripts/build_release.sh +++ b/scripts/build_release.sh @@ -31,10 +31,10 @@ flag="" case $chain in -juno) +juno | terra) flag="-osmosis_token_factory" ;; -terra | migaloo) +migaloo) flag="-token_factory" ;; injective) From 82d9f4f91a7559313c3d9f72c51dbf7af9fcd20e Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Mon, 23 Oct 2023 15:23:58 +0100 Subject: [PATCH 13/22] chore: add injective factory --- .../stableswap_3pool/src/commands.rs | 38 ++++- .../stableswap_3pool/src/helpers.rs | 26 +++- .../terraswap_pair/src/commands.rs | 38 ++++- .../terraswap_pair/src/helpers.rs | 14 +- .../vault-network/vault/src/contract.rs | 14 +- .../vault/src/execute/deposit.rs | 26 +++- .../vault/src/execute/receive/mod.rs | 12 +- .../vault/src/execute/receive/withdraw.rs | 20 ++- packages/white-whale/Cargo.toml | 2 +- .../white-whale/src/pool_network/asset.rs | 12 +- .../src/pool_network/denom_injective.rs | 139 ++++++++++++++++++ packages/white-whale/src/pool_network/mod.rs | 2 + 12 files changed, 311 insertions(+), 32 deletions(-) create mode 100644 packages/white-whale/src/pool_network/denom_injective.rs diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs index bced40b2..bb582df2 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs @@ -5,15 +5,25 @@ use cosmwasm_std::{ use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use crate::contract::{MAX_AMP, MAX_AMP_CHANGE, MIN_AMP, MIN_RAMP_BLOCKS}; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use cosmwasm_std::coins; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use white_whale::pool_network::asset::is_factory_token; use white_whale::pool_network::asset::{ Asset, AssetInfo, AssetInfoRaw, TrioInfoRaw, MINIMUM_LIQUIDITY_AMOUNT, }; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::{Coin, MsgBurn, MsgMint}; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::{Coin, MsgBurn, MsgMint}; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::{Coin, MsgBurn, MsgMint}; use white_whale::pool_network::swap; @@ -672,7 +682,11 @@ fn mint_lp_token_msg( sender: String, amount: Uint128, ) -> Result, ContractError> { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_token.as_str()) { let mut messages = vec![]; messages.push(>::into(MsgMint { @@ -699,7 +713,11 @@ fn mint_lp_token_msg( })]) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(vec![CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_token, msg: to_binary(&Cw20ExecuteMsg::Mint { recipient, amount })?, @@ -714,7 +732,11 @@ fn burn_lp_token_msg( sender: String, amount: Uint128, ) -> Result { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_token.as_str()) { Ok(>::into(MsgBurn { sender, @@ -731,7 +753,11 @@ fn burn_lp_token_msg( })) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_token, msg: to_binary(&Cw20ExecuteMsg::Burn { amount })?, diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs index 9c4674fa..8afe24ce 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs @@ -6,11 +6,17 @@ use cosmwasm_std::{ use cw20::MinterResponse; use cw_storage_plus::Item; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use cosmwasm_std::CosmosMsg; use white_whale::pool_network::asset::{is_factory_token, Asset, AssetInfo, AssetInfoRaw}; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::MsgCreateDenom; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::MsgCreateDenom; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::MsgCreateDenom; use white_whale::pool_network::querier::query_token_info; @@ -207,7 +213,11 @@ pub fn instantiate_fees( /// Gets the total supply of the given liquidity token pub fn get_total_share(deps: &Deps, liquidity_token: String) -> StdResult { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] let total_share = if is_factory_token(liquidity_token.as_str()) { //bank query total deps.querier.query_supply(&liquidity_token)?.amount @@ -218,7 +228,11 @@ pub fn get_total_share(deps: &Deps, liquidity_token: String) -> StdResult>::into( MsgCreateDenom { diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs index afb99946..b6753f98 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs @@ -4,9 +4,17 @@ use cosmwasm_std::{ }; use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use cosmwasm_std::coins; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use white_whale::pool_network::asset::is_factory_token; use white_whale::pool_network::asset::{ get_total_share, has_factory_token, Asset, AssetInfo, AssetInfoRaw, PairInfoRaw, @@ -14,6 +22,8 @@ use white_whale::pool_network::asset::{ }; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::{Coin, MsgBurn, MsgMint}; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::{Coin, MsgBurn, MsgMint}; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::{Coin, MsgBurn, MsgMint}; use white_whale::pool_network::pair::{Config, Cw20HookMsg, FeatureToggle, PoolFee}; @@ -577,7 +587,11 @@ fn mint_lp_token_msg( sender: String, amount: Uint128, ) -> Result, ContractError> { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_token.as_str()) { let mut messages = vec![]; messages.push(>::into(MsgMint { @@ -604,7 +618,11 @@ fn mint_lp_token_msg( })]) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(vec![CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_token, msg: to_binary(&Cw20ExecuteMsg::Mint { recipient, amount })?, @@ -619,7 +637,11 @@ fn burn_lp_token_msg( sender: String, amount: Uint128, ) -> Result { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_token.as_str()) { Ok(>::into(MsgBurn { sender, @@ -635,7 +657,11 @@ fn burn_lp_token_msg( funds: vec![], })) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_token, msg: to_binary(&Cw20ExecuteMsg::Burn { amount })?, diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs index 81e49aef..ff915152 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs @@ -1,7 +1,11 @@ use std::ops::Mul; use cosmwasm_schema::cw_serde; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use cosmwasm_std::CosmosMsg; use cosmwasm_std::{ to_binary, Decimal, Decimal256, DepsMut, Env, ReplyOn, Response, StdError, StdResult, Storage, @@ -13,6 +17,8 @@ use cw_storage_plus::Item; use white_whale::pool_network::asset::{Asset, AssetInfo, AssetInfoRaw, PairType}; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::MsgCreateDenom; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::MsgCreateDenom; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::MsgCreateDenom; use white_whale::pool_network::pair::{InstantiateMsg, PoolFee}; @@ -427,7 +433,11 @@ pub fn create_lp_token( Ok(pair_info) })?; - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] return Ok( Response::new().add_message(>::into( MsgCreateDenom { diff --git a/contracts/liquidity_hub/vault-network/vault/src/contract.rs b/contracts/liquidity_hub/vault-network/vault/src/contract.rs index f17fdc1c..32ed68d6 100644 --- a/contracts/liquidity_hub/vault-network/vault/src/contract.rs +++ b/contracts/liquidity_hub/vault-network/vault/src/contract.rs @@ -23,10 +23,16 @@ use crate::{ }; use crate::execute::receive::withdraw::withdraw; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use cosmwasm_std::CosmosMsg; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::MsgCreateDenom; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::MsgCreateDenom; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::MsgCreateDenom; @@ -95,7 +101,11 @@ pub fn instantiate( Ok(config) })?; - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] return Ok( response.add_message(>::into(MsgCreateDenom { sender: env.contract.address.to_string(), diff --git a/contracts/liquidity_hub/vault-network/vault/src/execute/deposit.rs b/contracts/liquidity_hub/vault-network/vault/src/execute/deposit.rs index 70cca3da..70f24dd2 100644 --- a/contracts/liquidity_hub/vault-network/vault/src/execute/deposit.rs +++ b/contracts/liquidity_hub/vault-network/vault/src/execute/deposit.rs @@ -1,14 +1,24 @@ -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use cosmwasm_std::coins; use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, MessageInfo, Response, Uint128, WasmMsg}; use cw20::{AllowanceResponse, Cw20ExecuteMsg}; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use white_whale::pool_network::asset::is_factory_token; use white_whale::pool_network::asset::AssetInfo; use white_whale::pool_network::asset::{get_total_share, MINIMUM_LIQUIDITY_AMOUNT}; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::{Coin, MsgMint}; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::{Coin, MsgMint}; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::{Coin, MsgMint}; @@ -153,7 +163,11 @@ fn mint_lp_token_msg( sender: String, amount: Uint128, ) -> Result, VaultError> { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_asset.as_str()) { let mut messages = vec![]; messages.push(>::into(MsgMint { @@ -180,7 +194,11 @@ fn mint_lp_token_msg( })]) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(vec![CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_asset, msg: to_binary(&Cw20ExecuteMsg::Mint { recipient, amount })?, diff --git a/contracts/liquidity_hub/vault-network/vault/src/execute/receive/mod.rs b/contracts/liquidity_hub/vault-network/vault/src/execute/receive/mod.rs index a973d62b..b08de1b0 100644 --- a/contracts/liquidity_hub/vault-network/vault/src/execute/receive/mod.rs +++ b/contracts/liquidity_hub/vault-network/vault/src/execute/receive/mod.rs @@ -36,7 +36,11 @@ pub fn receive( mod test { use cosmwasm_std::{to_binary, Addr, Uint128}; - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] use cosmwasm_std::testing::mock_info; use white_whale::pool_network::asset::AssetInfo; @@ -93,7 +97,11 @@ mod test { assert_eq!(res.unwrap_err(), VaultError::Unauthorized {}) } - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] #[test] fn cannot_receive_from_not_liquidity_token() { let (mut deps, env) = mock_instantiate( diff --git a/contracts/liquidity_hub/vault-network/vault/src/execute/receive/withdraw.rs b/contracts/liquidity_hub/vault-network/vault/src/execute/receive/withdraw.rs index e1f6f85a..fe5eacc9 100644 --- a/contracts/liquidity_hub/vault-network/vault/src/execute/receive/withdraw.rs +++ b/contracts/liquidity_hub/vault-network/vault/src/execute/receive/withdraw.rs @@ -3,11 +3,17 @@ use cosmwasm_std::{ }; use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg}; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use white_whale::pool_network::asset::is_factory_token; use white_whale::pool_network::asset::{get_total_share, AssetInfo}; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::{Coin, MsgBurn}; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::{Coin, MsgBurn}; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::{Coin, MsgBurn}; @@ -94,7 +100,11 @@ fn burn_lp_asset_msg( sender: String, amount: Uint128, ) -> Result { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_asset.as_str()) { Ok(>::into(MsgBurn { sender, @@ -110,7 +120,11 @@ fn burn_lp_asset_msg( funds: vec![], })) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_asset, msg: to_binary(&Cw20ExecuteMsg::Burn { amount })?, diff --git a/packages/white-whale/Cargo.toml b/packages/white-whale/Cargo.toml index 66f56ab3..0d76e866 100644 --- a/packages/white-whale/Cargo.toml +++ b/packages/white-whale/Cargo.toml @@ -15,7 +15,7 @@ documentation = "https://whitewhale.money" # for quicker tests, cargo test --lib # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -injective = [] +injective = ["token_factory"] token_factory = ["cosmwasm-std/stargate", "cosmwasm-std/cosmwasm_1_1"] osmosis_token_factory = ["token_factory"] # this is for the osmosis token factory proto definitions, which defer from the standard token factory :) diff --git a/packages/white-whale/src/pool_network/asset.rs b/packages/white-whale/src/pool_network/asset.rs index 04176e44..e3823c93 100644 --- a/packages/white-whale/src/pool_network/asset.rs +++ b/packages/white-whale/src/pool_network/asset.rs @@ -620,7 +620,11 @@ impl TrioInfoRaw { /// Gets the total supply of the given liquidity asset pub fn get_total_share(deps: &Deps, liquidity_asset: String) -> StdResult { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] let total_share = if is_factory_token(liquidity_asset.as_str()) { //bank query total deps.querier.query_supply(&liquidity_asset)?.amount @@ -631,7 +635,11 @@ pub fn get_total_share(deps: &Deps, liquidity_asset: String) -> StdResult. The resulting denom's admin is +/// originally set to be the creator, but this can be changed later. The token +/// denom does not indicate the current admin. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgCreateDenom")] +pub struct MsgCreateDenom { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + /// subdenom can be up to 44 "alphanumeric" characters long. + #[prost(string, tag = "2")] + pub subdenom: ::prost::alloc::string::String, +} + +/// MsgCreateDenomResponse is the return value of MsgCreateDenom +/// It returns the full string of the newly created denom +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgCreateDenomResponse")] +pub struct MsgCreateDenomResponse { + #[prost(string, tag = "1")] + pub new_token_denom: ::prost::alloc::string::String, +} + +/// MsgMint is the sdk.Msg type for allowing an admin account to mint +/// more of a token. For now, we only support minting to the sender account +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgMint")] +pub struct MsgMint { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgMintResponse")] +pub struct MsgMintResponse {} + +/// MsgBurn is the sdk.Msg type for allowing an admin account to burn +/// a token. For now, we only support burning from the sender account. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgBurn")] +pub struct MsgBurn { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgBurnResponse")] +pub struct MsgBurnResponse {} diff --git a/packages/white-whale/src/pool_network/mod.rs b/packages/white-whale/src/pool_network/mod.rs index 52c2d7fc..e86c6456 100644 --- a/packages/white-whale/src/pool_network/mod.rs +++ b/packages/white-whale/src/pool_network/mod.rs @@ -1,6 +1,8 @@ pub mod asset; #[cfg(feature = "token_factory")] pub mod denom; +#[cfg(feature = "injective")] +pub mod denom_injective; #[cfg(feature = "osmosis_token_factory")] pub mod denom_osmosis; pub mod factory; From 06a55a6568edb2bfb92cd5072da8a5b6b85e4a17 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Tue, 24 Oct 2023 14:40:28 +0100 Subject: [PATCH 14/22] fix: vault deposits for assets with 18 decimals --- Cargo.lock | 6 +- .../pool-network/terraswap_pair/Cargo.toml | 6 +- .../terraswap_pair/src/commands.rs | 2 + .../terraswap_pair/src/tests/mock_app.rs | 14 +++ .../src/tests/mock_instantiate.rs | 53 ++++++++++ .../terraswap_pair/src/tests/mod.rs | 7 ++ .../src/tests/provide_liquidity.rs | 98 ++++++++++++++++++- .../terraswap_pair/src/tests/store_code.rs | 25 +++++ .../vault-network/vault/Cargo.toml | 2 +- .../vault/src/execute/deposit.rs | 58 ++++++++++- 10 files changed, 260 insertions(+), 11 deletions(-) create mode 100644 contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_app.rs create mode 100644 contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_instantiate.rs create mode 100644 contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/store_code.rs diff --git a/Cargo.lock b/Cargo.lock index 91b1f7a9..ea48ae48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1434,13 +1434,15 @@ dependencies = [ [[package]] name = "terraswap-pair" -version = "1.3.2" +version = "1.3.3" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-multi-test", "cw-storage-plus", "cw2", "cw20", + "cw20-base", "integer-sqrt", "protobuf", "schemars", @@ -1542,7 +1544,7 @@ checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" [[package]] name = "vault" -version = "1.2.4" +version = "1.2.5" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml b/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml index 4f1f403d..0111fe44 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "terraswap-pair" -version = "1.3.2" +version = "1.3.3" authors = [ "Terraform Labs, PTE.", "DELIGHT LABS", @@ -45,3 +45,7 @@ thiserror.workspace = true protobuf.workspace = true white-whale.workspace = true cosmwasm-schema.workspace = true + +[dev-dependencies] +cw-multi-test.workspace = true +cw20-base.workspace = true \ No newline at end of file diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs index b6753f98..b8a3bbcf 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs @@ -228,8 +228,10 @@ pub fn provide_liquidity( )); } + println!("first"); share } else { + println!("here"); // min(1, 2) // 1. sqrt(deposit_0 * exchange_rate_0_to_1 * deposit_0) * (total_share / sqrt(pool_0 * pool_1)) // == deposit_0 * total_share / pool_0 diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_app.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_app.rs new file mode 100644 index 00000000..5829d9b4 --- /dev/null +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_app.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::{Addr, Coin}; +use cw_multi_test::{App, AppBuilder, BankKeeper}; + +pub fn mock_app_with_balance(balances: Vec<(Addr, Vec)>) -> App { + let bank = BankKeeper::new(); + + AppBuilder::new() + .with_bank(bank) + .build(|router, _api, storage| { + balances.into_iter().for_each(|(account, amount)| { + router.bank.init_balance(storage, &account, amount).unwrap() + }); + }) +} diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_instantiate.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_instantiate.rs new file mode 100644 index 00000000..464f6bee --- /dev/null +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_instantiate.rs @@ -0,0 +1,53 @@ +use cosmwasm_std::testing::mock_info; +use cosmwasm_std::{Addr, MessageInfo}; +use cw_multi_test::{App, Executor}; + +use white_whale::fee::Fee; +use white_whale::pool_network::asset::{AssetInfo, PairType}; +use white_whale::pool_network::pair::PoolFee; + +use crate::tests::store_code::{store_cw20_token_code, store_pool}; + +pub fn mock_creator() -> MessageInfo { + mock_info("creatorcreator", &[]) +} + +/// Instantiates a pool +pub fn app_mock_instantiate( + app: &mut App, + asset_infos: [AssetInfo; 2], + asset_decimals: [u8; 2], +) -> Addr { + let pool_id = store_pool(app); + let token_id = store_cw20_token_code(app); + + let creator = mock_creator().sender; + + app.instantiate_contract( + pool_id, + creator.clone(), + &white_whale::pool_network::pair::InstantiateMsg { + asset_infos, + token_code_id: token_id, + asset_decimals, + pool_fees: PoolFee { + protocol_fee: Fee { + share: Default::default(), + }, + swap_fee: Fee { + share: Default::default(), + }, + burn_fee: Fee { + share: Default::default(), + }, + }, + fee_collector_addr: creator.to_string(), + pair_type: PairType::ConstantProduct, + token_factory_lp: false, + }, + &[], + "mock pool", + None, + ) + .unwrap() +} diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mod.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mod.rs index da675e98..74f77b92 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mod.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mod.rs @@ -6,3 +6,10 @@ mod stableswap; mod swap; mod testing; mod withdrawals; + +#[cfg(feature = "injective")] +mod mock_app; +#[cfg(feature = "injective")] +mod mock_instantiate; +#[cfg(feature = "injective")] +mod store_code; diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/provide_liquidity.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/provide_liquidity.rs index 684e0820..1d54b0f1 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/provide_liquidity.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/provide_liquidity.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "token_factory")] -use crate::state::LP_SYMBOL; use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; #[cfg(feature = "token_factory")] use cosmwasm_std::{coin, BankMsg}; @@ -8,8 +6,8 @@ use cosmwasm_std::{ SubMsgResult, Uint128, WasmMsg, }; use cw20::Cw20ExecuteMsg; -use white_whale::fee::Fee; +use white_whale::fee::Fee; #[cfg(feature = "token_factory")] use white_whale::pool_network; use white_whale::pool_network::asset::{Asset, AssetInfo, PairType, MINIMUM_LIQUIDITY_AMOUNT}; @@ -20,6 +18,17 @@ use white_whale::pool_network::pair::{ExecuteMsg, InstantiateMsg, PoolFee}; use crate::contract::{execute, instantiate, reply}; use crate::error::ContractError; +#[cfg(feature = "token_factory")] +use crate::state::LP_SYMBOL; + +#[cfg(feature = "injective")] +use crate::tests::mock_app::mock_app_with_balance; +#[cfg(feature = "injective")] +use crate::tests::mock_instantiate::{app_mock_instantiate, mock_creator}; +#[cfg(feature = "injective")] +use cosmwasm_std::coin; +#[cfg(feature = "injective")] +use cw_multi_test::Executor; #[test] fn provide_liquidity_cw20_lp() { @@ -815,3 +824,86 @@ fn provide_liquidity_tokenfactory_lp() { assert_eq!(bank_send_msg, bank_send_msg_expected); } + +#[cfg(feature = "injective")] +#[test] +fn provide_liquidity_18_decimals() { + let mut app = mock_app_with_balance(vec![( + mock_creator().sender, + vec![ + coin(1_000_000_000_000_000_000000000000000000, "inj"), + coin(1_000_000_000_000_000_000000000000000000, "jni"), + ], + )]); + + let pool_addr = app_mock_instantiate( + &mut app, + [ + AssetInfo::NativeToken { + denom: "inj".to_string(), + }, + AssetInfo::NativeToken { + denom: "jni".to_string(), + }, + ], + [18u8, 18u8], + ); + + //deposit + app.execute_contract( + mock_creator().sender, + pool_addr.clone(), + &ExecuteMsg::ProvideLiquidity { + assets: [ + Asset { + info: AssetInfo::NativeToken { + denom: "inj".to_string(), + }, + amount: Uint128::new(1_000_000_000_000_000000000000000000), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "jni".to_string(), + }, + amount: Uint128::new(1_000_000_000_000_000000000000000000), + }, + ], + slippage_tolerance: None, + receiver: None, + }, + &vec![ + coin(1_000_000_000_000_000000000000000000, "inj"), + coin(1_000_000_000_000_000000000000000000, "jni"), + ], + ) + .unwrap(); + + //deposit again + app.execute_contract( + mock_creator().sender, + pool_addr.clone(), + &ExecuteMsg::ProvideLiquidity { + assets: [ + Asset { + info: AssetInfo::NativeToken { + denom: "inj".to_string(), + }, + amount: Uint128::new(1_000_000_000_000_000000000000000000), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "jni".to_string(), + }, + amount: Uint128::new(1_000_000_000_000_000000000000000000), + }, + ], + slippage_tolerance: None, + receiver: None, + }, + &vec![ + coin(1_000_000_000_000_000000000000000000, "inj"), + coin(1_000_000_000_000_000000000000000000, "jni"), + ], + ) + .unwrap(); +} diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/store_code.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/store_code.rs new file mode 100644 index 00000000..62e7b819 --- /dev/null +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/store_code.rs @@ -0,0 +1,25 @@ +use cw_multi_test::{App, ContractWrapper}; + +use crate::contract; + +/// Stores the store_pool contract to the app. +pub fn store_pool(app: &mut App) -> u64 { + let contract = Box::new( + ContractWrapper::new(contract::execute, contract::instantiate, contract::query) + .with_migrate(contract::migrate) + .with_reply(contract::reply), + ); + + app.store_code(contract) +} + +/// Stores the base CW20 contract to the app. +pub fn store_cw20_token_code(app: &mut App) -> u64 { + let contract = Box::new(ContractWrapper::new( + cw20_base::contract::execute, + cw20_base::contract::instantiate, + cw20_base::contract::query, + )); + + app.store_code(contract) +} diff --git a/contracts/liquidity_hub/vault-network/vault/Cargo.toml b/contracts/liquidity_hub/vault-network/vault/Cargo.toml index 6c8b24e8..73a5e4ad 100644 --- a/contracts/liquidity_hub/vault-network/vault/Cargo.toml +++ b/contracts/liquidity_hub/vault-network/vault/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vault" -version = "1.2.4" +version = "1.2.5" authors = ["kaimen-sano "] edition.workspace = true description = "Contract to handle a single vault that controls an asset" diff --git a/contracts/liquidity_hub/vault-network/vault/src/execute/deposit.rs b/contracts/liquidity_hub/vault-network/vault/src/execute/deposit.rs index 70f24dd2..e2c61bb8 100644 --- a/contracts/liquidity_hub/vault-network/vault/src/execute/deposit.rs +++ b/contracts/liquidity_hub/vault-network/vault/src/execute/deposit.rs @@ -4,7 +4,9 @@ feature = "injective" ))] use cosmwasm_std::coins; -use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, MessageInfo, Response, Uint128, WasmMsg}; +use cosmwasm_std::{ + to_binary, CosmosMsg, DepsMut, Env, MessageInfo, Response, Uint128, Uint256, WasmMsg, +}; use cw20::{AllowanceResponse, Cw20ExecuteMsg}; #[cfg(any( @@ -137,9 +139,10 @@ pub fn deposit( .checked_sub(collected_protocol_fees.amount)? .checked_sub(deposit_amount)?; - amount - .checked_mul(total_share)? - .checked_div(total_deposits)? + Uint256::from_uint128(amount) + .checked_mul(Uint256::from_uint128(total_share))? + .checked_div(Uint256::from_uint128(total_deposits))? + .try_into()? }; // mint LP token to sender @@ -670,4 +673,51 @@ mod test { // depositor2 is entitled to 3,333 / 18,666 of the total LP supply or 5,000 tokens // depositor3 is entitled to 5,333 / 18,666 of the total LP supply or 8,000 tokens } + + #[cfg(feature = "injective")] + #[test] + fn deposits_handle_18_decimals() { + // simulate an inj vault where users deposit large amounts of inj, even more than the inj supply + let second_depositor = Addr::unchecked("depositor2"); + + let mut app = mock_app_with_balance(vec![ + ( + mock_creator().sender, + coins(1_000_000_000_000000000000000000, "inj"), + ), + ( + second_depositor.clone(), + coins(1_000_000_000_000000000000000000, "inj"), + ), + ]); + + let vault_addr = app_mock_instantiate( + &mut app, + AssetInfo::NativeToken { + denom: "inj".to_string(), + }, + ); + + // first depositor deposits 1_000_000_000_000000000000000000 inj + app.execute_contract( + mock_creator().sender, + vault_addr.clone(), + &white_whale::vault_network::vault::ExecuteMsg::Deposit { + amount: Uint128::new(1_000_000_000_000000000000000000), + }, + &coins(1_000_000_000_000000000000000000, "inj"), + ) + .unwrap(); + + // second depositor deposits 1_000_000_000_000000000000000000 inj + app.execute_contract( + second_depositor.clone(), + vault_addr.clone(), + &white_whale::vault_network::vault::ExecuteMsg::Deposit { + amount: Uint128::new(1_000_000_000_000000000000000000), + }, + &coins(1_000_000_000_000000000000000000, "inj"), + ) + .unwrap(); + } } From 9b6126e5eccecdac1d3581c322e33e2bca39a95c Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Thu, 26 Oct 2023 14:02:51 +0100 Subject: [PATCH 15/22] chore: add flashloan profit log to vault router --- Cargo.lock | 2 +- .../vault-network/vault_router/Cargo.toml | 2 +- .../vault_router/src/execute/complete_loan.rs | 13 +++++++++++-- .../vault_router/src/execute/flash_loan.rs | 8 ++++++++ packages/white-whale/src/whale_lair.rs | 2 +- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea48ae48..26541397 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1585,7 +1585,7 @@ dependencies = [ [[package]] name = "vault_router" -version = "1.1.5" +version = "1.1.6" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/liquidity_hub/vault-network/vault_router/Cargo.toml b/contracts/liquidity_hub/vault-network/vault_router/Cargo.toml index 1bb5f8df..e6939bfe 100644 --- a/contracts/liquidity_hub/vault-network/vault_router/Cargo.toml +++ b/contracts/liquidity_hub/vault-network/vault_router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vault_router" -version = "1.1.5" +version = "1.1.6" authors = [ "kaimen-sano , Kerber0x ", ] diff --git a/contracts/liquidity_hub/vault-network/vault_router/src/execute/complete_loan.rs b/contracts/liquidity_hub/vault-network/vault_router/src/execute/complete_loan.rs index 40154f8a..9f5586b6 100644 --- a/contracts/liquidity_hub/vault-network/vault_router/src/execute/complete_loan.rs +++ b/contracts/liquidity_hub/vault-network/vault_router/src/execute/complete_loan.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - coins, to_binary, Addr, BankMsg, CosmosMsg, DepsMut, Env, MessageInfo, Response, WasmMsg, + attr, coins, to_binary, Addr, BankMsg, CosmosMsg, DepsMut, Env, MessageInfo, Response, WasmMsg, }; use white_whale::pool_network::asset::{Asset, AssetInfo}; use white_whale::vault_network::vault::PaybackAmountResponse; @@ -18,6 +18,8 @@ pub fn complete_loan( return Err(VaultRouterError::Unauthorized {}); } + let mut attributes = vec![]; + // pay back loans and profit let messages: Vec> = assets .into_iter() @@ -58,6 +60,12 @@ pub fn complete_loan( required_amount: payback_amount.payback_amount, })?; + attributes.push(attr( + "payback_amount", + payback_amount.payback_amount.to_string(), + )); + attributes.push(attr("profit_amount", profit_amount.to_string())); + let mut response_messages: Vec = vec![]; let payback_loan_msg: StdResult = match loaned_asset.info.clone() { AssetInfo::NativeToken { denom } => Ok(BankMsg::Send { @@ -106,7 +114,8 @@ pub fn complete_loan( Ok(Response::new() .add_messages(messages.concat()) - .add_attributes(vec![("method", "complete_loan")])) + .add_attributes(vec![("method", "complete_loan")]) + .add_attributes(attributes)) } #[cfg(test)] diff --git a/contracts/liquidity_hub/vault-network/vault_router/src/execute/flash_loan.rs b/contracts/liquidity_hub/vault-network/vault_router/src/execute/flash_loan.rs index a394eaa7..db3f84f5 100644 --- a/contracts/liquidity_hub/vault-network/vault_router/src/execute/flash_loan.rs +++ b/contracts/liquidity_hub/vault-network/vault_router/src/execute/flash_loan.rs @@ -398,6 +398,14 @@ mod tests { key: "method".to_string(), value: "complete_loan".to_string(), }, + Attribute { + key: "payback_amount".to_string(), + value: "1066".to_string(), + }, + Attribute { + key: "profit_amount".to_string(), + value: "0".to_string(), + }, ]), Event::new("transfer").add_attributes(vec![ Attribute { diff --git a/packages/white-whale/src/whale_lair.rs b/packages/white-whale/src/whale_lair.rs index 334dbf84..fa52328c 100644 --- a/packages/white-whale/src/whale_lair.rs +++ b/packages/white-whale/src/whale_lair.rs @@ -32,7 +32,7 @@ impl Default for Bond { Self { asset: Asset { info: AssetInfo::NativeToken { - denom: "".to_string(), + denom: String::new(), }, amount: Uint128::zero(), }, From 97079e7e3d3f869d06c299819423d5acd942b885 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Thu, 26 Oct 2023 16:00:21 +0100 Subject: [PATCH 16/22] chore: fix pool migration from v1.1.0 straight to v1.3.x --- .../terraswap_pair/src/contract.rs | 7 + .../terraswap_pair/src/migrations.rs | 120 +++++++++++++++++- .../deployment/deploy_env/base_injective.env | 2 +- 3 files changed, 126 insertions(+), 3 deletions(-) diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs index 7d2719b5..25145ae5 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs @@ -248,11 +248,18 @@ pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result Result<(), StdError> { Ok(()) } +#[cfg(not(feature = "injective"))] pub fn migrate_to_v120(deps: DepsMut) -> Result<(), StdError> { #[cw_serde] struct ConfigV110 { @@ -116,6 +124,7 @@ pub fn migrate_to_v120(deps: DepsMut) -> Result<(), StdError> { Ok(()) } +#[cfg(not(feature = "injective"))] /// Migrate to the StableSwap deployment /// /// Default to a ConstantProduct pool @@ -158,3 +167,110 @@ pub fn migrate_to_v130(deps: DepsMut) -> Result<(), StdError> { Ok(()) } + +#[cfg(feature = "injective")] +/// Migrates the state from v1.1.0 to v1.3.x in a single migration +pub fn migrate_to_v13x(deps: DepsMut) -> Result<(), StdError> { + // migration to v1.2.0 + #[cw_serde] + struct ConfigV110 { + pub owner: Addr, + pub fee_collector_addr: Addr, + pub pool_fees: PoolFeeV110, + pub feature_toggle: FeatureToggle, + } + + #[cw_serde] + struct PoolFeeV110 { + pub protocol_fee: Fee, + pub swap_fee: Fee, + } + + const CONFIG_V110: Item = Item::new("config"); + let config_v110 = CONFIG_V110.load(deps.storage)?; + + // Add burn fee to config. Zero fee is used as default. + let config = Config { + owner: config_v110.owner, + fee_collector_addr: config_v110.fee_collector_addr, + pool_fees: pool_network::pair::PoolFee { + protocol_fee: config_v110.pool_fees.protocol_fee, + swap_fee: config_v110.pool_fees.swap_fee, + burn_fee: Fee { + share: Decimal::zero(), + }, + }, + feature_toggle: config_v110.feature_toggle, + }; + + CONFIG.save(deps.storage, &config)?; + + #[cw_serde] + struct PairInfoRawV110 { + pub asset_infos: [AssetInfoRaw; 2], + pub contract_addr: CanonicalAddr, + pub liquidity_token: CanonicalAddr, + pub asset_decimals: [u8; 2], + } + + #[cw_serde] + struct PairInfoV110 { + pub asset_infos: [AssetInfo; 2], + pub contract_addr: String, + pub liquidity_token: String, + pub asset_decimals: [u8; 2], + } + + const PAIR_INFO_V110: Item = Item::new("pair_info"); + + // Instantiates the ALL_TIME_BURNED_FEES + let pair_info = PAIR_INFO_V110.load(deps.storage)?; + let asset_info_0 = pair_info.asset_infos[0].to_normal(deps.api)?; + let asset_info_1 = pair_info.asset_infos[1].to_normal(deps.api)?; + + instantiate_fees( + deps.storage, + asset_info_0, + asset_info_1, + ALL_TIME_BURNED_FEES, + )?; + + // migration to v1.2.0 + #[cw_serde] + pub struct PairInfoRawV120 { + pub asset_infos: [AssetInfoRaw; 2], + pub contract_addr: CanonicalAddr, + pub liquidity_token: CanonicalAddr, + pub asset_decimals: [u8; 2], + } + + #[cw_serde] + pub struct PairInfoRawV130 { + pub asset_infos: [AssetInfoRaw; 2], + pub contract_addr: CanonicalAddr, + pub liquidity_token: AssetInfoRaw, + pub asset_decimals: [u8; 2], + pub pair_type: PairType, + } + + const PAIR_INFO_V120: Item = Item::new("pair_info"); + const PAIR_INFO_V130: Item = Item::new("pair_info"); + + let config = PAIR_INFO_V120.load(deps.storage)?; + PAIR_INFO_V130.save( + deps.storage, + &PairInfoRawV130 { + asset_infos: config.asset_infos, + contract_addr: config.contract_addr, + // all liquidity tokens until this version are cw20 tokens + liquidity_token: AssetInfoRaw::Token { + contract_addr: config.liquidity_token, + }, + asset_decimals: config.asset_decimals, + // all pools until this version are ConstantProduct + pair_type: PairType::ConstantProduct, + }, + )?; + + Ok(()) +} diff --git a/scripts/deployment/deploy_env/base_injective.env b/scripts/deployment/deploy_env/base_injective.env index e5a04ef5..de2b469b 100644 --- a/scripts/deployment/deploy_env/base_injective.env +++ b/scripts/deployment/deploy_env/base_injective.env @@ -1,6 +1,6 @@ if [ -n "$ZSH_VERSION" ]; then # Using an array for TXFLAG - TXFLAG=(--node $RPC --chain-id $CHAIN_ID --gas-prices=500000000inj --gas 10000000 -y -b block --output json) + TXFLAG=(--node $RPC --chain-id $CHAIN_ID --gas-prices=500000000inj --gas 10000000 -y -b sync --output json) else # Using a string for TXFLAG TXFLAG="--node $RPC --chain-id $CHAIN_ID --gas-prices=500000000inj --gas 10000000 -y -b block --output json" From 38762d9bb0804591ba29b4b32973eff05e119f41 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Thu, 26 Oct 2023 16:30:32 +0100 Subject: [PATCH 17/22] chore: fix vault migration to go from v1.1.3 straight to v1.2.6 --- Cargo.lock | 2 +- .../vault-network/vault/Cargo.toml | 2 +- .../vault-network/vault/src/contract.rs | 10 +++ .../vault-network/vault/src/migrations.rs | 62 +++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26541397..3719a617 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1544,7 +1544,7 @@ checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" [[package]] name = "vault" -version = "1.2.5" +version = "1.2.6" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/liquidity_hub/vault-network/vault/Cargo.toml b/contracts/liquidity_hub/vault-network/vault/Cargo.toml index 73a5e4ad..a55ba4b7 100644 --- a/contracts/liquidity_hub/vault-network/vault/Cargo.toml +++ b/contracts/liquidity_hub/vault-network/vault/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vault" -version = "1.2.5" +version = "1.2.6" authors = ["kaimen-sano "] edition.workspace = true description = "Contract to handle a single vault that controls an asset" diff --git a/contracts/liquidity_hub/vault-network/vault/src/contract.rs b/contracts/liquidity_hub/vault-network/vault/src/contract.rs index 32ed68d6..be453023 100644 --- a/contracts/liquidity_hub/vault-network/vault/src/contract.rs +++ b/contracts/liquidity_hub/vault-network/vault/src/contract.rs @@ -229,6 +229,15 @@ pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result Result Result<(), StdError> { #[cw_serde] pub struct ConfigV113 { @@ -68,6 +69,7 @@ pub fn migrate_to_v120(deps: DepsMut) -> Result<(), StdError> { Ok(()) } +#[cfg(not(feature = "injective"))] pub fn migrate_to_v130(deps: DepsMut) -> Result<(), StdError> { #[cw_serde] pub struct ConfigV120 { @@ -114,3 +116,63 @@ pub fn migrate_to_v130(deps: DepsMut) -> Result<(), StdError> { Ok(()) } + +#[cfg(feature = "injective")] +pub fn migrate_to_v126(deps: DepsMut) -> Result<(), StdError> { + #[cw_serde] + pub struct ConfigV113 { + /// The owner of the vault + pub owner: Addr, + /// The asset info the vault manages + pub asset_info: AssetInfo, + /// If flash-loans are enabled + pub flash_loan_enabled: bool, + /// If deposits are enabled + pub deposit_enabled: bool, + /// If withdrawals are enabled + pub withdraw_enabled: bool, + /// The address of the liquidity token + pub liquidity_token: Addr, + /// The address of the fee collector + pub fee_collector_addr: Addr, + /// The fees associated with this vault + pub fees: VaultFeeV113, + } + + #[cw_serde] + pub struct VaultFeeV113 { + pub protocol_fee: Fee, + pub flash_loan_fee: Fee, + } + + const CONFIG_V113: Item = Item::new("config"); + + let config_v113 = CONFIG_V113.load(deps.storage)?; + + // Add burn fee to config. Zero fee is used as default. + let config = Config { + owner: config_v113.owner, + asset_info: config_v113.asset_info, + flash_loan_enabled: config_v113.flash_loan_enabled, + deposit_enabled: config_v113.deposit_enabled, + withdraw_enabled: config_v113.withdraw_enabled, + lp_asset: AssetInfo::Token { + contract_addr: config_v113.liquidity_token.to_string(), + }, + fee_collector_addr: config_v113.fee_collector_addr, + fees: VaultFee { + protocol_fee: config_v113.fees.protocol_fee, + flash_loan_fee: config_v113.fees.flash_loan_fee, + burn_fee: Fee { + share: Decimal::zero(), + }, + }, + }; + + CONFIG.save(deps.storage, &config)?; + + // initialize the burned fee storage item + initialize_fee(deps.storage, ALL_TIME_BURNED_FEES, config.asset_info)?; + + Ok(()) +} From c3d770f4a36449c3f8fc3de553cd213c9fe11f0b Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Fri, 27 Oct 2023 09:48:40 +0100 Subject: [PATCH 18/22] ci: add separate cargo tarpaulin ci work for injective --- .github/workflows/ci-test-fmt-check.yaml | 58 +++++++++++++++++-- .../terraswap_pair/src/commands.rs | 2 - 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-test-fmt-check.yaml b/.github/workflows/ci-test-fmt-check.yaml index b7b9c9ac..590ff9b8 100644 --- a/.github/workflows/ci-test-fmt-check.yaml +++ b/.github/workflows/ci-test-fmt-check.yaml @@ -14,8 +14,8 @@ env: CARGO_TERM_COLOR: always jobs: - test_and_check: - name: Test and check + test_and_check-token_factory_feature: + name: Test and check Token factory feature runs-on: ubuntu-latest steps: @@ -43,11 +43,11 @@ jobs: override: true components: rustfmt, clippy - - name: Run cargo-tarpaulin + - name: Run cargo-tarpaulin token_factory feature uses: actions-rs/tarpaulin@v0.1 with: version: "0.15.0" - args: '--features "injective token_factory" --locked -- --test-threads 4' + args: '--features "token_factory" --locked -- --test-threads 4' - name: Upload to codecov.io uses: codecov/codecov-action@v3 @@ -75,3 +75,53 @@ jobs: chmod +x ./scripts/build_schemas.sh ./scripts/build_schemas.sh true shell: bash + + test_and_check-token_injective_feature: + name: Test and check Injective feature + runs-on: ubuntu-latest + + steps: + # Cancel any existing runs to save on CI time + # - name: Cancel Previous Runs + # uses: styfle/cancel-workflow-action@0.9.1 + # with: + # access_token: ${{ github.token }} + # Checkout code, with submodules using PAT + - name: Checkout sources + uses: actions/checkout@v3 + + # Use Rust Cache to speed up subsequent jobs with no cargo lock changes + - name: Use Rust cache + uses: Swatinem/rust-cache@v2 + with: + key: "test" + + # Install rust + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.70.0 + override: true + components: rustfmt, clippy + + - name: Run cargo-tarpaulin injective feature + uses: actions-rs/tarpaulin@v0.1 + with: + version: "0.15.0" + args: '--features "injective" --locked -- --test-threads 4' + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --locked -- -D warnings + + #- name: Run cosmwasm linter + # run: cargo dylint cw_lint --workspace + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs index b8a3bbcf..b6753f98 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs @@ -228,10 +228,8 @@ pub fn provide_liquidity( )); } - println!("first"); share } else { - println!("here"); // min(1, 2) // 1. sqrt(deposit_0 * exchange_rate_0_to_1 * deposit_0) * (total_share / sqrt(pool_0 * pool_1)) // == deposit_0 * total_share / pool_0 From 5dbc3e74f9046d70720a86e6a95f280f61102fb0 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Tue, 5 Dec 2023 10:13:09 +0000 Subject: [PATCH 19/22] chore: make fee aggregation permissionless --- Cargo.lock | 2 +- .../liquidity_hub/fee_collector/Cargo.toml | 2 +- .../liquidity_hub/fee_collector/src/commands.rs | 17 +++++++++++------ .../liquidity_hub/fee_collector/src/contract.rs | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3719a617..e2e7ade2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,7 +509,7 @@ dependencies = [ [[package]] name = "fee_collector" -version = "1.1.2" +version = "1.1.3" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/liquidity_hub/fee_collector/Cargo.toml b/contracts/liquidity_hub/fee_collector/Cargo.toml index 50b87a89..53121bda 100644 --- a/contracts/liquidity_hub/fee_collector/Cargo.toml +++ b/contracts/liquidity_hub/fee_collector/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fee_collector" -version = "1.1.2" +version = "1.1.3" authors = ["Kerber0x "] edition.workspace = true description = "Contract to collect the fees accrued by the pools and vaults in the liquidity hub" diff --git a/contracts/liquidity_hub/fee_collector/src/commands.rs b/contracts/liquidity_hub/fee_collector/src/commands.rs index bfa14771..336159a6 100644 --- a/contracts/liquidity_hub/fee_collector/src/commands.rs +++ b/contracts/liquidity_hub/fee_collector/src/commands.rs @@ -170,17 +170,22 @@ pub fn update_config( /// Aggregates the fees collected into the given asset_info. pub fn aggregate_fees( mut deps: DepsMut, - info: MessageInfo, env: Env, - ask_asset_info: AssetInfo, + mut ask_asset_info: AssetInfo, aggregate_fees_for: FeesFor, ) -> Result { let config: Config = CONFIG.load(deps.storage)?; - // only the owner or the contract itself can aggregate the fees - if info.sender != config.owner && info.sender != env.contract.address { - return Err(ContractError::Unauthorized {}); - } + // query fee collector + let fee_distributor_config: white_whale::fee_distributor::Config = + deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: config.fee_distributor.to_string(), + msg: to_binary(&white_whale::fee_distributor::QueryMsg::Config {})?, + }))?; + + // This was done in order to make this function permissionless so anyone can trigger the fee aggregation + // The signature for `ExecuteMsg::AggregateFees` was kept the same to avoid migrating multiple contracts + ask_asset_info = fee_distributor_config.distribution_asset; let mut aggregate_fees_messages: Vec = Vec::new(); diff --git a/contracts/liquidity_hub/fee_collector/src/contract.rs b/contracts/liquidity_hub/fee_collector/src/contract.rs index bcdf0420..2562a13b 100644 --- a/contracts/liquidity_hub/fee_collector/src/contract.rs +++ b/contracts/liquidity_hub/fee_collector/src/contract.rs @@ -128,7 +128,7 @@ pub fn execute( ExecuteMsg::AggregateFees { asset_info, aggregate_fees_for, - } => commands::aggregate_fees(deps, info, env, asset_info, aggregate_fees_for), + } => commands::aggregate_fees(deps, env, asset_info, aggregate_fees_for), ExecuteMsg::ForwardFees { epoch, forward_fees_as, From 12627e3c2627214d86372b9c8939e181419dbefc Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Tue, 5 Dec 2023 11:19:24 +0000 Subject: [PATCH 20/22] chore: cleanup unused variables --- .../fee_collector/schema/fee_collector.json | 8 ++------ .../fee_collector/schema/raw/execute.json | 8 ++------ .../fee_collector/src/commands.rs | 19 ++++--------------- .../fee_collector/src/contract.rs | 17 ++++++++--------- .../fee_collector/src/queries.rs | 15 ++++++++++++++- .../liquidity_hub/fee_collector/src/state.rs | 2 +- packages/white-whale/src/fee_collector.rs | 9 +++------ 7 files changed, 34 insertions(+), 44 deletions(-) diff --git a/contracts/liquidity_hub/fee_collector/schema/fee_collector.json b/contracts/liquidity_hub/fee_collector/schema/fee_collector.json index c748c1ce..8552cba1 100644 --- a/contracts/liquidity_hub/fee_collector/schema/fee_collector.json +++ b/contracts/liquidity_hub/fee_collector/schema/fee_collector.json @@ -35,7 +35,7 @@ "additionalProperties": false }, { - "description": "Swaps the assets (fees) sitting in the fee collector into the given [AssetInfo] if possible. A [SwapRoute] should be available at the router to be able to make the swaps.", + "description": "Swaps the assets (fees) sitting in the fee collector into the distribution asset set by the fee collector. A [SwapRoute] should be available at the router to be able to make the swaps.", "type": "object", "required": [ "aggregate_fees" @@ -44,15 +44,11 @@ "aggregate_fees": { "type": "object", "required": [ - "aggregate_fees_for", - "asset_info" + "aggregate_fees_for" ], "properties": { "aggregate_fees_for": { "$ref": "#/definitions/FeesFor" - }, - "asset_info": { - "$ref": "#/definitions/AssetInfo" } }, "additionalProperties": false diff --git a/contracts/liquidity_hub/fee_collector/schema/raw/execute.json b/contracts/liquidity_hub/fee_collector/schema/raw/execute.json index 4f1dede2..840dd6b2 100644 --- a/contracts/liquidity_hub/fee_collector/schema/raw/execute.json +++ b/contracts/liquidity_hub/fee_collector/schema/raw/execute.json @@ -25,7 +25,7 @@ "additionalProperties": false }, { - "description": "Swaps the assets (fees) sitting in the fee collector into the given [AssetInfo] if possible. A [SwapRoute] should be available at the router to be able to make the swaps.", + "description": "Swaps the assets (fees) sitting in the fee collector into the distribution asset set by the fee collector. A [SwapRoute] should be available at the router to be able to make the swaps.", "type": "object", "required": [ "aggregate_fees" @@ -34,15 +34,11 @@ "aggregate_fees": { "type": "object", "required": [ - "aggregate_fees_for", - "asset_info" + "aggregate_fees_for" ], "properties": { "aggregate_fees_for": { "$ref": "#/definitions/FeesFor" - }, - "asset_info": { - "$ref": "#/definitions/AssetInfo" } }, "additionalProperties": false diff --git a/contracts/liquidity_hub/fee_collector/src/commands.rs b/contracts/liquidity_hub/fee_collector/src/commands.rs index 336159a6..379c09da 100644 --- a/contracts/liquidity_hub/fee_collector/src/commands.rs +++ b/contracts/liquidity_hub/fee_collector/src/commands.rs @@ -13,6 +13,7 @@ use white_whale::pool_network::router::SwapOperation; use white_whale::vault_network::vault_factory::VaultsResponse; use crate::contract::{FEES_AGGREGATION_REPLY_ID, FEES_COLLECTION_REPLY_ID}; +use crate::queries::query_distribution_asset; use crate::state::{read_temporal_asset_infos, store_temporal_asset_info, CONFIG, TMP_EPOCH}; use crate::ContractError; @@ -171,21 +172,12 @@ pub fn update_config( pub fn aggregate_fees( mut deps: DepsMut, env: Env, - mut ask_asset_info: AssetInfo, aggregate_fees_for: FeesFor, ) -> Result { let config: Config = CONFIG.load(deps.storage)?; - // query fee collector - let fee_distributor_config: white_whale::fee_distributor::Config = - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: config.fee_distributor.to_string(), - msg: to_binary(&white_whale::fee_distributor::QueryMsg::Config {})?, - }))?; - - // This was done in order to make this function permissionless so anyone can trigger the fee aggregation - // The signature for `ExecuteMsg::AggregateFees` was kept the same to avoid migrating multiple contracts - ask_asset_info = fee_distributor_config.distribution_asset; + // query fee collector to get the distribution asset + let ask_asset_info = query_distribution_asset(deps.as_ref())?; let mut aggregate_fees_messages: Vec = Vec::new(); @@ -340,7 +332,6 @@ pub fn forward_fees( info: MessageInfo, env: Env, epoch: Epoch, - forward_fees_as: AssetInfo, ) -> Result { let config = CONFIG.load(deps.storage)?; @@ -397,7 +388,6 @@ pub fn forward_fees( contract_addr: env.contract.address.to_string(), funds: vec![], msg: to_binary(&ExecuteMsg::AggregateFees { - asset_info: forward_fees_as.clone(), aggregate_fees_for: FeesFor::Factory { factory_addr: config.vault_factory.to_string(), factory_type: FactoryType::Vault { @@ -417,7 +407,6 @@ pub fn forward_fees( contract_addr: env.contract.address.to_string(), funds: vec![], msg: to_binary(&ExecuteMsg::AggregateFees { - asset_info: forward_fees_as.clone(), aggregate_fees_for: FeesFor::Factory { factory_addr: config.pool_factory.to_string(), factory_type: FactoryType::Pool { @@ -437,7 +426,7 @@ pub fn forward_fees( messages.push(pools_fee_aggregation_msg); // saving the epoch and the asset info to forward the fees as in temp storage - TMP_EPOCH.save(deps.storage, &(epoch, forward_fees_as))?; + TMP_EPOCH.save(deps.storage, &epoch)?; Ok(Response::new() .add_attribute("action", "forward_fees") diff --git a/contracts/liquidity_hub/fee_collector/src/contract.rs b/contracts/liquidity_hub/fee_collector/src/contract.rs index 2562a13b..5a0ce7cf 100644 --- a/contracts/liquidity_hub/fee_collector/src/contract.rs +++ b/contracts/liquidity_hub/fee_collector/src/contract.rs @@ -13,6 +13,7 @@ use white_whale::fee_collector::{ use white_whale::pool_network::asset::{Asset, AssetInfo, ToCoins}; use crate::error::ContractError; +use crate::queries::query_distribution_asset; use crate::state::{CONFIG, TMP_EPOCH}; use crate::ContractError::MigrateInvalidVersion; use crate::{commands, migrations, queries}; @@ -49,10 +50,12 @@ pub fn instantiate( #[entry_point] pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { if msg.id == FEES_AGGREGATION_REPLY_ID { - let (mut epoch, asset_info) = TMP_EPOCH + let mut epoch = TMP_EPOCH .may_load(deps.storage)? .ok_or(ContractError::CannotReadEpoch {})?; + let asset_info = query_distribution_asset(deps.as_ref())?; + let token_balance: Uint128 = match asset_info.clone() { AssetInfo::Token { .. } => { return Err(ContractError::InvalidContractsFeeAggregation {}) @@ -125,14 +128,10 @@ pub fn execute( pool_factory, vault_factory, ), - ExecuteMsg::AggregateFees { - asset_info, - aggregate_fees_for, - } => commands::aggregate_fees(deps, env, asset_info, aggregate_fees_for), - ExecuteMsg::ForwardFees { - epoch, - forward_fees_as, - } => commands::forward_fees(deps, info, env, epoch, forward_fees_as), + ExecuteMsg::AggregateFees { aggregate_fees_for } => { + commands::aggregate_fees(deps, env, aggregate_fees_for) + } + ExecuteMsg::ForwardFees { epoch, .. } => commands::forward_fees(deps, info, env, epoch), } } diff --git a/contracts/liquidity_hub/fee_collector/src/queries.rs b/contracts/liquidity_hub/fee_collector/src/queries.rs index 5dd6ca21..fb436ee3 100644 --- a/contracts/liquidity_hub/fee_collector/src/queries.rs +++ b/contracts/liquidity_hub/fee_collector/src/queries.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{to_binary, Addr, Deps, QueryRequest, StdResult, WasmQuery}; use white_whale::fee_collector::{Config, ContractType, FactoryType, FeesFor}; use white_whale::pool_network; -use white_whale::pool_network::asset::Asset; +use white_whale::pool_network::asset::{Asset, AssetInfo}; use white_whale::pool_network::factory::PairsResponse; use white_whale::pool_network::pair::ProtocolFeesResponse as ProtocolPairFeesResponse; use white_whale::vault_network::vault::ProtocolFeesResponse as ProtocolVaultFeesResponse; @@ -143,3 +143,16 @@ fn query_fees_for_factory( Ok(fees) } + +/// Queries the fee collector to get the distribution asset +pub(crate) fn query_distribution_asset(deps: Deps) -> StdResult { + let config: Config = CONFIG.load(deps.storage)?; + + let fee_distributor_config: white_whale::fee_distributor::Config = + deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: config.fee_distributor.to_string(), + msg: to_binary(&white_whale::fee_distributor::QueryMsg::Config {})?, + }))?; + + Ok(fee_distributor_config.distribution_asset) +} diff --git a/contracts/liquidity_hub/fee_collector/src/state.rs b/contracts/liquidity_hub/fee_collector/src/state.rs index 2026a838..cfc6dadf 100644 --- a/contracts/liquidity_hub/fee_collector/src/state.rs +++ b/contracts/liquidity_hub/fee_collector/src/state.rs @@ -6,7 +6,7 @@ use white_whale::pool_network::asset::AssetInfo; pub const CONFIG: Item = Item::new("config"); pub const TMP_ASSET_INFOS: Map = Map::new("tmp_asset_infos"); -pub const TMP_EPOCH: Item<(Epoch, AssetInfo)> = Item::new("tmp_epoch"); +pub const TMP_EPOCH: Item = Item::new("tmp_epoch"); pub fn store_temporal_asset_info(deps: DepsMut, asset_info: AssetInfo) -> StdResult<()> { let key = asset_info diff --git a/packages/white-whale/src/fee_collector.rs b/packages/white-whale/src/fee_collector.rs index 57a80ccb..d4c68196 100644 --- a/packages/white-whale/src/fee_collector.rs +++ b/packages/white-whale/src/fee_collector.rs @@ -10,12 +10,9 @@ pub struct InstantiateMsg {} pub enum ExecuteMsg { /// Collects protocol fees based on the configuration indicated by [FeesFor] CollectFees { collect_fees_for: FeesFor }, - /// Swaps the assets (fees) sitting in the fee collector into the given [AssetInfo] if possible. - /// A [SwapRoute] should be available at the router to be able to make the swaps. - AggregateFees { - asset_info: AssetInfo, - aggregate_fees_for: FeesFor, - }, + /// Swaps the assets (fees) sitting in the fee collector into the distribution asset set by the + /// fee collector. A [SwapRoute] should be available at the router to be able to make the swaps. + AggregateFees { aggregate_fees_for: FeesFor }, /// Forward fees to the fee distributor. This will collect and aggregate the fees, to send them back to the fee distributor. ForwardFees { epoch: Epoch, From 149890abb102ffaae7dc95938bde7bf6e0f1e187 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Tue, 5 Dec 2023 14:28:49 +0000 Subject: [PATCH 21/22] chore: make fee collection permissionless --- Cargo.lock | 2 +- contracts/liquidity_hub/fee_collector/Cargo.toml | 2 +- .../liquidity_hub/fee_collector/src/commands.rs | 14 +------------- .../liquidity_hub/fee_collector/src/contract.rs | 2 +- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e2e7ade2..c7293902 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,7 +509,7 @@ dependencies = [ [[package]] name = "fee_collector" -version = "1.1.3" +version = "1.1.4" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/liquidity_hub/fee_collector/Cargo.toml b/contracts/liquidity_hub/fee_collector/Cargo.toml index 53121bda..14d253d3 100644 --- a/contracts/liquidity_hub/fee_collector/Cargo.toml +++ b/contracts/liquidity_hub/fee_collector/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fee_collector" -version = "1.1.3" +version = "1.1.4" authors = ["Kerber0x "] edition.workspace = true description = "Contract to collect the fees accrued by the pools and vaults in the liquidity hub" diff --git a/contracts/liquidity_hub/fee_collector/src/commands.rs b/contracts/liquidity_hub/fee_collector/src/commands.rs index 379c09da..44047ff3 100644 --- a/contracts/liquidity_hub/fee_collector/src/commands.rs +++ b/contracts/liquidity_hub/fee_collector/src/commands.rs @@ -19,19 +19,7 @@ use crate::ContractError; /// Collects fees accrued by the pools and vaults. If a factory is provided then it only collects the /// fees from its children. -pub fn collect_fees( - deps: DepsMut, - info: MessageInfo, - env: Env, - collect_fees_for: FeesFor, -) -> Result { - let config: Config = CONFIG.load(deps.storage)?; - - // only the owner or the contract itself can aggregate the fees - if info.sender != config.owner && info.sender != env.contract.address { - return Err(ContractError::Unauthorized {}); - } - +pub fn collect_fees(deps: DepsMut, collect_fees_for: FeesFor) -> Result { let mut collect_fees_messages: Vec = Vec::new(); match collect_fees_for { diff --git a/contracts/liquidity_hub/fee_collector/src/contract.rs b/contracts/liquidity_hub/fee_collector/src/contract.rs index 5a0ce7cf..f6e0dfd4 100644 --- a/contracts/liquidity_hub/fee_collector/src/contract.rs +++ b/contracts/liquidity_hub/fee_collector/src/contract.rs @@ -111,7 +111,7 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::CollectFees { collect_fees_for } => { - commands::collect_fees(deps, info, env, collect_fees_for) + commands::collect_fees(deps, collect_fees_for) } ExecuteMsg::UpdateConfig { owner, From 66793ef91c39a1bd0f8a669616d94a87a7e81fe4 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Wed, 6 Dec 2023 11:21:48 +0000 Subject: [PATCH 22/22] test: fix tests --- .../fee_collector/src/tests/integration.rs | 161 ++++++++++++++++-- .../fee_collector/src/tests/testing.rs | 50 +----- 2 files changed, 152 insertions(+), 59 deletions(-) diff --git a/contracts/liquidity_hub/fee_collector/src/tests/integration.rs b/contracts/liquidity_hub/fee_collector/src/tests/integration.rs index fdb11861..a953f8f1 100644 --- a/contracts/liquidity_hub/fee_collector/src/tests/integration.rs +++ b/contracts/liquidity_hub/fee_collector/src/tests/integration.rs @@ -43,6 +43,7 @@ fn collect_all_factories_cw20_fees_successfully() { let creator = mock_creator(); let fee_collector_id = store_fee_collector_code(&mut app); + let fee_distributor_id = store_fee_distributor_code(&mut app); let pool_factory_id = store_pool_factory_code(&mut app); let pool_router_id = store_pool_router_code(&mut app); let pair_id = store_pair_code(&mut app); @@ -380,6 +381,43 @@ fn collect_all_factories_cw20_fees_successfully() { let ask_asset = AssetInfo::Token { contract_addr: cw20_tokens[0].to_string(), }; + + // init fee distributor + + let fee_distributor_address = app + .instantiate_contract( + fee_distributor_id, + creator.clone().sender, + &white_whale::fee_distributor::InstantiateMsg { + bonding_contract_addr: "whale_lair".clone().to_string(), + fee_collector_addr: fee_collector_address.clone().to_string(), + grace_period: Uint64::new(1), + epoch_config: EpochConfig { + duration: Uint64::new(86_400_000_000_000u64), // a day + genesis_epoch: Uint64::new(1678802400_000000000u64), // March 14, 2023 2:00:00 PM + }, + distribution_asset: ask_asset.clone(), + }, + &[], + "fee_distributor", + None, + ) + .unwrap(); + + app.execute_contract( + creator.sender.clone(), + fee_collector_address.clone(), + &white_whale::fee_collector::ExecuteMsg::UpdateConfig { + owner: None, + pool_router: None, + fee_distributor: Some(fee_distributor_address.to_string()), + pool_factory: None, + vault_factory: None, + }, + &[], + ) + .unwrap(); + let mut ask_asset_original_balance = Uint128::zero(); for (asset_addr, asset) in assets_collected.clone() { let balance_res: BalanceResponse = app @@ -458,7 +496,6 @@ fn collect_all_factories_cw20_fees_successfully() { creator.sender, fee_collector_address.clone(), &AggregateFees { - asset_info: ask_asset, aggregate_fees_for: FeesFor::Factory { factory_addr: pool_factory_address.to_string(), factory_type: FactoryType::Pool { @@ -941,6 +978,7 @@ fn collect_pools_native_fees_successfully() { let mut app = mock_app_with_balance(balances); let fee_collector_id = store_fee_collector_code(&mut app); + let fee_distributor_id = store_fee_distributor_code(&mut app); let pool_factory_id = store_pool_factory_code(&mut app); let pool_router_id = store_pool_router_code(&mut app); let pair_id = store_pair_code(&mut app); @@ -1285,6 +1323,41 @@ fn collect_pools_native_fees_successfully() { let ask_asset = AssetInfo::Token { contract_addr: cw20_tokens[0].to_string(), }; + + let fee_distributor_address = app + .instantiate_contract( + fee_distributor_id, + creator.clone().sender, + &white_whale::fee_distributor::InstantiateMsg { + bonding_contract_addr: "whale_lair".clone().to_string(), + fee_collector_addr: fee_collector_address.clone().to_string(), + grace_period: Uint64::new(1), + epoch_config: EpochConfig { + duration: Uint64::new(86_400_000_000_000u64), // a day + genesis_epoch: Uint64::new(1678802400_000000000u64), // March 14, 2023 2:00:00 PM + }, + distribution_asset: ask_asset.clone(), + }, + &[], + "fee_distributor", + None, + ) + .unwrap(); + + app.execute_contract( + creator.sender.clone(), + fee_collector_address.clone(), + &white_whale::fee_collector::ExecuteMsg::UpdateConfig { + owner: None, + pool_router: None, + fee_distributor: Some(fee_distributor_address.to_string()), + pool_factory: None, + vault_factory: None, + }, + &[], + ) + .unwrap(); + let mut ask_asset_original_balance = Uint128::zero(); for (asset_id, asset) in assets_collected.clone() { if asset_id == "native" { @@ -1435,7 +1508,6 @@ fn collect_pools_native_fees_successfully() { creator.sender, fee_collector_address.clone(), &AggregateFees { - asset_info: ask_asset, aggregate_fees_for: FeesFor::Factory { factory_addr: pool_factory_address.to_string(), factory_type: FactoryType::Pool { @@ -2154,6 +2226,7 @@ fn aggregate_fees_for_vault() { let mut app = mock_app_with_balance(balances); let fee_collector_id = store_fee_collector_code(&mut app); + let fee_distributor_id = store_fee_distributor_code(&mut app); let vault_factory_id = store_vault_factory_code(&mut app); let pool_factory_id = store_pool_factory_code(&mut app); let pool_router_id = store_pool_router_code(&mut app); @@ -2174,6 +2247,42 @@ fn aggregate_fees_for_vault() { ) .unwrap(); + let fee_distributor_address = app + .instantiate_contract( + fee_distributor_id, + creator.clone().sender, + &white_whale::fee_distributor::InstantiateMsg { + bonding_contract_addr: "whale_lair".clone().to_string(), + fee_collector_addr: fee_collector_address.clone().to_string(), + grace_period: Uint64::new(1), + epoch_config: EpochConfig { + duration: Uint64::new(86_400_000_000_000u64), // a day + genesis_epoch: Uint64::new(1678802400_000000000u64), // March 14, 2023 2:00:00 PM + }, + distribution_asset: AssetInfo::NativeToken { + denom: "uatom".to_string(), + }, + }, + &[], + "fee_distributor", + None, + ) + .unwrap(); + + app.execute_contract( + creator.sender.clone(), + fee_collector_address.clone(), + &white_whale::fee_collector::ExecuteMsg::UpdateConfig { + owner: None, + pool_router: None, + fee_distributor: Some(fee_distributor_address.to_string()), + pool_factory: None, + vault_factory: None, + }, + &[], + ) + .unwrap(); + let vault_factory_address = app .instantiate_contract( vault_factory_id, @@ -2512,7 +2621,6 @@ fn aggregate_fees_for_vault() { creator.sender.clone(), fee_collector_address.clone(), &AggregateFees { - asset_info: ask_asset.clone(), aggregate_fees_for: FeesFor::Factory { factory_addr: vault_factory_address.to_string(), factory_type: FactoryType::Vault { @@ -2650,7 +2758,6 @@ fn aggregate_fees_for_vault() { creator.sender, fee_collector_address.clone(), &AggregateFees { - asset_info: ask_asset.clone(), aggregate_fees_for: FeesFor::Factory { factory_addr: vault_factory_address.to_string(), factory_type: FactoryType::Vault { @@ -5748,6 +5855,7 @@ fn aggregate_fees_unsuccessfully() { let mut app = mock_app(); let fee_collector_id = store_fee_collector_code(&mut app); + let fee_distributor_id = store_fee_distributor_code(&mut app); let fee_collector_address = app .instantiate_contract( @@ -5760,15 +5868,48 @@ fn aggregate_fees_unsuccessfully() { ) .unwrap(); - // try to aggregate fees from an unauthorized address + let fee_distributor_address = app + .instantiate_contract( + fee_distributor_id, + creator.clone().sender, + &white_whale::fee_distributor::InstantiateMsg { + bonding_contract_addr: "whale_lair".to_string(), + fee_collector_addr: fee_collector_address.to_string(), + grace_period: Uint64::new(5), + epoch_config: EpochConfig { + duration: Uint64::new(86400000000000), + genesis_epoch: Default::default(), + }, + distribution_asset: AssetInfo::NativeToken { + denom: "uwhale".to_string(), + }, + }, + &[], + "fee_distributor", + None, + ) + .unwrap(); + + // update the fee_distributor_address on fee collector + app.execute_contract( + creator.sender.clone(), + fee_collector_address.clone(), + &UpdateConfig { + owner: None, + pool_router: None, + fee_distributor: Some(fee_distributor_address.to_string()), + pool_factory: None, + vault_factory: None, + }, + &[], + ) + .unwrap(); + let err = app .execute_contract( - Addr::unchecked("unauthorized"), + Addr::unchecked("anyone"), fee_collector_address.clone(), &AggregateFees { - asset_info: AssetInfo::NativeToken { - denom: "uwhale".to_string(), - }, aggregate_fees_for: FeesFor::Contracts { contracts: vec![] }, }, &[], @@ -5777,7 +5918,7 @@ fn aggregate_fees_unsuccessfully() { assert_eq!( err.downcast::().unwrap(), - ContractError::Unauthorized {} + ContractError::InvalidContractsFeeAggregation {} ); } diff --git a/contracts/liquidity_hub/fee_collector/src/tests/testing.rs b/contracts/liquidity_hub/fee_collector/src/tests/testing.rs index 8de50dc1..e1e6f204 100644 --- a/contracts/liquidity_hub/fee_collector/src/tests/testing.rs +++ b/contracts/liquidity_hub/fee_collector/src/tests/testing.rs @@ -2,16 +2,12 @@ use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{from_binary, Addr, DepsMut, MessageInfo, Response}; use cw2::{get_contract_version, set_contract_version, ContractVersion}; use std::env; -use white_whale::pool_network::asset::AssetInfo; use crate::contract::{execute, instantiate, migrate, query}; use white_whale::pool_network::mock_querier::mock_dependencies; use crate::ContractError; -use white_whale::fee_collector::ExecuteMsg::AggregateFees; -use white_whale::fee_collector::{ - Config, ExecuteMsg, FeesFor, InstantiateMsg, MigrateMsg, QueryMsg, -}; +use white_whale::fee_collector::{Config, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; pub fn mock_instantiation(deps: DepsMut, info: MessageInfo) -> Result { let msg = InstantiateMsg {}; @@ -32,27 +28,6 @@ fn proper_initialization() { assert_eq!("owner".to_string(), config_res.owner); } -#[test] -fn collect_fees_unsuccessfully_unauthorized() { - let mut deps = mock_dependencies(&[]); - let info = mock_info("owner", &[]); - mock_instantiation(deps.as_mut(), info).unwrap(); - - // unauthorized tries collecting fees - let info = mock_info("unauthorized", &[]); - let msg = ExecuteMsg::CollectFees { - collect_fees_for: FeesFor::Contracts { contracts: vec![] }, - }; - - let res = execute(deps.as_mut(), mock_env(), info, msg); - - match res { - Ok(_) => panic!("should return ContractError::Unauthorized"), - Err(ContractError::Unauthorized {}) => (), - _ => panic!("should return ContractError::Unauthorized"), - } -} - #[test] fn test_update_config_successfully() { let mut deps = mock_dependencies(&[]); @@ -149,26 +124,3 @@ fn test_migration() { _ => panic!("should return ContractError::Std"), } } - -#[test] -fn test_aggregate_fee_for_contracts_err() { - let mut deps = mock_dependencies(&[]); - let info = mock_info("owner", &[]); - mock_instantiation(deps.as_mut(), info.clone()).unwrap(); - - // should error, can't collect fees for contracts - let msg = AggregateFees { - asset_info: AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - aggregate_fees_for: FeesFor::Contracts { contracts: vec![] }, - }; - - let res = execute(deps.as_mut(), mock_env(), info, msg); - - match res { - Ok(_) => panic!("should return ContractError::InvalidContractsFeeAggregation"), - Err(ContractError::InvalidContractsFeeAggregation {}) => (), - _ => panic!("should return ContractError::InvalidContractsFeeAggregation"), - } -}