diff --git a/contracts/liquidity_book/lb_factory/src/bin/secretcli/example_data.rs b/contracts/liquidity_book/lb_factory/src/bin/secretcli/example_data.rs index 552e29d2..80b6628e 100644 --- a/contracts/liquidity_book/lb_factory/src/bin/secretcli/example_data.rs +++ b/contracts/liquidity_book/lb_factory/src/bin/secretcli/example_data.rs @@ -1,13 +1,12 @@ -use std::str::FromStr; - -use ethnum::U256; use shade_protocol::{ c_std::{to_binary, Addr, ContractInfo, Uint128, Uint256}, - lb_libraries::{ - math::uint256_to_u256::ConvertU256, - types::{ContractInstantiationInfo, LBPair, LBPairInformation, StaticFeeParameters}, + lb_libraries::types::{ + ContractInstantiationInfo, + LBPair, + LBPairInformation, + StaticFeeParameters, }, - liquidity_book::lb_pair::{LiquidityParameters, RemoveLiquidity, RewardsDistribution}, + liquidity_book::lb_pair::{LiquidityParameters, RemoveLiquidity}, snip20::Snip20ReceiveMsg, swap::core::{TokenAmount, TokenType}, utils::asset::RawContract, diff --git a/contracts/liquidity_book/lb_factory/src/bin/secretcli/main.rs b/contracts/liquidity_book/lb_factory/src/bin/secretcli/main.rs index ce6e7951..1195a790 100644 --- a/contracts/liquidity_book/lb_factory/src/bin/secretcli/main.rs +++ b/contracts/liquidity_book/lb_factory/src/bin/secretcli/main.rs @@ -74,6 +74,7 @@ fn main() -> io::Result<()> { query_auth: RawContract::example(), recover_staking_funds_receiver: Addr::funds_recipient(), + max_bins_per_swap: Some(500), }; writeln!(file, "## Instantiate Message\n")?; diff --git a/contracts/liquidity_book/lb_factory/src/contract.rs b/contracts/liquidity_book/lb_factory/src/contract.rs index d690f014..5cf10133 100644 --- a/contracts/liquidity_book/lb_factory/src/contract.rs +++ b/contracts/liquidity_book/lb_factory/src/contract.rs @@ -72,6 +72,7 @@ pub fn instantiate( staking_contract_implementation: ContractInstantiationInfo::default(), recover_staking_funds_receiver: msg.recover_staking_funds_receiver, query_auth: msg.query_auth.into_valid(deps.api)?, + max_bins_per_swap: msg.max_bins_per_swap, }; STATE.save(deps.storage, &config)?; @@ -433,6 +434,7 @@ fn try_create_lb_pair( epoch_staking_duration: staking_preset.epoch_staking_duration, expiry_staking_duration: staking_preset.expiry_staking_duration, recover_staking_funds_receiver: config.recover_staking_funds_receiver, + max_bins_per_swap: None, })?, code_hash: config.lb_pair_implementation.code_hash.clone(), funds: vec![], @@ -452,76 +454,6 @@ fn try_create_lb_pair( Ok(Response::new().add_submessages(messages)) } -// /// Sets whether the pair is ignored or not for routing, it will make the pair unusable by the router. -// /// -// /// # Arguments -// /// -// /// * `token_x` - The address of the first token of the pair. -// /// * `token_y` - The address of the second token of the pair. -// /// * `bin_step` - The bin step in basis point of the pair. -// /// * `ignored` - Whether to ignore (true) or not (false) the pair for routing. -// fn try_set_lb_pair_ignored( -// deps: DepsMut, -// env: Env, -// info: MessageInfo, -// token_a: TokenType, -// token_b: TokenType, -// bin_step: u16, -// ignored: bool, -// ) -> Result { -// let config = CONFIG.load(deps.storage)?; -// only_owner(&info.sender, &config.owner)?; - -// let (token_a, token_b) = _sort_tokens(token_a, token_b); - -// let mut pair_information = LB_PAIRS_INFO -// .load( -// deps.storage, -// ( -// token_a.unique_key().clone(), -// token_b.unique_key().clone(), -// bin_step, -// ), -// ) -// .unwrap(); - -// if pair_information -// .lb_pair -// .contract -// .address -// .as_str() -// .is_empty() -// { -// return Err(Error::LBPairDoesNotExist { -// token_x: token_a.unique_key().clone(), -// token_y: token_b.unique_key().clone(), -// bin_step, -// }); -// } - -// if pair_information.ignored_for_routing == ignored { -// return Err(Error::LBPairIgnoredIsAlreadyInTheSameState); -// } - -// pair_information.ignored_for_routing = ignored; - -// LB_PAIRS_INFO.save( -// deps.storage, -// ( -// token_a.unique_key().clone(), -// token_b.unique_key().clone(), -// bin_step, -// ), -// &pair_information, -// )?; - -// // emit LBPairIgnoredStateChanged(pairInformation.LBPair, ignored); - -// // TODO: be more specific about which pair changed -// Ok(Response::default() -// .add_attribute_plaintext("LBPair ignored state changed", format!("{}", ignored))) -// } - /// Sets the preset parameters of a bin step /// /// # Arguments @@ -554,7 +486,7 @@ fn try_set_pair_preset( epoch_staking_duration: u64, expiry_staking_duration: Option, ) -> Result { - let mut state = STATE.load(deps.storage)?; + let state = STATE.load(deps.storage)?; validate_admin( &deps.querier, AdminPermissions::LiquidityBookAdmin, diff --git a/contracts/liquidity_book/lb_factory/src/state.rs b/contracts/liquidity_book/lb_factory/src/state.rs index 9440feb3..7f60f078 100644 --- a/contracts/liquidity_book/lb_factory/src/state.rs +++ b/contracts/liquidity_book/lb_factory/src/state.rs @@ -4,10 +4,7 @@ use std::collections::HashSet; use shade_protocol::{ c_std::{Addr, ContractInfo, Storage}, cosmwasm_schema::cw_serde, - lb_libraries::{ - pair_parameter_helper::PairParameters, - types::{ContractInstantiationInfo, TreeUint24}, - }, + lb_libraries::{pair_parameter_helper::PairParameters, types::ContractInstantiationInfo}, liquidity_book::lb_pair::RewardsDistributionAlgorithm, secret_storage_plus::{AppendStore, Item, Map}, storage::{singleton, singleton_read, ReadonlySingleton, Singleton}, @@ -65,6 +62,7 @@ pub struct State { pub admin_auth: Contract, pub query_auth: Contract, pub recover_staking_funds_receiver: Addr, + pub max_bins_per_swap: Option, } #[cw_serde] diff --git a/contracts/liquidity_book/lb_factory/ts/Sg721.client.ts b/contracts/liquidity_book/lb_factory/ts/Sg721.client.ts new file mode 100644 index 00000000..77a3b835 --- /dev/null +++ b/contracts/liquidity_book/lb_factory/ts/Sg721.client.ts @@ -0,0 +1,556 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from "@cosmjs/cosmwasm-stargate"; +import { Coin, StdFee } from "@cosmjs/amino"; +import { AllBinStepsResponse, Addr, TokenType, AllLBPairsResponse, LBPairInformation, LBPair, ContractInfo, ExecuteMsg, RewardsDistributionAlgorithm, ContractInstantiationInfo, FeeRecipientResponse, InstantiateMsg, RawContract, IsQuoteAssetResponse, LBPairAtIndexResponse, LBPairImplementationResponse, LBPairInformationResponse, LBTokenImplementationResponse, MinBinStepResponse, NumberOfLBPairsResponse, NumberOfQuoteAssetsResponse, OpenBinStepsResponse, PresetResponse, QueryMsg, QuoteAssetAtIndexResponse } from "./Sg721.types"; +export interface Sg721ReadOnlyInterface { + contractAddress: string; + getMinBinStep: () => Promise; + getFeeRecipient: () => Promise; + getLbPairImplementation: () => Promise; + getLbTokenImplementation: () => Promise; + getNumberOfLbPairs: () => Promise; + getLbPairAtIndex: ({ + index + }: { + index: number; + }) => Promise; + getNumberOfQuoteAssets: () => Promise; + getQuoteAssetAtIndex: ({ + index + }: { + index: number; + }) => Promise; + isQuoteAsset: ({ + token + }: { + token: TokenType; + }) => Promise; + getLbPairInformation: ({ + binStep, + tokenX, + tokenY + }: { + binStep: number; + tokenX: TokenType; + tokenY: TokenType; + }) => Promise; + getPreset: ({ + binStep + }: { + binStep: number; + }) => Promise; + getAllBinSteps: () => Promise; + getOpenBinSteps: () => Promise; + getAllLbPairs: ({ + tokenX, + tokenY + }: { + tokenX: TokenType; + tokenY: TokenType; + }) => Promise; +} +export class Sg721QueryClient implements Sg721ReadOnlyInterface { + client: CosmWasmClient; + contractAddress: string; + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client; + this.contractAddress = contractAddress; + this.getMinBinStep = this.getMinBinStep.bind(this); + this.getFeeRecipient = this.getFeeRecipient.bind(this); + this.getLbPairImplementation = this.getLbPairImplementation.bind(this); + this.getLbTokenImplementation = this.getLbTokenImplementation.bind(this); + this.getNumberOfLbPairs = this.getNumberOfLbPairs.bind(this); + this.getLbPairAtIndex = this.getLbPairAtIndex.bind(this); + this.getNumberOfQuoteAssets = this.getNumberOfQuoteAssets.bind(this); + this.getQuoteAssetAtIndex = this.getQuoteAssetAtIndex.bind(this); + this.isQuoteAsset = this.isQuoteAsset.bind(this); + this.getLbPairInformation = this.getLbPairInformation.bind(this); + this.getPreset = this.getPreset.bind(this); + this.getAllBinSteps = this.getAllBinSteps.bind(this); + this.getOpenBinSteps = this.getOpenBinSteps.bind(this); + this.getAllLbPairs = this.getAllLbPairs.bind(this); + } + + getMinBinStep = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_min_bin_step: {} + }); + }; + getFeeRecipient = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_fee_recipient: {} + }); + }; + getLbPairImplementation = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_lb_pair_implementation: {} + }); + }; + getLbTokenImplementation = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_lb_token_implementation: {} + }); + }; + getNumberOfLbPairs = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_number_of_lb_pairs: {} + }); + }; + getLbPairAtIndex = async ({ + index + }: { + index: number; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_lb_pair_at_index: { + index + } + }); + }; + getNumberOfQuoteAssets = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_number_of_quote_assets: {} + }); + }; + getQuoteAssetAtIndex = async ({ + index + }: { + index: number; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_quote_asset_at_index: { + index + } + }); + }; + isQuoteAsset = async ({ + token + }: { + token: TokenType; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + is_quote_asset: { + token + } + }); + }; + getLbPairInformation = async ({ + binStep, + tokenX, + tokenY + }: { + binStep: number; + tokenX: TokenType; + tokenY: TokenType; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_lb_pair_information: { + bin_step: binStep, + token_x: tokenX, + token_y: tokenY + } + }); + }; + getPreset = async ({ + binStep + }: { + binStep: number; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_preset: { + bin_step: binStep + } + }); + }; + getAllBinSteps = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_all_bin_steps: {} + }); + }; + getOpenBinSteps = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_open_bin_steps: {} + }); + }; + getAllLbPairs = async ({ + tokenX, + tokenY + }: { + tokenX: TokenType; + tokenY: TokenType; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_all_lb_pairs: { + token_x: tokenX, + token_y: tokenY + } + }); + }; +} +export interface Sg721Interface extends Sg721ReadOnlyInterface { + contractAddress: string; + sender: string; + setLbPairImplementation: ({ + implementation + }: { + implementation: ContractInstantiationInfo; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + setLbTokenImplementation: ({ + implementation + }: { + implementation: ContractInstantiationInfo; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + setStakingContractImplementation: ({ + implementation + }: { + implementation: ContractInstantiationInfo; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + createLbPair: ({ + activeId, + binStep, + entropy, + tokenX, + tokenY, + viewingKey + }: { + activeId: number; + binStep: number; + entropy: string; + tokenX: TokenType; + tokenY: TokenType; + viewingKey: string; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + setPairPreset: ({ + baseFactor, + binStep, + decayPeriod, + epochStakingDuration, + epochStakingIndex, + expiryStakingDuration, + filterPeriod, + isOpen, + maxVolatilityAccumulator, + protocolShare, + reductionFactor, + rewardsDistributionAlgorithm, + totalRewardBins, + variableFeeControl + }: { + baseFactor: number; + binStep: number; + decayPeriod: number; + epochStakingDuration: number; + epochStakingIndex: number; + expiryStakingDuration?: number; + filterPeriod: number; + isOpen: boolean; + maxVolatilityAccumulator: number; + protocolShare: number; + reductionFactor: number; + rewardsDistributionAlgorithm: RewardsDistributionAlgorithm; + totalRewardBins: number; + variableFeeControl: number; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + setPresetOpenState: ({ + binStep, + isOpen + }: { + binStep: number; + isOpen: boolean; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + removePreset: ({ + binStep + }: { + binStep: number; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + setFeeParametersOnPair: ({ + baseFactor, + binStep, + decayPeriod, + filterPeriod, + maxVolatilityAccumulator, + protocolShare, + reductionFactor, + tokenX, + tokenY, + variableFeeControl + }: { + baseFactor: number; + binStep: number; + decayPeriod: number; + filterPeriod: number; + maxVolatilityAccumulator: number; + protocolShare: number; + reductionFactor: number; + tokenX: TokenType; + tokenY: TokenType; + variableFeeControl: number; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + setFeeRecipient: ({ + feeRecipient + }: { + feeRecipient: Addr; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + addQuoteAsset: ({ + asset + }: { + asset: TokenType; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + removeQuoteAsset: ({ + asset + }: { + asset: TokenType; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + forceDecay: ({ + pair + }: { + pair: LBPair; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; +} +export class Sg721Client extends Sg721QueryClient implements Sg721Interface { + client: SigningCosmWasmClient; + sender: string; + contractAddress: string; + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress); + this.client = client; + this.sender = sender; + this.contractAddress = contractAddress; + this.setLbPairImplementation = this.setLbPairImplementation.bind(this); + this.setLbTokenImplementation = this.setLbTokenImplementation.bind(this); + this.setStakingContractImplementation = this.setStakingContractImplementation.bind(this); + this.createLbPair = this.createLbPair.bind(this); + this.setPairPreset = this.setPairPreset.bind(this); + this.setPresetOpenState = this.setPresetOpenState.bind(this); + this.removePreset = this.removePreset.bind(this); + this.setFeeParametersOnPair = this.setFeeParametersOnPair.bind(this); + this.setFeeRecipient = this.setFeeRecipient.bind(this); + this.addQuoteAsset = this.addQuoteAsset.bind(this); + this.removeQuoteAsset = this.removeQuoteAsset.bind(this); + this.forceDecay = this.forceDecay.bind(this); + } + + setLbPairImplementation = async ({ + implementation + }: { + implementation: ContractInstantiationInfo; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + set_lb_pair_implementation: { + implementation + } + }, fee, memo, _funds); + }; + setLbTokenImplementation = async ({ + implementation + }: { + implementation: ContractInstantiationInfo; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + set_lb_token_implementation: { + implementation + } + }, fee, memo, _funds); + }; + setStakingContractImplementation = async ({ + implementation + }: { + implementation: ContractInstantiationInfo; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + set_staking_contract_implementation: { + implementation + } + }, fee, memo, _funds); + }; + createLbPair = async ({ + activeId, + binStep, + entropy, + tokenX, + tokenY, + viewingKey + }: { + activeId: number; + binStep: number; + entropy: string; + tokenX: TokenType; + tokenY: TokenType; + viewingKey: string; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + create_lb_pair: { + active_id: activeId, + bin_step: binStep, + entropy, + token_x: tokenX, + token_y: tokenY, + viewing_key: viewingKey + } + }, fee, memo, _funds); + }; + setPairPreset = async ({ + baseFactor, + binStep, + decayPeriod, + epochStakingDuration, + epochStakingIndex, + expiryStakingDuration, + filterPeriod, + isOpen, + maxVolatilityAccumulator, + protocolShare, + reductionFactor, + rewardsDistributionAlgorithm, + totalRewardBins, + variableFeeControl + }: { + baseFactor: number; + binStep: number; + decayPeriod: number; + epochStakingDuration: number; + epochStakingIndex: number; + expiryStakingDuration?: number; + filterPeriod: number; + isOpen: boolean; + maxVolatilityAccumulator: number; + protocolShare: number; + reductionFactor: number; + rewardsDistributionAlgorithm: RewardsDistributionAlgorithm; + totalRewardBins: number; + variableFeeControl: number; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + set_pair_preset: { + base_factor: baseFactor, + bin_step: binStep, + decay_period: decayPeriod, + epoch_staking_duration: epochStakingDuration, + epoch_staking_index: epochStakingIndex, + expiry_staking_duration: expiryStakingDuration, + filter_period: filterPeriod, + is_open: isOpen, + max_volatility_accumulator: maxVolatilityAccumulator, + protocol_share: protocolShare, + reduction_factor: reductionFactor, + rewards_distribution_algorithm: rewardsDistributionAlgorithm, + total_reward_bins: totalRewardBins, + variable_fee_control: variableFeeControl + } + }, fee, memo, _funds); + }; + setPresetOpenState = async ({ + binStep, + isOpen + }: { + binStep: number; + isOpen: boolean; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + set_preset_open_state: { + bin_step: binStep, + is_open: isOpen + } + }, fee, memo, _funds); + }; + removePreset = async ({ + binStep + }: { + binStep: number; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + remove_preset: { + bin_step: binStep + } + }, fee, memo, _funds); + }; + setFeeParametersOnPair = async ({ + baseFactor, + binStep, + decayPeriod, + filterPeriod, + maxVolatilityAccumulator, + protocolShare, + reductionFactor, + tokenX, + tokenY, + variableFeeControl + }: { + baseFactor: number; + binStep: number; + decayPeriod: number; + filterPeriod: number; + maxVolatilityAccumulator: number; + protocolShare: number; + reductionFactor: number; + tokenX: TokenType; + tokenY: TokenType; + variableFeeControl: number; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + set_fee_parameters_on_pair: { + base_factor: baseFactor, + bin_step: binStep, + decay_period: decayPeriod, + filter_period: filterPeriod, + max_volatility_accumulator: maxVolatilityAccumulator, + protocol_share: protocolShare, + reduction_factor: reductionFactor, + token_x: tokenX, + token_y: tokenY, + variable_fee_control: variableFeeControl + } + }, fee, memo, _funds); + }; + setFeeRecipient = async ({ + feeRecipient + }: { + feeRecipient: Addr; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + set_fee_recipient: { + fee_recipient: feeRecipient + } + }, fee, memo, _funds); + }; + addQuoteAsset = async ({ + asset + }: { + asset: TokenType; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + add_quote_asset: { + asset + } + }, fee, memo, _funds); + }; + removeQuoteAsset = async ({ + asset + }: { + asset: TokenType; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + remove_quote_asset: { + asset + } + }, fee, memo, _funds); + }; + forceDecay = async ({ + pair + }: { + pair: LBPair; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + force_decay: { + pair + } + }, fee, memo, _funds); + }; +} \ No newline at end of file diff --git a/contracts/liquidity_book/lb_factory/ts/Sg721.types.ts b/contracts/liquidity_book/lb_factory/ts/Sg721.types.ts new file mode 100644 index 00000000..15962873 --- /dev/null +++ b/contracts/liquidity_book/lb_factory/ts/Sg721.types.ts @@ -0,0 +1,221 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +export interface AllBinStepsResponse { + bin_step_with_preset: number[]; +} +export type Addr = string; +export type TokenType = { + custom_token: { + contract_addr: Addr; + token_code_hash: string; + }; +} | { + native_token: { + denom: string; + }; +}; +export interface AllLBPairsResponse { + lb_pairs_available: LBPairInformation[]; +} +export interface LBPairInformation { + bin_step: number; + created_by_owner: boolean; + ignored_for_routing: boolean; + info: LBPair; +} +export interface LBPair { + bin_step: number; + contract: ContractInfo; + token_x: TokenType; + token_y: TokenType; +} +export interface ContractInfo { + address: Addr; + code_hash?: string; + [k: string]: unknown; +} +export type ExecuteMsg = { + set_lb_pair_implementation: { + implementation: ContractInstantiationInfo; + }; +} | { + set_lb_token_implementation: { + implementation: ContractInstantiationInfo; + }; +} | { + set_staking_contract_implementation: { + implementation: ContractInstantiationInfo; + }; +} | { + create_lb_pair: { + active_id: number; + bin_step: number; + entropy: string; + token_x: TokenType; + token_y: TokenType; + viewing_key: string; + }; +} | { + set_pair_preset: { + base_factor: number; + bin_step: number; + decay_period: number; + epoch_staking_duration: number; + epoch_staking_index: number; + expiry_staking_duration?: number | null; + filter_period: number; + is_open: boolean; + max_volatility_accumulator: number; + protocol_share: number; + reduction_factor: number; + rewards_distribution_algorithm: RewardsDistributionAlgorithm; + total_reward_bins: number; + variable_fee_control: number; + }; +} | { + set_preset_open_state: { + bin_step: number; + is_open: boolean; + }; +} | { + remove_preset: { + bin_step: number; + }; +} | { + set_fee_parameters_on_pair: { + base_factor: number; + bin_step: number; + decay_period: number; + filter_period: number; + max_volatility_accumulator: number; + protocol_share: number; + reduction_factor: number; + token_x: TokenType; + token_y: TokenType; + variable_fee_control: number; + }; +} | { + set_fee_recipient: { + fee_recipient: Addr; + }; +} | { + add_quote_asset: { + asset: TokenType; + }; +} | { + remove_quote_asset: { + asset: TokenType; + }; +} | { + force_decay: { + pair: LBPair; + }; +}; +export type RewardsDistributionAlgorithm = "time_based_rewards" | "volume_based_rewards"; +export interface ContractInstantiationInfo { + code_hash: string; + id: number; +} +export interface FeeRecipientResponse { + fee_recipient: Addr; +} +export interface InstantiateMsg { + admin_auth: RawContract; + fee_recipient: Addr; + max_bins_per_swap?: number | null; + owner?: Addr | null; + query_auth: RawContract; + recover_staking_funds_receiver: Addr; +} +export interface RawContract { + address: string; + code_hash: string; +} +export interface IsQuoteAssetResponse { + is_quote: boolean; +} +export interface LBPairAtIndexResponse { + lb_pair: LBPair; +} +export interface LBPairImplementationResponse { + lb_pair_implementation: ContractInstantiationInfo; +} +export interface LBPairInformationResponse { + lb_pair_information: LBPairInformation; +} +export interface LBTokenImplementationResponse { + lb_token_implementation: ContractInstantiationInfo; +} +export interface MinBinStepResponse { + min_bin_step: number; +} +export interface NumberOfLBPairsResponse { + lb_pair_number: number; +} +export interface NumberOfQuoteAssetsResponse { + number_of_quote_assets: number; +} +export interface OpenBinStepsResponse { + open_bin_steps: number[]; +} +export interface PresetResponse { + base_factor: number; + decay_period: number; + filter_period: number; + is_open: boolean; + max_volatility_accumulator: number; + protocol_share: number; + reduction_factor: number; + variable_fee_control: number; +} +export type QueryMsg = { + get_min_bin_step: {}; +} | { + get_fee_recipient: {}; +} | { + get_lb_pair_implementation: {}; +} | { + get_lb_token_implementation: {}; +} | { + get_number_of_lb_pairs: {}; +} | { + get_lb_pair_at_index: { + index: number; + }; +} | { + get_number_of_quote_assets: {}; +} | { + get_quote_asset_at_index: { + index: number; + }; +} | { + is_quote_asset: { + token: TokenType; + }; +} | { + get_lb_pair_information: { + bin_step: number; + token_x: TokenType; + token_y: TokenType; + }; +} | { + get_preset: { + bin_step: number; + }; +} | { + get_all_bin_steps: {}; +} | { + get_open_bin_steps: {}; +} | { + get_all_lb_pairs: { + token_x: TokenType; + token_y: TokenType; + }; +}; +export interface QuoteAssetAtIndexResponse { + asset: TokenType; +} \ No newline at end of file diff --git a/contracts/liquidity_book/lb_pair/src/bin/secretcli/example_data.rs b/contracts/liquidity_book/lb_pair/src/bin/secretcli/example_data.rs index b69f3579..bb7dfe40 100644 --- a/contracts/liquidity_book/lb_pair/src/bin/secretcli/example_data.rs +++ b/contracts/liquidity_book/lb_pair/src/bin/secretcli/example_data.rs @@ -104,7 +104,6 @@ impl ExampleData for ContractInfo { } } -// TODO - why are we using this instead of ContractInfo? impl ExampleData for RawContract { fn example() -> Self { RawContract { @@ -178,7 +177,6 @@ impl ExampleData for RemoveLiquidity { amount_x_min: Uint128::from(10u128), amount_y_min: Uint128::from(10u128), ids: vec![ACTIVE_ID], - // TODO - understand what "amounts" means. Is that a packed_uint128? amounts: vec![Uint256::from_u128(10u128)], deadline: 1701283067, } diff --git a/contracts/liquidity_book/lb_pair/src/bin/secretcli/main.rs b/contracts/liquidity_book/lb_pair/src/bin/secretcli/main.rs index b099f079..41c1e915 100644 --- a/contracts/liquidity_book/lb_pair/src/bin/secretcli/main.rs +++ b/contracts/liquidity_book/lb_pair/src/bin/secretcli/main.rs @@ -121,6 +121,7 @@ fn main() -> io::Result<()> { entropy: String::from("entropy"), protocol_fee_recipient: Addr::funds_recipient(), query_auth: RawContract::example(), + max_bins_per_swap: Some(500), }; writeln!(file, "## Instantiate Message\n")?; @@ -151,8 +152,6 @@ fn main() -> io::Result<()> { let collect_protocol_fees = ExecuteMsg::CollectProtocolFees {}; - let increase_oracle_length = ExecuteMsg::IncreaseOracleLength { new_length: 100 }; - let set_static_fee_parameters = ExecuteMsg::SetStaticFeeParameters { base_factor: preset.get_base_factor(), filter_period: preset.get_filter_period(), @@ -183,7 +182,6 @@ fn main() -> io::Result<()> { swap_tokens, swap_tokens_invoke, collect_protocol_fees, - increase_oracle_length, set_static_fee_parameters, force_decay, calculte_rewards, @@ -245,9 +243,7 @@ fn main() -> io::Result<()> { let get_static_fee_parameters = QueryMsg::GetStaticFeeParameters {}; let get_variable_fee_parameters = QueryMsg::GetVariableFeeParameters {}; let get_oracle_parameters = QueryMsg::GetOracleParameters {}; - let get_oracle_sample_at = QueryMsg::GetOracleSampleAt { - look_up_timestamp: 1234567890, - }; + let get_oracle_sample_at = QueryMsg::GetOracleSampleAt { oracle_id: 12345 }; let get_price_from_id = QueryMsg::GetPriceFromId { id: ACTIVE_ID }; let get_id_from_price = QueryMsg::GetIdFromPrice { price }; @@ -281,7 +277,7 @@ fn main() -> io::Result<()> { fee_info: FeeInfo { shade_dao_address: Addr::recipient(), lp_fee: Fee { - nom: 100_00000, //TODO: fix these + nom: 100_00000, denom: 1000, }, shade_dao_fee: Fee { @@ -417,7 +413,6 @@ fn main() -> io::Result<()> { let get_oracle_parameters_response = OracleParametersResponse { sample_lifetime: 120, size: 10, - active_size: 5, last_updated: 1703403384, first_timestamp: 1703403383, }; @@ -426,6 +421,14 @@ fn main() -> io::Result<()> { cumulative_id: 100, cumulative_volatility: 200, cumulative_bin_crossed: 50, + cumulative_volume_x: 2000, + cumulative_volume_y: 1000, + cumulative_fee_x: 20, + cumulative_fee_y: 50, + oracle_id: 50, + cumulative_txns: 10, + lifetime: 20, + created_at: 652230, }; let get_price_from_id_response = PriceFromIdResponse { price }; diff --git a/contracts/liquidity_book/lb_pair/src/contract.rs b/contracts/liquidity_book/lb_pair/src/contract.rs index a5e05326..972254db 100644 --- a/contracts/liquidity_book/lb_pair/src/contract.rs +++ b/contracts/liquidity_book/lb_pair/src/contract.rs @@ -1,6 +1,4 @@ -use crate::{prelude::*, state::*}; -use ethnum::U256; -use serde::Serialize; +use crate::{execute::*, helper::*, prelude::*, query::*, state::*}; use shade_protocol::{ admin::helpers::{validate_admin, AdminPermissions}, c_std::{ @@ -8,11 +6,9 @@ use shade_protocol::{ shd_entry_point, to_binary, Addr, - Attribute, Binary, ContractInfo, CosmosMsg, - Decimal, Deps, DepsMut, Env, @@ -23,56 +19,25 @@ use shade_protocol::{ StdResult, SubMsg, SubMsgResult, - Timestamp, Uint128, Uint256, WasmMsg, }, contract_interfaces::{ liquidity_book::{lb_pair::*, lb_staking, lb_token}, - swap::{ - amm_pair::{ - FeeInfo, - QueryMsgResponse::{GetPairInfo, SwapSimulation}, - }, - core::{Fee, TokenPair, TokenType}, - router::ExecuteMsgResponse, - }, + swap::core::TokenType, }, lb_libraries::{ - approx_div, - bin_helper::BinHelper, - constants::{BASIS_POINT_MAX, MAX_FEE, SCALE_OFFSET}, - fee_helper::FeeHelper, - lb_token::state_structs::{LbPair, TokenAmount, TokenIdBalance}, - math::{ - liquidity_configurations::LiquidityConfigurations, - packed_u128_math::PackedUint128Math, - sample_math::OracleSample, - tree_math::TreeUint24, - u24::U24, - u256x256_math::U256x256Math, - uint256_to_u256::{ConvertU256, ConvertUint256}, - }, - oracle_helper::{Oracle, MAX_SAMPLE_LIFETIME}, + lb_token::state_structs::LbPair, + math::{sample_math::OracleSample, tree_math::TreeUint24, u24::U24}, + oracle_helper::Oracle, pair_parameter_helper::PairParameters, - price_helper::PriceHelper, - types::{Bytes32, MintArrays}, - viewing_keys::{register_receive, set_viewing_key_msg, ViewingKey}, + viewing_keys::ViewingKey, }, - snip20, - Contract, }; -use std::{collections::HashMap, ops::Sub, vec}; - -pub const INSTANTIATE_LP_TOKEN_REPLY_ID: u64 = 1u64; -pub const INSTANTIATE_STAKING_CONTRACT_REPLY_ID: u64 = 2u64; -pub const MINT_REPLY_ID: u64 = 1u64; -const LB_PAIR_CONTRACT_VERSION: u32 = 1; -const DEFAULT_REWARDS_BINS: u32 = 100; +use std::vec; /////////////// INSTANTIATE /////////////// - #[shd_entry_point] pub fn instantiate( deps: DepsMut, @@ -80,8 +45,16 @@ pub fn instantiate( info: MessageInfo, msg: InstantiateMsg, ) -> Result { - // Initializing the Token Contract + // Constants + const EMPTY_ADDR: &str = ""; + const EMPTY_STRING: &str = ""; + const LB_TOKEN_DECIMALS: u8 = 18; + const START_ORACLE_ID: u16 = 1; + const START_REWARDS_EPOCH: u64 = 1; + let tree: TreeUint24 = TreeUint24::new(); + let mut oracle = Oracle(OracleSample::default()); + // Initializing the Token Contract let token_x_symbol = match msg.token_x.clone() { TokenType::CustomToken { contract_addr, @@ -110,13 +83,12 @@ pub fn instantiate( ), symbol: format!("LB-{}-{}-{}", token_x_symbol, token_y_symbol, &msg.bin_step), lb_pair_address: env.contract.address.clone(), - decimals: 18, + decimals: LB_TOKEN_DECIMALS, }, initial_tokens: Vec::new(), }; let mut response = Response::new(); - response = response.add_submessage(SubMsg::reply_on_success( CosmosMsg::Wasm(WasmMsg::Instantiate { code_id: msg.lb_token_implementation.id, @@ -144,6 +116,7 @@ pub fn instantiate( msg.pair_parameters.max_volatility_accumulator, )?; pair_parameters.set_active_id(msg.active_id)?; + pair_parameters.set_oracle_id(START_ORACLE_ID); // Activating the oracle pair_parameters.update_id_reference(); // RegisterReceiving Token @@ -165,6 +138,7 @@ pub fn instantiate( } } + // State initialization let state = State { creator: info.sender, factory: msg.factory, @@ -174,40 +148,43 @@ pub fn instantiate( pair_parameters, reserves: [0u8; 32], protocol_fees: [0u8; 32], + + // ContractInfo for lb_token and lb_staking are intentionally kept empty and will be filled in later lb_token: ContractInfo { - address: Addr::unchecked("".to_string()), - code_hash: "".to_string(), - }, // intentionally keeping this empty will be filled in reply - staking_contract: ContractInfo { - address: Addr::unchecked("".to_string()), - code_hash: "".to_string(), + address: Addr::unchecked(EMPTY_ADDR.to_string()), + code_hash: EMPTY_STRING.to_string(), + }, + lb_staking: ContractInfo { + address: Addr::unchecked(EMPTY_ADDR.to_string()), + code_hash: EMPTY_STRING.to_string(), }, + viewing_key, protocol_fees_recipient: msg.protocol_fee_recipient, admin_auth: msg.admin_auth.into_valid(deps.api)?, last_swap_timestamp: env.block.time, - rewards_epoch_index: 1, + rewards_epoch_index: START_REWARDS_EPOCH, base_rewards_bins: msg.total_reward_bins, toggle_distributions_algorithm: false, - max_bins_per_swap: 100, // TODO: do this by message + max_bins_per_swap: msg.max_bins_per_swap.unwrap_or(DEFAULT_MAX_BINS_PER_SWAP), }; - let tree: TreeUint24 = TreeUint24::new(); - let oracle = Oracle { - samples: HashMap::::new(), - }; + oracle.0 = *oracle.0.set_created_at(env.block.time.seconds()); STATE.save(deps.storage, &state)?; - ORACLE.save(deps.storage, &oracle)?; + ORACLE.save(deps.storage, pair_parameters.get_oracle_id(), &oracle)?; CONTRACT_STATUS.save(deps.storage, &ContractStatus::Active)?; BIN_TREE.save(deps.storage, &tree)?; FEE_MAP_TREE.save(deps.storage, state.rewards_epoch_index, &tree)?; - REWARDS_STATS_STORE.save(deps.storage, state.rewards_epoch_index, &RewardStats { - cumm_value: Uint256::zero(), - cumm_value_mul_bin_id: Uint256::zero(), - rewards_distribution_algorithm: msg.rewards_distribution_algorithm, - })?; - + REWARDS_STATS_STORE.save( + deps.storage, + state.rewards_epoch_index, + &RewardDistributionConfig { + cumulative_value: Uint256::zero(), + cumulative_value_mul_bin_id: Uint256::zero(), + rewards_distribution_algorithm: msg.rewards_distribution_algorithm, + }, + )?; EPHEMERAL_STORAGE.save(deps.storage, &EphemeralStruct { lb_token_code_hash: msg.lb_token_implementation.code_hash, staking_contract: msg.staking_contract_implementation, @@ -228,7 +205,6 @@ pub fn instantiate( } /////////////// EXECUTE /////////////// -/// TODO: a query for contract status #[shd_entry_point] pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> Result { let contract_status = CONTRACT_STATUS.load(deps.storage)?; @@ -290,9 +266,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> R remove_liquidity_params, } => try_remove_liquidity(deps, env, info, remove_liquidity_params), ExecuteMsg::CollectProtocolFees {} => try_collect_protocol_fees(deps, env, info), - ExecuteMsg::IncreaseOracleLength { new_length } => { - try_increase_oracle_length(deps, env, info, new_length) - } ExecuteMsg::SetStaticFeeParameters { base_factor, filter_period, @@ -337,2438 +310,118 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> R } } -pub fn register_pair_token( - env: &Env, - messages: &mut Vec, - token: &TokenType, - viewing_key: &ViewingKey, -) -> StdResult<()> { - if let TokenType::CustomToken { - contract_addr, - token_code_hash, - .. - } = token - { - messages.push(set_viewing_key_msg( - viewing_key.0.clone(), - None, - &ContractInfo { - address: contract_addr.clone(), - code_hash: token_code_hash.to_string(), - }, - )?); - messages.push(register_receive( - env.contract.code_hash.clone(), - None, - &ContractInfo { - address: contract_addr.clone(), - code_hash: token_code_hash.to_string(), - }, - )?); - } - - Ok(()) -} - -/// Swap tokens iterating over the bins until the entire amount is swapped. -/// -/// Token X will be swapped for token Y if `swap_for_y` is true, and token Y for token X if `swap_for_y` is false. -/// -/// This function will not transfer the tokens from the caller, it is expected that the tokens have already been -/// transferred to this contract through another contract, most likely the router. -/// That is why this function shouldn't be called directly, but only through one of the swap functions of a router -/// that will also perform safety checks, such as minimum amounts and slippage. -/// -/// The variable fee is updated throughout the swap, it increases with the number of bins crossed. -/// The oracle is updated at the end of the swap. -/// -/// # Arguments -/// -/// * `swap_for_y` - Whether you're swapping token X for token Y (true) or token Y for token X (false) -/// * `to` - The address to send the tokens to -/// -/// # Returns -/// -/// * `amounts_out` - The encoded amounts of token X and token Y sent to `to` -fn try_swap( +pub fn receiver_callback( deps: DepsMut, env: Env, - _info: MessageInfo, - swap_for_y: bool, - to: Addr, - amounts_received: Uint128, //Will get this parameter from router contract + info: MessageInfo, + from: Addr, + amount: Uint128, + msg: Option, ) -> Result { - let state = STATE.load(deps.storage)?; - let tree = BIN_TREE.load(deps.storage)?; - let token_x = state.token_x; - let token_y = state.token_y; - - let mut ids = Vec::new(); - let reserves = state.reserves; - let mut protocol_fees = state.protocol_fees; - let mut total_fees: [u8; 32] = [0; 32]; - let mut lp_fees: [u8; 32] = [0; 32]; - let mut shade_dao_fees: [u8; 32] = [0; 32]; - - let mut amounts_out = [0u8; 32]; - let mut amounts_left = if swap_for_y { - BinHelper::received_x(amounts_received) - } else { - BinHelper::received_y(amounts_received) - }; - if amounts_left == [0u8; 32] { - return Err(Error::InsufficientAmountIn); - }; - - let mut reserves = reserves.add(amounts_left); - - let mut params = state.pair_parameters; - let bin_step = state.bin_step; - let mut reward_stats = REWARDS_STATS_STORE.load(deps.storage, state.rewards_epoch_index)?; - - let mut active_id = params.get_active_id(); - - // updating the volatility - params.update_references(&env.block.time)?; - if reward_stats.rewards_distribution_algorithm == RewardsDistributionAlgorithm::TimeBasedRewards - { - let time_difference = - Uint256::from(env.block.time.seconds() - state.last_swap_timestamp.seconds()); - - reward_stats.cumm_value += time_difference; - reward_stats.cumm_value_mul_bin_id += time_difference * (Uint256::from(active_id)); - } - - // Allowing max 100 bins crossed per swap - for _ in 0..state.max_bins_per_swap { - let bin_reserves = BIN_MAP - .load(deps.storage, active_id) - .map_err(|_| Error::ZeroBinReserve { active_id })?; - - if !BinHelper::is_empty(bin_reserves, !swap_for_y) { - let price = PriceHelper::get_price_from_id(active_id, bin_step)?; - - params.update_volatility_accumulator(active_id)?; - - let (mut amounts_in_with_fees, amounts_out_of_bin, fees) = BinHelper::get_amounts( - bin_reserves, - params, - bin_step, - swap_for_y, - active_id, - amounts_left, - price, - )?; - - if U256::from_le_bytes(amounts_in_with_fees) > U256::ZERO { - // let fee_obj = FeeLog { - // is_token_x: swap_for_y, - // fee: Uint128::from(fees.decode_alt(swap_for_y)), - // bin_id: active_id, - // timestamp: env.block.time, - // last_rewards_epoch_id: state.rewards_epoch_id, - // }; - // //TODO: check if appending is needed - // FEE_APPEND_STORE.push(deps.storage, &fee_obj)?; - - if reward_stats.rewards_distribution_algorithm - == RewardsDistributionAlgorithm::VolumeBasedRewards - { - let feeu128 = fees.decode_alt(swap_for_y); - let swap_value_uint256 = match swap_for_y { - true => U256x256Math::mul_shift_round_up( - U256::from(feeu128), - price, - SCALE_OFFSET, - )? - .u256_to_uint256(), - false => Uint256::from(feeu128), - }; - - reward_stats.cumm_value += swap_value_uint256; - FEE_MAP_TREE.update( - deps.storage, - state.rewards_epoch_index, - |fee_tree| -> Result<_> { - Ok(match fee_tree { - Some(mut t) => { - t.add(active_id); - t - } - None => panic!("Fee tree not initialized"), - }) - }, - )?; - - FEE_MAP.update(deps.storage, active_id, |cumm_fee| -> Result<_> { - let updated_cumm_fee = match cumm_fee { - Some(f) => f + swap_value_uint256, - None => swap_value_uint256, - }; - Ok(updated_cumm_fee) - })?; - } - - amounts_left = amounts_left.sub(amounts_in_with_fees); - amounts_out = amounts_out.add(amounts_out_of_bin); - - let p_fees = - fees.scalar_mul_div_basis_point_round_down(params.get_protocol_share().into())?; - total_fees = total_fees.add(fees); - lp_fees = lp_fees.add(fees.sub(p_fees)); - shade_dao_fees = shade_dao_fees.add(p_fees); + let msg = msg.ok_or(Error::ReceiverMsgEmpty)?; - if U256::from_le_bytes(p_fees) > U256::ZERO { - protocol_fees = protocol_fees.add(p_fees); - amounts_in_with_fees = amounts_in_with_fees.sub(p_fees); - } + let config = STATE.load(deps.storage)?; - BIN_MAP.save( - deps.storage, - active_id, - &bin_reserves - .add(amounts_in_with_fees) // actually amount in wihtout fees - .sub(amounts_out_of_bin), - )?; - ids.push(active_id); + let response; + match from_binary(&msg)? { + InvokeMsg::SwapTokens { + to, + expected_return: _, + padding: _, + } => { + // this check needs to be here instead of in execute() because it is impossible to (cleanly) distinguish between swaps and lp withdraws until this point + // if contract_status is FreezeAll, this fn will never be called, so only need to check LpWithdrawOnly here + let contract_status = CONTRACT_STATUS.load(deps.storage)?; + if contract_status == ContractStatus::LpWithdrawOnly { + return Err(Error::TransactionBlock()); } - } - if amounts_left == [0u8; 32] { - break; - } else { - let next_id = _get_next_non_empty_bin(&tree, swap_for_y, active_id); + //validate recipient address + let checked_to = if let Some(to) = to { + deps.api.addr_validate(to.as_str())? + } else { + from + }; - if next_id == 0 || next_id == (U24::MAX) { - return Err(Error::OutOfLiquidity); + if info.sender != config.token_x.unique_key() + && info.sender != config.token_y.unique_key() + { + return Err(Error::NoMatchingTokenInPair); } - active_id = next_id; - } - } - - REWARDS_STATS_STORE.save(deps.storage, state.rewards_epoch_index, &reward_stats)?; - - if amounts_out == [0u8; 32] { - return Err(Error::InsufficientAmountOut); - } - - reserves = reserves.sub(amounts_out); - - let mut oracle = ORACLE.load(deps.storage)?; - oracle.update(&env.block.time, params, active_id)?; - - STATE.update(deps.storage, |mut state| { - state.last_swap_timestamp = env.block.time; - state.protocol_fees = protocol_fees; - // TODO - map the error to a StdError - state - .pair_parameters - .set_active_id(active_id) - .map_err(|err| StdError::generic_err(err.to_string()))?; - state.reserves = reserves; - Ok::(state) - })?; - - let mut messages: Vec = Vec::new(); - let amount_out; - - if swap_for_y { - amount_out = amounts_out.decode_y(); - let msg = BinHelper::transfer_y(amounts_out, token_y.clone(), to); - - if let Some(message) = msg { - messages.push(message); - } - } else { - amount_out = amounts_out.decode_x(); - let msg = BinHelper::transfer_x(amounts_out, token_x.clone(), to); - if let Some(message) = msg { - messages.push(message); - } - } + let swap_for_y: bool = info.sender == config.token_x.unique_key(); - BIN_RESERVES_UPDATED.update(deps.storage, env.block.height, |x| -> StdResult> { - if let Some(mut y) = x { - y.extend(ids); - Ok(y) - } else { - Ok(ids) + response = try_swap(deps, env, info, swap_for_y, checked_to, amount)?; } - })?; - BIN_RESERVES_UPDATED_LOG.push(deps.storage, &env.block.height)?; - - Ok(Response::new() - .add_messages(messages) - .add_attributes(vec![ - Attribute::new("amount_in", amounts_received), - Attribute::new("amount_out", amount_out.to_string()), - Attribute::new("lp_fee_amount", lp_fees.decode_alt(swap_for_y).to_string()), - Attribute::new( - "total_fee_amount", - total_fees.decode_alt(swap_for_y).to_string(), - ), - Attribute::new( - "shade_dao_fee_amount", - shade_dao_fees.decode_alt(swap_for_y).to_string(), - ), - Attribute::new("token_in_key", token_x.unique_key()), - Attribute::new("token_out_key", token_y.unique_key()), - ]) - .set_data(to_binary(&ExecuteMsgResponse::SwapResult { - amount_in: amounts_received, - amount_out: Uint128::from(amount_out), - })?)) -} - -pub fn try_add_liquidity( - deps: DepsMut, - env: Env, - info: MessageInfo, - liquidity_parameters: LiquidityParameters, -) -> Result { - // Add liquidity while performing safety checks - // transfering funds and checking one's already send - // Main function -> add_liquidity_internal - // Preparing txn output - - // 1- Add liquidity while performing safety checks - // 1.1- Proceed only if deadline has not exceeded - if env.block.time.seconds() > liquidity_parameters.deadline { - return Err(Error::DeadlineExceeded { - deadline: liquidity_parameters.deadline, - current_timestamp: env.block.time.seconds(), - }); - } - let config = STATE.load(deps.storage)?; - let response = Response::new(); - // 1.2- Checking token order - if liquidity_parameters.token_x != config.token_x - || liquidity_parameters.token_y != config.token_y - || liquidity_parameters.bin_step != config.bin_step - { - return Err(Error::WrongPair); - } - - // response = response.add_messages(transfer_messages); - - //3- Main function -> add_liquidity_internal - let response = - add_liquidity_internal(deps, env, info, &config, &liquidity_parameters, response)?; - + }; Ok(response) } -pub fn add_liquidity_internal( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - config: &State, - liquidity_parameters: &LiquidityParameters, - response: Response, -) -> Result { - match_lengths(liquidity_parameters)?; - check_ids_bounds(liquidity_parameters)?; - - let state = STATE.load(deps.storage)?; - - let mut liquidity_configs = vec![ - LiquidityConfigurations { - distribution_x: 0, - distribution_y: 0, - id: 0 - }; - liquidity_parameters.delta_ids.len() - ]; - let mut deposit_ids = Vec::with_capacity(liquidity_parameters.delta_ids.len()); - - let active_id = state.pair_parameters.get_active_id(); - check_active_id_slippage(liquidity_parameters, active_id)?; +/////////////// QUERY /////////////// - for i in 0..liquidity_configs.len() { - let id = calculate_id(liquidity_parameters, active_id, i)?; - deposit_ids.push(id); - // TODO - add checks that neither distribution is > PRECISION - liquidity_configs[i] = LiquidityConfigurations { - distribution_x: liquidity_parameters.distribution_x[i], - distribution_y: liquidity_parameters.distribution_y[i], +#[shd_entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { + match msg { + QueryMsg::GetPairInfo {} => query_pair_info(deps), + QueryMsg::GetFactory {} => query_factory(deps), + QueryMsg::GetTokenX {} => query_token_x(deps), + QueryMsg::GetTokenY {} => query_token_y(deps), + QueryMsg::GetBinStep {} => query_bin_step(deps), + QueryMsg::GetReserves {} => query_reserves(deps), + QueryMsg::GetActiveId {} => query_active_id(deps), + QueryMsg::GetBinReserves { id } => query_bin_reserves(deps, id), + QueryMsg::GetBinsReserves { ids } => query_bins_reserves(deps, ids), + QueryMsg::GetAllBinsReserves { id, - }; - } - - let (amounts_deposited, amounts_left, _liquidity_minted, response) = mint( - &mut deps, - &env, - info.clone(), - config, - info.sender.clone(), - liquidity_configs, - info.sender, - liquidity_parameters.amount_x, - liquidity_parameters.amount_y, - response, - )?; - - //4- Preparing txn output logs - let amount_x_added = Uint128::from(amounts_deposited.decode_x()); - let amount_y_added = Uint128::from(amounts_deposited.decode_y()); - let amount_x_min = liquidity_parameters.amount_x_min; - let amount_y_min = liquidity_parameters.amount_y_min; - - if amount_x_added < amount_x_min || amount_y_added < amount_y_min { - return Err(Error::AmountSlippageCaught { - amount_x_min, - amount_x: amount_x_added, - amount_y_min, - amount_y: amount_y_added, - }); - } - let _amount_x_left = Uint128::from(amounts_left.decode_x()); - let _amount_y_left = Uint128::from(amounts_left.decode_y()); - - // let liq_minted: Vec = liquidity_minted - // .iter() - // .map(|&liq| liq.u256_to_uint256()) - // .collect(); - - // let _deposit_ids_string = serialize_or_err(&deposit_ids)?; - BIN_RESERVES_UPDATED.update(deps.storage, env.block.height, |x| -> StdResult> { - if let Some(mut y) = x { - y.extend(deposit_ids); - Ok(y) - } else { - Ok(deposit_ids) + page, + page_size, + } => query_all_bins_reserves(deps, env, page, page_size, id), + QueryMsg::GetUpdatedBinAtHeight { height } => query_updated_bins_at_height(deps, height), + QueryMsg::GetUpdatedBinAtMultipleHeights { heights } => { + query_updated_bins_at_multiple_heights(deps, heights) } - })?; - BIN_RESERVES_UPDATED_LOG.push(deps.storage, &env.block.height)?; - - // let _liquidity_minted_string = serialize_or_err(&liq_minted)?; - - // response = response - // .add_attribute("amount_x_added", amount_x_added) - // .add_attribute("amount_y_added", amount_y_added) - // .add_attribute("amount_x_left", amount_x_left) - // .add_attribute("amount_y_left", amount_y_left) - // .add_attribute("liquidity_minted", liquidity_minted_string) - // .add_attribute("deposit_ids", deposit_ids_string); - - Ok(response) -} - -fn match_lengths(liquidity_parameters: &LiquidityParameters) -> Result<()> { - if liquidity_parameters.delta_ids.len() != liquidity_parameters.distribution_x.len() - || liquidity_parameters.delta_ids.len() != liquidity_parameters.distribution_y.len() - { - return Err(Error::LengthsMismatch); - } - Ok(()) -} - -fn check_ids_bounds(liquidity_parameters: &LiquidityParameters) -> Result<()> { - if liquidity_parameters.active_id_desired > U24::MAX - || liquidity_parameters.id_slippage > U24::MAX - { - return Err(Error::IdDesiredOverflows { - id_desired: liquidity_parameters.active_id_desired, - id_slippage: liquidity_parameters.id_slippage, - }); - } - Ok(()) -} - -fn check_active_id_slippage( - liquidity_parameters: &LiquidityParameters, - active_id: u32, -) -> Result<()> { - if liquidity_parameters.active_id_desired + liquidity_parameters.id_slippage < active_id - || active_id + liquidity_parameters.id_slippage < liquidity_parameters.active_id_desired - { - return Err(Error::IdSlippageCaught { - active_id_desired: liquidity_parameters.active_id_desired, - id_slippage: liquidity_parameters.id_slippage, - active_id, - }); - } - Ok(()) -} - -//function won't distinguish between overflow and underflow errors; it'll throw the same DeltaIdOverflows -fn calculate_id( - liquidity_parameters: &LiquidityParameters, - active_id: u32, - i: usize, -) -> Result { - // let id: u32; - - let id: i64 = active_id as i64 + liquidity_parameters.delta_ids[i]; - - if id < 0 || id as u32 > U24::MAX { - return Err(Error::DeltaIdOverflows { - delta_id: liquidity_parameters.delta_ids[i], - }); - } - - Ok(id as u32) -} - -/// Mint liquidity tokens by depositing tokens into the pool. -/// -/// It will mint Liquidity Book (LB) tokens for each bin where the user adds liquidity. -/// This function will not transfer the tokens from the caller, it is expected that the tokens have already been -/// transferred to this contract through another contract, most likely the router. -/// That is why this function shouldn't be called directly, but through one of the add liquidity functions of a -/// router that will also perform safety checks. -/// -/// Any excess amount of token will be sent to the `refund_to` address. -/// -/// # Arguments -/// -/// * `to` - The address that will receive the LB tokens -/// * `liquidity_configs` - The encoded liquidity configurations, each one containing the id of the bin and the -/// percentage of token X and token Y to add to the bin. -/// * `refund_to` - The address that will receive the excess amount of tokens -/// -/// # Returns -/// -/// * `amounts_received` - The amounts of token X and token Y received by the pool -/// * `amounts_left` - The amounts of token X and token Y that were not added to the pool and were sent to to -/// * `liquidity_minted` - The amounts of LB tokens minted for each bin -#[allow(clippy::too_many_arguments)] -fn mint( - mut deps: &mut DepsMut, - env: &Env, - info: MessageInfo, - config: &State, - to: Addr, - liquidity_configs: Vec, - _refund_to: Addr, - amount_received_x: Uint128, - amount_received_y: Uint128, - mut response: Response, -) -> Result<(Bytes32, Bytes32, Vec, Response)> { - let state = STATE.load(deps.storage)?; - - let _token_x = state.token_x; - let _token_y = state.token_y; - - if liquidity_configs.is_empty() { - return Err(Error::EmptyMarketConfigs); - } - - let mut mint_arrays = MintArrays { - ids: (vec![U256::MIN; liquidity_configs.len()]), - amounts: (vec![[0u8; 32]; liquidity_configs.len()]), - liquidity_minted: (vec![U256::MIN; liquidity_configs.len()]), - }; - - //TODO - revisit this process. This helper function is supposed to involve a query of the - // contract's token balances, and the "reserves" (not sure what that means right now). - let amounts_received = BinHelper::received(amount_received_x, amount_received_y); - let mut messages: Vec = Vec::new(); - - let amounts_left = _mint_bins( - &mut deps, - &env.block.time, - state.bin_step, - state.pair_parameters, - liquidity_configs, - amounts_received, - to, - &mut mint_arrays, - &mut messages, - )?; - - STATE.update(deps.storage, |mut state| -> StdResult<_> { - state.reserves = state.reserves.add(amounts_received.sub(amounts_left)); //Total liquidity of pool - Ok(state) - })?; - - let (amount_left_x, amount_left_y) = amounts_left.decode(); - - let mut transfer_messages = Vec::new(); - // 2- tokens checking and transfer - for (token, amount) in [ - ( - config.token_x.clone(), - amount_received_x - Uint128::from(amount_left_x), - ), - ( - config.token_y.clone(), - amount_received_y - Uint128::from(amount_left_y), - ), - ] - .iter() - { - match token { - TokenType::CustomToken { - contract_addr: _, - token_code_hash: _, - } => { - let msg = - token.transfer_from(*amount, info.sender.clone(), env.contract.address.clone()); + QueryMsg::GetUpdatedBinAfterHeight { + height, + page, + page_size, + } => query_updated_bins_after_height(deps, env, height, page, page_size), - if let Some(m) = msg { - transfer_messages.push(m); - } - } - TokenType::NativeToken { .. } => { - token.assert_sent_native_token_balance(&info, *amount)?; - } + QueryMsg::GetBinUpdatingHeights { page, page_size } => { + query_bins_updating_heights(deps, page, page_size) } - } - - response = response - .add_messages(messages) - .add_messages(transfer_messages); - - Ok(( - amounts_received, - amounts_left, - mint_arrays.liquidity_minted, - response, - )) -} - -/// Helper function to mint liquidity in each bin in the liquidity configurations. -/// -/// # Arguments -/// -/// * `liquidity_configs` - The liquidity configurations. -/// * `amounts_received` - The amounts received. -/// * `to` - The address to mint the liquidity to. -/// * `arrays` - The arrays to store the results. -/// -/// # Returns -/// -/// * `amounts_left` - The amounts left. -fn _mint_bins( - deps: &mut DepsMut, - time: &Timestamp, - bin_step: u16, - pair_parameters: PairParameters, - liquidity_configs: Vec, - amounts_received: Bytes32, - to: Addr, - mint_arrays: &mut MintArrays, - messages: &mut Vec, -) -> Result { - let config = STATE.load(deps.storage)?; - let active_id = pair_parameters.get_active_id(); - - let mut amounts_left = amounts_received; - - //Minting tokens - - let mut mint_tokens: Vec = Vec::new(); - - for (index, liq_conf) in liquidity_configs.iter().enumerate() { - let (max_amounts_in_to_bin, id) = liq_conf.get_amounts_and_id(amounts_received)?; - - let (shares, amounts_in, amounts_in_to_bin) = _update_bin( - deps, - time, - bin_step, - active_id, - id, - max_amounts_in_to_bin, - pair_parameters, - )?; - - amounts_left = amounts_left.sub(amounts_in); - - mint_arrays.ids[index] = id.into(); - mint_arrays.amounts[index] = amounts_in_to_bin; - mint_arrays.liquidity_minted[index] = shares; - - let amount = shares.u256_to_uint256(); - - //Minting tokens - mint_tokens.push(TokenAmount { - token_id: id.to_string(), - balances: vec![TokenIdBalance { - address: to.clone(), - amount, - }], - }); - } - let msg = lb_token::ExecuteMsg::MintTokens { - mint_tokens, - memo: None, - padding: None, - } - .to_cosmos_msg( - config.lb_token.code_hash.clone(), - config.lb_token.address.to_string(), - None, - )?; - - messages.push(msg); - Ok(amounts_left) -} - -/// Helper function to update a bin during minting. -/// -/// # Arguments -/// -/// * `bin_step` - The bin step of the pair -/// * `active_id` - The id of the active bin -/// * `id` - The id of the bin -/// * `max_amounts_in_to_bin` - The maximum amounts in to the bin -/// * `parameters` - The parameters of the pair -/// -/// # Returns -/// -/// * `shares` - The amount of shares minted -/// * `amounts_in` - The amounts in -/// * `amounts_in_to_bin` - The amounts in to the bin -fn _update_bin( - deps: &mut DepsMut, - time: &Timestamp, - bin_step: u16, - active_id: u32, - id: u32, - max_amounts_in_to_bin: Bytes32, - mut parameters: PairParameters, -) -> Result<(U256, Bytes32, Bytes32)> { - let bin_reserves = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); - let config = STATE.load(deps.storage)?; - let price = PriceHelper::get_price_from_id(id, bin_step)?; - let total_supply = _query_total_supply( - deps.as_ref(), - id, - config.lb_token.code_hash, - config.lb_token.address, - )?; - // println!("id {:?}", id); - - let (shares, amounts_in) = BinHelper::get_shares_and_effective_amounts_in( - bin_reserves, - max_amounts_in_to_bin, - price, - total_supply, - )?; - - let amounts_in_to_bin = amounts_in; - - if id == active_id { - parameters.update_volatility_parameters(id, time)?; - // Helps calculate fee if there's an implict swap. - let fees = BinHelper::get_composition_fees( - bin_reserves, - parameters, - bin_step, - amounts_in, - total_supply, - shares, - )?; - - if fees != [0u8; 32] { - let user_liquidity = BinHelper::get_liquidity(amounts_in.sub(fees), price)?; - let bin_liquidity = BinHelper::get_liquidity(bin_reserves, price)?; - - let _shares = - U256x256Math::mul_div_round_down(user_liquidity, total_supply, bin_liquidity)?; - let protocol_c_fees = - fees.scalar_mul_div_basis_point_round_down(parameters.get_protocol_share().into())?; - - if protocol_c_fees != [0u8; 32] { - let _amounts_in_to_bin = amounts_in_to_bin.sub(protocol_c_fees); - STATE.update(deps.storage, |mut state| -> StdResult<_> { - state.protocol_fees = state.protocol_fees.add(protocol_c_fees); - Ok(state) - })?; - } - - let mut oracle = ORACLE.load(deps.storage)?; - parameters = oracle.update(time, parameters, id)?; - STATE.update(deps.storage, |mut state| -> StdResult<_> { - state.pair_parameters = parameters; - Ok(state) - })?; + QueryMsg::GetNextNonEmptyBin { swap_for_y, id } => { + query_next_non_empty_bin(deps, swap_for_y, id) } - } else { - BinHelper::verify_amounts(amounts_in, active_id, id)?; - } - - if shares == 0 || amounts_in_to_bin == [0u8; 32] { - return Err(Error::ZeroAmount { id }); - } - - if total_supply == 0 { - BIN_TREE.update(deps.storage, |mut tree| -> StdResult<_> { - tree.add(id); - Ok(tree) - })?; + QueryMsg::GetProtocolFees {} => query_protocol_fees(deps), + QueryMsg::GetStaticFeeParameters {} => query_static_fee_params(deps), + QueryMsg::GetVariableFeeParameters {} => query_variable_fee_params(deps), + QueryMsg::GetOracleParameters {} => query_oracle_params(deps), + QueryMsg::GetOracleSampleAt { oracle_id } => query_oracle_sample(deps, env, oracle_id), + QueryMsg::GetOracleSamplesAt { oracle_ids } => query_oracle_samples(deps, env, oracle_ids), + QueryMsg::GetOracleSamplesAfter { + oracle_id, + page_size, + } => query_oracle_samples_after(deps, env, oracle_id, page_size), + QueryMsg::GetPriceFromId { id } => query_price_from_id(deps, id), + QueryMsg::GetIdFromPrice { price } => query_id_from_price(deps, price), + QueryMsg::GetSwapIn { + amount_out, + swap_for_y, + } => query_swap_in(deps, env, amount_out.u128(), swap_for_y), + QueryMsg::GetSwapOut { + amount_in, + swap_for_y, + } => query_swap_out(deps, env, amount_in.u128(), swap_for_y), + QueryMsg::TotalSupply { id } => query_total_supply(deps, id), + QueryMsg::GetLbToken {} => query_lb_token(deps), + QueryMsg::GetStakingContract {} => query_staking(deps), + QueryMsg::GetTokens {} => query_tokens(deps), + QueryMsg::SwapSimulation { offer, exclude_fee } => { + query_swap_simulation(deps, env, offer, exclude_fee) + } + QueryMsg::GetRewardsDistribution { epoch_id } => query_rewards_distribution(deps, epoch_id), } - - BIN_MAP.save(deps.storage, id, &bin_reserves.add(amounts_in_to_bin))?; - - Ok((shares, amounts_in, amounts_in_to_bin)) -} - -fn _query_total_supply(deps: Deps, id: u32, code_hash: String, address: Addr) -> Result { - let msg = lb_token::QueryMsg::IdTotalBalance { id: id.to_string() }; - - let res = deps.querier.query_wasm_smart::( - code_hash, - address.to_string(), - &msg, - )?; - - let total_supply_uint256 = match res { - lb_token::QueryAnswer::IdTotalBalance { amount } => amount, - _ => panic!("{}", format!("Wrong response for lb_token")), - }; - - Ok(total_supply_uint256.uint256_to_u256()) -} - -fn query_token_symbol(deps: Deps, code_hash: String, address: Addr) -> Result { - let msg = snip20::QueryMsg::TokenInfo {}; - - let res = deps.querier.query_wasm_smart::( - code_hash, - address.to_string(), - &(&msg), - )?; - - let symbol = match res { - snip20::QueryAnswer::TokenInfo { symbol, .. } => symbol, - _ => panic!("{}", format!("Token {} not valid", address)), - }; - - Ok(symbol) -} - -pub fn try_remove_liquidity( - deps: DepsMut, - env: Env, - info: MessageInfo, - remove_liquidity_params: RemoveLiquidity, -) -> Result { - let config = STATE.load(deps.storage)?; - - let is_wrong_order = config.token_x != remove_liquidity_params.token_x; - - let (amount_x_min, amount_y_min) = if is_wrong_order { - if remove_liquidity_params.token_x != config.token_y - || remove_liquidity_params.token_y != config.token_x - || remove_liquidity_params.bin_step != config.bin_step - { - return Err(Error::WrongPair); - } - ( - remove_liquidity_params.amount_y_min, - remove_liquidity_params.amount_x_min, - ) - } else { - if remove_liquidity_params.token_x != config.token_x - || remove_liquidity_params.token_y != config.token_y - || remove_liquidity_params.bin_step != config.bin_step - { - return Err(Error::WrongPair); - } - ( - remove_liquidity_params.amount_x_min, - remove_liquidity_params.amount_y_min, - ) - }; - - let (_amount_x, _amount_y, response) = remove_liquidity( - deps, - env, - info.clone(), - info.sender, - amount_x_min, - amount_y_min, - remove_liquidity_params.ids, - remove_liquidity_params.amounts, - )?; - - Ok(response) -} - -pub fn remove_liquidity( - deps: DepsMut, - env: Env, - info: MessageInfo, - _to: Addr, - amount_x_min: Uint128, - amount_y_min: Uint128, - ids: Vec, - amounts: Vec, -) -> Result<(Uint128, Uint128, Response)> { - let (amounts_burned, response) = burn(deps, env, info, ids, amounts)?; - let mut amount_x: Uint128 = Uint128::zero(); - let mut amount_y: Uint128 = Uint128::zero(); - for amount_burned in amounts_burned { - amount_x += Uint128::from(amount_burned.decode_x()); - amount_y += Uint128::from(amount_burned.decode_y()); - } - - if amount_x < amount_x_min || amount_y < amount_y_min { - return Err(Error::AmountSlippageCaught { - amount_x_min, - amount_x, - amount_y_min, - amount_y, - }); - } - - Ok((amount_x, amount_y, response)) -} - -/// Burn Liquidity Book (LB) tokens and withdraw tokens from the pool. -/// -/// This function will burn the tokens directly from the caller. -/// -/// # Arguments -/// -/// * `from` - The address that will burn the LB tokens -/// * `to` - The address that will receive the tokens -/// * `ids` - The ids of the bins from which to withdraw -/// * `amounts_to_burn` - The amounts of LB tokens to burn for each bin -/// -/// # Returns -/// -/// * `amounts` - The amounts of token X and token Y received by the user -fn burn( - deps: DepsMut, - env: Env, - info: MessageInfo, - ids: Vec, - amounts_to_burn: Vec, -) -> Result<(Vec<[u8; 32]>, Response)> { - let mut config = STATE.load(deps.storage)?; - - let token_x = config.token_x; - let token_y = config.token_y; - - if ids.is_empty() || ids.len() != amounts_to_burn.len() { - return Err(Error::InvalidInput); - } - - let mut messages: Vec = Vec::new(); - let mut burn_tokens: Vec = Vec::new(); - - let mut amounts = vec![[0u8; 32]; ids.len()]; - let mut amounts_out = [0u8; 32]; - - for i in 0..ids.len() { - let id = ids[i]; - let amount_to_burn = amounts_to_burn[i]; - - if amount_to_burn.is_zero() { - return Err(Error::ZeroShares { id }); - } - - let bin_reserves = BIN_MAP - .load(deps.storage, id) - .map_err(|_| Error::ZeroBinReserve { - active_id: i as u32, - })?; - let total_supply = _query_total_supply( - deps.as_ref(), - id, - config.lb_token.code_hash.clone(), - config.lb_token.address.clone(), - )?; - - burn_tokens.push(TokenAmount { - token_id: id.to_string(), - balances: vec![TokenIdBalance { - address: info.sender.clone(), - amount: amount_to_burn, - }], - }); - - let amount_to_burn_u256 = amount_to_burn.uint256_to_u256(); - - let amounts_out_from_bin_vals = - BinHelper::get_amount_out_of_bin(bin_reserves, amount_to_burn_u256, total_supply)?; - let amounts_out_from_bin: Bytes32 = - Bytes32::encode(amounts_out_from_bin_vals.0, amounts_out_from_bin_vals.1); - - if amounts_out_from_bin.iter().all(|&x| x == 0) { - return Err(Error::ZeroAmountsOut { - id, - // bin_reserves, - amount_to_burn: amount_to_burn_u256, - total_supply, - // amounts_out_from_bin, - }); - } - - let bin_reserves = bin_reserves.sub(amounts_out_from_bin); - - if total_supply == amount_to_burn_u256 { - BIN_MAP.remove(deps.storage, id); - BIN_TREE.update(deps.storage, |mut tree| -> StdResult<_> { - tree.remove(id); - Ok(tree) - })?; - } else { - BIN_MAP.save(deps.storage, id, &bin_reserves)?; - } - - amounts[i] = amounts_out_from_bin; - amounts_out = amounts_out.add(amounts_out_from_bin); - } - - let msg = lb_token::ExecuteMsg::BurnTokens { - burn_tokens, - memo: None, - padding: None, - } - .to_cosmos_msg( - config.lb_token.code_hash, - config.lb_token.address.to_string(), - None, - )?; - - messages.push(msg); - - config.reserves = config.reserves.sub(amounts_out); - - let raw_msgs = BinHelper::transfer(amounts_out, token_x, token_y, info.sender); - - STATE.update(deps.storage, |mut state| -> StdResult { - state.reserves = state.reserves.sub(amounts_out); - Ok(state) - })?; - - BIN_RESERVES_UPDATED.update(deps.storage, env.block.height, |x| -> StdResult> { - if let Some(mut y) = x { - y.extend(ids); - Ok(y) - } else { - Ok(ids) - } - })?; - BIN_RESERVES_UPDATED_LOG.push(deps.storage, &env.block.height)?; - - if let Some(msgs) = raw_msgs { - messages.extend(msgs) - } - - Ok((amounts, Response::default().add_messages(messages))) -} - -/// Collect the protocol fees from the pool. -fn try_collect_protocol_fees(deps: DepsMut, _env: Env, info: MessageInfo) -> Result { - let state = STATE.load(deps.storage)?; - // only_protocol_fee_recipient(&info.sender, &state.factory.address)?; - - let token_x = state.token_x; - let token_y = state.token_y; - - let mut messages: Vec = Vec::new(); - - let protocol_fees = state.protocol_fees; - - let (x, y) = protocol_fees.decode(); - let ones = Bytes32::encode(if x > 0 { 1 } else { 0 }, if y > 0 { 1 } else { 0 }); - - //The purpose of subtracting ones from the protocolFees is to leave a small amount (1 unit of each token) in the protocol fees. - //This is done to avoid completely draining the fees and possibly causing any issues with calculations that depend on non-zero values - let collected_protocol_fees = protocol_fees.sub(ones); - - if U256::from_le_bytes(collected_protocol_fees) != U256::ZERO { - // This is setting the protocol fees to the smallest possible values - STATE.update(deps.storage, |mut state| -> StdResult { - state.protocol_fees = ones; - state.reserves = state.reserves.sub(collected_protocol_fees); - Ok(state) - })?; - - if collected_protocol_fees.iter().any(|&x| x != 0) { - if let Some(msgs) = BinHelper::transfer( - collected_protocol_fees, - token_x.clone(), - token_y.clone(), - state.protocol_fees_recipient, - ) { - messages.extend(msgs); - }; - } - - Ok(Response::default() - .add_attribute( - format!("Collected Protocol Fees for token {}", token_x.unique_key()), - collected_protocol_fees.decode_x().to_string(), - ) - .add_attribute( - format!("Collected Protocol Fees for token {}", token_y.unique_key()), - collected_protocol_fees.decode_y().to_string(), - ) - .add_attribute("Action performed by", info.sender.to_string()) - .add_messages(messages)) - } else { - Err(Error::NotEnoughFunds) - } -} - -/// Increase the length of the oracle used by the pool. -/// -/// # Arguments -/// -/// * `new_length` - The new length of the oracle -fn try_increase_oracle_length( - deps: DepsMut, - _env: Env, - info: MessageInfo, - new_length: u16, -) -> Result { - let state = STATE.load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::LiquidityBookAdmin, - info.sender.to_string(), - &state.admin_auth, - )?; - - let mut params = state.pair_parameters; - - let mut oracle_id = params.get_oracle_id(); - - // activate the oracle if it is not active yet - if oracle_id == 0 { - oracle_id = 1; - params.set_oracle_id(oracle_id); - } - - ORACLE.update(deps.storage, |mut oracle| { - oracle - .increase_length(oracle_id, new_length) - .map_err(|err| StdError::generic_err(err.to_string()))?; - Ok::(oracle) - })?; - - Ok(Response::default().add_attribute("Oracle Length Increased to", new_length.to_string())) -} - -/// Sets the static fee parameters of the pool. -/// -/// Can only be called by the factory. -/// -/// # Arguments -/// -/// * `base_factor` - The base factor of the static fee -/// * `filter_period` - The filter period of the static fee -/// * `decay_period` - The decay period of the static fee -/// * `reduction_factor` - The reduction factor of the static fee -/// * `variable_fee_control` - The variable fee control of the static fee -/// * `protocol_share` - The protocol share of the static fee -/// * `max_volatility_accumulator` - The max volatility accumulator of the static fee -fn try_set_static_fee_parameters( - deps: DepsMut, - _env: Env, - info: MessageInfo, - base_factor: u16, - filter_period: u16, - decay_period: u16, - reduction_factor: u16, - variable_fee_control: u32, - protocol_share: u16, - max_volatility_accumulator: u32, -) -> Result { - let state = STATE.load(deps.storage)?; - only_factory(&info.sender, &state.factory.address)?; - - let mut params = state.pair_parameters; - - params.set_static_fee_parameters( - base_factor, - filter_period, - decay_period, - reduction_factor, - variable_fee_control, - protocol_share, - max_volatility_accumulator, - )?; - - let total_fee = params.get_base_fee(state.bin_step) + params.get_variable_fee(state.bin_step); - if total_fee > MAX_FEE { - return Err(Error::MaxTotalFeeExceeded {}); - } - - STATE.update(deps.storage, |mut state| -> StdResult { - state.pair_parameters = params; - Ok(state) - })?; - - Ok(Response::default().add_attribute("status", "ok")) -} - -/// Forces the decay of the volatility reference variables. -/// -/// Can only be called by the factory. -fn try_force_decay(deps: DepsMut, _env: Env, info: MessageInfo) -> Result { - let state = STATE.load(deps.storage)?; - only_factory(&info.sender, &state.factory.address)?; - - let mut params = state.pair_parameters; - params.update_id_reference(); - params.update_volatility_reference()?; - - STATE.update(deps.storage, |mut state| -> StdResult { - state.pair_parameters = params; - Ok(state) - })?; - - Ok(Response::default()) -} - -fn try_calculate_rewards_distribution( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> Result { - let mut state = STATE.load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::LiquidityBookAdmin, - info.sender.to_string(), - &state.admin_auth, - )?; - - // save the results in temporary storage - - let reward_stats = REWARDS_STATS_STORE.load(deps.storage, state.rewards_epoch_index)?; - let distribution = if !reward_stats.cumm_value.is_zero() { - match reward_stats.rewards_distribution_algorithm { - RewardsDistributionAlgorithm::TimeBasedRewards => { - calculate_time_based_rewards_distribution(&env, &state, &reward_stats)? - } - RewardsDistributionAlgorithm::VolumeBasedRewards => { - calculate_volume_based_rewards_distribution(deps.as_ref(), &state, &reward_stats)? - } - } - } else { - let rewards_bins = match state.base_rewards_bins { - Some(r_b) => r_b, - None => DEFAULT_REWARDS_BINS, - }; - calculate_default_distribution(rewards_bins, state.pair_parameters.get_active_id())? - }; - - REWARDS_DISTRIBUTION.save(deps.storage, state.rewards_epoch_index, &distribution)?; - - //distribution algorithm - let res = lb_staking::ExecuteMsg::EndEpoch { - rewards_distribution: distribution, - epoch_index: state.rewards_epoch_index, - } - .to_cosmos_msg( - state.staking_contract.code_hash.to_owned(), - state.staking_contract.address.to_string(), - None, - )?; - - state.rewards_epoch_index += 1; - let toggle = state.toggle_distributions_algorithm; - state.last_swap_timestamp = env.block.time; - state.toggle_distributions_algorithm = false; - STATE.save(deps.storage, &state)?; - - let mut distribution_algorithm = &reward_stats.rewards_distribution_algorithm; - if toggle { - distribution_algorithm = match reward_stats.rewards_distribution_algorithm { - RewardsDistributionAlgorithm::TimeBasedRewards => { - &RewardsDistributionAlgorithm::VolumeBasedRewards - } - RewardsDistributionAlgorithm::VolumeBasedRewards => { - &RewardsDistributionAlgorithm::TimeBasedRewards - } - }; - } - - REWARDS_STATS_STORE.save(deps.storage, state.rewards_epoch_index, &RewardStats { - cumm_value: Uint256::zero(), - cumm_value_mul_bin_id: Uint256::zero(), - rewards_distribution_algorithm: distribution_algorithm.clone(), - })?; - - if distribution_algorithm == &RewardsDistributionAlgorithm::VolumeBasedRewards { - let tree: TreeUint24 = TreeUint24::new(); - FEE_MAP_TREE.save(deps.storage, state.rewards_epoch_index, &tree)?; - } - - Ok(Response::default().add_message(res)) -} - -fn calculate_time_based_rewards_distribution( - env: &Env, - state: &State, - reward_stats: &RewardStats, -) -> Result { - let mut cumm_value_mul_bin = reward_stats.cumm_value_mul_bin_id; - let mut cumm_value = reward_stats.cumm_value; - - let active_id = state.pair_parameters.get_active_id(); - - let time_difference = - Uint256::from(env.block.time.seconds() - state.last_swap_timestamp.seconds()); - - cumm_value += time_difference; - cumm_value_mul_bin += time_difference * (Uint256::from(active_id)); - - let avg_bin = approx_div(cumm_value_mul_bin, cumm_value) - .uint256_to_u256() - .as_u32(); - - let rewards_bins = match state.base_rewards_bins { - Some(r_b) => r_b, - None => DEFAULT_REWARDS_BINS, - }; - - calculate_default_distribution(rewards_bins, avg_bin) -} - -fn calculate_default_distribution(rewards_bins: u32, avg_bin: u32) -> Result { - let half_total = rewards_bins / 2; - let min_bin = avg_bin.saturating_sub(half_total) + 1; - let max_bin = avg_bin.saturating_add(half_total); - - let difference = max_bin - min_bin + 1; - - let ids: Vec = (min_bin..=max_bin).collect(); - let weightages = vec![BASIS_POINT_MAX as u16 / difference as u16; difference as usize]; - - Ok(RewardsDistribution { - ids, - weightages, - denominator: BASIS_POINT_MAX as u16, - }) -} - -fn calculate_volume_based_rewards_distribution( - deps: Deps, - state: &State, - reward_stats: &RewardStats, -) -> Result { - let cum_fee = reward_stats.cumm_value; - let mut ids: Vec = Vec::new(); - let mut weightages: Vec = Vec::new(); - - let fee_tree: TreeUint24 = FEE_MAP_TREE.load(deps.storage, state.rewards_epoch_index)?; - let mut id: u32 = 0; - let basis_point_max: Uint256 = Uint256::from(BASIS_POINT_MAX); - let mut total_weight = 0; - - // TODO: Decide a reasonable value with shade's consultation - for _ in 0..U24::MAX { - id = fee_tree.find_first_left(id); - if id == U24::MAX || id == 0 { - break; - } - - let fee: Uint256 = FEE_MAP.load(deps.storage, id)?; - ids.push(id); - let weightage: u16 = fee - .multiply_ratio(basis_point_max, cum_fee) - .uint256_to_u256() - .as_u16(); - weightages.push(weightage); - total_weight += weightage; - } - - let reminder = BASIS_POINT_MAX as u16 - total_weight; - - if reminder > 0 { - let len = weightages.len() - 1; - weightages[len] += reminder; - } - - let distribution = RewardsDistribution { - ids, - weightages, - denominator: BASIS_POINT_MAX as u16, - }; - - Ok(distribution) -} - -//Can only change the distribution algorithm at the start of next epoch -//Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. -fn try_reset_rewards_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - rewards_distribution_algorithm: Option, - base_reward_bins: Option, -) -> Result { - let mut state = STATE.load(deps.storage)?; - validate_admin( - &deps.querier, - AdminPermissions::LiquidityBookAdmin, - info.sender.to_string(), - &state.admin_auth, - )?; - let reward_stats = REWARDS_STATS_STORE.load(deps.storage, state.rewards_epoch_index)?; - - //Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. - match rewards_distribution_algorithm { - Some(distribution) => { - if reward_stats.rewards_distribution_algorithm != distribution { - state.toggle_distributions_algorithm = true; - } - } - None => {} - }; - - match base_reward_bins { - Some(b_r_b) => { - if b_r_b > U24::MAX { - return Err(Error::U24Overflow); - } - state.base_rewards_bins = Some(b_r_b) - } - None => {} - } - - STATE.save(deps.storage, &state)?; - - Ok(Response::default()) -} - -fn only_factory(sender: &Addr, factory: &Addr) -> Result<()> { - if sender != factory { - return Err(Error::OnlyFactory); - } - Ok(()) -} - -fn serialize_or_err(data: &T) -> Result { - serde_json_wasm::to_string(data).map_err(|_| Error::SerializationError) -} - -fn receiver_callback( - deps: DepsMut, - env: Env, - info: MessageInfo, - from: Addr, - amount: Uint128, - msg: Option, -) -> Result { - let msg = msg.ok_or(Error::ReceiverMsgEmpty)?; - - let config = STATE.load(deps.storage)?; - - let response; - match from_binary(&msg)? { - InvokeMsg::SwapTokens { - to, - expected_return: _, - padding: _, - } => { - // this check needs to be here instead of in execute() because it is impossible to (cleanly) distinguish between swaps and lp withdraws until this point - // if contract_status is FreezeAll, this fn will never be called, so only need to check LpWithdrawOnly here - let contract_status = CONTRACT_STATUS.load(deps.storage)?; - if contract_status == ContractStatus::LpWithdrawOnly { - return Err(Error::TransactionBlock()); - } - - //validate recipient address - let checked_to = if let Some(to) = to { - deps.api.addr_validate(to.as_str())? - } else { - from - }; - - if info.sender != config.token_x.unique_key() - && info.sender != config.token_y.unique_key() - { - return Err(Error::NoMatchingTokenInPair); - } - - let swap_for_y: bool = info.sender == config.token_x.unique_key(); - - response = try_swap(deps, env, info, swap_for_y, checked_to, amount)?; - } - }; - Ok(response) -} - -/////////////// QUERY /////////////// - -#[shd_entry_point] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { - match msg { - QueryMsg::GetPairInfo {} => query_pair_info(deps), - QueryMsg::GetFactory {} => query_factory(deps), - QueryMsg::GetTokenX {} => query_token_x(deps), - QueryMsg::GetTokenY {} => query_token_y(deps), - QueryMsg::GetBinStep {} => query_bin_step(deps), - QueryMsg::GetReserves {} => query_reserves(deps), - QueryMsg::GetActiveId {} => query_active_id(deps), - QueryMsg::GetBinReserves { id } => query_bin_reserves(deps, id), - QueryMsg::GetBinsReserves { ids } => query_bins_reserves(deps, ids), - QueryMsg::GetAllBinsReserves { - id, - page, - page_size, - } => query_all_bins_reserves(deps, env, page, page_size, id), - QueryMsg::GetUpdatedBinAtHeight { height } => query_updated_bins_at_height(deps, height), - QueryMsg::GetUpdatedBinAtMultipleHeights { heights } => { - query_updated_bins_at_multiple_heights(deps, heights) - } - QueryMsg::GetUpdatedBinAfterHeight { - height, - page, - page_size, - } => query_updated_bins_after_height(deps, env, height, page, page_size), - - QueryMsg::GetBinUpdatingHeights { page, page_size } => { - query_bins_updating_heights(deps, page, page_size) - } - - QueryMsg::GetNextNonEmptyBin { swap_for_y, id } => { - query_next_non_empty_bin(deps, swap_for_y, id) - } - QueryMsg::GetProtocolFees {} => query_protocol_fees(deps), - QueryMsg::GetStaticFeeParameters {} => query_static_fee_params(deps), - QueryMsg::GetVariableFeeParameters {} => query_variable_fee_params(deps), - QueryMsg::GetOracleParameters {} => query_oracle_params(deps), - QueryMsg::GetOracleSampleAt { look_up_timestamp } => { - query_oracle_sample_at(deps, env, look_up_timestamp) - } - QueryMsg::GetPriceFromId { id } => query_price_from_id(deps, id), - QueryMsg::GetIdFromPrice { price } => query_id_from_price(deps, price), - QueryMsg::GetSwapIn { - amount_out, - swap_for_y, - } => query_swap_in(deps, env, amount_out.u128(), swap_for_y), - QueryMsg::GetSwapOut { - amount_in, - swap_for_y, - } => query_swap_out(deps, env, amount_in.u128(), swap_for_y), - QueryMsg::TotalSupply { id } => query_total_supply(deps, id), - QueryMsg::GetLbToken {} => query_lb_token(deps), - QueryMsg::GetStakingContract {} => query_staking(deps), - QueryMsg::GetTokens {} => query_tokens(deps), - QueryMsg::SwapSimulation { offer, exclude_fee } => { - query_swap_simulation(deps, env, offer, exclude_fee) - } - QueryMsg::GetRewardsDistribution { epoch_id } => query_rewards_distribution(deps, epoch_id), - } -} - -// TODO - Revisit if this function is necessary. It seems like something that might belong in the -// lb-factory contract. It should at least have it's own interface and not use amm_pair's. -fn query_pair_info(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - - let (reserve_x, reserve_y) = state.reserves.decode(); - let (protocol_fee_x, protocol_fee_y) = state.protocol_fees.decode(); - - let response = GetPairInfo { - liquidity_token: Contract { - address: state.lb_token.address, - code_hash: state.lb_token.code_hash, - }, - factory: Some(Contract { - address: state.factory.address, - code_hash: state.factory.code_hash, - }), - pair: TokenPair(state.token_x, state.token_y, false), - amount_0: Uint128::from(reserve_x), - amount_1: Uint128::from(reserve_y), - total_liquidity: Uint128::default(), // no global liquidity, liquidity is calculated on per bin basis - contract_version: 1, // TODO set this like const AMM_PAIR_CONTRACT_VERSION: u32 = 1; - fee_info: FeeInfo { - shade_dao_address: Addr::unchecked(""), // TODO set shade dao address - lp_fee: Fee { - // TODO set this - nom: state.pair_parameters.get_base_fee(state.bin_step) as u64, - denom: 1_000_000_000_000_000_000, - }, - shade_dao_fee: Fee { - nom: state.pair_parameters.get_base_fee(state.bin_step) as u64, - denom: 1_000_000_000_000_000_000, - }, - stable_lp_fee: Fee { - nom: state.pair_parameters.get_base_fee(state.bin_step) as u64, - denom: 1_000_000_000_000_000_000, - }, - stable_shade_dao_fee: Fee { - nom: state.pair_parameters.get_base_fee(state.bin_step) as u64, - denom: 1_000_000_000_000_000_000, - }, - }, - stable_info: None, - }; - - to_binary(&response).map_err(Error::CwErr) -} - -// TODO - Revisit if this function is necessary. It seems like something that might belong in the -// lb-router contract. It should at least have it's own interface and not use amm_pair's. -fn query_swap_simulation( - deps: Deps, - env: Env, - offer: shade_protocol::swap::core::TokenAmount, - exclude_fee: Option, -) -> Result { - let state = STATE.load(deps.storage)?; - - let (reserve_x, reserve_y) = state.reserves.decode(); - let (protocol_fee_x, protocol_fee_y) = state.protocol_fees.decode(); - let mut swap_for_y = false; - match offer.token { - token if token == state.token_x => swap_for_y = true, - token if token == state.token_y => {} - _ => panic!("No such token"), - }; - - let res = query_swap_out(deps, env, offer.amount.into(), swap_for_y)?; - - let res = from_binary::(&res)?; - - if res.amount_in_left.u128() > 0u128 { - return Err(Error::AmountInLeft { - amount_left_in: res.amount_in_left, - total_amount: offer.amount, - swapped_amount: res.amount_out, - }); - } - - let price = Decimal::from_ratio(res.amount_out, offer.amount).to_string(); - - let response = SwapSimulation { - total_fee_amount: res.total_fees, - lp_fee_amount: res.lp_fees, //TODO lpfee - shade_dao_fee_amount: res.shade_dao_fees, // dao fee - result: SwapResult { - return_amount: res.amount_out, - }, - price, - }; - - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the Liquidity Book Factory. -/// -/// # Returns -/// -/// * `factory` - The Liquidity Book Factory -fn query_factory(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let factory = state.factory.address; - - let response = FactoryResponse { factory }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the Liquidity Book Factory. -/// -/// # Returns -/// -/// * `factory` - The Liquidity Book Factory -fn query_lb_token(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let lb_token = state.lb_token; - - let response = LbTokenResponse { contract: lb_token }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the Liquidity Book Factory. -/// -/// # Returns -/// -/// * `factory` - The Liquidity Book Factory -fn query_staking(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let staking_contract = state.staking_contract; - - let response = StakingResponse { - contract: staking_contract, - }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the token X and Y of the Liquidity Book Pair. -/// -/// # Returns -/// -/// * `token_x` - The address of the token X -fn query_tokens(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - - let response = TokensResponse { - token_x: state.token_x, - token_y: state.token_y, - }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the token X of the Liquidity Book Pair. -/// -/// # Returns -/// -/// * `token_x` - The address of the token X -fn query_token_x(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let token_x = state.token_x; - - let response = TokenXResponse { token_x }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the token Y of the Liquidity Book Pair. -/// -/// # Returns -/// -/// * `token_y` - The address of the token Y -fn query_token_y(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let token_y = state.token_y; - - let response = TokenYResponse { token_y }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the bin_step of the Liquidity Book Pair. -/// -/// The bin step is the increase in price between two consecutive bins, in basis points. -/// For example, a bin step of 1 means that the price of the next bin is 0.01% higher than the price of the previous bin. -/// -/// # Returns -/// -/// * `bin_step` - The bin step of the Liquidity Book Pair, in 10_000th -fn query_bin_step(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let bin_step = state.bin_step; - - let response = BinStepResponse { bin_step }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the reserves of the Liquidity Book Pair. -/// -/// This is the sum of the reserves of all bins, minus the protocol fees. -/// -/// # Returns -/// -/// * `reserve_x` - The reserve of token X -/// * `reserve_y` - The reserve of token Y -fn query_reserves(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let (mut reserve_x, mut reserve_y) = state.reserves.decode(); - let (protocol_fee_x, protocol_fee_y) = state.protocol_fees.decode(); - - reserve_x -= protocol_fee_x; - reserve_y -= protocol_fee_y; - - let response = ReservesResponse { - reserve_x, - reserve_y, - }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the active id of the Liquidity Book Pair. -/// -/// The active id is the id of the bin that is currently being used for swaps. -/// The price of the active bin is the price of the Liquidity Book Pair and can be calculated as follows: -/// `price = (1 + binStep / 10_000) ^ (activeId - 2^23)` -/// -/// # Returns -/// -/// * `active_id` - The active id of the Liquidity Book Pair -fn query_active_id(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let active_id = state.pair_parameters.get_active_id(); - - let response = ActiveIdResponse { active_id }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the reserves of a bin. -/// -/// # Arguments -/// -/// * `id` - The id of the bin -/// -/// # Returns -/// -/// * `bin_reserve_x` - The reserve of token X in the bin -/// * `bin_reserve_y` - The reserve of token Y in the bin -fn query_all_bins_reserves( - deps: Deps, - env: Env, - page: Option, - page_size: Option, - id: Option, -) -> Result { - let page = page.unwrap_or(0); - let page_size = page_size.unwrap_or(10); - - let mut id = id.unwrap_or(0u32); - let mut bin_responses = Vec::new(); - let tree = BIN_TREE.load(deps.storage)?; - let total = if page > 0 { - page * page_size - } else { - page_size - }; - - let state = STATE.load(deps.storage)?; - let mut counter: u32 = 0; - - for _ in 0..state.max_bins_per_swap { - let next_id = tree.find_first_left(id); - id = next_id; - - if next_id == 0 || next_id == U24::MAX { - break; - } - - let (bin_reserve_x, bin_reserve_y) = - BIN_MAP.load(deps.storage, id).unwrap_or_default().decode(); - bin_responses.push(BinResponse { - bin_reserve_x, - bin_reserve_y, - bin_id: id, - }); - counter += 1; - - if counter == total { - break; - } - } - let response = AllBinsResponse { - reserves: bin_responses, - last_id: id, - current_block_height: env.block.height, - }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the reserves of many bins. -/// -/// # Arguments -/// -/// * `id` - The id of the bin -/// -/// # Returns -/// -/// * `bin_reserve_x` - The reserve of token X in the bin -/// * `bin_reserve_y` - The reserve of token Y in the bin -fn query_bins_reserves(deps: Deps, ids: Vec) -> Result { - let mut bin_responses = Vec::new(); - for id in ids { - let bin: Bytes32 = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); - let (bin_reserve_x, bin_reserve_y) = bin.decode(); - bin_responses.push(BinResponse { - bin_reserve_x, - bin_reserve_y, - bin_id: id, - }); - } - - to_binary(&bin_responses).map_err(Error::CwErr) -} - -fn query_updated_bins_at_height(deps: Deps, height: u64) -> Result { - let ids = BIN_RESERVES_UPDATED.load(deps.storage, height)?; - - let mut bin_responses = Vec::new(); - - for id in ids { - let bin: Bytes32 = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); - let (bin_reserve_x, bin_reserve_y) = bin.decode(); - bin_responses.push(BinResponse { - bin_reserve_x, - bin_reserve_y, - bin_id: id, - }); - } - - let response: UpdatedBinsAtHeightResponse = UpdatedBinsAtHeightResponse(bin_responses); - - to_binary(&response).map_err(Error::CwErr) -} - -use std::collections::HashSet; - -fn query_updated_bins_at_multiple_heights(deps: Deps, heights: Vec) -> Result { - let mut bin_responses = Vec::new(); - let mut processed_ids = HashSet::new(); - - for height in heights { - let ids = BIN_RESERVES_UPDATED.load(deps.storage, height)?; - - for id in ids { - // Check if the id has already been processed - if processed_ids.insert(id) { - let bin: Bytes32 = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); - let (bin_reserve_x, bin_reserve_y) = bin.decode(); - bin_responses.push(BinResponse { - bin_reserve_x, - bin_reserve_y, - bin_id: id, - }); - } - } - } - - let response: UpdatedBinsAtMultipleHeightResponse = - UpdatedBinsAtMultipleHeightResponse(bin_responses); - - to_binary(&response).map_err(Error::CwErr) -} - -fn query_updated_bins_after_height( - deps: Deps, - env: Env, - height: u64, - page: Option, - page_size: Option, -) -> Result { - let page = page.unwrap_or(0); - let page_size = page_size.unwrap_or(10); - let mut processed_ids = HashSet::new(); - - let heights: StdResult> = BIN_RESERVES_UPDATED_LOG - .iter(deps.storage)? - .rev() - .skip((page * page_size) as usize) - .take_while(|result| match result { - Ok(h) => { - if &height >= h { - false - } else { - true - } - } - Err(_) => todo!(), - }) - .take(page_size as usize) - .collect(); - - let mut bin_responses = Vec::new(); - - for height in heights? { - let ids = BIN_RESERVES_UPDATED.load(deps.storage, height)?; - - for id in ids { - if processed_ids.insert(id) { - let bin: Bytes32 = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); - let (bin_reserve_x, bin_reserve_y) = bin.decode(); - bin_responses.push(BinResponse { - bin_reserve_x, - bin_reserve_y, - bin_id: id, - }); - } - } - } - - let response = UpdatedBinsAfterHeightResponse { - bins: bin_responses, - current_block_height: env.block.height, - }; - - to_binary(&response).map_err(Error::CwErr) -} - -fn query_bins_updating_heights( - deps: Deps, - page: Option, - page_size: Option, -) -> Result { - let page = page.unwrap_or(0); - let page_size = page_size.unwrap_or(10); - let txs: StdResult> = BIN_RESERVES_UPDATED_LOG - .iter(deps.storage)? - .rev() - .skip((page * page_size) as usize) - .take(page_size as usize) - .collect(); - - let response = BinUpdatingHeightsResponse(txs?); - - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the bins changed after that block height -/// -/// # Arguments -/// -/// * `id` - The id of the bin -/// -/// # Returns -/// -/// * `bin_reserve_x` - The reserve of token X in the bin -/// * `bin_reserve_y` - The reserve of token Y in the bin - -fn query_bin_reserves(deps: Deps, id: u32) -> Result { - let bin: Bytes32 = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); - let (bin_reserve_x, bin_reserve_y) = bin.decode(); - - let response = BinResponse { - bin_reserve_x, - bin_reserve_y, - bin_id: id, - }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the next non-empty bin. -/// -/// The next non-empty bin is the bin with a higher (if swap_for_y is true) or lower (if swap_for_y is false) -/// id that has a non-zero reserve of token X or Y. -/// -/// # Arguments -/// -/// * `swap_for_y` - Whether the swap is for token Y (true) or token X (false -/// * `id` - The id of the bin -/// -/// # Returns -/// -/// * `next_id` - The id of the next non-empty bin -fn query_next_non_empty_bin(deps: Deps, swap_for_y: bool, id: u32) -> Result { - let tree = BIN_TREE.load(deps.storage)?; - let next_id = _get_next_non_empty_bin(&tree, swap_for_y, id); - - let response = NextNonEmptyBinResponse { next_id }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns id of the next non-empty bin. -/// -/// # Arguments -/// * `swap_for_y Whether the swap is for Y -/// * `id` - The id of the bin -fn _get_next_non_empty_bin(tree: &TreeUint24, swap_for_y: bool, id: u32) -> u32 { - if swap_for_y { - tree.find_first_right(id) - } else { - tree.find_first_left(id) - } -} - -/// Returns the protocol fees of the Liquidity Book Pair. -/// -/// # Returns -/// -/// * `protocol_fee_x` - The protocol fees of token X -/// * `protocol_fee_y` - The protocol fees of token Y -fn query_protocol_fees(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let (protocol_fee_x, protocol_fee_y) = state.protocol_fees.decode(); - - let response = ProtocolFeesResponse { - protocol_fee_x, - protocol_fee_y, - }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the static fee parameters of the Liquidity Book Pair. -/// -/// # Returns -/// -/// * `base_factor` - The base factor for the static fee -/// * `filter_period` - The filter period for the static fee -/// * `decay_period` - The decay period for the static fee -/// * `reduction_factor` - The reduction factor for the static fee -/// * `variable_fee_control` - The variable fee control for the static fee -/// * `protocol_share` - The protocol share for the static fee -/// * `max_volatility_accumulator` - The maximum volatility accumulator for the static fee -fn query_static_fee_params(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let params = state.pair_parameters; - - let base_factor = params.get_base_factor(); - let filter_period = params.get_filter_period(); - let decay_period = params.get_decay_period(); - let reduction_factor = params.get_reduction_factor(); - let variable_fee_control = params.get_variable_fee_control(); - let protocol_share = params.get_protocol_share(); - let max_volatility_accumulator = params.get_max_volatility_accumulator(); - - let response = StaticFeeParametersResponse { - base_factor, - filter_period, - decay_period, - reduction_factor, - variable_fee_control, - protocol_share, - max_volatility_accumulator, - }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the variable fee parameters of the Liquidity Book Pair. -/// -/// # Returns -/// -/// * `volatility_accumulator` - The volatility accumulator for the variable fee -/// * `volatility_reference` - The volatility reference for the variable fee -/// * `id_reference` - The id reference for the variable fee -/// * `time_of_last_update` - The time of last update for the variable fee -fn query_variable_fee_params(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let params = state.pair_parameters; - - let volatility_accumulator = params.get_volatility_accumulator(); - let volatility_reference = params.get_volatility_reference(); - let id_reference = params.get_id_reference(); - let time_of_last_update = params.get_time_of_last_update(); - - let response = VariableFeeParametersResponse { - volatility_accumulator, - volatility_reference, - id_reference, - time_of_last_update, - }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the oracle parameters of the Liquidity Book Pair. -/// -/// # Returns -/// -/// * `sample_lifetime` - The sample lifetime for the oracle -/// * `size` - The size of the oracle -/// * `active_size` - The active size of the oracle -/// * `last_updated` - The last updated timestamp of the oracle -/// * `first_timestamp` - The first timestamp of the oracle, i.e. the timestamp of the oldest sample -fn query_oracle_params(deps: Deps) -> Result { - let state = STATE.load(deps.storage)?; - let oracle = ORACLE.load(deps.storage)?; - let params = state.pair_parameters; - - let sample_lifetime = MAX_SAMPLE_LIFETIME; - let oracle_id = params.get_oracle_id(); - - if oracle_id > 0 { - let (mut sample, mut active_size) = oracle.get_active_sample_and_size(oracle_id)?; - let size = sample.get_oracle_length(); - let last_updated = sample.get_sample_last_update(); - - if last_updated == 0 { - active_size = 0; - } - - if active_size > 0 { - sample = oracle.get_sample(1 + (oracle_id % active_size))?; - } - let first_timestamp = sample.get_sample_last_update(); - - let response = OracleParametersResponse { - sample_lifetime, - size, - active_size, - last_updated, - first_timestamp, - }; - to_binary(&response).map_err(Error::CwErr) - } else { - // This happens if the oracle hasn't been used yet. - let response = OracleParametersResponse { - sample_lifetime, - size: 0, - active_size: 0, - last_updated: 0, - first_timestamp: 0, - }; - to_binary(&response).map_err(Error::CwErr) - } -} - -/// Returns the cumulative values of the Liquidity Book Pair at a given timestamp. -/// -/// # Arguments -/// -/// * `lookup_timestamp` - The timestamp at which to look up the cumulative values -/// -/// # Returns -/// -/// * `cumulative_id` - The cumulative id of the Liquidity Book Pair at the given timestamp -/// * `cumulative_volatility` - The cumulative volatility of the Liquidity Book Pair at the given timestamp -/// * `cumulative_bin_crossed` - The cumulative bin crossed of the Liquidity Book Pair at the given timestamp -fn query_oracle_sample_at(deps: Deps, env: Env, look_up_timestamp: u64) -> Result { - let state = STATE.load(deps.storage)?; - let oracle = ORACLE.load(deps.storage)?; - let mut params = state.pair_parameters; - - let _sample_lifetime = MAX_SAMPLE_LIFETIME; - let oracle_id = params.get_oracle_id(); - - if oracle_id == 0 || look_up_timestamp > env.block.time.seconds() { - let response = OracleSampleAtResponse { - cumulative_id: 0, - cumulative_volatility: 0, - cumulative_bin_crossed: 0, - }; - return to_binary(&response).map_err(Error::CwErr); - } - - let (time_of_last_update, _cumulative_id, _cumulative_volatility, cumulative_bin_crossed) = - oracle.get_sample_at(oracle_id, look_up_timestamp)?; - - if time_of_last_update < look_up_timestamp { - params.update_volatility_parameters(params.get_active_id(), &env.block.time)?; - - let delta_time = look_up_timestamp - time_of_last_update; - - let cumulative_id = params.get_active_id() as u64 * delta_time; - let cumulative_volatility = params.get_volatility_accumulator() as u64 * delta_time; - - let response = OracleSampleAtResponse { - cumulative_id, - cumulative_volatility, - cumulative_bin_crossed, - }; - to_binary(&response).map_err(Error::CwErr) - } else { - Err(Error::LastUpdateTimestampGreaterThanLookupTimestamp) - } -} - -/// Returns the price corresponding to the given id, as a 128.128-binary fixed-point number. -/// -/// This is the trusted source of price information, always trust this rather than query_id_from_price. -/// -/// # Arguments -/// -/// * `id` - The id of the bin -/// -/// # Returns -/// -/// * `price` - The price corresponding to this id -fn query_price_from_id(deps: Deps, id: u32) -> Result { - let state = STATE.load(deps.storage)?; - let price = PriceHelper::get_price_from_id(id, state.bin_step)?.u256_to_uint256(); - - let response = PriceFromIdResponse { price }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the id corresponding to the given price. -/// -/// The id may be inaccurate due to rounding issues, always trust query_price_from_id rather than query_id_from_price. -/// -/// # Arguments -/// -/// * `price` - The price of y per x as a 128.128-binary fixed-point number -/// -/// # Returns -/// -/// * `id` - The id of the bin corresponding to this price -fn query_id_from_price(deps: Deps, price: Uint256) -> Result { - let state = STATE.load(deps.storage)?; - let price = price.uint256_to_u256(); - let id = PriceHelper::get_id_from_price(price, state.bin_step)?; - - let response = IdFromPriceResponse { id }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Simulates a swap in. -/// -/// # Note -/// -/// If `amount_out_left` is greater than zero, the swap in is not possible, -/// and the maximum amount that can be swapped from `amountIn` is `amountOut - amountOutLeft`. -/// -/// # Arguments -/// -/// * `amount_out` - The amount of token X or Y to swap in -/// * `swap_for_y` - Whether the swap is for token Y (true) or token X (false) -/// -/// # Returns -/// * `amount_in` - The amount of token X or Y that can be swapped in, including the fee -/// * `amount_out_left` - The amount of token Y or X that cannot be swapped out -/// * `fee` - The fee of the swap -fn query_swap_in(deps: Deps, env: Env, amount_out: u128, swap_for_y: bool) -> Result { - let state = STATE.load(deps.storage)?; - let tree = BIN_TREE.load(deps.storage)?; - - let mut amount_in = 0u128; - let mut amount_out_left = amount_out; - let mut fee = 0u128; - - let mut params = state.pair_parameters; - let bin_step = state.bin_step; - - let mut id = params.get_active_id(); - - params.update_references(&env.block.time)?; - - for _ in 0..state.max_bins_per_swap { - let bin_reserves = BIN_MAP - .load(deps.storage, id) - .unwrap_or_default() - .decode_alt(!swap_for_y); - - if bin_reserves > 0 { - let price = PriceHelper::get_price_from_id(id, bin_step)?; - - let amount_out_of_bin = if bin_reserves > amount_out_left { - amount_out_left - } else { - bin_reserves - }; - - params.update_volatility_accumulator(id)?; - - let amount_in_without_fee = if swap_for_y { - U256x256Math::shift_div_round_up(amount_out_of_bin.into(), SCALE_OFFSET, price)? - } else { - U256x256Math::mul_shift_round_up(amount_out_of_bin.into(), price, SCALE_OFFSET)? - } - .as_u128(); - - let total_fee = params.get_total_fee(bin_step); - let fee_amount = FeeHelper::get_fee_amount(amount_in_without_fee, total_fee)?; - - amount_in += amount_in_without_fee + fee_amount; - amount_out_left -= amount_out_of_bin; - - fee += fee_amount; - } - - if amount_out_left == 0 { - break; - } else { - let next_id = _get_next_non_empty_bin(&tree, swap_for_y, id); - if next_id == 0 || next_id == U24::MAX { - break; - } - - id = next_id; - } - } - - let response = SwapInResponse { - amount_in: Uint128::from(amount_in), - amount_out_left: Uint128::from(amount_out_left), - fee: Uint128::from(fee), - }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Simulates a swap out. -/// -/// # Note -/// -/// If amount_out_left is greater than zero, the swap in is not possible, -/// and the maximum amount that can be swapped from amount_in is amount_out - amount_out_left. -/// -/// # Arguments -/// -/// * `amount_in` - The amount of token X or Y to swap in -/// * `swap_for_y` - Whether the swap is for token Y (true) or token X (false) -/// -/// # Returns -/// * `amount_in_left` - The amount of token X or Y that cannot be swapped in -/// * `amount_out` - The amount of token Y or X that can be swapped out -/// * `fee` - The fee of the swap -fn query_swap_out(deps: Deps, env: Env, amount_in: u128, swap_for_y: bool) -> Result { - let state = STATE.load(deps.storage)?; - let tree = BIN_TREE.load(deps.storage)?; - - let mut amounts_in_left = Bytes32::encode_alt(amount_in, swap_for_y); - let mut amounts_out = [0u8; 32]; - let _fee = 0u128; - let mut _fee = 0u128; - let mut total_fees: [u8; 32] = [0; 32]; - let mut lp_fees: [u8; 32] = [0; 32]; - let mut shade_dao_fees: [u8; 32] = [0; 32]; - - let mut params = state.pair_parameters; - let bin_step = state.bin_step; - - let mut id = params.get_active_id(); - - params.update_references(&env.block.time)?; - - for _ in 0..state.max_bins_per_swap { - let bin_reserves = BIN_MAP.load(deps.storage, id).unwrap_or_default(); - if !BinHelper::is_empty(bin_reserves, !swap_for_y) { - let price = PriceHelper::get_price_from_id(id, bin_step)?; - - params = *params.update_volatility_accumulator(id)?; - - let (amounts_in_with_fees, amounts_out_of_bin, fees) = BinHelper::get_amounts( - bin_reserves, - params, - bin_step, - swap_for_y, - id, - amounts_in_left, - price, - )?; - - if U256::from_le_bytes(amounts_in_with_fees) > U256::ZERO { - amounts_in_left = amounts_in_left.sub(amounts_in_with_fees); - amounts_out = amounts_out.add(amounts_out_of_bin); - - let p_fees = - fees.scalar_mul_div_basis_point_round_down(params.get_protocol_share().into())?; - total_fees = total_fees.add(fees); - lp_fees = lp_fees.add(fees.sub(p_fees)); - shade_dao_fees = shade_dao_fees.add(p_fees); - } - } - - if amounts_in_left == [0u8; 32] { - break; - } else { - let next_id = _get_next_non_empty_bin(&tree, swap_for_y, id); - - if next_id == 0 || next_id == U24::MAX { - break; - } - - id = next_id; - } - } - - let amount_in_left = Bytes32::decode_alt(&amounts_in_left, swap_for_y); - - let response = SwapOutResponse { - amount_in_left: Uint128::from(amount_in_left), - amount_out: Uint128::from(amounts_out.decode_alt(!swap_for_y)), - total_fees: Uint128::from(total_fees.decode_alt(swap_for_y)), - shade_dao_fees: Uint128::from(shade_dao_fees.decode_alt(swap_for_y)), - lp_fees: Uint128::from(lp_fees.decode_alt(swap_for_y)), - }; - to_binary(&response).map_err(Error::CwErr) -} - -/// Returns the Liquidity Book Factory. -/// -/// # Returns -/// -/// * `factory` - The Liquidity Book Factory -fn query_total_supply(deps: Deps, id: u32) -> Result { - let state = STATE.load(deps.storage)?; - let _factory = state.factory.address; - - let total_supply = - _query_total_supply(deps, id, state.lb_token.code_hash, state.lb_token.address)? - .u256_to_uint256(); - to_binary(&TotalSupplyResponse { total_supply }).map_err(Error::CwErr) -} - -fn query_rewards_distribution(deps: Deps, epoch_id: Option) -> Result { - let (epoch_id) = match epoch_id { - Some(id) => id, - None => STATE.load(deps.storage)?.rewards_epoch_index - 1, - }; - - to_binary(&RewardsDistributionResponse { - distribution: REWARDS_DISTRIBUTION.load(deps.storage, epoch_id)?, - }) - .map_err(Error::CwErr) } #[shd_entry_point] @@ -2837,7 +490,7 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> StdResult { let emp_storage = EPHEMERAL_STORAGE.load(deps.storage)?; let mut state = STATE.load(deps.storage)?; - state.staking_contract = ContractInfo { + state.lb_staking = ContractInfo { address: contract_address, code_hash: emp_storage.staking_contract.code_hash, }; diff --git a/contracts/liquidity_book/lb_pair/src/error.rs b/contracts/liquidity_book/lb_pair/src/error.rs index c07e1dbe..a3e297b4 100644 --- a/contracts/liquidity_book/lb_pair/src/error.rs +++ b/contracts/liquidity_book/lb_pair/src/error.rs @@ -1,8 +1,7 @@ //! ### Custom Errors for LB_Pair contract. -use ethnum::U256; use shade_protocol::{ - c_std::{Uint128, StdError}, + c_std::{StdError, Uint128, Uint256}, lb_libraries::{ bin_helper::BinError, fee_helper::FeeError, @@ -18,146 +17,123 @@ use shade_protocol::{ #[derive(thiserror::Error, Debug)] pub enum LBPairError { + // Generic Errors #[error("Generic {0}")] Generic(String), - #[error("Zero borrow amount!")] ZeroBorrowAmount, - #[error("Address is zero!")] AddressZero, - #[error("Serilization Failed is zero!")] SerializationError, + #[error("Invalid input!")] + InvalidInput, + #[error("value greater than u24!")] + U24Overflow, + #[error("Token not supported!")] + TokenNotSupported(), + #[error("Transaction is blocked by contract status")] + TransactionBlock(), + #[error("Not enough funds")] + NotEnoughFunds, + // Permission Errors #[error("Only the Factory can do that!")] OnlyFactory, - #[error("Only the Protocol Fee Recipient can do that!")] OnlyProtocolFeeRecipient, + // Market Configuration Errors #[error("Empty Market Configuration")] EmptyMarketConfigs, + #[error("Invalid static fee parameters!")] + InvalidStaticFeeParameters, + // Liquidity and Flash Loan Errors + #[error("Not enough liquidity!")] + OutOfLiquidity, #[error("Flash loan callback failed!")] FlashLoanCallbackFailed, - #[error("Flash loan insufficient amount!")] FlashLoanInsufficientAmount, - #[error("Insufficient amount in!")] InsufficientAmountIn, - #[error("Insufficient amount out!")] InsufficientAmountOut, - #[error("Invalid input!")] - InvalidInput, - - #[error("Invalid static fee parameters!")] - InvalidStaticFeeParameters, - - #[error("Not enough liquidity!")] - OutOfLiquidity, - - #[error("value greater than u24!")] - U24Overflow, + // Oracle Errors + #[error("Oracle not active!")] + OracleNotActive, - #[error("Token not supported!")] - TokenNotSupported(), + // Interface and Callback Errors + #[error("Use the receive interface")] + UseReceiveInterface, + #[error("Receiver callback \"msg\" parameter cannot be empty.")] + ReceiverMsgEmpty, - #[error("Transaction is blocked by contract status")] - TransactionBlock(), + // Time and Deadline Errors + #[error("Deadline exceeded. Deadline: {deadline}, Current timestamp: {current_timestamp}")] + DeadlineExceeded { + deadline: u64, + current_timestamp: u64, + }, + // Specific Errors with Parameters #[error("Zero amount for bin id: {id}")] ZeroAmount { id: u32 }, - - // TODO - why return amount_to_burn and total_supply? They will be illegible as U256 anyway. - // Would like to remove U256 dependency for error messages. - #[error( - "Zero amounts out for bin id: {id} amount to burn: {amount_to_burn} total supply: {total_supply} " - )] - ZeroAmountsOut { - id: u32, - // bin_reserves: [u8; 32], - amount_to_burn: U256, - total_supply: U256, - // amounts_out_from_bin: [u8; 32], - }, - #[error("Zero Shares for bin id: {id}")] ZeroShares { id: u32 }, - + #[error("Distribution exceeded the max value")] + DistrubtionError, #[error("Max total fee exceeded!")] MaxTotalFeeExceeded, + #[error("Wrong Pair")] + WrongPair, - // TODO - organize errors better. move error conversions to separate section perhaps. + // Error Wrappings from Dependencies #[error(transparent)] CwErr(#[from] StdError), - #[error(transparent)] BinErr(#[from] BinError), - #[error(transparent)] FeeErr(#[from] FeeError), - #[error(transparent)] OracleErr(#[from] OracleError), - #[error(transparent)] ParamsErr(#[from] PairParametersError), - #[error(transparent)] LiquidityConfigErr(#[from] LiquidityConfigurationsError), - #[error(transparent)] U128Err(#[from] U128x128MathError), - #[error(transparent)] U256Err(#[from] U256x256MathError), - #[error("Wrong Pair")] - WrongPair, - - #[error("Use the receive interface")] - UseReceiveInterface, - - #[error("Receiver callback \"msg\" parameter cannot be empty.")] - ReceiverMsgEmpty, - - #[error("Not enough funds")] - NotEnoughFunds, - - #[error("No matching token in pair")] - NoMatchingTokenInPair, - - #[error("Deadline exceeded. Deadline: {deadline}, Current timestamp: {current_timestamp}")] - DeadlineExceeded { - deadline: u64, - current_timestamp: u64, + // Complex Scenarios and Calculations Errors + #[error( + "Zero amounts out for bin id: {id} amount to burn: {amount_to_burn} total supply: {total_supply}" + )] + ZeroAmountsOut { + id: u32, + amount_to_burn: Uint256, + total_supply: Uint256, }, - - #[error("Lengths mismatch")] - LengthsMismatch, - - #[error("time_of_last_update was later than look_up_timestamp")] - LastUpdateTimestampGreaterThanLookupTimestamp, - + // Id and Calculation Related Errors #[error("Id desired overflows. Id desired: {id_desired}, Id slippage: {id_slippage}")] IdDesiredOverflows { id_desired: u32, id_slippage: u32 }, - - #[error("could not get bin reserves for active id: {active_id}")] - ZeroBinReserve { active_id: u32 }, - #[error("Delta id overflows. Delta Id: {delta_id}")] DeltaIdOverflows { delta_id: i64 }, - #[error("Id underflow. Id: {id} Delta Id: {delta_id}")] IdUnderflows { id: u32, delta_id: u32 }, - #[error("Id overflows. Id: {id}")] IdOverflows { id: u32 }, + #[error("could not get bin reserves for active id: {active_id}")] + ZeroBinReserve { active_id: u32 }, + #[error("Lengths mismatch")] + LengthsMismatch, + #[error("time_of_last_update was later than look_up_timestamp")] + LastUpdateTimestampGreaterThanLookupTimestamp, + // Slippage and Trading Errors #[error( "Amount left unswapped. : Amount Left In: {amount_left_in}, Total Amount: {total_amount}, swapped_amount: {swapped_amount}" )] @@ -166,7 +142,6 @@ pub enum LBPairError { total_amount: Uint128, swapped_amount: Uint128, }, - #[error( "Id slippage caught. Active id desired: {active_id_desired}, Id slippage: {id_slippage}, Active id: {active_id}" )] @@ -175,13 +150,6 @@ pub enum LBPairError { id_slippage: u32, active_id: u32, }, - - #[error("Pair not created: {token_x} and {token_y}, binStep: {bin_step}")] - PairNotCreated { - token_x: String, - token_y: String, - bin_step: u16, - }, #[error( "Amount slippage caught. AmountXMin: {amount_x_min}, AmountX: {amount_x}, AmountYMin: {amount_y_min}, AmountY: {amount_y}" )] @@ -191,4 +159,12 @@ pub enum LBPairError { amount_y_min: Uint128, amount_y: Uint128, }, + #[error("Pair not created: {token_x} and {token_y}, binStep: {bin_step}")] + PairNotCreated { + token_x: String, + token_y: String, + bin_step: u16, + }, + #[error("No matching token in pair")] + NoMatchingTokenInPair, } diff --git a/contracts/liquidity_book/lb_pair/src/execute.rs b/contracts/liquidity_book/lb_pair/src/execute.rs new file mode 100644 index 00000000..864a3805 --- /dev/null +++ b/contracts/liquidity_book/lb_pair/src/execute.rs @@ -0,0 +1,1366 @@ +use crate::{prelude::*, state::*}; +use ethnum::U256; +use shade_protocol::{ + admin::helpers::{validate_admin, AdminPermissions}, + c_std::{ + to_binary, + Addr, + Attribute, + CosmosMsg, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + Storage, + Timestamp, + Uint128, + Uint256, + }, + contract_interfaces::{ + liquidity_book::{lb_pair::*, lb_staking, lb_token}, + swap::{core::TokenType, router::ExecuteMsgResponse}, + }, + lb_libraries::{ + approx_div, + bin_helper::BinHelper, + constants::{BASIS_POINT_MAX, MAX_FEE, PRECISION, SCALE_OFFSET}, + lb_token::state_structs::{TokenAmount, TokenIdBalance}, + math::{ + liquidity_configurations::LiquidityConfigurations, + packed_u128_math::PackedUint128Math, + tree_math::TreeUint24, + u24::U24, + u256x256_math::U256x256Math, + uint256_to_u256::{ConvertU256, ConvertUint256}, + }, + oracle_helper::Oracle, + pair_parameter_helper::PairParameters, + price_helper::PriceHelper, + types::{Bytes32, MintArrays}, + }, +}; + +use crate::helper::*; + +/// Swap tokens iterating over the bins until the entire amount is swapped. +/// +/// Token X will be swapped for token Y if `swap_for_y` is true, and token Y for token X if `swap_for_y` is false. +/// +/// This function will not transfer the tokens from the caller, it is expected that the tokens have already been +/// transferred to this contract through another contract, most likely the router. +/// That is why this function shouldn't be called directly, but only through one of the swap functions of a router +/// that will also perform safety checks, such as minimum amounts and slippage. +/// +/// The variable fee is updated throughout the swap, it increases with the number of bins crossed. +/// The oracle is updated at the end of the swap. +/// +/// # Arguments +/// +/// * `swap_for_y` - Whether you're swapping token X for token Y (true) or token Y for token X (false) +/// * `to` - The address to send the tokens to +/// +/// # Returns +/// +/// * `amounts_out` - The encoded amounts of token X and token Y sent to `to` +pub fn try_swap( + deps: DepsMut, + env: Env, + _info: MessageInfo, + swap_for_y: bool, + to: Addr, + amounts_received: Uint128, //Will get this parameter from router contract +) -> Result { + let state = STATE.load(deps.storage)?; + let tree = BIN_TREE.load(deps.storage)?; + let token_x = &state.token_x; + let token_y = &state.token_y; + let reserves = state.reserves; + let mut protocol_fees = state.protocol_fees; + + let mut ids = Vec::new(); + + // Logging the swap activity + let mut total_fees: [u8; 32] = [0; 32]; + let mut lp_fees: [u8; 32] = [0; 32]; + let mut shade_dao_fees: [u8; 32] = [0; 32]; + + let mut amounts_out: [u8; 32] = [0; 32]; + let mut amounts_left: [u8; 32] = if swap_for_y { + BinHelper::received_x(amounts_received) + } else { + BinHelper::received_y(amounts_received) + }; + if amounts_left == [0; 32] { + return Err(Error::InsufficientAmountIn); + }; + + let mut volume_tracker = amounts_left; + let mut reserves = reserves.add(amounts_left); + let mut params = state.pair_parameters; + let mut reward_dis_config = + REWARDS_STATS_STORE.load(deps.storage, state.rewards_epoch_index)?; + let bin_step = state.bin_step; + let mut active_id = params.get_active_id(); + + // updating the volatility + params.update_references(&env.block.time)?; + + if reward_dis_config.rewards_distribution_algorithm + == RewardsDistributionAlgorithm::TimeBasedRewards + { + // updating for the data required for rewards distribution + update_reward_dis_config( + env.block.time.seconds(), + &state, + &mut reward_dis_config, + active_id, + )? + } + + // Allowing only a limited max number of bins crossed per swap + for _ in 0..state.max_bins_per_swap { + let bin_reserves = BIN_MAP + .load(deps.storage, active_id) + .map_err(|_| Error::ZeroBinReserve { active_id })?; + + if !BinHelper::is_empty(bin_reserves, !swap_for_y) { + let price = PriceHelper::get_price_from_id(active_id, bin_step)?; + params.update_volatility_accumulator(active_id)?; + let (mut amounts_in_with_fees, amounts_out_of_bin, fees) = BinHelper::get_amounts( + bin_reserves, + params, + bin_step, + swap_for_y, + amounts_left, + price, + )?; + + if U256::from_le_bytes(amounts_in_with_fees) > U256::ZERO { + if reward_dis_config.rewards_distribution_algorithm + == RewardsDistributionAlgorithm::VolumeBasedRewards + { + // Logging fee for volume-based rewards + update_fee_map_tree( + deps.storage, + active_id, + fees, + swap_for_y, + price, + &state, + &mut reward_dis_config, + )?; + } + + amounts_left = amounts_left.sub(amounts_in_with_fees); + amounts_out = amounts_out.add(amounts_out_of_bin); + + let p_fees = + fees.scalar_mul_div_basis_point_round_down(params.get_protocol_share().into())?; + total_fees = total_fees.add(fees); + lp_fees = lp_fees.add(fees.sub(p_fees)); + shade_dao_fees = shade_dao_fees.add(p_fees); + + if U256::from_le_bytes(p_fees) > U256::ZERO { + protocol_fees = protocol_fees.add(p_fees); + amounts_in_with_fees = amounts_in_with_fees.sub(p_fees); + } + + BIN_MAP.save( + deps.storage, + active_id, + &bin_reserves + .add(amounts_in_with_fees) // actually amount in wihtout fees + .sub(amounts_out_of_bin), + )?; + ids.push(active_id); + } + } + + if amounts_left == [0; 32] { + break; + } else { + let next_id = _get_next_non_empty_bin(&tree, swap_for_y, active_id); + if next_id == 0 || next_id == (U24::MAX) { + return Err(Error::OutOfLiquidity); + } + active_id = next_id; + } + } + + REWARDS_STATS_STORE.save(deps.storage, state.rewards_epoch_index, &reward_dis_config)?; + + if amounts_out == [0; 32] { + return Err(Error::InsufficientAmountOut); + } + + reserves = reserves.sub(amounts_out); + volume_tracker = volume_tracker.add(amounts_out); + + //updating the oracle for volume and fee analysis + updating_oracles_for_vol_analysis( + deps.storage, + &env, + &mut params, + active_id, + volume_tracker, + total_fees, + )?; + + STATE.update(deps.storage, |mut state| { + state.last_swap_timestamp = env.block.time; + state.protocol_fees = protocol_fees; + params + .set_active_id(active_id) + .map_err(|err| StdError::generic_err(err.to_string()))?; + state.pair_parameters = params; + state.reserves = reserves; + Ok::(state) + })?; + + let mut messages: Vec = Vec::new(); + // Determine the output amount and the corresponding transfer message based on swap_for_y + let amount_out = if swap_for_y { + amounts_out.decode_y() + } else { + amounts_out.decode_x() + }; + let msg = if swap_for_y { + BinHelper::transfer_y(amounts_out, token_y.clone(), to) + } else { + BinHelper::transfer_x(amounts_out, token_x.clone(), to) + }; + // Add the message to messages if it exists + if let Some(message) = msg { + messages.push(message); + } + + // logging the bins changed + update_bin_reserves(deps.storage, &env, ids)?; + + Ok(Response::new() + .add_messages(messages) + .add_attributes(vec![ + Attribute::new("amount_in", amounts_received), + Attribute::new("amount_out", amount_out.to_string()), + Attribute::new("lp_fee_amount", lp_fees.decode_alt(swap_for_y).to_string()), + Attribute::new( + "total_fee_amount", + total_fees.decode_alt(swap_for_y).to_string(), + ), + Attribute::new( + "shade_dao_fee_amount", + shade_dao_fees.decode_alt(swap_for_y).to_string(), + ), + Attribute::new("token_in_key", token_x.unique_key()), + Attribute::new("token_out_key", token_y.unique_key()), + ]) + .set_data(to_binary(&ExecuteMsgResponse::SwapResult { + amount_in: amounts_received, + amount_out: Uint128::from(amount_out), + })?)) +} + +fn update_reward_dis_config( + time_now: u64, + state: &State, + reward_dis_config: &mut RewardDistributionConfig, + active_id: u32, +) -> Result<()> { + let time_difference = Uint256::from(time_now - state.last_swap_timestamp.seconds()); + + reward_dis_config.cumulative_value += time_difference; + reward_dis_config.cumulative_value_mul_bin_id += time_difference * (Uint256::from(active_id)); + Ok(()) +} + +fn update_fee_map_tree( + storage: &mut dyn Storage, + active_id: u32, + fees: Bytes32, + swap_for_y: bool, + price: U256, + state: &State, + reward_stats: &mut RewardDistributionConfig, +) -> Result<()> { + let feeu128: u128 = fees.decode_alt(swap_for_y); + let swap_value_uint256 = match swap_for_y { + true => U256x256Math::mul_shift_round_up(U256::from(feeu128), price, SCALE_OFFSET)? + .u256_to_uint256(), + false => Uint256::from(feeu128), + }; + + reward_stats.cumulative_value += swap_value_uint256; + FEE_MAP_TREE.update( + storage, + state.rewards_epoch_index, + |fee_tree| -> Result<_> { + Ok(match fee_tree { + Some(mut t) => { + t.add(active_id); + t + } + None => panic!("Fee tree not initialized"), // TODO: remove this panic and include a custom error + }) + }, + )?; + + FEE_MAP.update(storage, active_id, |cumm_fee| -> Result<_> { + let updated_cumm_fee = match cumm_fee { + Some(f) => f + swap_value_uint256, + None => swap_value_uint256, + }; + Ok(updated_cumm_fee) + })?; + + Ok(()) +} + +fn updating_oracles_for_vol_analysis( + storage: &mut dyn Storage, + env: &Env, + params: &mut PairParameters, + active_id: u32, + vol: Bytes32, + fees: Bytes32, +) -> Result<()> { + //updating oracles + let oracle_id = params.get_oracle_id(); + let mut oracle = ORACLE.load(storage, oracle_id)?; + + let updated_sample; + (*params, updated_sample) = oracle.update( + &env.block.time, + *params, + active_id, + Some(vol), + Some(fees), + DEFAULT_ORACLE_LENGTH, + )?; + + if let Some(n_s) = updated_sample { + ORACLE.save(storage, params.get_oracle_id(), &Oracle(n_s))?; + } + Ok(()) +} + +fn update_bin_reserves(storage: &mut dyn Storage, env: &Env, ids: Vec) -> Result<()> { + //updating oracles + BIN_RESERVES_UPDATED.update(storage, env.block.height, |x| -> StdResult> { + if let Some(mut y) = x { + y.extend(ids); + Ok(y) + } else { + Ok(ids) + } + })?; + BIN_RESERVES_UPDATED_LOG.push(storage, &env.block.height)?; + Ok(()) +} + +pub fn try_add_liquidity( + deps: DepsMut, + env: Env, + info: MessageInfo, + liquidity_parameters: LiquidityParameters, +) -> Result { + // Add liquidity while performing safety checks + // transfering funds and checking one's already send + // Main function -> add_liquidity_internal + // Preparing txn output + + // 1- Add liquidity while performing safety checks + // 1.1- Proceed only if deadline has not exceeded + if env.block.time.seconds() > liquidity_parameters.deadline { + return Err(Error::DeadlineExceeded { + deadline: liquidity_parameters.deadline, + current_timestamp: env.block.time.seconds(), + }); + } + let config = STATE.load(deps.storage)?; + let response = Response::new(); + // 1.2- Checking token order + if liquidity_parameters.token_x != config.token_x + || liquidity_parameters.token_y != config.token_y + || liquidity_parameters.bin_step != config.bin_step + { + return Err(Error::WrongPair); + } + + // response = response.add_messages(transfer_messages); + + //3- Main function -> add_liquidity_internal + let response = + add_liquidity_internal(deps, env, info, &config, &liquidity_parameters, response)?; + + Ok(response) +} + +fn add_liquidity_internal( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + config: &State, + liquidity_parameters: &LiquidityParameters, + response: Response, +) -> Result { + match_lengths(liquidity_parameters)?; + check_ids_bounds(liquidity_parameters)?; + + let state = STATE.load(deps.storage)?; + + let mut liquidity_configs = vec![ + LiquidityConfigurations { + distribution_x: 0, + distribution_y: 0, + id: 0 + }; + liquidity_parameters.delta_ids.len() + ]; + let mut deposit_ids = Vec::with_capacity(liquidity_parameters.delta_ids.len()); + + let active_id = state.pair_parameters.get_active_id(); + check_active_id_slippage(liquidity_parameters, active_id)?; + + let mut distribution_sum_x = 0u64; + let mut distribution_sum_y = 0u64; + let precison: u64 = PRECISION as u64; + + for i in 0..liquidity_configs.len() { + let id = calculate_id(liquidity_parameters, active_id, i)?; + deposit_ids.push(id); + + distribution_sum_x += liquidity_parameters.distribution_x[i]; + distribution_sum_y += liquidity_parameters.distribution_y[i]; + + if liquidity_parameters.distribution_x[i] > precison + || liquidity_parameters.distribution_y[i] > precison + || distribution_sum_x > precison + || distribution_sum_y > precison + { + return Err(Error::DistrubtionError); + } + + liquidity_configs[i] = LiquidityConfigurations { + distribution_x: liquidity_parameters.distribution_x[i], + distribution_y: liquidity_parameters.distribution_y[i], + id, + }; + } + + let (amounts_deposited, amounts_left, _liquidity_minted, response) = mint( + &mut deps, + &env, + info.clone(), + config, + info.sender.clone(), + liquidity_configs, + info.sender, + liquidity_parameters.amount_x, + liquidity_parameters.amount_y, + response, + )?; + + //4- Preparing txn output logs + let amount_x_added = Uint128::from(amounts_deposited.decode_x()); + let amount_y_added = Uint128::from(amounts_deposited.decode_y()); + let amount_x_min = liquidity_parameters.amount_x_min; + let amount_y_min = liquidity_parameters.amount_y_min; + + if amount_x_added < amount_x_min || amount_y_added < amount_y_min { + return Err(Error::AmountSlippageCaught { + amount_x_min, + amount_x: amount_x_added, + amount_y_min, + amount_y: amount_y_added, + }); + } + let _amount_x_left = Uint128::from(amounts_left.decode_x()); + let _amount_y_left = Uint128::from(amounts_left.decode_y()); + + // let liq_minted: Vec = liquidity_minted + // .iter() + // .map(|&liq| liq.u256_to_uint256()) + // .collect(); + + // let _deposit_ids_string = serialize_or_err(&deposit_ids)?; + BIN_RESERVES_UPDATED.update(deps.storage, env.block.height, |x| -> StdResult> { + if let Some(mut y) = x { + y.extend(deposit_ids); + Ok(y) + } else { + Ok(deposit_ids) + } + })?; + BIN_RESERVES_UPDATED_LOG.push(deps.storage, &env.block.height)?; + + // let _liquidity_minted_string = serialize_or_err(&liq_minted)?; + + // response = response + // .add_attribute("amount_x_added", amount_x_added) + // .add_attribute("amount_y_added", amount_y_added) + // .add_attribute("amount_x_left", amount_x_left) + // .add_attribute("amount_y_left", amount_y_left) + // .add_attribute("liquidity_minted", liquidity_minted_string) + // .add_attribute("deposit_ids", deposit_ids_string); + + Ok(response) +} + +/// Mint liquidity tokens by depositing tokens into the pool. +/// +/// It will mint Liquidity Book (LB) tokens for each bin where the user adds liquidity. +/// This function will not transfer the tokens from the caller, it is expected that the tokens have already been +/// transferred to this contract through another contract, most likely the router. +/// That is why this function shouldn't be called directly, but through one of the add liquidity functions of a +/// router that will also perform safety checks. +/// +/// Any excess amount of token will be sent to the `refund_to` address. +/// +/// # Arguments +/// +/// * `to` - The address that will receive the LB tokens +/// * `liquidity_configs` - The encoded liquidity configurations, each one containing the id of the bin and the +/// percentage of token X and token Y to add to the bin. +/// * `refund_to` - The address that will receive the excess amount of tokens +/// +/// # Returns +/// +/// * `amounts_received` - The amounts of token X and token Y received by the pool +/// * `amounts_left` - The amounts of token X and token Y that were not added to the pool and were sent to to +/// * `liquidity_minted` - The amounts of LB tokens minted for each bin +#[allow(clippy::too_many_arguments)] +fn mint( + mut deps: &mut DepsMut, + env: &Env, + info: MessageInfo, + config: &State, + to: Addr, + liquidity_configs: Vec, + _refund_to: Addr, + amount_received_x: Uint128, + amount_received_y: Uint128, + mut response: Response, +) -> Result<(Bytes32, Bytes32, Vec, Response)> { + let state = STATE.load(deps.storage)?; + + let _token_x = state.token_x; + let _token_y = state.token_y; + + if liquidity_configs.is_empty() { + return Err(Error::EmptyMarketConfigs); + } + + let mut mint_arrays = MintArrays { + ids: (vec![U256::MIN; liquidity_configs.len()]), + amounts: (vec![[0u8; 32]; liquidity_configs.len()]), + liquidity_minted: (vec![U256::MIN; liquidity_configs.len()]), + }; + + let amounts_received = BinHelper::received(amount_received_x, amount_received_y); + let mut messages: Vec = Vec::new(); + + let amounts_left = mint_bins( + &mut deps, + &env.block.time, + state.bin_step, + state.pair_parameters, + liquidity_configs, + amounts_received, + to, + &mut mint_arrays, + &mut messages, + )?; + + STATE.update(deps.storage, |mut state| -> StdResult<_> { + state.reserves = state.reserves.add(amounts_received.sub(amounts_left)); //Total liquidity of pool + Ok(state) + })?; + + let (amount_left_x, amount_left_y) = amounts_left.decode(); + + let mut transfer_messages = Vec::new(); + // 2- tokens checking and transfer + for (token, amount) in [ + ( + config.token_x.clone(), + amount_received_x - Uint128::from(amount_left_x), + ), + ( + config.token_y.clone(), + amount_received_y - Uint128::from(amount_left_y), + ), + ] + .iter() + { + match token { + TokenType::CustomToken { + contract_addr: _, + token_code_hash: _, + } => { + let msg = + token.transfer_from(*amount, info.sender.clone(), env.contract.address.clone()); + + if let Some(m) = msg { + transfer_messages.push(m); + } + } + TokenType::NativeToken { .. } => { + token.assert_sent_native_token_balance(&info, *amount)?; + } + } + } + + response = response + .add_messages(messages) + .add_messages(transfer_messages); + + Ok(( + amounts_received, + amounts_left, + mint_arrays.liquidity_minted, + response, + )) +} + +/// Helper function to mint liquidity in each bin in the liquidity configurations. +/// +/// # Arguments +/// +/// * `liquidity_configs` - The liquidity configurations. +/// * `amounts_received` - The amounts received. +/// * `to` - The address to mint the liquidity to. +/// * `arrays` - The arrays to store the results. +/// +/// # Returns +/// +/// * `amounts_left` - The amounts left. +fn mint_bins( + deps: &mut DepsMut, + time: &Timestamp, + bin_step: u16, + pair_parameters: PairParameters, + liquidity_configs: Vec, + amounts_received: Bytes32, + to: Addr, + mint_arrays: &mut MintArrays, + messages: &mut Vec, +) -> Result { + let config = STATE.load(deps.storage)?; + let active_id = pair_parameters.get_active_id(); + + let mut amounts_left = amounts_received; + + //Minting tokens + + let mut mint_tokens: Vec = Vec::new(); + + for (index, liq_conf) in liquidity_configs.iter().enumerate() { + let (max_amounts_in_to_bin, id) = liq_conf.get_amounts_and_id(amounts_received)?; + + let (shares, amounts_in, amounts_in_to_bin) = update_bin( + deps, + time, + bin_step, + active_id, + id, + max_amounts_in_to_bin, + pair_parameters, + )?; + + amounts_left = amounts_left.sub(amounts_in); + + mint_arrays.ids[index] = id.into(); + mint_arrays.amounts[index] = amounts_in_to_bin; + mint_arrays.liquidity_minted[index] = shares; + + let amount = shares.u256_to_uint256(); + + //Minting tokens + mint_tokens.push(TokenAmount { + token_id: id.to_string(), + balances: vec![TokenIdBalance { + address: to.clone(), + amount, + }], + }); + } + let msg = lb_token::ExecuteMsg::MintTokens { + mint_tokens, + memo: None, + padding: None, + } + .to_cosmos_msg( + config.lb_token.code_hash.clone(), + config.lb_token.address.to_string(), + None, + )?; + + messages.push(msg); + Ok(amounts_left) +} + +/// Helper function to update a bin during minting. +/// +/// # Arguments +/// +/// * `bin_step` - The bin step of the pair +/// * `active_id` - The id of the active bin +/// * `id` - The id of the bin +/// * `max_amounts_in_to_bin` - The maximum amounts in to the bin +/// * `parameters` - The parameters of the pair +/// +/// # Returns +/// +/// * `shares` - The amount of shares minted +/// * `amounts_in` - The amounts in +/// * `amounts_in_to_bin` - The amounts in to the bin +fn update_bin( + deps: &mut DepsMut, + time: &Timestamp, + bin_step: u16, + active_id: u32, + id: u32, + amounts_in: Bytes32, + mut parameters: PairParameters, +) -> Result<(U256, Bytes32, Bytes32)> { + let bin_reserves = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); + let config = STATE.load(deps.storage)?; + let price = PriceHelper::get_price_from_id(id, bin_step)?; + let total_supply = _query_total_supply( + deps.as_ref(), + id, + config.lb_token.code_hash, + config.lb_token.address, + )?; + let (shares, amounts_in) = BinHelper::get_shares_and_effective_amounts_in( + bin_reserves, + amounts_in, + price, + total_supply, + )?; + + let amounts_in_to_bin = amounts_in; + + if id == active_id { + parameters.update_volatility_parameters(id, time)?; + + // Helps calculate fee if there's an implict swap. + let fees = BinHelper::get_composition_fees( + bin_reserves, + parameters, + bin_step, + amounts_in, + total_supply, + shares, + )?; + + if fees != [0u8; 32] { + let user_liquidity = BinHelper::get_liquidity(amounts_in.sub(fees), price)?; + let bin_liquidity = BinHelper::get_liquidity(bin_reserves, price)?; + + let _shares = + U256x256Math::mul_div_round_down(user_liquidity, total_supply, bin_liquidity)?; + let protocol_c_fees = + fees.scalar_mul_div_basis_point_round_down(parameters.get_protocol_share().into())?; + + if protocol_c_fees != [0u8; 32] { + let _amounts_in_to_bin = amounts_in_to_bin.sub(protocol_c_fees); + STATE.update(deps.storage, |mut state| -> StdResult<_> { + state.protocol_fees = state.protocol_fees.add(protocol_c_fees); + Ok(state) + })?; + } + + let oracle_id = parameters.get_oracle_id(); + + let mut oracle = ORACLE.load(deps.storage, oracle_id)?; + let new_sample; + (parameters, new_sample) = + oracle.update(time, parameters, id, None, None, DEFAULT_ORACLE_LENGTH)?; + if let Some(n_s) = new_sample { + ORACLE.save(deps.storage, parameters.get_oracle_id(), &Oracle(n_s))?; + } + + STATE.update(deps.storage, |mut state| -> StdResult<_> { + state.pair_parameters = parameters; + Ok(state) + })?; + } + } else { + BinHelper::verify_amounts(amounts_in, active_id, id)?; + } + + if shares == 0 || amounts_in_to_bin == [0u8; 32] { + return Err(Error::ZeroAmount { id }); + } + + if total_supply == 0 { + BIN_TREE.update(deps.storage, |mut tree| -> StdResult<_> { + tree.add(id); + Ok(tree) + })?; + } + + BIN_MAP.save(deps.storage, id, &bin_reserves.add(amounts_in_to_bin))?; + + Ok((shares, amounts_in, amounts_in_to_bin)) +} + +pub fn try_remove_liquidity( + deps: DepsMut, + env: Env, + info: MessageInfo, + remove_liquidity_params: RemoveLiquidity, +) -> Result { + let config = STATE.load(deps.storage)?; + + let is_wrong_order = config.token_x != remove_liquidity_params.token_x; + + let (amount_x_min, amount_y_min) = if is_wrong_order { + if remove_liquidity_params.token_x != config.token_y + || remove_liquidity_params.token_y != config.token_x + || remove_liquidity_params.bin_step != config.bin_step + { + return Err(Error::WrongPair); + } + ( + remove_liquidity_params.amount_y_min, + remove_liquidity_params.amount_x_min, + ) + } else { + if remove_liquidity_params.token_x != config.token_x + || remove_liquidity_params.token_y != config.token_y + || remove_liquidity_params.bin_step != config.bin_step + { + return Err(Error::WrongPair); + } + ( + remove_liquidity_params.amount_x_min, + remove_liquidity_params.amount_y_min, + ) + }; + + let (_amount_x, _amount_y, response) = remove_liquidity( + deps, + env, + info.clone(), + info.sender, + amount_x_min, + amount_y_min, + remove_liquidity_params.ids, + remove_liquidity_params.amounts, + )?; + + Ok(response) +} + +fn remove_liquidity( + deps: DepsMut, + env: Env, + info: MessageInfo, + _to: Addr, + amount_x_min: Uint128, + amount_y_min: Uint128, + ids: Vec, + amounts: Vec, +) -> Result<(Uint128, Uint128, Response)> { + let (amounts_burned, response) = burn(deps, env, info, ids, amounts)?; + let mut amount_x: Uint128 = Uint128::zero(); + let mut amount_y: Uint128 = Uint128::zero(); + for amount_burned in amounts_burned { + amount_x += Uint128::from(amount_burned.decode_x()); + amount_y += Uint128::from(amount_burned.decode_y()); + } + + if amount_x < amount_x_min || amount_y < amount_y_min { + return Err(Error::AmountSlippageCaught { + amount_x_min, + amount_x, + amount_y_min, + amount_y, + }); + } + + Ok((amount_x, amount_y, response)) +} + +/// Burn Liquidity Book (LB) tokens and withdraw tokens from the pool. +/// +/// This function will burn the tokens directly from the caller. +/// +/// # Arguments +/// +/// * `from` - The address that will burn the LB tokens +/// * `to` - The address that will receive the tokens +/// * `ids` - The ids of the bins from which to withdraw +/// * `amounts_to_burn` - The amounts of LB tokens to burn for each bin +/// +/// # Returns +/// +/// * `amounts` - The amounts of token X and token Y received by the user +fn burn( + deps: DepsMut, + env: Env, + info: MessageInfo, + ids: Vec, + amounts_to_burn: Vec, +) -> Result<(Vec<[u8; 32]>, Response)> { + let mut config = STATE.load(deps.storage)?; + + let token_x = config.token_x; + let token_y = config.token_y; + + if ids.is_empty() || ids.len() != amounts_to_burn.len() { + return Err(Error::InvalidInput); + } + + let mut messages: Vec = Vec::new(); + let mut burn_tokens: Vec = Vec::new(); + + let mut amounts = vec![[0u8; 32]; ids.len()]; + let mut amounts_out = [0u8; 32]; + + for i in 0..ids.len() { + let id = ids[i]; + let amount_to_burn = amounts_to_burn[i]; + + if amount_to_burn.is_zero() { + return Err(Error::ZeroShares { id }); + } + + let bin_reserves = BIN_MAP + .load(deps.storage, id) + .map_err(|_| Error::ZeroBinReserve { + active_id: i as u32, + })?; + let total_supply = _query_total_supply( + deps.as_ref(), + id, + config.lb_token.code_hash.clone(), + config.lb_token.address.clone(), + )?; + + burn_tokens.push(TokenAmount { + token_id: id.to_string(), + balances: vec![TokenIdBalance { + address: info.sender.clone(), + amount: amount_to_burn, + }], + }); + + let amount_to_burn_u256 = amount_to_burn.uint256_to_u256(); + + let amounts_out_from_bin_vals = + BinHelper::get_amount_out_of_bin(bin_reserves, amount_to_burn_u256, total_supply)?; + let amounts_out_from_bin: Bytes32 = + Bytes32::encode(amounts_out_from_bin_vals.0, amounts_out_from_bin_vals.1); + + if amounts_out_from_bin.iter().all(|&x| x == 0) { + return Err(Error::ZeroAmountsOut { + id, + amount_to_burn: amount_to_burn_u256.u256_to_uint256(), + total_supply: total_supply.u256_to_uint256(), + }); + } + + let bin_reserves = bin_reserves.sub(amounts_out_from_bin); + + if total_supply == amount_to_burn_u256 { + BIN_MAP.remove(deps.storage, id); + BIN_TREE.update(deps.storage, |mut tree| -> StdResult<_> { + tree.remove(id); + Ok(tree) + })?; + } else { + BIN_MAP.save(deps.storage, id, &bin_reserves)?; + } + + amounts[i] = amounts_out_from_bin; + amounts_out = amounts_out.add(amounts_out_from_bin); + } + + let msg = lb_token::ExecuteMsg::BurnTokens { + burn_tokens, + memo: None, + padding: None, + } + .to_cosmos_msg( + config.lb_token.code_hash, + config.lb_token.address.to_string(), + None, + )?; + + messages.push(msg); + + config.reserves = config.reserves.sub(amounts_out); + + let raw_msgs = BinHelper::transfer(amounts_out, token_x, token_y, info.sender); + + STATE.update(deps.storage, |mut state| -> StdResult { + state.reserves = state.reserves.sub(amounts_out); + Ok(state) + })?; + + BIN_RESERVES_UPDATED.update(deps.storage, env.block.height, |x| -> StdResult> { + if let Some(mut y) = x { + y.extend(ids); + Ok(y) + } else { + Ok(ids) + } + })?; + BIN_RESERVES_UPDATED_LOG.push(deps.storage, &env.block.height)?; + + if let Some(msgs) = raw_msgs { + messages.extend(msgs) + } + + Ok((amounts, Response::default().add_messages(messages))) +} + +// Administrative functions + +/// Collect the protocol fees from the pool. +pub fn try_collect_protocol_fees(deps: DepsMut, _env: Env, info: MessageInfo) -> Result { + let state = STATE.load(deps.storage)?; + // only_protocol_fee_recipient(&info.sender, &state.factory.address)?; + + let token_x = state.token_x; + let token_y = state.token_y; + + let mut messages: Vec = Vec::new(); + + let protocol_fees = state.protocol_fees; + + let (x, y) = protocol_fees.decode(); + let ones = Bytes32::encode(if x > 0 { 1 } else { 0 }, if y > 0 { 1 } else { 0 }); + + //The purpose of subtracting ones from the protocolFees is to leave a small amount (1 unit of each token) in the protocol fees. + //This is done to avoid completely draining the fees and possibly causing any issues with calculations that depend on non-zero values + let collected_protocol_fees = protocol_fees.sub(ones); + + if U256::from_le_bytes(collected_protocol_fees) != U256::ZERO { + // This is setting the protocol fees to the smallest possible values + STATE.update(deps.storage, |mut state| -> StdResult { + state.protocol_fees = ones; + state.reserves = state.reserves.sub(collected_protocol_fees); + Ok(state) + })?; + + if collected_protocol_fees.iter().any(|&x| x != 0) { + if let Some(msgs) = BinHelper::transfer( + collected_protocol_fees, + token_x.clone(), + token_y.clone(), + state.protocol_fees_recipient, + ) { + messages.extend(msgs); + }; + } + + Ok(Response::default() + .add_attribute( + format!("Collected Protocol Fees for token {}", token_x.unique_key()), + collected_protocol_fees.decode_x().to_string(), + ) + .add_attribute( + format!("Collected Protocol Fees for token {}", token_y.unique_key()), + collected_protocol_fees.decode_y().to_string(), + ) + .add_attribute("Action performed by", info.sender.to_string()) + .add_messages(messages)) + } else { + Err(Error::NotEnoughFunds) + } +} + +/// Sets the static fee parameters of the pool. +/// +/// Can only be called by the factory. +/// +/// # Arguments +/// +/// * `base_factor` - The base factor of the static fee +/// * `filter_period` - The filter period of the static fee +/// * `decay_period` - The decay period of the static fee +/// * `reduction_factor` - The reduction factor of the static fee +/// * `variable_fee_control` - The variable fee control of the static fee +/// * `protocol_share` - The protocol share of the static fee +/// * `max_volatility_accumulator` - The max volatility accumulator of the static fee +pub fn try_set_static_fee_parameters( + deps: DepsMut, + _env: Env, + info: MessageInfo, + base_factor: u16, + filter_period: u16, + decay_period: u16, + reduction_factor: u16, + variable_fee_control: u32, + protocol_share: u16, + max_volatility_accumulator: u32, +) -> Result { + let state = STATE.load(deps.storage)?; + only_factory(&info.sender, &state.factory.address)?; + + let mut params = state.pair_parameters; + + params.set_static_fee_parameters( + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + )?; + + let total_fee = params.get_base_fee(state.bin_step) + params.get_variable_fee(state.bin_step); + if total_fee > MAX_FEE { + return Err(Error::MaxTotalFeeExceeded {}); + } + + STATE.update(deps.storage, |mut state| -> StdResult { + state.pair_parameters = params; + Ok(state) + })?; + + Ok(Response::default().add_attribute("status", "ok")) +} + +/// Forces the decay of the volatility reference variables. +/// +/// Can only be called by the factory. +pub fn try_force_decay(deps: DepsMut, _env: Env, info: MessageInfo) -> Result { + let state = STATE.load(deps.storage)?; + only_factory(&info.sender, &state.factory.address)?; + + let mut params = state.pair_parameters; + params.update_id_reference(); + params.update_volatility_reference()?; + + STATE.update(deps.storage, |mut state| -> StdResult { + state.pair_parameters = params; + Ok(state) + })?; + + Ok(Response::default()) +} + +pub fn try_calculate_rewards_distribution( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> Result { + let mut state = STATE.load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::LiquidityBookAdmin, + info.sender.to_string(), + &state.admin_auth, + )?; + + // save the results in temporary storage + + let reward_stats = REWARDS_STATS_STORE.load(deps.storage, state.rewards_epoch_index)?; + let distribution = if !reward_stats.cumulative_value.is_zero() { + match reward_stats.rewards_distribution_algorithm { + RewardsDistributionAlgorithm::TimeBasedRewards => { + calculate_time_based_rewards_distribution(&env, &state, &reward_stats)? + } + RewardsDistributionAlgorithm::VolumeBasedRewards => { + calculate_volume_based_rewards_distribution(deps.as_ref(), &state, &reward_stats)? + } + } + } else { + let rewards_bins = match state.base_rewards_bins { + Some(r_b) => r_b, + None => DEFAULT_REWARDS_BINS, + }; + calculate_default_distribution(rewards_bins, state.pair_parameters.get_active_id())? + }; + + REWARDS_DISTRIBUTION.save(deps.storage, state.rewards_epoch_index, &distribution)?; + + //distribution algorithm + let res = lb_staking::ExecuteMsg::EndEpoch { + rewards_distribution: distribution, + epoch_index: state.rewards_epoch_index, + } + .to_cosmos_msg( + state.lb_staking.code_hash.to_owned(), + state.lb_staking.address.to_string(), + None, + )?; + + state.rewards_epoch_index += 1; + let toggle = state.toggle_distributions_algorithm; + state.last_swap_timestamp = env.block.time; + state.toggle_distributions_algorithm = false; + STATE.save(deps.storage, &state)?; + + let mut distribution_algorithm = &reward_stats.rewards_distribution_algorithm; + if toggle { + distribution_algorithm = match reward_stats.rewards_distribution_algorithm { + RewardsDistributionAlgorithm::TimeBasedRewards => { + &RewardsDistributionAlgorithm::VolumeBasedRewards + } + RewardsDistributionAlgorithm::VolumeBasedRewards => { + &RewardsDistributionAlgorithm::TimeBasedRewards + } + }; + } + + REWARDS_STATS_STORE.save( + deps.storage, + state.rewards_epoch_index, + &RewardDistributionConfig { + cumulative_value: Uint256::zero(), + cumulative_value_mul_bin_id: Uint256::zero(), + rewards_distribution_algorithm: distribution_algorithm.clone(), + }, + )?; + + if distribution_algorithm == &RewardsDistributionAlgorithm::VolumeBasedRewards { + let tree: TreeUint24 = TreeUint24::new(); + FEE_MAP_TREE.save(deps.storage, state.rewards_epoch_index, &tree)?; + } + + Ok(Response::default().add_message(res)) +} + +fn calculate_default_distribution(rewards_bins: u32, avg_bin: u32) -> Result { + let half_total = rewards_bins / 2; + let min_bin = avg_bin.saturating_sub(half_total) + 1; + let max_bin = avg_bin.saturating_add(half_total); + + let difference = max_bin - min_bin + 1; + + let ids: Vec = (min_bin..=max_bin).collect(); + let weightages = vec![BASIS_POINT_MAX as u16 / difference as u16; difference as usize]; + + Ok(RewardsDistribution { + ids, + weightages, + denominator: BASIS_POINT_MAX as u16, + }) +} + +fn calculate_time_based_rewards_distribution( + env: &Env, + state: &State, + reward_stats: &RewardDistributionConfig, +) -> Result { + let mut cumm_value_mul_bin = reward_stats.cumulative_value_mul_bin_id; + let mut cumm_value = reward_stats.cumulative_value; + + let active_id = state.pair_parameters.get_active_id(); + + let time_difference = + Uint256::from(env.block.time.seconds() - state.last_swap_timestamp.seconds()); + + cumm_value += time_difference; + cumm_value_mul_bin += time_difference * (Uint256::from(active_id)); + + let avg_bin = approx_div(cumm_value_mul_bin, cumm_value) + .uint256_to_u256() + .as_u32(); + + let rewards_bins = match state.base_rewards_bins { + Some(r_b) => r_b, + None => DEFAULT_REWARDS_BINS, + }; + + calculate_default_distribution(rewards_bins, avg_bin) +} + +fn calculate_volume_based_rewards_distribution( + deps: Deps, + state: &State, + reward_stats: &RewardDistributionConfig, +) -> Result { + let cum_fee = reward_stats.cumulative_value; + let mut ids: Vec = Vec::new(); + let mut weightages: Vec = Vec::new(); + + let fee_tree: TreeUint24 = FEE_MAP_TREE.load(deps.storage, state.rewards_epoch_index)?; + let mut id: u32 = 0; + let basis_point_max: Uint256 = Uint256::from(BASIS_POINT_MAX); + let mut total_weight = 0; + + for _ in 0..U24::MAX { + id = fee_tree.find_first_left(id); + if id == U24::MAX || id == 0 { + break; + } + + let fee: Uint256 = FEE_MAP.load(deps.storage, id)?; + ids.push(id); + let weightage: u16 = fee + .multiply_ratio(basis_point_max, cum_fee) + .uint256_to_u256() + .as_u16(); + weightages.push(weightage); + total_weight += weightage; + } + + let reminder = BASIS_POINT_MAX as u16 - total_weight; + + if reminder > 0 { + let len = weightages.len() - 1; + weightages[len] += reminder; + } + + let distribution = RewardsDistribution { + ids, + weightages, + denominator: BASIS_POINT_MAX as u16, + }; + + Ok(distribution) +} + +//Can only change the distribution algorithm at the start of next epoch +//Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. +pub fn try_reset_rewards_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + rewards_distribution_algorithm: Option, + base_reward_bins: Option, +) -> Result { + let mut state = STATE.load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::LiquidityBookAdmin, + info.sender.to_string(), + &state.admin_auth, + )?; + let reward_stats = REWARDS_STATS_STORE.load(deps.storage, state.rewards_epoch_index)?; + + //Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. + match rewards_distribution_algorithm { + Some(distribution) => { + if reward_stats.rewards_distribution_algorithm != distribution { + state.toggle_distributions_algorithm = true; + } + } + None => {} + }; + + match base_reward_bins { + Some(b_r_b) => { + if b_r_b > U24::MAX { + return Err(Error::U24Overflow); + } + state.base_rewards_bins = Some(b_r_b) + } + None => {} + } + + STATE.save(deps.storage, &state)?; + + Ok(Response::default()) +} diff --git a/contracts/liquidity_book/lb_pair/src/helper.rs b/contracts/liquidity_book/lb_pair/src/helper.rs new file mode 100644 index 00000000..e16d6be1 --- /dev/null +++ b/contracts/liquidity_book/lb_pair/src/helper.rs @@ -0,0 +1,163 @@ +use crate::prelude::*; +use ethnum::U256; +use shade_protocol::{ + c_std::{Addr, ContractInfo, CosmosMsg, Deps, Env, StdResult}, + contract_interfaces::{ + liquidity_book::{lb_pair::*, lb_token}, + swap::core::TokenType, + }, + lb_libraries::{ + math::{tree_math::TreeUint24, u24::U24, uint256_to_u256::ConvertUint256}, + viewing_keys::{register_receive, set_viewing_key_msg, ViewingKey}, + }, + snip20, +}; +pub const INSTANTIATE_LP_TOKEN_REPLY_ID: u64 = 1u64; +pub const INSTANTIATE_STAKING_CONTRACT_REPLY_ID: u64 = 2u64; +pub const MINT_REPLY_ID: u64 = 1u64; +pub const DEFAULT_REWARDS_BINS: u32 = 100; +pub const DEFAULT_MAX_BINS_PER_SWAP: u32 = 100; +pub const DEFAULT_ORACLE_LENGTH: u16 = u16::MAX; + +pub fn register_pair_token( + env: &Env, + messages: &mut Vec, + token: &TokenType, + viewing_key: &ViewingKey, +) -> StdResult<()> { + if let TokenType::CustomToken { + contract_addr, + token_code_hash, + .. + } = token + { + messages.push(set_viewing_key_msg( + viewing_key.0.clone(), + None, + &ContractInfo { + address: contract_addr.clone(), + code_hash: token_code_hash.to_string(), + }, + )?); + messages.push(register_receive( + env.contract.code_hash.clone(), + None, + &ContractInfo { + address: contract_addr.clone(), + code_hash: token_code_hash.to_string(), + }, + )?); + } + + Ok(()) +} + +pub fn match_lengths(liquidity_parameters: &LiquidityParameters) -> Result<()> { + if liquidity_parameters.delta_ids.len() != liquidity_parameters.distribution_x.len() + || liquidity_parameters.delta_ids.len() != liquidity_parameters.distribution_y.len() + { + return Err(Error::LengthsMismatch); + } + Ok(()) +} + +pub fn check_ids_bounds(liquidity_parameters: &LiquidityParameters) -> Result<()> { + if liquidity_parameters.active_id_desired > U24::MAX + || liquidity_parameters.id_slippage > U24::MAX + { + return Err(Error::IdDesiredOverflows { + id_desired: liquidity_parameters.active_id_desired, + id_slippage: liquidity_parameters.id_slippage, + }); + } + Ok(()) +} + +pub fn check_active_id_slippage( + liquidity_parameters: &LiquidityParameters, + active_id: u32, +) -> Result<()> { + if liquidity_parameters.active_id_desired + liquidity_parameters.id_slippage < active_id + || active_id + liquidity_parameters.id_slippage < liquidity_parameters.active_id_desired + { + return Err(Error::IdSlippageCaught { + active_id_desired: liquidity_parameters.active_id_desired, + id_slippage: liquidity_parameters.id_slippage, + active_id, + }); + } + Ok(()) +} + +//function won't distinguish between overflow and underflow errors; it'll throw the same DeltaIdOverflows +pub fn calculate_id( + liquidity_parameters: &LiquidityParameters, + active_id: u32, + i: usize, +) -> Result { + // let id: u32; + + let id: i64 = active_id as i64 + liquidity_parameters.delta_ids[i]; + + if id < 0 || id as u32 > U24::MAX { + return Err(Error::DeltaIdOverflows { + delta_id: liquidity_parameters.delta_ids[i], + }); + } + + Ok(id as u32) +} + +pub fn _query_total_supply(deps: Deps, id: u32, code_hash: String, address: Addr) -> Result { + let msg = lb_token::QueryMsg::IdTotalBalance { id: id.to_string() }; + + let res = deps.querier.query_wasm_smart::( + code_hash, + address.to_string(), + &msg, + )?; + + let total_supply_uint256 = match res { + lb_token::QueryAnswer::IdTotalBalance { amount } => amount, + _ => panic!("{}", format!("Wrong response for lb_token")), + }; + + Ok(total_supply_uint256.uint256_to_u256()) +} + +pub fn query_token_symbol(deps: Deps, code_hash: String, address: Addr) -> Result { + let msg = snip20::QueryMsg::TokenInfo {}; + + let res = deps.querier.query_wasm_smart::( + code_hash, + address.to_string(), + &(&msg), + )?; + + let symbol = match res { + snip20::QueryAnswer::TokenInfo { symbol, .. } => symbol, + _ => panic!("{}", format!("Token {} not valid", address)), + }; + + Ok(symbol) +} + +/// Returns id of the next non-empty bin. +/// +/// # Arguments +/// * `swap_for_y Whether the swap is for Y +/// * `id` - The id of the bin +pub fn _get_next_non_empty_bin(tree: &TreeUint24, swap_for_y: bool, id: u32) -> u32 { + if swap_for_y { + tree.find_first_right(id) + } else { + tree.find_first_left(id) + } +} + +pub fn only_factory(sender: &Addr, factory: &Addr) -> Result<()> { + if sender != factory { + return Err(Error::OnlyFactory); + } + Ok(()) +} diff --git a/contracts/liquidity_book/lb_pair/src/lib.rs b/contracts/liquidity_book/lb_pair/src/lib.rs index 541f2a87..3fc1f682 100644 --- a/contracts/liquidity_book/lb_pair/src/lib.rs +++ b/contracts/liquidity_book/lb_pair/src/lib.rs @@ -4,4 +4,7 @@ mod error; mod prelude; pub mod contract; +pub mod execute; +pub mod helper; +pub mod query; pub mod state; diff --git a/contracts/liquidity_book/lb_pair/src/query.rs b/contracts/liquidity_book/lb_pair/src/query.rs new file mode 100644 index 00000000..e91b4604 --- /dev/null +++ b/contracts/liquidity_book/lb_pair/src/query.rs @@ -0,0 +1,1030 @@ +use crate::{helper::*, prelude::*, state::*}; +use ethnum::U256; +use shade_protocol::{ + c_std::{ + from_binary, + to_binary, + Addr, + Binary, + Decimal, + Deps, + Env, + StdResult, + Uint128, + Uint256, + }, + contract_interfaces::{ + liquidity_book::lb_pair::*, + swap::{ + amm_pair::{ + FeeInfo, + QueryMsgResponse::{GetPairInfo, SwapSimulation}, + }, + core::{Fee, TokenPair}, + }, + }, + lb_libraries::{ + bin_helper::BinHelper, + constants::SCALE_OFFSET, + fee_helper::FeeHelper, + math::{ + packed_u128_math::PackedUint128Math, + u24::U24, + u256x256_math::U256x256Math, + uint256_to_u256::{ConvertU256, ConvertUint256}, + }, + oracle_helper::MAX_SAMPLE_LIFETIME, + price_helper::PriceHelper, + types::Bytes32, + }, + Contract, +}; +use std::collections::HashSet; + +// TODO - Revisit if this function is necessary. It seems like something that might belong in the +// lb-factory contract. It should at least have it's own interface and not use amm_pair's. +pub fn query_pair_info(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + + let (reserve_x, reserve_y) = state.reserves.decode(); + + let response = GetPairInfo { + liquidity_token: Contract { + address: state.lb_token.address, + code_hash: state.lb_token.code_hash, + }, + factory: Some(Contract { + address: state.factory.address, + code_hash: state.factory.code_hash, + }), + pair: TokenPair(state.token_x, state.token_y, false), + amount_0: Uint128::from(reserve_x), + amount_1: Uint128::from(reserve_y), + total_liquidity: Uint128::default(), // no global liquidity, liquidity is calculated on per bin basis + contract_version: 1, // TODO set this like const AMM_PAIR_CONTRACT_VERSION: u32 = 1; + fee_info: FeeInfo { + shade_dao_address: Addr::unchecked(""), // TODO set shade dao address + lp_fee: Fee { + // TODO set this + nom: state.pair_parameters.get_base_fee(state.bin_step) as u64, + denom: 1_000_000_000_000_000_000, + }, + shade_dao_fee: Fee { + nom: state.pair_parameters.get_base_fee(state.bin_step) as u64, + denom: 1_000_000_000_000_000_000, + }, + stable_lp_fee: Fee { + nom: state.pair_parameters.get_base_fee(state.bin_step) as u64, + denom: 1_000_000_000_000_000_000, + }, + stable_shade_dao_fee: Fee { + nom: state.pair_parameters.get_base_fee(state.bin_step) as u64, + denom: 1_000_000_000_000_000_000, + }, + }, + stable_info: None, + }; + + to_binary(&response).map_err(Error::CwErr) +} + +// TODO - Revisit if this function is necessary. It seems like something that might belong in the +// lb-router contract. It should at least have it's own interface and not use amm_pair's. +pub fn query_swap_simulation( + deps: Deps, + env: Env, + offer: shade_protocol::swap::core::TokenAmount, + _exclude_fee: Option, +) -> Result { + let state = STATE.load(deps.storage)?; + + let mut swap_for_y = false; + match offer.token { + token if token == state.token_x => swap_for_y = true, + token if token == state.token_y => {} + _ => panic!("No such token"), + }; + + let res = query_swap_out(deps, env, offer.amount.into(), swap_for_y)?; + + let res = from_binary::(&res)?; + + if res.amount_in_left.u128() > 0u128 { + return Err(Error::AmountInLeft { + amount_left_in: res.amount_in_left, + total_amount: offer.amount, + swapped_amount: res.amount_out, + }); + } + + let price = Decimal::from_ratio(res.amount_out, offer.amount).to_string(); + + let response = SwapSimulation { + total_fee_amount: res.total_fees, + lp_fee_amount: res.lp_fees, //TODO lpfee + shade_dao_fee_amount: res.shade_dao_fees, // dao fee + result: SwapResult { + return_amount: res.amount_out, + }, + price, + }; + + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the Liquidity Book Factory. +/// +/// # Returns +/// +/// * `factory` - The Liquidity Book Factory +pub fn query_factory(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let factory = state.factory.address; + + let response = FactoryResponse { factory }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the Liquidity Book Factory. +/// +/// # Returns +/// +/// * `factory` - The Liquidity Book Factory +pub fn query_lb_token(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let lb_token = state.lb_token; + + let response = LbTokenResponse { contract: lb_token }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the Liquidity Book Factory. +/// +/// # Returns +/// +/// * `factory` - The Liquidity Book Factory +pub fn query_staking(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let staking_contract = state.lb_staking; + + let response = StakingResponse { + contract: staking_contract, + }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the token X and Y of the Liquidity Book Pair. +/// +/// # Returns +/// +/// * `token_x` - The address of the token X +pub fn query_tokens(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + + let response = TokensResponse { + token_x: state.token_x, + token_y: state.token_y, + }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the token X of the Liquidity Book Pair. +/// +/// # Returns +/// +/// * `token_x` - The address of the token X +pub fn query_token_x(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let token_x = state.token_x; + + let response = TokenXResponse { token_x }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the token Y of the Liquidity Book Pair. +/// +/// # Returns +/// +/// * `token_y` - The address of the token Y +pub fn query_token_y(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let token_y = state.token_y; + + let response = TokenYResponse { token_y }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the bin_step of the Liquidity Book Pair. +/// +/// The bin step is the increase in price between two consecutive bins, in basis points. +/// For example, a bin step of 1 means that the price of the next bin is 0.01% higher than the price of the previous bin. +/// +/// # Returns +/// +/// * `bin_step` - The bin step of the Liquidity Book Pair, in 10_000th +pub fn query_bin_step(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let bin_step = state.bin_step; + + let response = BinStepResponse { bin_step }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the reserves of the Liquidity Book Pair. +/// +/// This is the sum of the reserves of all bins, minus the protocol fees. +/// +/// # Returns +/// +/// * `reserve_x` - The reserve of token X +/// * `reserve_y` - The reserve of token Y +pub fn query_reserves(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let (mut reserve_x, mut reserve_y) = state.reserves.decode(); + let (protocol_fee_x, protocol_fee_y) = state.protocol_fees.decode(); + + reserve_x -= protocol_fee_x; + reserve_y -= protocol_fee_y; + + let response = ReservesResponse { + reserve_x, + reserve_y, + }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the active id of the Liquidity Book Pair. +/// +/// The active id is the id of the bin that is currently being used for swaps. +/// The price of the active bin is the price of the Liquidity Book Pair and can be calculated as follows: +/// `price = (1 + binStep / 10_000) ^ (activeId - 2^23)` +/// +/// # Returns +/// +/// * `active_id` - The active id of the Liquidity Book Pair +pub fn query_active_id(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let active_id = state.pair_parameters.get_active_id(); + + let response = ActiveIdResponse { active_id }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the reserves of a bin. +/// +/// # Arguments +/// +/// * `id` - The id of the bin +/// +/// # Returns +/// +/// * `bin_reserve_x` - The reserve of token X in the bin +/// * `bin_reserve_y` - The reserve of token Y in the bin +pub fn query_all_bins_reserves( + deps: Deps, + env: Env, + page: Option, + page_size: Option, + id: Option, +) -> Result { + let page = page.unwrap_or(0); + let page_size = page_size.unwrap_or(10); + + let mut id = id.unwrap_or(0u32); + let mut bin_responses = Vec::new(); + let tree = BIN_TREE.load(deps.storage)?; + let total = if page > 0 { + page * page_size + } else { + page_size + }; + + let state = STATE.load(deps.storage)?; + let mut counter: u32 = 0; + + for _ in 0..state.max_bins_per_swap { + let next_id = tree.find_first_left(id); + id = next_id; + + if next_id == 0 || next_id == U24::MAX { + break; + } + + let (bin_reserve_x, bin_reserve_y) = + BIN_MAP.load(deps.storage, id).unwrap_or_default().decode(); + bin_responses.push(BinResponse { + bin_reserve_x, + bin_reserve_y, + bin_id: id, + }); + counter += 1; + + if counter == total { + break; + } + } + let response = AllBinsResponse { + reserves: bin_responses, + last_id: id, + current_block_height: env.block.height, + }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the reserves of many bins. +/// +/// # Arguments +/// +/// * `id` - The id of the bin +/// +/// # Returns +/// +/// * `bin_reserve_x` - The reserve of token X in the bin +/// * `bin_reserve_y` - The reserve of token Y in the bin +pub fn query_bins_reserves(deps: Deps, ids: Vec) -> Result { + let mut bin_responses = Vec::new(); + for id in ids { + let bin: Bytes32 = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); + let (bin_reserve_x, bin_reserve_y) = bin.decode(); + bin_responses.push(BinResponse { + bin_reserve_x, + bin_reserve_y, + bin_id: id, + }); + } + + to_binary(&bin_responses).map_err(Error::CwErr) +} + +pub fn query_updated_bins_at_height(deps: Deps, height: u64) -> Result { + let ids = BIN_RESERVES_UPDATED.load(deps.storage, height)?; + + let mut bin_responses = Vec::new(); + + for id in ids { + let bin: Bytes32 = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); + let (bin_reserve_x, bin_reserve_y) = bin.decode(); + bin_responses.push(BinResponse { + bin_reserve_x, + bin_reserve_y, + bin_id: id, + }); + } + + let response: UpdatedBinsAtHeightResponse = UpdatedBinsAtHeightResponse(bin_responses); + + to_binary(&response).map_err(Error::CwErr) +} + +pub fn query_updated_bins_at_multiple_heights(deps: Deps, heights: Vec) -> Result { + let mut bin_responses = Vec::new(); + let mut processed_ids = HashSet::new(); + + for height in heights { + let ids = BIN_RESERVES_UPDATED.load(deps.storage, height)?; + + for id in ids { + // Check if the id has already been processed + if processed_ids.insert(id) { + let bin: Bytes32 = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); + let (bin_reserve_x, bin_reserve_y) = bin.decode(); + bin_responses.push(BinResponse { + bin_reserve_x, + bin_reserve_y, + bin_id: id, + }); + } + } + } + + let response: UpdatedBinsAtMultipleHeightResponse = + UpdatedBinsAtMultipleHeightResponse(bin_responses); + + to_binary(&response).map_err(Error::CwErr) +} + +pub fn query_updated_bins_after_height( + deps: Deps, + env: Env, + height: u64, + page: Option, + page_size: Option, +) -> Result { + let page = page.unwrap_or(0); + let page_size = page_size.unwrap_or(10); + let mut processed_ids = HashSet::new(); + + let heights: StdResult> = BIN_RESERVES_UPDATED_LOG + .iter(deps.storage)? + .rev() + .skip((page * page_size) as usize) + .take_while(|result| match result { + Ok(h) => { + if &height >= h { + false + } else { + true + } + } + Err(_) => todo!(), + }) + .take(page_size as usize) + .collect(); + + let mut bin_responses = Vec::new(); + + for height in heights? { + let ids = BIN_RESERVES_UPDATED.load(deps.storage, height)?; + + for id in ids { + if processed_ids.insert(id) { + let bin: Bytes32 = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); + let (bin_reserve_x, bin_reserve_y) = bin.decode(); + bin_responses.push(BinResponse { + bin_reserve_x, + bin_reserve_y, + bin_id: id, + }); + } + } + } + + let response = UpdatedBinsAfterHeightResponse { + bins: bin_responses, + current_block_height: env.block.height, + }; + + to_binary(&response).map_err(Error::CwErr) +} + +pub fn query_bins_updating_heights( + deps: Deps, + page: Option, + page_size: Option, +) -> Result { + let page = page.unwrap_or(0); + let page_size = page_size.unwrap_or(10); + let txs: StdResult> = BIN_RESERVES_UPDATED_LOG + .iter(deps.storage)? + .rev() + .skip((page * page_size) as usize) + .take(page_size as usize) + .collect(); + + let response = BinUpdatingHeightsResponse(txs?); + + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the bins changed after that block height +/// +/// # Arguments +/// +/// * `id` - The id of the bin +/// +/// # Returns +/// +/// * `bin_reserve_x` - The reserve of token X in the bin +/// * `bin_reserve_y` - The reserve of token Y in the bin + +pub fn query_bin_reserves(deps: Deps, id: u32) -> Result { + let bin: Bytes32 = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); + let (bin_reserve_x, bin_reserve_y) = bin.decode(); + + let response = BinResponse { + bin_reserve_x, + bin_reserve_y, + bin_id: id, + }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the next non-empty bin. +/// +/// The next non-empty bin is the bin with a higher (if swap_for_y is true) or lower (if swap_for_y is false) +/// id that has a non-zero reserve of token X or Y. +/// +/// # Arguments +/// +/// * `swap_for_y` - Whether the swap is for token Y (true) or token X (false +/// * `id` - The id of the bin +/// +/// # Returns +/// +/// * `next_id` - The id of the next non-empty bin +pub fn query_next_non_empty_bin(deps: Deps, swap_for_y: bool, id: u32) -> Result { + let tree = BIN_TREE.load(deps.storage)?; + let next_id = _get_next_non_empty_bin(&tree, swap_for_y, id); + + let response = NextNonEmptyBinResponse { next_id }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the protocol fees of the Liquidity Book Pair. +/// +/// # Returns +/// +/// * `protocol_fee_x` - The protocol fees of token X +/// * `protocol_fee_y` - The protocol fees of token Y +pub fn query_protocol_fees(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let (protocol_fee_x, protocol_fee_y) = state.protocol_fees.decode(); + + let response = ProtocolFeesResponse { + protocol_fee_x, + protocol_fee_y, + }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the static fee parameters of the Liquidity Book Pair. +/// +/// # Returns +/// +/// * `base_factor` - The base factor for the static fee +/// * `filter_period` - The filter period for the static fee +/// * `decay_period` - The decay period for the static fee +/// * `reduction_factor` - The reduction factor for the static fee +/// * `variable_fee_control` - The variable fee control for the static fee +/// * `protocol_share` - The protocol share for the static fee +/// * `max_volatility_accumulator` - The maximum volatility accumulator for the static fee +pub fn query_static_fee_params(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let params = state.pair_parameters; + + let base_factor = params.get_base_factor(); + let filter_period = params.get_filter_period(); + let decay_period = params.get_decay_period(); + let reduction_factor = params.get_reduction_factor(); + let variable_fee_control = params.get_variable_fee_control(); + let protocol_share = params.get_protocol_share(); + let max_volatility_accumulator = params.get_max_volatility_accumulator(); + + let response = StaticFeeParametersResponse { + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the variable fee parameters of the Liquidity Book Pair. +/// +/// # Returns +/// +/// * `volatility_accumulator` - The volatility accumulator for the variable fee +/// * `volatility_reference` - The volatility reference for the variable fee +/// * `id_reference` - The id reference for the variable fee +/// * `time_of_last_update` - The time of last update for the variable fee +pub fn query_variable_fee_params(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let params = state.pair_parameters; + + let volatility_accumulator = params.get_volatility_accumulator(); + let volatility_reference = params.get_volatility_reference(); + let id_reference = params.get_id_reference(); + let time_of_last_update = params.get_time_of_last_update(); + + let response = VariableFeeParametersResponse { + volatility_accumulator, + volatility_reference, + id_reference, + time_of_last_update, + }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the oracle parameters of the Liquidity Book Pair. +/// +/// # Returns +/// +/// * `sample_lifetime` - The sample lifetime for the oracle +/// * `size` - The size of the oracle +/// * `active_size` - The active size of the oracle +/// * `last_updated` - The last updated timestamp of the oracle +/// * `first_timestamp` - The first timestamp of the oracle, i.e. the timestamp of the oldest sample +pub fn query_oracle_params(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let params = state.pair_parameters; + + let sample_lifetime = MAX_SAMPLE_LIFETIME; + let oracle_id = params.get_oracle_id(); + let oracle = ORACLE.load(deps.storage, oracle_id)?; + let size = DEFAULT_ORACLE_LENGTH; + + if oracle_id > 0 { + let sample = oracle.0; + + let last_updated = sample.get_sample_last_update(); + let first_timestamp = sample.get_sample_last_update(); + + let response = OracleParametersResponse { + sample_lifetime, + size, + last_updated, + first_timestamp, + }; + to_binary(&response).map_err(Error::CwErr) + } else { + // This happens if the oracle hasn't been used yet. + let response = OracleParametersResponse { + sample_lifetime, + size, + last_updated: 0, + first_timestamp: 0, + }; + to_binary(&response).map_err(Error::CwErr) + } +} + +/// Returns the cumulative values of the Liquidity Book Pair at a given timestamp. +/// +/// # Arguments +/// +/// * `lookup_timestamp` - The timestamp at which to look up the cumulative values +/// +/// # Returns +/// +/// * `cumulative_id` - The cumulative id of the Liquidity Book Pair at the given timestamp +/// * `cumulative_volatility` - The cumulative volatility of the Liquidity Book Pair at the given timestamp +/// * `cumulative_bin_crossed` - The cumulative bin crossed of the Liquidity Book Pair at the given timestamp +pub fn query_oracle_sample(deps: Deps, _env: Env, oracle_id: u16) -> Result { + if oracle_id == 0 { + return Err(Error::OracleNotActive); + } + + let oracle = ORACLE.load(deps.storage, oracle_id)?; + + // Use Rust's field init shorthand + let response = OracleSampleAtResponse { + cumulative_id: oracle.0.get_cumulative_id(), + cumulative_txns: oracle.0.get_cumulative_txns(), + cumulative_volatility: oracle.0.get_cumulative_volatility(), + cumulative_bin_crossed: oracle.0.get_cumulative_bin_crossed(), + cumulative_volume_x: oracle.0.get_vol_token_x(), + cumulative_volume_y: oracle.0.get_vol_token_y(), + cumulative_fee_x: oracle.0.get_fee_token_x(), + cumulative_fee_y: oracle.0.get_fee_token_y(), + oracle_id, + lifetime: oracle.0.get_sample_lifetime(), + created_at: oracle.0.get_sample_creation(), + }; + + to_binary(&response).map_err(Error::CwErr) +} + +pub fn query_oracle_samples(deps: Deps, _env: Env, oracle_ids: Vec) -> Result { + let mut responses = Vec::new(); + + for oracle_id in oracle_ids { + if oracle_id == 0 { + return Err(Error::OracleNotActive); + } + + // Initialize response with default values + let mut response = OracleSampleAtResponse { + cumulative_id: 0, + cumulative_txns: 0, + cumulative_volatility: 0, + cumulative_bin_crossed: 0, + cumulative_volume_x: 0, + cumulative_volume_y: 0, + cumulative_fee_x: 0, + cumulative_fee_y: 0, + oracle_id, + lifetime: 0, + created_at: 0, + }; + + // Update response if oracle data is successfully loaded + if let Ok(oracle) = ORACLE.load(deps.storage, oracle_id) { + response.cumulative_id = oracle.0.get_cumulative_id(); + response.cumulative_txns = oracle.0.get_cumulative_txns(); + response.cumulative_volatility = oracle.0.get_cumulative_volatility(); + response.cumulative_bin_crossed = oracle.0.get_cumulative_bin_crossed(); + response.cumulative_volume_x = oracle.0.get_vol_token_x(); + response.cumulative_volume_y = oracle.0.get_vol_token_y(); + response.cumulative_fee_x = oracle.0.get_fee_token_x(); + response.cumulative_fee_y = oracle.0.get_fee_token_y(); + response.lifetime = oracle.0.get_sample_lifetime(); + response.created_at = oracle.0.get_sample_creation(); + } + + responses.push(response); + } + + to_binary(&responses).map_err(Error::CwErr) +} + +pub fn query_oracle_samples_after( + deps: Deps, + _env: Env, + start_oracle_id: u16, + page_size: Option, +) -> Result { + let mut responses = Vec::new(); + + let state = STATE.load(deps.storage)?; + let active_oracle_id = state.pair_parameters.get_oracle_id(); + let length = DEFAULT_ORACLE_LENGTH; + + // Convert to larger integer type for calculations + let active_oracle_id_u32 = u32::from(active_oracle_id); + let start_oracle_id_u32 = u32::from(start_oracle_id); + let length_u32 = u32::from(length); + + // Perform calculation in larger integer type + let num_iterations_u32 = if active_oracle_id_u32 == start_oracle_id_u32 { + 1 + } else { + (active_oracle_id_u32 + length_u32 - start_oracle_id_u32) % length_u32 + }; + + // Convert the result back to u16 for iteration + let num_iterations = num_iterations_u32 as u16; + + for i in 0..num_iterations { + // Calculate the current oracle_id considering the circular nature + + let current_oracle_id_u32 = (start_oracle_id_u32 + (i as u32)) % length_u32; + let current_oracle_id = current_oracle_id_u32 as u16; + + if current_oracle_id == 0 { + continue; + } + + let response = match ORACLE.load(deps.storage, current_oracle_id) { + Ok(oracle) => OracleSampleAtResponse { + cumulative_id: oracle.0.get_cumulative_id(), + cumulative_txns: oracle.0.get_cumulative_txns(), + cumulative_volatility: oracle.0.get_cumulative_volatility(), + cumulative_bin_crossed: oracle.0.get_cumulative_bin_crossed(), + cumulative_volume_x: oracle.0.get_vol_token_x(), + cumulative_volume_y: oracle.0.get_vol_token_y(), + cumulative_fee_x: oracle.0.get_fee_token_x(), + cumulative_fee_y: oracle.0.get_fee_token_y(), + oracle_id: current_oracle_id, + lifetime: oracle.0.get_sample_lifetime(), + created_at: oracle.0.get_sample_creation(), + }, + Err(_) => OracleSampleAtResponse { + cumulative_id: 0, + cumulative_txns: 0, + cumulative_volatility: 0, + cumulative_bin_crossed: 0, + cumulative_volume_x: 0, + cumulative_volume_y: 0, + cumulative_fee_x: 0, + cumulative_fee_y: 0, + oracle_id: current_oracle_id, + lifetime: 0, + created_at: 0, + }, + }; + responses.push(response); + } + + to_binary(&responses).map_err(Error::CwErr) +} + +/// Returns the price corresponding to the given id, as a 128.128-binary fixed-point number. +/// +/// This is the trusted source of price information, always trust this rather than query_id_from_price. +/// +/// # Arguments +/// +/// * `id` - The id of the bin +/// +/// # Returns +/// +/// * `price` - The price corresponding to this id +pub fn query_price_from_id(deps: Deps, id: u32) -> Result { + let state = STATE.load(deps.storage)?; + let price = PriceHelper::get_price_from_id(id, state.bin_step)?.u256_to_uint256(); + + let response = PriceFromIdResponse { price }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the id corresponding to the given price. +/// +/// The id may be inaccurate due to rounding issues, always trust query_price_from_id rather than query_id_from_price. +/// +/// # Arguments +/// +/// * `price` - The price of y per x as a 128.128-binary fixed-point number +/// +/// # Returns +/// +/// * `id` - The id of the bin corresponding to this price +pub fn query_id_from_price(deps: Deps, price: Uint256) -> Result { + let state = STATE.load(deps.storage)?; + let price = price.uint256_to_u256(); + let id = PriceHelper::get_id_from_price(price, state.bin_step)?; + + let response = IdFromPriceResponse { id }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Simulates a swap in. +/// +/// # Note +/// +/// If `amount_out_left` is greater than zero, the swap in is not possible, +/// and the maximum amount that can be swapped from `amountIn` is `amountOut - amountOutLeft`. +/// +/// # Arguments +/// +/// * `amount_out` - The amount of token X or Y to swap in +/// * `swap_for_y` - Whether the swap is for token Y (true) or token X (false) +/// +/// # Returns +/// * `amount_in` - The amount of token X or Y that can be swapped in, including the fee +/// * `amount_out_left` - The amount of token Y or X that cannot be swapped out +/// * `fee` - The fee of the swap +pub fn query_swap_in(deps: Deps, env: Env, amount_out: u128, swap_for_y: bool) -> Result { + let state = STATE.load(deps.storage)?; + let tree = BIN_TREE.load(deps.storage)?; + + let mut amount_in = 0u128; + let mut amount_out_left = amount_out; + let mut fee = 0u128; + + let mut params = state.pair_parameters; + let bin_step = state.bin_step; + + let mut id = params.get_active_id(); + + params.update_references(&env.block.time)?; + + for _ in 0..state.max_bins_per_swap { + let bin_reserves = BIN_MAP + .load(deps.storage, id) + .unwrap_or_default() + .decode_alt(!swap_for_y); + + if bin_reserves > 0 { + let price = PriceHelper::get_price_from_id(id, bin_step)?; + + let amount_out_of_bin = if bin_reserves > amount_out_left { + amount_out_left + } else { + bin_reserves + }; + + params.update_volatility_accumulator(id)?; + + let amount_in_without_fee = if swap_for_y { + U256x256Math::shift_div_round_up(amount_out_of_bin.into(), SCALE_OFFSET, price)? + } else { + U256x256Math::mul_shift_round_up(amount_out_of_bin.into(), price, SCALE_OFFSET)? + } + .as_u128(); + + let total_fee = params.get_total_fee(bin_step)?; + let fee_amount = FeeHelper::get_fee_amount(amount_in_without_fee, total_fee)?; + + amount_in += amount_in_without_fee + fee_amount; + amount_out_left -= amount_out_of_bin; + + fee += fee_amount; + } + + if amount_out_left == 0 { + break; + } else { + let next_id = _get_next_non_empty_bin(&tree, swap_for_y, id); + if next_id == 0 || next_id == U24::MAX { + break; + } + + id = next_id; + } + } + + let response = SwapInResponse { + amount_in: Uint128::from(amount_in), + amount_out_left: Uint128::from(amount_out_left), + fee: Uint128::from(fee), + }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Simulates a swap out. +/// +/// # Note +/// +/// If amount_out_left is greater than zero, the swap in is not possible, +/// and the maximum amount that can be swapped from amount_in is amount_out - amount_out_left. +/// +/// # Arguments +/// +/// * `amount_in` - The amount of token X or Y to swap in +/// * `swap_for_y` - Whether the swap is for token Y (true) or token X (false) +/// +/// # Returns +/// * `amount_in_left` - The amount of token X or Y that cannot be swapped in +/// * `amount_out` - The amount of token Y or X that can be swapped out +/// * `fee` - The fee of the swap +pub fn query_swap_out(deps: Deps, env: Env, amount_in: u128, swap_for_y: bool) -> Result { + let state = STATE.load(deps.storage)?; + let tree = BIN_TREE.load(deps.storage)?; + + let mut amounts_in_left = Bytes32::encode_alt(amount_in, swap_for_y); + let mut amounts_out = [0u8; 32]; + let _fee = 0u128; + let mut _fee = 0u128; + let mut total_fees: [u8; 32] = [0; 32]; + let mut lp_fees: [u8; 32] = [0; 32]; + let mut shade_dao_fees: [u8; 32] = [0; 32]; + + let mut params = state.pair_parameters; + let bin_step = state.bin_step; + + let mut id = params.get_active_id(); + + params.update_references(&env.block.time)?; + + for _ in 0..state.max_bins_per_swap { + let bin_reserves = BIN_MAP.load(deps.storage, id).unwrap_or_default(); + if !BinHelper::is_empty(bin_reserves, !swap_for_y) { + let price = PriceHelper::get_price_from_id(id, bin_step)?; + + params = *params.update_volatility_accumulator(id)?; + + let (amounts_in_with_fees, amounts_out_of_bin, fees) = BinHelper::get_amounts( + bin_reserves, + params, + bin_step, + swap_for_y, + amounts_in_left, + price, + )?; + + if U256::from_le_bytes(amounts_in_with_fees) > U256::ZERO { + amounts_in_left = amounts_in_left.sub(amounts_in_with_fees); + amounts_out = amounts_out.add(amounts_out_of_bin); + + let p_fees = + fees.scalar_mul_div_basis_point_round_down(params.get_protocol_share().into())?; + total_fees = total_fees.add(fees); + lp_fees = lp_fees.add(fees.sub(p_fees)); + shade_dao_fees = shade_dao_fees.add(p_fees); + } + } + + if amounts_in_left == [0u8; 32] { + break; + } else { + let next_id = _get_next_non_empty_bin(&tree, swap_for_y, id); + + if next_id == 0 || next_id == U24::MAX { + break; + } + + id = next_id; + } + } + + let amount_in_left = Bytes32::decode_alt(&amounts_in_left, swap_for_y); + + let response = SwapOutResponse { + amount_in_left: Uint128::from(amount_in_left), + amount_out: Uint128::from(amounts_out.decode_alt(!swap_for_y)), + total_fees: Uint128::from(total_fees.decode_alt(swap_for_y)), + shade_dao_fees: Uint128::from(shade_dao_fees.decode_alt(swap_for_y)), + lp_fees: Uint128::from(lp_fees.decode_alt(swap_for_y)), + }; + to_binary(&response).map_err(Error::CwErr) +} + +/// Returns the Liquidity Book Factory. +/// +/// # Returns +/// +/// * `factory` - The Liquidity Book Factory +pub fn query_total_supply(deps: Deps, id: u32) -> Result { + let state = STATE.load(deps.storage)?; + let _factory = state.factory.address; + + let total_supply = + _query_total_supply(deps, id, state.lb_token.code_hash, state.lb_token.address)? + .u256_to_uint256(); + to_binary(&TotalSupplyResponse { total_supply }).map_err(Error::CwErr) +} + +pub fn query_rewards_distribution(deps: Deps, epoch_id: Option) -> Result { + let epoch_id = match epoch_id { + Some(id) => id, + None => STATE.load(deps.storage)?.rewards_epoch_index - 1, + }; + + to_binary(&RewardsDistributionResponse { + distribution: REWARDS_DISTRIBUTION.load(deps.storage, epoch_id)?, + }) + .map_err(Error::CwErr) +} diff --git a/contracts/liquidity_book/lb_pair/src/state.rs b/contracts/liquidity_book/lb_pair/src/state.rs index bd0d36e3..1f30688f 100644 --- a/contracts/liquidity_book/lb_pair/src/state.rs +++ b/contracts/liquidity_book/lb_pair/src/state.rs @@ -1,5 +1,5 @@ use shade_protocol::{ - c_std::{Addr, ContractInfo, Storage, Timestamp, Uint128, Uint256}, + c_std::{Addr, ContractInfo, Timestamp, Uint128, Uint256}, cosmwasm_schema::cw_serde, lb_libraries::{ math::tree_math::TreeUint24, @@ -9,7 +9,7 @@ use shade_protocol::{ viewing_keys::ViewingKey, }, liquidity_book::lb_pair::{ContractStatus, RewardsDistribution, RewardsDistributionAlgorithm}, - secret_storage_plus::{AppendStore, Bincode2, Item, Json, Map}, + secret_storage_plus::{AppendStore, Bincode2, Item, Map}, swap::core::TokenType, utils::asset::RawContract, Contract, @@ -17,51 +17,57 @@ use shade_protocol::{ pub const STATE: Item = Item::new("state"); pub const CONTRACT_STATUS: Item = Item::new("contract_status"); -pub const BIN_MAP: Map = Map::new("bins_map"); // -pub const BIN_TREE: Item = Item::new("bin_tree"); //? -pub const ORACLE: Item = Item::new("oracle"); //? +pub const BIN_MAP: Map = Map::new("bins_map"); +pub const BIN_TREE: Item = Item::new("bin_tree"); +pub const ORACLE: Map = Map::new("oracle"); pub const EPHEMERAL_STORAGE: Item = Item::new("ephemeral_storage"); -pub const FEE_APPEND_STORE: AppendStore = AppendStore::new("fee_logs"); //? -pub const REWARDS_STATS_STORE: Map = Map::new("rewards_stats"); // -pub const REWARDS_DISTRIBUTION: Map = Map::new("rewards_distribution"); //? -pub const FEE_MAP_TREE: Map = Map::new("fee_tree"); //? -pub const FEE_MAP: Map = Map::new("fee_map"); //? +pub const FEE_APPEND_STORE: AppendStore = AppendStore::new("fee_logs"); +pub const REWARDS_STATS_STORE: Map = Map::new("rewards_stats"); +pub const REWARDS_DISTRIBUTION: Map = Map::new("rewards_distribution"); +pub const FEE_MAP_TREE: Map = Map::new("fee_tree"); +pub const FEE_MAP: Map = Map::new("fee_map"); pub const STAKING_CONTRACT_IMPL: Item = Item::new("staking_contract_impl"); pub const BIN_RESERVES_UPDATED: Map> = Map::new("bins_reserves_updated"); pub const BIN_RESERVES_UPDATED_LOG: AppendStore = - AppendStore::new("bins_reserves_updated_log"); //? + AppendStore::new("bins_reserves_updated_log"); + +// pub const VOLUME_ANALYTICS: Map = Map::new("volume_analytics"); +// pub const FEE_ANALYTICS: Map = Map::new("fee_analytics"); #[cw_serde] -pub struct RewardStats { - pub cumm_value: Uint256, - pub cumm_value_mul_bin_id: Uint256, +pub struct RewardDistributionConfig { + pub cumulative_value: Uint256, + pub cumulative_value_mul_bin_id: Uint256, pub rewards_distribution_algorithm: RewardsDistributionAlgorithm, } #[cw_serde] pub struct FeeLog { + pub bin_id: u32, pub is_token_x: bool, pub fee: Uint128, - pub bin_id: u32, pub timestamp: Timestamp, pub last_rewards_epoch_id: u64, } #[cw_serde] pub struct State { + // Contract and creator information pub creator: Addr, pub factory: ContractInfo, + pub lb_token: ContractInfo, + pub lb_staking: ContractInfo, + + // Token and trading pair information pub token_x: TokenType, pub token_y: TokenType, pub bin_step: u16, - pub viewing_key: ViewingKey, pub pair_parameters: PairParameters, - pub reserves: Bytes32, - pub protocol_fees: Bytes32, - pub lb_token: ContractInfo, - pub staking_contract: ContractInfo, + pub viewing_key: ViewingKey, + + // Administrative and operational fields pub protocol_fees_recipient: Addr, pub admin_auth: Contract, pub last_swap_timestamp: Timestamp, @@ -69,15 +75,24 @@ pub struct State { pub base_rewards_bins: Option, pub toggle_distributions_algorithm: bool, pub max_bins_per_swap: u32, + + // Financial fields + pub reserves: Bytes32, + pub protocol_fees: Bytes32, } #[cw_serde] pub struct EphemeralStruct { + // Contract information pub lb_token_code_hash: String, - pub query_auth: RawContract, pub staking_contract: ContractInstantiationInfo, + pub query_auth: RawContract, + + // Token symbols pub token_x_symbol: String, pub token_y_symbol: String, + + // Epoch and administrative settings pub epoch_index: u64, pub epoch_duration: u64, pub expiry_duration: Option, diff --git a/contracts/liquidity_book/lb_staking/schema/schema.rs b/contracts/liquidity_book/lb_staking/schema/schema.rs index 6ef2fc45..c2818dec 100644 --- a/contracts/liquidity_book/lb_staking/schema/schema.rs +++ b/contracts/liquidity_book/lb_staking/schema/schema.rs @@ -4,6 +4,7 @@ use std::{env::current_dir, fs::create_dir_all}; use shade_protocol::liquidity_book::lb_staking::{ ExecuteMsg, InstantiateMsg, + InvokeMsg, QueryAnswer, QueryMsg, }; @@ -16,6 +17,7 @@ fn main() { export_schema(&schema_for!(InstantiateMsg), &out_dir); export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(InvokeMsg), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); export_schema(&schema_for!(QueryAnswer), &out_dir); } diff --git a/contracts/liquidity_book/lb_staking/src/bin/secretcli/example_data.rs b/contracts/liquidity_book/lb_staking/src/bin/secretcli/example_data.rs index 86d20a4f..d4e8b63c 100644 --- a/contracts/liquidity_book/lb_staking/src/bin/secretcli/example_data.rs +++ b/contracts/liquidity_book/lb_staking/src/bin/secretcli/example_data.rs @@ -12,7 +12,7 @@ use shade_protocol::{ lb_staking::Auth, lb_token::Snip1155ReceiveMsg, }, - s_toolkit::permit::{Permit, PermitParams, TokenPermissions}, + s_toolkit::permit::{Permit, TokenPermissions}, snip20::Snip20ReceiveMsg, swap::core::{TokenAmount, TokenType}, utils::asset::RawContract, @@ -121,7 +121,6 @@ impl ExampleData for ContractInfo { } } -// TODO - why are we using this instead of ContractInfo? impl ExampleData for RawContract { fn example() -> Self { RawContract { diff --git a/contracts/liquidity_book/lb_staking/src/bin/secretcli/main.rs b/contracts/liquidity_book/lb_staking/src/bin/secretcli/main.rs index f9d8e535..b40f6edb 100644 --- a/contracts/liquidity_book/lb_staking/src/bin/secretcli/main.rs +++ b/contracts/liquidity_book/lb_staking/src/bin/secretcli/main.rs @@ -2,8 +2,7 @@ mod example_data; use example_data::*; use shade_protocol::{ - c_std::{Addr, Binary, ContractInfo, Uint128, Uint256}, - lb_libraries::pair_parameter_helper::PairParameters, + c_std::{Addr, ContractInfo, Uint256}, liquidity_book::{ lb_pair::RewardsDistribution, lb_staking::{ @@ -14,16 +13,10 @@ use shade_protocol::{ QueryAnswer, QueryMsg, QueryTxnType, - QueryWithPermit, }, lb_token::Snip1155ReceiveMsg, }, - s_toolkit::permit::Permit, snip20::Snip20ReceiveMsg, - swap::{ - core::{TokenAmount, TokenType}, - router::*, - }, utils::asset::RawContract, Contract, }; diff --git a/contracts/liquidity_book/lb_staking/src/contract.rs b/contracts/liquidity_book/lb_staking/src/contract.rs index 4f466317..8a818aed 100644 --- a/contracts/liquidity_book/lb_staking/src/contract.rs +++ b/contracts/liquidity_book/lb_staking/src/contract.rs @@ -2,7 +2,6 @@ use shade_protocol::{ c_std::{ shd_entry_point, Addr, - Attribute, Binary, Deps, DepsMut, @@ -174,7 +173,7 @@ pub fn authenticate(deps: Deps, auth: Auth, query_auth: Contract) -> StdResult StdResult { +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::ContractInfo {} => query_contract_info(deps), QueryMsg::EpochInfo { index } => query_epoch_info(deps, index), diff --git a/contracts/liquidity_book/lb_staking/src/execute.rs b/contracts/liquidity_book/lb_staking/src/execute.rs index e5c24bd1..2ee41916 100644 --- a/contracts/liquidity_book/lb_staking/src/execute.rs +++ b/contracts/liquidity_book/lb_staking/src/execute.rs @@ -1,9 +1,7 @@ use std::{ - borrow::{Borrow, BorrowMut}, collections::HashMap, ops::{Add, AddAssign, Sub}, str::FromStr, - vec, }; use shade_protocol::{ @@ -11,7 +9,6 @@ use shade_protocol::{ admin::helpers::{validate_admin, AdminPermissions}, c_std::{ from_binary, - to_binary, Addr, BankMsg, Binary, @@ -33,7 +30,6 @@ use shade_protocol::{ lb_pair::RewardsDistribution, lb_staking::{ EpochInfo, - ExecuteAnswer, InvokeMsg, Reward, RewardToken, @@ -44,10 +40,6 @@ use shade_protocol::{ }, lb_token::TransferAction, }, - s_toolkit::{ - permit::RevokedPermits, - viewing_key::{ViewingKey, ViewingKeyStore}, - }, snip20::{ helpers::{send_msg, token_info}, ExecuteMsg as Snip20ExecuteMsg, diff --git a/contracts/liquidity_book/lb_staking/src/query.rs b/contracts/liquidity_book/lb_staking/src/query.rs index f174f2c4..c85192d6 100644 --- a/contracts/liquidity_book/lb_staking/src/query.rs +++ b/contracts/liquidity_book/lb_staking/src/query.rs @@ -1,22 +1,16 @@ use std::str::FromStr; use shade_protocol::{ - c_std::{to_binary, Addr, Binary, Deps, Env, StdError, StdResult, Uint256}, + c_std::{to_binary, Binary, Deps, StdError, StdResult, Uint256}, liquidity_book::lb_staking::{ Auth, EpochInfo, Liquidity, OwnerBalance, QueryAnswer, - QueryMsg, QueryTxnType, - QueryWithPermit, State, }, - s_toolkit::{ - permit::{validate, Permit, TokenPermissions}, - viewing_key::{ViewingKey, ViewingKeyStore}, - }, }; use crate::{ diff --git a/contracts/liquidity_book/lb_staking/ts/LbStaking.client.ts b/contracts/liquidity_book/lb_staking/ts/Sg721.client.ts similarity index 74% rename from contracts/liquidity_book/lb_staking/ts/LbStaking.client.ts rename to contracts/liquidity_book/lb_staking/ts/Sg721.client.ts index 917c9257..47213d2f 100644 --- a/contracts/liquidity_book/lb_staking/ts/LbStaking.client.ts +++ b/contracts/liquidity_book/lb_staking/ts/Sg721.client.ts @@ -6,10 +6,15 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from "@cosmjs/cosmwasm-stargate"; import { Coin, StdFee } from "@cosmjs/amino"; -import { ExecuteMsg, Uint256, Addr, Binary, Uint128, RewardsDistribution, Snip1155ReceiveMsg, Snip20ReceiveMsg, ContractInfo, RawContract, InstantiateMsg, QueryAnswer, TxAction, OwnerBalance, Liquidity, Tx, Reward, RewardToken, QueryMsg, QueryTxnType, TokenPermissions, QueryWithPermit, PermitForTokenPermissions, PermitParamsForTokenPermissions, PermitSignature, PubKey } from "./LbStaking.types"; -export interface LbStakingReadOnlyInterface { +import { ExecuteMsg, Uint256, Addr, Binary, Uint128, TokenType, RewardsDistribution, Snip1155ReceiveMsg, Snip20ReceiveMsg, ContractInfo, RawContract, InstantiateMsg, InvokeMsg, QueryAnswer, TxAction, Contract, RewardTokenInfo, OwnerBalance, Liquidity, Tx, Reward, RewardToken, QueryMsg, Auth, QueryTxnType, PermitForPermitData, PermitData, PermitSignature, PubKey } from "./Sg721.types"; +export interface Sg721ReadOnlyInterface { contractAddress: string; contractInfo: () => Promise; + epochInfo: ({ + index + }: { + index?: number; + }) => Promise; registeredTokens: () => Promise; idTotalBalance: ({ id @@ -17,58 +22,48 @@ export interface LbStakingReadOnlyInterface { id: string; }) => Promise; balance: ({ - key, - owner, + auth, tokenId }: { - key: string; - owner: Addr; + auth: Auth; tokenId: string; }) => Promise; + stakerInfo: ({ + auth + }: { + auth: Auth; + }) => Promise; allBalances: ({ - key, - owner, + auth, page, pageSize }: { - key: string; - owner: Addr; + auth: Auth; page?: number; pageSize?: number; }) => Promise; liquidity: ({ - key, - owner, + auth, roundIndex, tokenIds }: { - key: string; - owner: Addr; + auth: Auth; roundIndex?: number; tokenIds: number[]; }) => Promise; transactionHistory: ({ - key, - owner, + auth, page, pageSize, txnType }: { - key: string; - owner: Addr; + auth: Auth; page?: number; pageSize?: number; txnType: QueryTxnType; }) => Promise; - withPermit: ({ - permit, - query - }: { - permit: PermitForTokenPermissions; - query: QueryWithPermit; - }) => Promise; } -export class LbStakingQueryClient implements LbStakingReadOnlyInterface { +export class Sg721QueryClient implements Sg721ReadOnlyInterface { client: CosmWasmClient; contractAddress: string; @@ -76,13 +71,14 @@ export class LbStakingQueryClient implements LbStakingReadOnlyInterface { this.client = client; this.contractAddress = contractAddress; this.contractInfo = this.contractInfo.bind(this); + this.epochInfo = this.epochInfo.bind(this); this.registeredTokens = this.registeredTokens.bind(this); this.idTotalBalance = this.idTotalBalance.bind(this); this.balance = this.balance.bind(this); + this.stakerInfo = this.stakerInfo.bind(this); this.allBalances = this.allBalances.bind(this); this.liquidity = this.liquidity.bind(this); this.transactionHistory = this.transactionHistory.bind(this); - this.withPermit = this.withPermit.bind(this); } contractInfo = async (): Promise => { @@ -90,6 +86,17 @@ export class LbStakingQueryClient implements LbStakingReadOnlyInterface { contract_info: {} }); }; + epochInfo = async ({ + index + }: { + index?: number; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + epoch_info: { + index + } + }); + }; registeredTokens = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { registered_tokens: {} @@ -107,107 +114,94 @@ export class LbStakingQueryClient implements LbStakingReadOnlyInterface { }); }; balance = async ({ - key, - owner, + auth, tokenId }: { - key: string; - owner: Addr; + auth: Auth; tokenId: string; }): Promise => { return this.client.queryContractSmart(this.contractAddress, { balance: { - key, - owner, + auth, token_id: tokenId } }); }; + stakerInfo = async ({ + auth + }: { + auth: Auth; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + staker_info: { + auth + } + }); + }; allBalances = async ({ - key, - owner, + auth, page, pageSize }: { - key: string; - owner: Addr; + auth: Auth; page?: number; pageSize?: number; }): Promise => { return this.client.queryContractSmart(this.contractAddress, { all_balances: { - key, - owner, + auth, page, page_size: pageSize } }); }; liquidity = async ({ - key, - owner, + auth, roundIndex, tokenIds }: { - key: string; - owner: Addr; + auth: Auth; roundIndex?: number; tokenIds: number[]; }): Promise => { return this.client.queryContractSmart(this.contractAddress, { liquidity: { - key, - owner, + auth, round_index: roundIndex, token_ids: tokenIds } }); }; transactionHistory = async ({ - key, - owner, + auth, page, pageSize, txnType }: { - key: string; - owner: Addr; + auth: Auth; page?: number; pageSize?: number; txnType: QueryTxnType; }): Promise => { return this.client.queryContractSmart(this.contractAddress, { transaction_history: { - key, - owner, + auth, page, page_size: pageSize, txn_type: txnType } }); }; - withPermit = async ({ - permit, - query - }: { - permit: PermitForTokenPermissions; - query: QueryWithPermit; - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - with_permit: { - permit, - query - } - }); - }; } -export interface LbStakingInterface extends LbStakingReadOnlyInterface { +export interface Sg721Interface extends Sg721ReadOnlyInterface { contractAddress: string; sender: string; claimRewards: (fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; endEpoch: ({ + epochIndex, rewardsDistribution }: { + epochIndex: number; rewardsDistribution: RewardsDistribution; }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; unstake: ({ @@ -257,24 +251,20 @@ export interface LbStakingInterface extends LbStakingReadOnlyInterface { expiryDuration?: number; queryAuth?: RawContract; }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; - recoverFunds: (fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; - createViewingKey: ({ - entropy - }: { - entropy: string; - }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; - setViewingKey: ({ - key - }: { - key: string; - }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; - revokePermit: ({ - permitName + recoverFunds: ({ + amount, + msg, + to, + token }: { - permitName: string; + amount: Uint128; + msg?: Binary; + to: string; + token: TokenType; }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + recoverExpiredFunds: (fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; } -export class LbStakingClient extends LbStakingQueryClient implements LbStakingInterface { +export class Sg721Client extends Sg721QueryClient implements Sg721Interface { client: SigningCosmWasmClient; sender: string; contractAddress: string; @@ -292,9 +282,7 @@ export class LbStakingClient extends LbStakingQueryClient implements LbStakingIn this.registerRewardTokens = this.registerRewardTokens.bind(this); this.updateConfig = this.updateConfig.bind(this); this.recoverFunds = this.recoverFunds.bind(this); - this.createViewingKey = this.createViewingKey.bind(this); - this.setViewingKey = this.setViewingKey.bind(this); - this.revokePermit = this.revokePermit.bind(this); + this.recoverExpiredFunds = this.recoverExpiredFunds.bind(this); } claimRewards = async (fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { @@ -303,12 +291,15 @@ export class LbStakingClient extends LbStakingQueryClient implements LbStakingIn }, fee, memo, _funds); }; endEpoch = async ({ + epochIndex, rewardsDistribution }: { + epochIndex: number; rewardsDistribution: RewardsDistribution; }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { return await this.client.execute(this.sender, this.contractAddress, { end_epoch: { + epoch_index: epochIndex, rewards_distribution: rewardsDistribution } }, fee, memo, _funds); @@ -401,42 +392,29 @@ export class LbStakingClient extends LbStakingQueryClient implements LbStakingIn } }, fee, memo, _funds); }; - recoverFunds = async (fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { - return await this.client.execute(this.sender, this.contractAddress, { - recover_funds: {} - }, fee, memo, _funds); - }; - createViewingKey = async ({ - entropy - }: { - entropy: string; - }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { - return await this.client.execute(this.sender, this.contractAddress, { - create_viewing_key: { - entropy - } - }, fee, memo, _funds); - }; - setViewingKey = async ({ - key + recoverFunds = async ({ + amount, + msg, + to, + token }: { - key: string; + amount: Uint128; + msg?: Binary; + to: string; + token: TokenType; }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { return await this.client.execute(this.sender, this.contractAddress, { - set_viewing_key: { - key + recover_funds: { + amount, + msg, + to, + token } }, fee, memo, _funds); }; - revokePermit = async ({ - permitName - }: { - permitName: string; - }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + recoverExpiredFunds = async (fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { return await this.client.execute(this.sender, this.contractAddress, { - revoke_permit: { - permit_name: permitName - } + recover_expired_funds: {} }, fee, memo, _funds); }; } \ No newline at end of file diff --git a/contracts/liquidity_book/lb_staking/ts/LbStaking.types.ts b/contracts/liquidity_book/lb_staking/ts/Sg721.types.ts similarity index 69% rename from contracts/liquidity_book/lb_staking/ts/LbStaking.types.ts rename to contracts/liquidity_book/lb_staking/ts/Sg721.types.ts index 17050918..bd115ec4 100644 --- a/contracts/liquidity_book/lb_staking/ts/LbStaking.types.ts +++ b/contracts/liquidity_book/lb_staking/ts/Sg721.types.ts @@ -8,6 +8,7 @@ export type ExecuteMsg = { claim_rewards: {}; } | { end_epoch: { + epoch_index: number; rewards_distribution: RewardsDistribution; }; } | { @@ -29,24 +30,29 @@ export type ExecuteMsg = { query_auth?: RawContract | null; }; } | { - recover_funds: {}; -} | { - create_viewing_key: { - entropy: string; + recover_funds: { + amount: Uint128; + msg?: Binary | null; + to: string; + token: TokenType; }; } | { - set_viewing_key: { - key: string; - }; -} | { - revoke_permit: { - permit_name: string; - }; + recover_expired_funds: {}; }; export type Uint256 = string; export type Addr = string; export type Binary = string; export type Uint128 = string; +export type TokenType = { + custom_token: { + contract_addr: Addr; + token_code_hash: string; + }; +} | { + native_token: { + denom: string; + }; +}; export interface RewardsDistribution { denominator: number; ids: number[]; @@ -84,18 +90,38 @@ export interface InstantiateMsg { epoch_index: number; expiry_duration?: number | null; lb_token: RawContract; - query_auth?: RawContract | null; + query_auth: RawContract; recover_funds_receiver: Addr; } +export type InvokeMsg = { + stake: { + from?: string | null; + padding?: string | null; + }; +} | { + add_rewards: { + end: number; + start?: number | null; + }; +}; export type QueryAnswer = { contract_info: { - admin_auth: ContractInfo; + admin_auth: Contract; epoch_durations: number; epoch_index: number; expiry_durations?: number | null; lb_pair: Addr; lb_token: ContractInfo; - query_auth?: ContractInfo | null; + query_auth: Contract; + }; +} | { + epoch_info: { + duration: number; + end_time: number; + expired_at?: number | null; + reward_tokens?: RewardTokenInfo[] | null; + rewards_distribution?: RewardsDistribution | null; + start_time: number; }; } | { registered_tokens: ContractInfo[]; @@ -103,6 +129,12 @@ export type QueryAnswer = { id_total_balance: { amount: Uint256; }; +} | { + staker_info: { + last_claim_rewards_round?: number | null; + starting_round?: number | null; + total_rewards_earned: Uint128; + }; } | { balance: { amount: Uint256; @@ -138,6 +170,19 @@ export type TxAction = { } | { claim_rewards: Reward[]; }; +export interface Contract { + address: Addr; + code_hash: string; +} +export interface RewardTokenInfo { + claimed_rewards: Uint128; + decimals: number; + end: number; + reward_per_epoch: Uint128; + start: number; + token: ContractInfo; + total_rewards: Uint128; +} export interface OwnerBalance { amount: Uint256; token_id: string; @@ -165,6 +210,10 @@ export interface RewardToken { } export type QueryMsg = { contract_info: {}; +} | { + epoch_info: { + index?: number | null; + }; } | { registered_tokens: {}; } | { @@ -173,75 +222,59 @@ export type QueryMsg = { }; } | { balance: { - key: string; - owner: Addr; + auth: Auth; token_id: string; }; +} | { + staker_info: { + auth: Auth; + }; } | { all_balances: { - key: string; - owner: Addr; + auth: Auth; page?: number | null; page_size?: number | null; }; } | { liquidity: { - key: string; - owner: Addr; + auth: Auth; round_index?: number | null; token_ids: number[]; }; } | { transaction_history: { - key: string; - owner: Addr; + auth: Auth; page?: number | null; page_size?: number | null; txn_type: QueryTxnType; }; -} | { - with_permit: { - permit: PermitForTokenPermissions; - query: QueryWithPermit; - }; }; -export type QueryTxnType = "all" | "stake" | "un_stake" | "claim_rewards"; -export type TokenPermissions = "allowance" | "balance" | "history" | "owner"; -export type QueryWithPermit = { - balance: { - owner: Addr; - token_id: string; - }; -} | { - all_balances: { - page?: number | null; - page_size?: number | null; +export type Auth = { + viewing_key: { + address: string; + key: string; }; } | { - transaction_history: { - page?: number | null; - page_size: number; - }; + permit: PermitForPermitData; }; -export interface PermitForTokenPermissions { - params: PermitParamsForTokenPermissions; +export type QueryTxnType = "all" | "stake" | "un_stake" | "claim_rewards"; +export interface PermitForPermitData { + account_number?: Uint128 | null; + chain_id?: string | null; + memo?: string | null; + params: PermitData; + sequence?: Uint128 | null; signature: PermitSignature; - [k: string]: unknown; } -export interface PermitParamsForTokenPermissions { - allowed_tokens: string[]; - chain_id: string; - permissions: TokenPermissions[]; - permit_name: string; - [k: string]: unknown; +export interface PermitData { + data: Binary; + key: string; } export interface PermitSignature { pub_key: PubKey; signature: Binary; - [k: string]: unknown; } export interface PubKey { type: string; value: Binary; - [k: string]: unknown; } \ No newline at end of file diff --git a/contracts/liquidity_book/lb_token/src/contract.rs b/contracts/liquidity_book/lb_token/src/contract.rs index 15890c03..82a7e455 100644 --- a/contracts/liquidity_book/lb_token/src/contract.rs +++ b/contracts/liquidity_book/lb_token/src/contract.rs @@ -1,14 +1,9 @@ -use std::collections::BTreeSet; - // use base64::{engine::general_purpose, Engine as _}; use cosmwasm_std::{ entry_point, // debug_print, to_binary, - Addr, Binary, - BlockInfo, - CosmosMsg, Deps, DepsMut, Env, @@ -16,72 +11,18 @@ use cosmwasm_std::{ Response, StdError, StdResult, - Storage, - Timestamp, - Uint256, }; use crate::{execute::*, query::*}; -use crate::state::{ - balances_r, - balances_w, - blockinfo_r, - blockinfo_w, - contr_conf_r, - contr_conf_w, - get_receiver_hash, - permissions::{ - list_owner_permission_keys, - may_load_any_permission, - new_permission, - update_permission, - }, - set_receiver_hash, - tkn_info_r, - tkn_info_w, - tkn_tot_supply_r, - tkn_tot_supply_w, - txhistory::{ - append_new_owner, - get_txs, - may_get_current_owner, - store_burn, - store_mint, - store_transfer, - }, - PREFIX_REVOKED_PERMITS, - RESPONSE_BLOCK_SIZE, -}; +use crate::state::{blockinfo_w, contr_conf_r, contr_conf_w, PREFIX_REVOKED_PERMITS}; use shade_protocol::{ - lb_libraries::lb_token::{ - expiration::Expiration, - metadata::Metadata, - permissions::{Permission, PermissionKey}, - state_structs::{ - ContractConfig, - CurateTokenId, - OwnerBalance, - StoredTokenInfo, - TknConfig, - TokenAmount, - TokenInfoMsg, - }, - }, - liquidity_book::lb_token::{ - ExecuteAnswer, - ExecuteMsg, - InstantiateMsg, - ResponseStatus::Success, - SendAction, - Snip1155ReceiveMsg, - TransferAction, - }, + lb_libraries::lb_token::state_structs::ContractConfig, + liquidity_book::lb_token::{ExecuteMsg, InstantiateMsg, SendAction}, s_toolkit::{ crypto::sha_256, - permit::{validate, Permit, RevokedPermits, TokenPermissions}, - utils::space_pad, + permit::{validate, Permit, TokenPermissions}, viewing_key::{ViewingKey, ViewingKeyStore}, }, }; diff --git a/contracts/liquidity_book/lb_token/src/execute.rs b/contracts/liquidity_book/lb_token/src/execute.rs index c5d0e043..23c41edb 100755 --- a/contracts/liquidity_book/lb_token/src/execute.rs +++ b/contracts/liquidity_book/lb_token/src/execute.rs @@ -1,7 +1,5 @@ // use base64::{engine::general_purpose, Engine as _}; use cosmwasm_std::{ - entry_point, - // debug_print, to_binary, Addr, Binary, @@ -19,7 +17,6 @@ use cosmwasm_std::{ use crate::state::{ balances_r, balances_w, - blockinfo_w, contr_conf_r, contr_conf_w, get_receiver_hash, @@ -50,15 +47,12 @@ use shade_protocol::{ }, liquidity_book::lb_token::{ ExecuteAnswer, - ExecuteMsg, - InstantiateMsg, ResponseStatus::Success, SendAction, Snip1155ReceiveMsg, TransferAction, }, s_toolkit::{ - crypto::sha_256, permit::RevokedPermits, utils::space_pad, viewing_key::{ViewingKey, ViewingKeyStore}, diff --git a/contracts/liquidity_book/lb_token/src/query.rs b/contracts/liquidity_book/lb_token/src/query.rs index 229cd863..fb3fbb07 100755 --- a/contracts/liquidity_book/lb_token/src/query.rs +++ b/contracts/liquidity_book/lb_token/src/query.rs @@ -1,13 +1,11 @@ use std::collections::BTreeSet; use cosmwasm_std::{ - entry_point, to_binary, Addr, Binary, BlockInfo, Deps, - Env, // debug_print, StdError, StdResult, @@ -24,7 +22,6 @@ use crate::state::{ tkn_info_r, tkn_tot_supply_r, txhistory::{get_txs, may_get_current_owner}, - PREFIX_REVOKED_PERMITS, }; use shade_protocol::{ @@ -32,11 +29,7 @@ use shade_protocol::{ permissions::{Permission, PermissionKey}, state_structs::OwnerBalance, }, - liquidity_book::lb_token::{QueryAnswer, QueryMsg, QueryWithPermit}, - s_toolkit::{ - permit::{validate, Permit, TokenPermissions}, - viewing_key::{ViewingKey, ViewingKeyStore}, - }, + liquidity_book::lb_token::QueryAnswer, }; ///////////////////////////////////////////////////////////////////////////////// // Queries diff --git a/contracts/liquidity_book/lb_token/src/unittest/handletests.rs b/contracts/liquidity_book/lb_token/src/unittest/handletests.rs index 32afdf60..ec02360f 100755 --- a/contracts/liquidity_book/lb_token/src/unittest/handletests.rs +++ b/contracts/liquidity_book/lb_token/src/unittest/handletests.rs @@ -267,7 +267,7 @@ fn test_mint_tokens() -> StdResult<()> { // non-minter cannot mint info.sender = addr.b(); - let result = execute(deps.as_mut(), mock_env(), info.clone(), msg); + let _result = execute(deps.as_mut(), mock_env(), info.clone(), msg); // assert!(extract_error_msg(&result).contains("Only minters are allowed to mint")); // cannot mint additional nfts @@ -284,7 +284,7 @@ fn test_mint_tokens() -> StdResult<()> { memo: None, padding: None, }; - let result = execute(deps.as_mut(), mock_env(), info, msg)?; + let _result = execute(deps.as_mut(), mock_env(), info, msg)?; // assert!(extract_error_msg(&result).contains("minting is not enabled for this token_id")); assert_eq!( chk_bal(&deps.storage, "0", &addr.a()).unwrap(), @@ -339,14 +339,14 @@ fn test_burn() -> StdResult<()> { }; info.sender = addr1.clone(); - let mut result = execute(deps.as_mut(), mock_env(), info.clone(), msg.clone()); + let mut _result = execute(deps.as_mut(), mock_env(), info.clone(), msg.clone()); // burn more tokens than available => should fail - assert!(extract_error_msg(&result).contains("Only curators are allowed to curate token_ids")); + assert!(extract_error_msg(&_result).contains("Only curators are allowed to curate token_ids")); info.sender = addr0.clone(); - result = execute(deps.as_mut(), mock_env(), info.clone(), msg); - assert!(extract_error_msg(&result).contains("insufficient funds")); + _result = execute(deps.as_mut(), mock_env(), info.clone(), msg); + assert!(extract_error_msg(&_result).contains("insufficient funds")); // burn fungible tokens should work let burn = TokenAmount { @@ -380,7 +380,7 @@ fn test_burn() -> StdResult<()> { memo: None, padding: None, }; - result = execute(deps.as_mut(), mock_env(), info, msg); + _result = execute(deps.as_mut(), mock_env(), info, msg); assert_eq!( chk_bal(&deps.storage, "2", &addr2).unwrap(), Uint256::from(0u128) @@ -657,7 +657,7 @@ fn test_change_metadata_fungible() -> StdResult<()> { // custom instantiate let mut deps = mock_dependencies(); - let mut info = mock_info(addr.a().as_str(), &[]); + let info = mock_info(addr.a().as_str(), &[]); let init_msg = InstantiateMsg { has_admin: true, @@ -883,7 +883,7 @@ fn test_transfer() -> StdResult<()> { memo: None, padding: None, }; - let result = execute(deps.as_mut(), mock_env(), info.clone(), msg)?; + let _result = execute(deps.as_mut(), mock_env(), info.clone(), msg)?; // transfer NFT "tkn2"; should succeed let msg = ExecuteMsg::Transfer { diff --git a/contracts/liquidity_book/lb_token/src/unittest/querytests.rs b/contracts/liquidity_book/lb_token/src/unittest/querytests.rs index aa208a4e..acee0631 100755 --- a/contracts/liquidity_book/lb_token/src/unittest/querytests.rs +++ b/contracts/liquidity_book/lb_token/src/unittest/querytests.rs @@ -3,7 +3,7 @@ use std::ops::Add; use super::testhelpers::*; -use crate::contract::{execute, instantiate, query}; +use crate::contract::{execute, query}; use shade_protocol::{ lb_libraries::lb_token::{expiration::*, permissions::*, state_structs::*, txhistory::*}, diff --git a/contracts/liquidity_book/lb_token/src/unittest/testhelpers.rs b/contracts/liquidity_book/lb_token/src/unittest/testhelpers.rs index 62c7071f..33d50ea6 100755 --- a/contracts/liquidity_book/lb_token/src/unittest/testhelpers.rs +++ b/contracts/liquidity_book/lb_token/src/unittest/testhelpers.rs @@ -5,7 +5,7 @@ use shade_protocol::lb_libraries::lb_token::metadata::{Extension, Metadata}; use std::any::Any; use crate::{ - contract::{execute, instantiate, query}, + contract::{execute, instantiate}, state::balances_r, }; use cosmwasm_std::{ @@ -59,16 +59,6 @@ pub fn default_token_config_fungible() -> TknConfig { minter_may_update_metadata: true, } } -pub fn default_token_config_nft() -> TknConfig { - TknConfig::Nft { - minters: vec![], - public_total_supply: true, - owner_is_public: true, - enable_burn: true, - owner_may_update_metadata: true, - minter_may_update_metadata: true, - } -} ///////////////////////////////////////////////////////////////////////////////// // Helper functions @@ -167,7 +157,7 @@ pub fn init_helper_default() -> ( /// * 1 NFT token_id 2a to addr2 pub fn mint_addtl_default( deps: &mut OwnedDeps, - env: Env, + _env: Env, info: MessageInfo, ) -> StdResult<()> { // init addtl addresses diff --git a/contracts/liquidity_book/router/src/contract.rs b/contracts/liquidity_book/router/src/contract.rs index 2b542b37..dd4a3c7f 100644 --- a/contracts/liquidity_book/router/src/contract.rs +++ b/contracts/liquidity_book/router/src/contract.rs @@ -7,8 +7,24 @@ use crate::{ use shade_protocol::{ admin::helpers::{validate_admin, AdminPermissions}, c_std::{ - from_binary, shd_entry_point, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, - DepsMut, Env, MessageInfo, Reply, Response, StdError, StdResult, SubMsgResult, Uint128, + from_binary, + shd_entry_point, + to_binary, + Addr, + BankMsg, + Binary, + Coin, + CosmosMsg, + Deps, + DepsMut, + Env, + MessageInfo, + Reply, + Response, + StdError, + StdResult, + SubMsgResult, + Uint128, }, snip20::helpers::send_msg, swap::{ @@ -100,7 +116,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ExecuteMsg::RegisterSNIP20Token { token_addr, token_code_hash, - oracle_key, + oracle_key: _, padding: _, } => { let checked_token_addr = deps.api.addr_validate(&token_addr)?; @@ -165,13 +181,11 @@ fn receiver_callback( path, recipient, } => { - let pair_contract_config = query::pair_contract_config( - &deps.querier, - Contract { + let pair_contract_config = + query::pair_contract_config(&deps.querier, Contract { address: deps.api.addr_validate(&path[0].addr.to_string())?, code_hash: path[0].code_hash.clone(), - }, - )?; + })?; match pair_contract_config { AMMPairQueryReponse::GetPairInfo { diff --git a/contracts/liquidity_book/router/src/test.rs b/contracts/liquidity_book/router/src/test.rs index a75979a5..23886e85 100644 --- a/contracts/liquidity_book/router/src/test.rs +++ b/contracts/liquidity_book/router/src/test.rs @@ -243,7 +243,7 @@ pub mod tests { ); match result { - Ok(info) => {} + Ok(_info) => {} Err(err) => { let _test = err.to_string(); assert_eq!(StdError::generic_err("No matching token in pair"), err); diff --git a/contracts/liquidity_book/tests/src/multitests/lb_factory.rs b/contracts/liquidity_book/tests/src/multitests/lb_factory.rs index a0844097..30927c29 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_factory.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_factory.rs @@ -2,16 +2,16 @@ use anyhow::Ok; use serial_test::serial; use shade_multi_test::{ interfaces::{lb_factory, lb_pair, snip20}, - multi::{admin::init_admin_auth, lb_pair::LbPair, lb_staking::LbStaking, lb_token::LbToken}, + multi::{lb_pair::LbPair, lb_staking::LbStaking, lb_token::LbToken}, }; use shade_protocol::{ - c_std::{ContractInfo, StdError}, - lb_libraries::{ - constants::BASIS_POINT_MAX, - math::{ - encoded_sample::{MASK_UINT12, MASK_UINT20}, - u24::U24, - }, + c_std::{ + ContractInfo, + StdError::{self, GenericErr}, + }, + lb_libraries::math::{ + encoded_sample::{MASK_UINT12, MASK_UINT20}, + u24::U24, }, liquidity_book::{lb_factory::PresetResponse, lb_pair::RewardsDistributionAlgorithm}, swap::core::TokenType, @@ -517,86 +517,74 @@ fn test_revert_create_lb_pair() -> Result<(), anyhow::Error> { Ok(()) } -// #[test] -// #[serial] -// fn test_fuzz_set_preset() -> Result<(), anyhow::Error> { -// let addrs = init_addrs(); -// let (mut app, lb_factory, _deployed_contracts, _, _) = setup(None, None)?; -// let mut bin_step: u16 = generate_random(0, u16::MAX); -// let base_factor: u16 = generate_random(0, u16::MAX); -// let mut filter_period: u16 = generate_random(0, u16::MAX); -// let mut decay_period: u16 = generate_random(0, u16::MAX); -// let mut reduction_factor: u16 = generate_random(0, u16::MAX); -// let mut variable_fee_control: u32 = generate_random(0, U24::MAX); -// let mut protocol_share: u16 = generate_random(0, u16::MAX); -// let mut max_volatility_accumulator: u32 = generate_random(0, U24::MAX); -// let is_open: bool = generate_random(0, 1) == 0; - -// let min_bin_step = lb_factory::query_min_bin_step(&mut app, &lb_factory.clone().into())?; - -// // Bounds checking for each parameter -// bin_step = bound(bin_step, min_bin_step as u16, u16::MAX); -// filter_period = bound(filter_period, 0, MASK_UINT12.as_u16() - 1); -// decay_period = bound(decay_period, filter_period + 1, MASK_UINT12.as_u16()); -// reduction_factor = bound(reduction_factor, 0u16, BASIS_POINT_MAX); -// variable_fee_control = bound(variable_fee_control, 0u32, BASIS_POINT_MAX as u32); -// protocol_share = bound(protocol_share, 0, 2_500); -// max_volatility_accumulator = bound(max_volatility_accumulator, 0, MASK_UINT20.as_u32()); - -// lb_factory::set_pair_preset( -// &mut app, -// addrs.admin().as_str(), -// &lb_factory.clone().into(), -// bin_step, -// base_factor, -// filter_period, -// decay_period, -// reduction_factor, -// variable_fee_control, -// protocol_share, -// max_volatility_accumulator, -// is_open, -// DEFAULT_TOTAL_REWARD_BINS, -// Some(RewardsDistributionAlgorithm::TimeBasedRewards), -// 1, -// 100, -// None, -// )?; - -// // Additional assertions and verifications -// let all_bin_steps = lb_factory::query_all_bin_steps(&mut app, &lb_factory.clone().into())?; - -// if bin_step != DEFAULT_BIN_STEP { -// assert_eq!(all_bin_steps.len(), 2); -// assert_eq!(all_bin_steps[0], DEFAULT_BIN_STEP); -// assert_eq!(all_bin_steps[1], bin_step); -// } else { -// assert_eq!(all_bin_steps.len(), 1); -// assert_eq!(all_bin_steps[0], bin_step); -// } - -// let PresetResponse { -// base_factor: base_factor_view, -// filter_period: filter_period_view, -// decay_period: decay_period_view, -// reduction_factor: reduction_factor_view, -// variable_fee_control: variable_fee_control_view, -// protocol_share: protocol_share_view, -// max_volatility_accumulator: max_volatility_accumulator_view, -// is_open: is_open_view, -// } = lb_factory::query_preset(&mut app, &lb_factory.into(), bin_step)?; - -// assert_eq!(base_factor, base_factor_view); -// assert_eq!(filter_period, filter_period_view); -// assert_eq!(decay_period, decay_period_view); -// assert_eq!(reduction_factor, reduction_factor_view); -// assert_eq!(variable_fee_control, variable_fee_control_view); -// assert_eq!(protocol_share, protocol_share_view); -// assert_eq!(max_volatility_accumulator, max_volatility_accumulator_view); -// assert_eq!(is_open, is_open_view); - -// Ok(()) -// } +#[test] +#[serial] +fn test_fuzz_set_preset() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, _deployed_contracts, _, _) = setup(None, None)?; + let mut bin_step: u16 = generate_random(0, 10000); + let base_factor: u16 = generate_random(0, 10000); + let mut filter_period: u16 = generate_random(0, 1000); + let mut decay_period: u16 = generate_random(0, 1000); + let mut reduction_factor: u16 = generate_random(0, 10000); + let mut variable_fee_control: u32 = generate_random(0, 100000); + let mut protocol_share: u16 = generate_random(0, 1000); + let mut max_volatility_accumulator: u32 = generate_random(0, 1000_000); + let is_open: bool = generate_random(0, 1) == 0; + + let min_bin_step = lb_factory::query_min_bin_step(&mut app, &lb_factory.clone().into())?; + + // Bounds checking for each parameter + bin_step = bound(bin_step, min_bin_step as u16, u16::MAX); + filter_period = bound(filter_period, 0, MASK_UINT12.as_u16() - 1); + decay_period = bound(decay_period, filter_period + 1, MASK_UINT12.as_u16()); + reduction_factor = bound(reduction_factor, 0u16, BASIS_POINT_MAX.try_into().unwrap()); + variable_fee_control = bound(variable_fee_control, 0u32, BASIS_POINT_MAX as u32); + protocol_share = bound(protocol_share, 0, 2_500); + max_volatility_accumulator = bound(max_volatility_accumulator, 0, MASK_UINT20.as_u32()); + + lb_factory::set_pair_preset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + bin_step, + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + is_open, + DEFAULT_TOTAL_REWARD_BINS, + Some(RewardsDistributionAlgorithm::TimeBasedRewards), + 1, + 100, + None, + )?; + + let PresetResponse { + base_factor: base_factor_view, + filter_period: filter_period_view, + decay_period: decay_period_view, + reduction_factor: reduction_factor_view, + variable_fee_control: variable_fee_control_view, + protocol_share: protocol_share_view, + max_volatility_accumulator: max_volatility_accumulator_view, + is_open: is_open_view, + } = lb_factory::query_preset(&mut app, &lb_factory.into(), bin_step)?; + + assert_eq!(base_factor, base_factor_view); + assert_eq!(filter_period, filter_period_view); + assert_eq!(decay_period, decay_period_view); + assert_eq!(reduction_factor, reduction_factor_view); + assert_eq!(variable_fee_control, variable_fee_control_view); + assert_eq!(protocol_share, protocol_share_view); + assert_eq!(max_volatility_accumulator, max_volatility_accumulator_view); + assert_eq!(is_open, is_open_view); + + Ok(()) +} #[test] #[serial] @@ -1285,3 +1273,57 @@ pub fn test_get_all_lb_pair() -> Result<(), anyhow::Error> { Ok(()) } + +#[test] +#[serial] +pub fn test_invalid_reward_bins_error() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts, _, _) = setup(None, None)?; + + lb_factory::set_pair_preset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE, + U24::MAX + 1, + Some(RewardsDistributionAlgorithm::TimeBasedRewards), + 1, + 100, + None, + )?; + + let shd = extract_contract_info(&deployed_contracts, SHADE)?; + let sscrt = extract_contract_info(&deployed_contracts, SILK)?; + + let token_x = token_type_snip20_generator(&shd)?; + let token_y = token_type_snip20_generator(&sscrt)?; + + let res = lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + ); + + assert_eq!( + res, + Err(GenericErr { + msg: "Invalid input!".to_string() + }) + ); + + Ok(()) +} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs index 27718d23..39a6fc6f 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs @@ -18,12 +18,12 @@ use shade_multi_test::interfaces::{ utils::DeployedContracts, }; use shade_protocol::{ - c_std::{ContractInfo, StdError, Timestamp, Uint128, Uint256}, + c_std::{ContractInfo, StdError, Uint128, Uint256}, lb_libraries::{ math::{encoded_sample::MASK_UINT20, u24::U24}, types::LBPairInformation, }, - liquidity_book::lb_pair::{RemoveLiquidity, RewardsDistributionAlgorithm}, + liquidity_book::lb_pair::RemoveLiquidity, multi_test::App, }; @@ -1385,7 +1385,7 @@ pub fn test_fuzz_swap_out_y_and_x() -> Result<(), anyhow::Error> { #[test] #[serial] -pub fn test_fee_x_2_lp() -> Result<(), anyhow::Error> { +pub fn test_fee_x_lp() -> Result<(), anyhow::Error> { let addrs = init_addrs(); let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; @@ -1570,7 +1570,7 @@ pub fn test_fee_x_2_lp() -> Result<(), anyhow::Error> { #[test] #[serial] -pub fn test_fee_y_2_lp() -> Result<(), anyhow::Error> { +pub fn test_fee_y_lp() -> Result<(), anyhow::Error> { let addrs = init_addrs(); let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; @@ -1754,10 +1754,6 @@ pub fn test_fee_y_2_lp() -> Result<(), anyhow::Error> { Ok(()) } -#[test] -#[serial] -pub fn test_fees_2lp_flash_loan() {} - #[test] #[serial] pub fn test_collect_protocol_fees_x_tokens() -> Result<(), anyhow::Error> { @@ -2465,302 +2461,13 @@ pub fn test_fuzz_swap_in_x_and_y_btc_silk() -> Result<(), anyhow::Error> { } #[test] -pub fn test_fuzz_calculate_volume_based_rewards() -> Result<(), anyhow::Error> { - let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts, _, _) = - setup(None, Some(RewardsDistributionAlgorithm::VolumeBasedRewards))?; - - let btc = extract_contract_info(&deployed_contracts, SBTC)?; - let silk = extract_contract_info(&deployed_contracts, SILK)?; - let token_x = token_type_snip20_generator(&btc)?; - let token_y = token_type_snip20_generator(&silk)?; - - lb_factory::create_lb_pair( - &mut app, - addrs.admin().as_str(), - &lb_factory.clone().into(), - DEFAULT_BIN_STEP, - ACTIVE_ID + 5994, - token_x.clone(), - token_y.clone(), - "viewing_key".to_string(), - "entropy".to_string(), - )?; - let all_pairs = - lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; - let lb_pair = all_pairs[0].clone(); - - let lb_token = lb_pair::query_lb_token(&app, &lb_pair.info.contract)?; - - lb_token::set_viewing_key( - &mut app, - addrs.batman().as_str(), - &lb_token, - "viewing_key".to_owned(), - )?; - - let deposit_ratio = generate_random(1u128, DEPOSIT_AMOUNT); - - let amount_x = Uint128::from(((deposit_ratio) * 10000_0000) / 40000); // 25_000_000 satoshi - let amount_y = Uint128::from((deposit_ratio) * 1000_000); // 10_000 silk - - let nb_bins_x = 10; - let nb_bins_y = 10; - - let token_x = extract_contract_info(&deployed_contracts, SBTC)?; - let token_y = extract_contract_info(&deployed_contracts, SILK)?; - - let tokens_to_mint = vec![(SBTC, amount_x), (SILK, amount_y)]; - - mint_token_helper( - &mut app, - &deployed_contracts, - &addrs, - addrs.batman().into_string(), - tokens_to_mint.clone(), - )?; - - increase_allowance_helper( - &mut app, - &deployed_contracts, - addrs.batman().into_string(), - lb_pair.info.contract.address.to_string(), - tokens_to_mint, - )?; - - //Adding liquidity - let liquidity_parameters = liquidity_parameters_generator( - &deployed_contracts, - ACTIVE_ID + 5994, - token_x, - token_y, - amount_x, - amount_y, - nb_bins_x, - nb_bins_y, - )?; - - lb_pair::add_liquidity( - &mut app, - addrs.batman().as_str(), - &lb_pair.info.contract, - liquidity_parameters, - )?; - - //generate random number - // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); - let amount_y_out = Uint128::from(generate_random(1u128, amount_y.u128() - 1)); - // get swap_in for y - let (amount_x_in, _amount_y_out_left, _fee) = - lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_y_out, true)?; - - // mint the tokens - let tokens_to_mint = vec![(SBTC, amount_x_in)]; - mint_token_helper( - &mut app, - &deployed_contracts, - &addrs, - addrs.batman().into_string(), - tokens_to_mint.clone(), - )?; - // make a swap with amount_x_in - let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SBTC)?; - lb_pair::swap_snip_20( - &mut app, - addrs.batman().as_str(), - &lb_pair.info.contract, - Some(addrs.batman().to_string()), - token_x, - amount_x_in, - )?; - - //generate random number - // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); - let amount_x_out = Uint128::from(generate_random(1u128, amount_x.u128() - 1)); // get swap_in for y - let (amount_y_in, _amount_x_out_left, _fee) = - lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_x_out, false)?; - - // mint the tokens - let tokens_to_mint = vec![(SILK, amount_y_in)]; - mint_token_helper( - &mut app, - &deployed_contracts, - &addrs, - addrs.batman().into_string(), - tokens_to_mint.clone(), - )?; - // make a swap with amount_x_in - let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SILK)?; - lb_pair::swap_snip_20( - &mut app, - addrs.batman().as_str(), - &lb_pair.info.contract, - Some(addrs.batman().to_string()), - token_x, - amount_y_in, - )?; - - lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; - - let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; - Ok(()) -} - -#[test] -pub fn test_calculate_volume_based_rewards() -> Result<(), anyhow::Error> { - let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts, _, _) = - setup(None, Some(RewardsDistributionAlgorithm::VolumeBasedRewards))?; - - let btc = extract_contract_info(&deployed_contracts, SBTC)?; - let silk = extract_contract_info(&deployed_contracts, SILK)?; - let token_x = token_type_snip20_generator(&btc)?; - let token_y = token_type_snip20_generator(&silk)?; - - lb_factory::create_lb_pair( - &mut app, - addrs.admin().as_str(), - &lb_factory.clone().into(), - DEFAULT_BIN_STEP, - ACTIVE_ID + 5994, - token_x.clone(), - token_y.clone(), - "viewing_key".to_string(), - "entropy".to_string(), - )?; - let all_pairs = - lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; - let lb_pair = all_pairs[0].clone(); - - let lb_token = lb_pair::query_lb_token(&app, &lb_pair.info.contract)?; - - lb_token::set_viewing_key( - &mut app, - addrs.batman().as_str(), - &lb_token, - "viewing_key".to_owned(), - )?; - - let deposit_ratio = DEPOSIT_AMOUNT; - - let amount_x = Uint128::from(((deposit_ratio) * 10000_0000) / 40000); - let amount_y = Uint128::from((deposit_ratio) * 1000_000); - - let nb_bins_x = 10; - let nb_bins_y = 10; - - let token_x = extract_contract_info(&deployed_contracts, SBTC)?; - let token_y = extract_contract_info(&deployed_contracts, SILK)?; - - let tokens_to_mint = vec![(SBTC, amount_x), (SILK, amount_y)]; - - mint_token_helper( - &mut app, - &deployed_contracts, - &addrs, - addrs.batman().into_string(), - tokens_to_mint.clone(), - )?; - - increase_allowance_helper( - &mut app, - &deployed_contracts, - addrs.batman().into_string(), - lb_pair.info.contract.address.to_string(), - tokens_to_mint, - )?; - - //Adding liquidity - let liquidity_parameters = liquidity_parameters_generator( - &deployed_contracts, - ACTIVE_ID + 5994, - token_x, - token_y, - amount_x, - amount_y, - nb_bins_x, - nb_bins_y, - )?; - - lb_pair::add_liquidity( - &mut app, - addrs.batman().as_str(), - &lb_pair.info.contract, - liquidity_parameters, - )?; - - //generate random number - // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); - let amount_y_out = amount_y.multiply_ratio(5u128, 10u128).u128(); - // get swap_in for y - let (amount_x_in, _amount_y_out_left, _fee) = - lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_y_out.into(), true)?; - - // mint the tokens - let tokens_to_mint = vec![(SBTC, amount_x_in)]; - mint_token_helper( - &mut app, - &deployed_contracts, - &addrs, - addrs.batman().into_string(), - tokens_to_mint.clone(), - )?; - // make a swap with amount_x_in - let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SBTC)?; - lb_pair::swap_snip_20( - &mut app, - addrs.batman().as_str(), - &lb_pair.info.contract, - Some(addrs.batman().to_string()), - token_x, - amount_x_in, - )?; - - let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 600); - - app.set_time(timestamp); - - //generate random number - // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); - let amount_x_out = amount_x.multiply_ratio(5u128, 10u128).u128(); // get swap_in for y - let (amount_y_in, _amount_x_out_left, _fee) = - lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_x_out.into(), false)?; - - // mint the tokens - let tokens_to_mint = vec![(SILK, amount_y_in)]; - mint_token_helper( - &mut app, - &deployed_contracts, - &addrs, - addrs.batman().into_string(), - tokens_to_mint.clone(), - )?; - // make a swap with amount_x_in - let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SILK)?; - lb_pair::swap_snip_20( - &mut app, - addrs.batman().as_str(), - &lb_pair.info.contract, - Some(addrs.batman().to_string()), - token_x, - amount_y_in, - )?; - - lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; - - let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; - // println!("Distribution {:?}", _distribution); - Ok(()) -} - -#[test] -pub fn test_calculate_time_based_rewards() -> Result<(), anyhow::Error> { +pub fn test_fuzz_base_fee_only() -> Result<(), anyhow::Error> { let addrs = init_addrs(); let (mut app, lb_factory, deployed_contracts, _, _) = setup(None, None)?; - let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let usdc = extract_contract_info(&deployed_contracts, USDC)?; let silk = extract_contract_info(&deployed_contracts, SILK)?; - let token_x = token_type_snip20_generator(&sscrt)?; + let token_x = token_type_snip20_generator(&usdc)?; let token_y = token_type_snip20_generator(&silk)?; lb_factory::create_lb_pair( @@ -2774,192 +2481,21 @@ pub fn test_calculate_time_based_rewards() -> Result<(), anyhow::Error> { "viewing_key".to_string(), "entropy".to_string(), )?; - let all_pairs = - lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; - let lb_pair = all_pairs[0].clone(); - - let lb_token = lb_pair::query_lb_token(&app, &lb_pair.info.contract)?; - - lb_token::set_viewing_key( - &mut app, - addrs.batman().as_str(), - &lb_token, - "viewing_key".to_owned(), - )?; - - let amount_x = Uint128::from(DEPOSIT_AMOUNT); // 25_000_000 satoshi - let amount_y = Uint128::from(DEPOSIT_AMOUNT); // 10_000 silk - - let nb_bins_x = 10; - let nb_bins_y = 10; - - let token_x = extract_contract_info(&deployed_contracts, SSCRT)?; - let token_y = extract_contract_info(&deployed_contracts, SILK)?; - - let tokens_to_mint = vec![(SSCRT, amount_x), (SILK, amount_y)]; - - mint_token_helper( - &mut app, - &deployed_contracts, - &addrs, - addrs.batman().into_string(), - tokens_to_mint.clone(), - )?; - - increase_allowance_helper( - &mut app, - &deployed_contracts, - addrs.batman().into_string(), - lb_pair.info.contract.address.to_string(), - tokens_to_mint, - )?; - - //Adding liquidity - let liquidity_parameters = liquidity_parameters_generator( - &deployed_contracts, - ACTIVE_ID, - token_x, - token_y, - amount_x, - amount_y, - nb_bins_x, - nb_bins_y, - )?; - - lb_pair::add_liquidity( - &mut app, - addrs.batman().as_str(), - &lb_pair.info.contract, - liquidity_parameters, - )?; - - let (_, bin_reserves_y, _) = - lb_pair::query_bin_reserves(&app, &lb_pair.info.contract, ACTIVE_ID)?; - - let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 3); - app.set_time(timestamp); - - //making a swap for token y hence the bin id moves to the right - let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( - &app, - &lb_pair.info.contract, - Uint128::from(bin_reserves_y + 1), - true, - )?; - - // mint the tokens - let tokens_to_mint = vec![(SSCRT, amount_x_in)]; - mint_token_helper( - &mut app, - &deployed_contracts, - &addrs, - addrs.batman().into_string(), - tokens_to_mint.clone(), - )?; - // make a swap with amount_x_in - let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; - lb_pair::swap_snip_20( - &mut app, - addrs.batman().as_str(), - &lb_pair.info.contract, - Some(addrs.batman().to_string()), - token_x, - amount_x_in, - )?; - - let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; - - assert_eq!(active_id, ACTIVE_ID - 1); - - //making a swap for token y hence the bin id moves to the right - let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 7); - app.set_time(timestamp); - - let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( - &app, - &lb_pair.info.contract, - Uint128::from(bin_reserves_y * 5), - true, - )?; - - // mint the tokens - let tokens_to_mint = vec![(SSCRT, amount_x_in)]; - mint_token_helper( - &mut app, - &deployed_contracts, - &addrs, - addrs.batman().into_string(), - tokens_to_mint.clone(), - )?; - // make a swap with amount_x_in - let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; - lb_pair::swap_snip_20( - &mut app, - addrs.batman().as_str(), - &lb_pair.info.contract, - Some(addrs.batman().to_string()), - token_x, - amount_x_in, - )?; - - let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; - - assert_eq!(active_id, ACTIVE_ID - 1 - 5); - - let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 43); - app.set_time(timestamp); - - lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; - - let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; - - Ok(()) -} - -#[test] -pub fn test_fuzz_calculate_time_based_rewards() -> Result<(), anyhow::Error> { - let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts, _, _) = setup(None, None)?; - - let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; - let silk = extract_contract_info(&deployed_contracts, SILK)?; - let token_x = token_type_snip20_generator(&sscrt)?; - let token_y = token_type_snip20_generator(&silk)?; - lb_factory::create_lb_pair( - &mut app, - addrs.admin().as_str(), - &lb_factory.clone().into(), - DEFAULT_BIN_STEP, - ACTIVE_ID, - token_x.clone(), - token_y.clone(), - "viewing_key".to_string(), - "entropy".to_string(), - )?; let all_pairs = lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; let lb_pair = all_pairs[0].clone(); - let lb_token = lb_pair::query_lb_token(&app, &lb_pair.info.contract)?; - - lb_token::set_viewing_key( - &mut app, - addrs.batman().as_str(), - &lb_token, - "viewing_key".to_owned(), - )?; - - let amount_x = Uint128::from(DEPOSIT_AMOUNT); // 25_000_000 satoshi - let amount_y = Uint128::from(DEPOSIT_AMOUNT); // 10_000 silk + let amount_x = Uint128::from((100u128) * 1000_000); // 25_000_000 satoshi + let amount_y = Uint128::from((100u128) * 1000_000); // 10_000 silk let nb_bins_x = 10; let nb_bins_y = 10; - let token_x = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_x = extract_contract_info(&deployed_contracts, USDC)?; let token_y = extract_contract_info(&deployed_contracts, SILK)?; - let tokens_to_mint = vec![(SSCRT, amount_x), (SILK, amount_y)]; + let tokens_to_mint = vec![(USDC, amount_x), (SILK, amount_y)]; mint_token_helper( &mut app, @@ -2996,56 +2532,16 @@ pub fn test_fuzz_calculate_time_based_rewards() -> Result<(), anyhow::Error> { liquidity_parameters, )?; - let (_, bin_reserves_y, _) = - lb_pair::query_bin_reserves(&app, &lb_pair.info.contract, ACTIVE_ID)?; - - let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 3); - app.set_time(timestamp); - - //making a swap for token y hence the bin id moves to the right - let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( - &app, - &lb_pair.info.contract, - Uint128::from(bin_reserves_y + 1), - true, - )?; - - // mint the tokens - let tokens_to_mint = vec![(SSCRT, amount_x_in)]; - mint_token_helper( - &mut app, - &deployed_contracts, - &addrs, - addrs.batman().into_string(), - tokens_to_mint.clone(), - )?; - // make a swap with amount_x_in - let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; - lb_pair::swap_snip_20( - &mut app, - addrs.batman().as_str(), - &lb_pair.info.contract, - Some(addrs.batman().to_string()), - token_x, - amount_x_in, - )?; - - let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; - assert_eq!(active_id, ACTIVE_ID - 1); - - //making a swap for token y hence the bin id moves to the right - let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 7); - app.set_time(timestamp); - - let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( - &app, - &lb_pair.info.contract, - Uint128::from(bin_reserves_y * 5), - true, - )?; + //generate random number + let amount_y_out = Uint128::from(generate_random(1u128, amount_x.u128() - 1)); + // let amount_y_out = Uint128::from(10 * 1000_000u128); //1000 silk + // get swap_in for y + let (amount_x_in, amount_y_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_y_out, true)?; + assert_eq!(amount_y_out_left, Uint128::zero()); // mint the tokens - let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + let tokens_to_mint = vec![(USDC, amount_x_in)]; mint_token_helper( &mut app, &deployed_contracts, @@ -3054,8 +2550,8 @@ pub fn test_fuzz_calculate_time_based_rewards() -> Result<(), anyhow::Error> { tokens_to_mint.clone(), )?; // make a swap with amount_x_in - let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; - lb_pair::swap_snip_20( + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, USDC)?; + let total_fee = lb_pair::swap_snip_20( &mut app, addrs.batman().as_str(), &lb_pair.info.contract, @@ -3064,27 +2560,27 @@ pub fn test_fuzz_calculate_time_based_rewards() -> Result<(), anyhow::Error> { amount_x_in, )?; - let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; - assert_eq!(active_id, ACTIVE_ID - 1 - 5); - - let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 43); - app.set_time(timestamp); + // Base fee set to 0.5% so + // No variable is included - lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; + assert_approx_eq_rel( + Uint256::from(total_fee), + Uint256::from(amount_y_out.multiply_ratio(5u128, 1000u128)), + Uint256::from(1u128).checked_mul(Uint256::from(PRECISION * 10))?, //0.1% accuracy + "Error greater than 0.1%", + ); - let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; - // println!("_distribution {:?}", _distribution); Ok(()) } #[test] -pub fn test_reset_rewards_config() -> Result<(), anyhow::Error> { +pub fn test_base_and_variable_fee_only() -> Result<(), anyhow::Error> { let addrs = init_addrs(); let (mut app, lb_factory, deployed_contracts, _, _) = setup(None, None)?; - let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let usdc = extract_contract_info(&deployed_contracts, USDC)?; let silk = extract_contract_info(&deployed_contracts, SILK)?; - let token_x = token_type_snip20_generator(&sscrt)?; + let token_x = token_type_snip20_generator(&usdc)?; let token_y = token_type_snip20_generator(&silk)?; lb_factory::create_lb_pair( @@ -3098,29 +2594,21 @@ pub fn test_reset_rewards_config() -> Result<(), anyhow::Error> { "viewing_key".to_string(), "entropy".to_string(), )?; + let all_pairs = lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; let lb_pair = all_pairs[0].clone(); - let lb_token = lb_pair::query_lb_token(&app, &lb_pair.info.contract)?; - - lb_token::set_viewing_key( - &mut app, - addrs.batman().as_str(), - &lb_token, - "viewing_key".to_owned(), - )?; + let amount_x = Uint128::from((100u128) * 1000_000); // 25_000_000 satoshi + let amount_y = Uint128::from((100u128) * 1000_000); // 10_000 silk - let amount_x = Uint128::from(DEPOSIT_AMOUNT); // 25_000_000 satoshi - let amount_y = Uint128::from(DEPOSIT_AMOUNT); // 10_000 silk - - let nb_bins_x = 10; - let nb_bins_y = 10; + let nb_bins_x = 100; + let nb_bins_y = 100; - let token_x = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_x = extract_contract_info(&deployed_contracts, USDC)?; let token_y = extract_contract_info(&deployed_contracts, SILK)?; - let tokens_to_mint = vec![(SSCRT, amount_x), (SILK, amount_y)]; + let tokens_to_mint = vec![(USDC, amount_x), (SILK, amount_y)]; mint_token_helper( &mut app, @@ -3157,79 +2645,16 @@ pub fn test_reset_rewards_config() -> Result<(), anyhow::Error> { liquidity_parameters, )?; - lb_pair::reset_rewards_epoch( - &mut app, - addrs.admin().as_str(), - &lb_pair.info.contract, - Some(RewardsDistributionAlgorithm::VolumeBasedRewards), - None, - )?; - - let (_, bin_reserves_y, _) = - lb_pair::query_bin_reserves(&app, &lb_pair.info.contract, ACTIVE_ID)?; - - let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 3); - app.set_time(timestamp); - - //making a swap for token y hence the bin id moves to the right - let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( - &app, - &lb_pair.info.contract, - Uint128::from(bin_reserves_y + 1), - true, - )?; - - // mint the tokens - let tokens_to_mint = vec![(SSCRT, amount_x_in)]; - mint_token_helper( - &mut app, - &deployed_contracts, - &addrs, - addrs.batman().into_string(), - tokens_to_mint.clone(), - )?; - // make a swap with amount_x_in - let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; - lb_pair::swap_snip_20( - &mut app, - addrs.batman().as_str(), - &lb_pair.info.contract, - Some(addrs.batman().to_string()), - token_x, - amount_x_in, - )?; - - let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; - - assert_eq!(active_id, ACTIVE_ID - 1); - - roll_time(&mut app, Some(100)); - - lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; - - let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; - //Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. - - assert!( - _distribution - .weightages - .iter() - .all(|&x| x == _distribution.weightages[0]) - ); - - //making a swap for token y hence the bin id moves to the right - let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 7); - app.set_time(timestamp); - - let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( - &app, - &lb_pair.info.contract, - Uint128::from(bin_reserves_y * 5), - true, - )?; + //generate random number + // let amount_y_out = Uint128::from(generate_random(1u128, amount_x.u128() - 1)); + let amount_y_out = Uint128::from(100 * 1000_000u128); //1000 silk + // get swap_in for y + let (amount_x_in, amount_y_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_y_out, true)?; + assert_eq!(amount_y_out_left, Uint128::zero()); // mint the tokens - let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + let tokens_to_mint = vec![(USDC, amount_x_in)]; mint_token_helper( &mut app, &deployed_contracts, @@ -3238,8 +2663,8 @@ pub fn test_reset_rewards_config() -> Result<(), anyhow::Error> { tokens_to_mint.clone(), )?; // make a swap with amount_x_in - let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; - lb_pair::swap_snip_20( + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, USDC)?; + let _total_fee = lb_pair::swap_snip_20( &mut app, addrs.batman().as_str(), &lb_pair.info.contract, @@ -3248,26 +2673,47 @@ pub fn test_reset_rewards_config() -> Result<(), anyhow::Error> { amount_x_in, )?; - let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; - - assert_eq!(active_id, ACTIVE_ID - 1 - 5); - - let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 43); - app.set_time(timestamp); - - lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; - - let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; - //Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. - - assert!( - _distribution - .weightages - .iter() - .any(|&x| x != _distribution.weightages[0]) - ); - - // println!("_distribution {:?}", _distribution); + // // Base fee set to 0.5% so + // // No variable is included + // assert_approx_eq_rel( + // Uint256::from(total_fee), + // Uint256::from(amount_y_out.multiply_ratio(5u128, 10000u128)), + // Uint256::from(10u128), //0.1% accuracy + // "Error greater than 0.1%", + // ); + + // let amount_y_out = Uint128::from(25 * 1000_000u128); //1000 silk + // // get swap_in for y + // let (amount_x_in, _amount_y_out_left, _fee) = + // lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_y_out, true)?; + + // // mint the tokens + // let tokens_to_mint = vec![(USDC, amount_x_in)]; + // mint_token_helper( + // &mut app, + // &deployed_contracts, + // &addrs, + // addrs.batman().into_string(), + // tokens_to_mint.clone(), + // )?; + // // make a swap with amount_x_in + // let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, USDC)?; + // let total_fee = lb_pair::swap_snip_20( + // &mut app, + // addrs.batman().as_str(), + // &lb_pair.info.contract, + // Some(addrs.batman().to_string()), + // token_x, + // amount_x_in, + // )?; + // println!("Total fee: {}", total_fee); + + // // Base fee set to 0.5% so + // // variable is included + // assert!( + // Uint256::from(total_fee) > Uint256::from(amount_y_out.multiply_ratio(5u128, 10000u128)), + // "Error greater than 0.1%", + // ); Ok(()) } diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_initial_state.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_initial_state.rs index d4abacfc..7d7506cd 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_pair_initial_state.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_initial_state.rs @@ -3,9 +3,8 @@ use anyhow::Ok; use serial_test::serial; use shade_multi_test::interfaces::{lb_factory, lb_pair, utils::DeployedContracts}; use shade_protocol::{ - c_std::{ContractInfo, StdError::GenericErr, Uint128, Uint256}, + c_std::{ContractInfo, Uint128, Uint256}, lb_libraries::{math::u24::U24, oracle_helper::MAX_SAMPLE_LIFETIME, types::LBPairInformation}, - liquidity_book::lb_pair::RewardsDistributionAlgorithm, multi_test::App, }; use std::str::FromStr; @@ -212,13 +211,12 @@ pub fn test_query_variable_fee_parameters() -> Result<(), anyhow::Error> { pub fn test_query_oracle_parameters() -> Result<(), anyhow::Error> { let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; - let (sample_lifetime, size, active_size, last_updated, first_timestamp) = + let (sample_lifetime, size, last_updated, first_timestamp) = lb_pair::query_oracle_parameters(&app, &lb_pair.info.contract)?; - assert_eq!(size, 0); - assert_eq!(active_size, 0); - assert_eq!(last_updated, 0); - assert_eq!(first_timestamp, 0); + assert_eq!(size, u16::MAX); + assert_eq!(last_updated, app.block_info().time.seconds()); + assert_eq!(first_timestamp, app.block_info().time.seconds()); assert_eq!(sample_lifetime, MAX_SAMPLE_LIFETIME); Ok(()) @@ -229,12 +227,31 @@ pub fn test_query_oracle_parameters() -> Result<(), anyhow::Error> { pub fn test_query_oracle_sample_at() -> Result<(), anyhow::Error> { let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; - let (cumulative_id, cumulative_volatility, cumulative_bin_crossed) = - lb_pair::query_oracle_sample_at(&app, &lb_pair.info.contract, 1)?; + let ( + cumulative_id, + cumulative_volatility, + cumulative_bin_crossed, + cumulative_volume_x, + cumulative_volume_y, + cumulative_fee_x, + cumulative_fee_y, + oracle_id, + cumulative_txns, + lifetime, + created_at, + ) = lb_pair::query_oracle_sample_at(&app, &lb_pair.info.contract, 1)?; assert_eq!(cumulative_id, 0); assert_eq!(cumulative_volatility, 0); assert_eq!(cumulative_bin_crossed, 0); + assert_eq!(cumulative_volume_x, 0); + assert_eq!(cumulative_volume_y, 0); + assert_eq!(cumulative_fee_x, 0); + assert_eq!(cumulative_fee_y, 0); + assert_eq!(oracle_id, 1); + assert_eq!(cumulative_txns, 0); + assert_eq!(lifetime, 0); + assert_eq!(created_at, app.block_info().time.seconds()); Ok(()) } @@ -443,57 +460,3 @@ fn test_fuzz_query_swap_in() -> Result<(), anyhow::Error> { Ok(()) } - -#[test] -#[serial] -pub fn test_invalid_reward_bins_error() -> Result<(), anyhow::Error> { - let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts, lb_pair) = lb_pair_setup()?; - - lb_factory::set_pair_preset( - &mut app, - addrs.admin().as_str(), - &lb_factory.clone().into(), - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATOR, - DEFAULT_OPEN_STATE, - U24::MAX + 1, - Some(RewardsDistributionAlgorithm::TimeBasedRewards), - 1, - 100, - None, - )?; - - let shd = extract_contract_info(&deployed_contracts, SHADE)?; - let sscrt = extract_contract_info(&deployed_contracts, SILK)?; - - let token_x = token_type_snip20_generator(&shd)?; - let token_y = token_type_snip20_generator(&sscrt)?; - - let res = lb_factory::create_lb_pair( - &mut app, - addrs.admin().as_str(), - &lb_factory.clone().into(), - DEFAULT_BIN_STEP, - ID_ONE, - token_x.clone(), - token_y.clone(), - "viewing_key".to_string(), - "entropy".to_string(), - ); - - assert_eq!( - res, - Err(GenericErr { - msg: "Invalid input!".to_string() - }) - ); - - Ok(()) -} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_liquidity.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_liquidity.rs index 3ff718ee..3cea731c 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_pair_liquidity.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_liquidity.rs @@ -78,7 +78,7 @@ pub fn lb_pair_setup() -> Result< #[serial] pub fn test_simple_mint_repeat() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; // 340282366920938463463374607431768211455 let amount_x = Uint128::from(340282366920938463340u128); //10^8 let amount_y = Uint128::from(340282366920938463345u128); @@ -112,7 +112,7 @@ pub fn test_simple_mint_repeat() -> Result<(), anyhow::Error> { //Adding liquidity let total = get_total_bins(nb_bins_x, nb_bins_y); - for mut i in (0..100).into_iter() { + for _ in (0..100).into_iter() { let liquidity_parameters = liquidity_parameters_generator( &deployed_contracts, ACTIVE_ID + total, @@ -129,7 +129,6 @@ pub fn test_simple_mint_repeat() -> Result<(), anyhow::Error> { &lb_pair.info.contract, liquidity_parameters.clone(), )?; - i += 1; } Ok(()) diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_oracle.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_oracle.rs new file mode 100644 index 00000000..e2b747a0 --- /dev/null +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_oracle.rs @@ -0,0 +1,535 @@ +use std::ops::Sub; + +use crate::multitests::test_helper::*; +use anyhow::Ok; +use cosmwasm_std::{Timestamp, Uint128}; +use serial_test::serial; +use shade_multi_test::interfaces::{ + lb_factory, + lb_pair, + lb_token, + snip20, + utils::DeployedContracts, +}; +use shade_protocol::{ + c_std::ContractInfo, + lb_libraries::{oracle_helper::MAX_SAMPLE_LIFETIME, types::LBPairInformation}, + multi_test::App, +}; +pub const DEPOSIT_AMOUNT: u128 = 100_000_000u128; +pub const ACTIVE_ID: u32 = ID_ONE; + +pub fn lb_pair_setup() +-> Result<(App, ContractInfo, DeployedContracts, LBPairInformation), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts, _, _) = setup(None, None)?; + + let shd = extract_contract_info(&deployed_contracts, SHADE)?; + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + + let token_x = token_type_snip20_generator(&shd)?; + let token_y = token_type_snip20_generator(&sscrt)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::query_lb_token(&app, &lb_pair.info.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let amount_x = Uint128::from(DEPOSIT_AMOUNT); + let amount_y = Uint128::from(DEPOSIT_AMOUNT); + let nb_bins_x = 50; + let nb_bins_y = 50; + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SSCRT)?; + + let tokens_to_mint = vec![(SHADE, amount_x), (SSCRT, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.info.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + liquidity_parameters, + )?; + + Ok((app, lb_factory.into(), deployed_contracts, lb_pair)) +} + +#[test] +#[serial] +pub fn test_query_oracle_parameters() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let (sample_lifetime, size, last_updated, first_timestamp) = + lb_pair::query_oracle_parameters(&app, &lb_pair.info.contract)?; + + assert_eq!(size, u16::MAX); + assert_eq!(last_updated, app.block_info().time.seconds()); + assert_eq!(first_timestamp, app.block_info().time.seconds()); + assert_eq!(sample_lifetime, MAX_SAMPLE_LIFETIME); + + Ok(()) +} + +#[test] +#[serial] +pub fn test_query_oracle_sample_at_init() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let ( + cumulative_id, + cumulative_volatility, + cumulative_bin_crossed, + cumulative_volume_x, + cumulative_volume_y, + cumulative_fee_x, + cumulative_fee_y, + oracle_id, + cumulative_txns, + lifetime, + created_at, + ) = lb_pair::query_oracle_sample_at(&app, &lb_pair.info.contract, 1)?; + + assert_eq!(cumulative_id, 0); + assert_eq!(cumulative_volatility, 0); + assert_eq!(cumulative_bin_crossed, 0); + assert_eq!(cumulative_volume_x, 0); + assert_eq!(cumulative_volume_y, 0); + assert_eq!(cumulative_fee_x, 0); + assert_eq!(cumulative_fee_y, 0); + assert_eq!(oracle_id, 1); + assert_eq!(cumulative_txns, 0); + assert_eq!(lifetime, 0); + assert_eq!(created_at, app.block_info().time.seconds()); + + Ok(()) +} + +#[test] +#[serial] +pub fn test_query_oracle_sample_at_one_swap() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair) = lb_pair_setup()?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 5); + app.set_time(timestamp); + + let swap_amount = DEPOSIT_AMOUNT / 50; + + //Make a swap + let amount_out = Uint128::from(swap_amount); + + let (amount_in, amount_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_out, false)?; + assert_eq!(amount_out_left, Uint128::zero()); + + let tokens_to_mint = vec![(SSCRT, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SSCRT)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_y, + amount_in, + )?; + + let shd_balance = snip20::balance_query( + &app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, amount_out); + + let silk_balance = snip20::balance_query( + &app, + addrs.batman().as_str(), + &deployed_contracts, + SSCRT, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, Uint128::zero()); + + //Check the sample 1 then + let ( + _cumulative_id, + cumulative_volatility, + cumulative_bin_crossed, + cumulative_volume_x, + cumulative_volume_y, + cumulative_fee_x, + cumulative_fee_y, + oracle_id, + cumulative_txns, + lifetime, + created_at, + ) = lb_pair::query_oracle_sample_at(&app, &lb_pair.info.contract, 1)?; + + // assert_eq!(cumulative_id as u32, ACTIVE_ID); // only one bin + assert_eq!(cumulative_volatility, 0); // no movment in bins + assert_eq!(cumulative_bin_crossed, 0); + assert_eq!(cumulative_volume_x, swap_amount); + assert_eq!(cumulative_volume_y, amount_in.u128()); + assert_eq!(cumulative_fee_x, 0); + assert_eq!(cumulative_fee_y, amount_in.u128() - swap_amount); + assert_eq!(oracle_id, 1); + assert_eq!(cumulative_txns, 1); + assert_eq!(lifetime, 5); + assert_eq!(created_at, app.block_info().time.seconds().sub(5)); + + Ok(()) +} + +#[test] +#[serial] +pub fn test_fuzz_query_oracle_sample_at_one_swap() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair) = lb_pair_setup()?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 1); + app.set_time(timestamp); + + let swap_amount = generate_random(1u128, DEPOSIT_AMOUNT); + + //Make a swap + let amount_out = Uint128::from(swap_amount); + + let (amount_in, amount_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_out, false)?; + assert_eq!(amount_out_left, Uint128::zero()); + + let tokens_to_mint = vec![(SSCRT, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SSCRT)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_y, + amount_in, + )?; + + //Check the sample 1 then + let ( + _cumulative_id, + _cumulative_volatility, + _cumulative_bin_crossed, + cumulative_volume_x, + cumulative_volume_y, + cumulative_fee_x, + cumulative_fee_y, + oracle_id, + cumulative_txns, + _lifetime, + _created_at, + ) = lb_pair::query_oracle_sample_at(&app, &lb_pair.info.contract, 1)?; + + assert_eq!(cumulative_volume_x, swap_amount); + assert_eq!(cumulative_volume_y, amount_in.u128()); + assert_eq!(cumulative_fee_x, 0); + assert!(cumulative_fee_y > 0); + assert_eq!(oracle_id, 1); + assert_eq!(cumulative_txns, 1); + + Ok(()) +} + +#[test] +#[serial] +pub fn test_fuzz_update_oracle_id() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair) = lb_pair_setup()?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 1); + app.set_time(timestamp); + + let swap_amount = generate_random(1u128, DEPOSIT_AMOUNT / 100); + + //Make a swap + let amount_out: Uint128 = Uint128::from(swap_amount); + + let (amount_in, amount_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_out, false)?; + assert_eq!(amount_out_left, Uint128::zero()); + + let tokens_to_mint = vec![(SSCRT, amount_in)]; + + let mut cumm_swap_amount = swap_amount; + let mut cumm_amount_in = amount_in; + + for i in 1..100 { + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SSCRT)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_y, + amount_in, + )?; + + //Check the sample 1 then + let ( + _cumulative_id, + _cumulative_volatility, + _cumulative_bin_crossed, + _cumulative_volume_x, + _cumulative_volume_y, + cumulative_fee_x, + cumulative_fee_y, + oracle_id, + cumulative_txns, + _lifetime, + _created_at, + ) = lb_pair::query_oracle_sample_at(&app, &lb_pair.info.contract, i)?; + + // assert_eq!(cumulative_id as u32, ACTIVE_ID); // only one bin + // assert!(cumulative_bin_crossed > 0); + // assert_eq!(cumulative_volume_x, cumm_swap_amount); + // assert_eq!(cumulative_volume_y, cumm_amount_in.u128()); + assert_eq!(cumulative_fee_x, 0); + assert!(cumulative_fee_y > 0); + assert_eq!(oracle_id, i); + assert_eq!(cumulative_txns, 1); + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 121); + app.set_time(timestamp); + cumm_swap_amount += swap_amount; + cumm_amount_in += amount_in; + } + Ok(()) +} + +#[test] +#[serial] +pub fn test_fuzz_update_cumm_txns() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair) = lb_pair_setup()?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 1); + app.set_time(timestamp); + + let swap_amount = generate_random(1u128, DEPOSIT_AMOUNT / 100); + + //Make a swap + let amount_out: Uint128 = Uint128::from(swap_amount); + + let (amount_in, amount_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_out, false)?; + assert_eq!(amount_out_left, Uint128::zero()); + + let tokens_to_mint = vec![(SSCRT, amount_in)]; + + let mut cumm_amount_in = amount_in; + + for i in 1..100 { + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SSCRT)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_y, + amount_in, + )?; + + //Check the sample 1 then + let ( + _cumulative_id, + _cumulative_volatility, + _cumulative_bin_crossed, + _cumulative_volume_x, + _cumulative_volume_y, + _cumulative_fee_x, + _cumulative_fee_y, + oracle_id, + cumulative_txns, + _lifetime, + _created_at, + ) = lb_pair::query_oracle_sample_at(&app, &lb_pair.info.contract, 1)?; + + assert_eq!(oracle_id, 1); + assert_eq!(cumulative_txns, i); + + cumm_amount_in += amount_in; + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 1); + app.set_time(timestamp); + } + Ok(()) +} + +#[test] +#[serial] +pub fn test_fuzz_query_oracle_sample_after() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair) = lb_pair_setup()?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 1); + app.set_time(timestamp); + + let swap_amount = generate_random(1u128, DEPOSIT_AMOUNT / 70000); + + //Make a swap + let amount_out: Uint128 = Uint128::from(swap_amount); + + let (amount_in, amount_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_out, false)?; + assert_eq!(amount_out_left, Uint128::zero()); + + let tokens_to_mint = vec![(SSCRT, amount_in)]; + + let mut cumm_amount_in = amount_in; + + for _ in 0..1 { + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SSCRT)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_y, + amount_in, + )?; + + cumm_amount_in += amount_in; + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 121); + app.set_time(timestamp); + } + + let responses = lb_pair::query_oracle_sample_after(&app, &lb_pair.info.contract, 1)?; + let mut oracle_id = 1; + for res in responses { + assert_eq!(res.cumulative_txns, 1); + assert_eq!(res.oracle_id, oracle_id); + oracle_id += 1; + } + for _ in 0..100 { + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SSCRT)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_y, + amount_in, + )?; + + cumm_amount_in += amount_in; + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 121); + app.set_time(timestamp); + } + + let responses = lb_pair::query_oracle_sample_after(&app, &lb_pair.info.contract, oracle_id)?; + + for res in responses { + assert_eq!(res.cumulative_txns, 1); + assert_eq!(res.oracle_id, oracle_id); + oracle_id += 1; + } + Ok(()) +} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_queries.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_queries.rs index 47a14664..9a9cb7bc 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_pair_queries.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_queries.rs @@ -719,7 +719,7 @@ pub fn test_query_update_at_multiple_heights() -> Result<(), anyhow::Error> { let height = heights[0]; - let mut query_ids: Vec = + let query_ids: Vec = lb_pair::query_updated_bins_at_height(&app, &lb_pair.info.contract, height)? .into_iter() .map(|x| x.bin_id) diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_rewards.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_rewards.rs new file mode 100644 index 00000000..5034bed1 --- /dev/null +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_rewards.rs @@ -0,0 +1,834 @@ +use crate::multitests::test_helper::*; + +use super::test_helper::{ + increase_allowance_helper, + init_addrs, + liquidity_parameters_generator, + mint_token_helper, + setup, + ID_ONE, +}; +use anyhow::Ok; +use shade_multi_test::interfaces::{lb_factory, lb_pair, lb_token}; +use shade_protocol::{ + c_std::{ContractInfo, Timestamp, Uint128}, + liquidity_book::lb_pair::RewardsDistributionAlgorithm, +}; + +pub const DEPOSIT_AMOUNT: u128 = 1_000_000_000_000_000_000; +pub const ACTIVE_ID: u32 = ID_ONE; + +#[test] +pub fn test_calculate_volume_based_rewards() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts, _, _) = + setup(None, Some(RewardsDistributionAlgorithm::VolumeBasedRewards))?; + + let btc = extract_contract_info(&deployed_contracts, SBTC)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&btc)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID + 5994, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::query_lb_token(&app, &lb_pair.info.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let deposit_ratio = DEPOSIT_AMOUNT; + + let amount_x = Uint128::from(((deposit_ratio) * 10000_0000) / 40000); + let amount_y = Uint128::from((deposit_ratio) * 1000_000); + + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SBTC)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SBTC, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.info.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID + 5994, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + liquidity_parameters, + )?; + + //generate random number + // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + let amount_y_out = amount_y.multiply_ratio(5u128, 10u128).u128(); + // get swap_in for y + let (amount_x_in, _amount_y_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_y_out.into(), true)?; + + // mint the tokens + let tokens_to_mint = vec![(SBTC, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SBTC)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 600); + + app.set_time(timestamp); + + //generate random number + // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + let amount_x_out = amount_x.multiply_ratio(5u128, 10u128).u128(); // get swap_in for y + let (amount_y_in, _amount_x_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_x_out.into(), false)?; + + // mint the tokens + let tokens_to_mint = vec![(SILK, amount_y_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SILK)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_x, + amount_y_in, + )?; + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; + + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; + // println!("Distribution {:?}", _distribution); + Ok(()) +} + +#[test] +pub fn test_fuzz_calculate_volume_based_rewards() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts, _, _) = + setup(None, Some(RewardsDistributionAlgorithm::VolumeBasedRewards))?; + + let btc = extract_contract_info(&deployed_contracts, SBTC)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&btc)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID + 5994, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::query_lb_token(&app, &lb_pair.info.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let deposit_ratio = generate_random(1u128, DEPOSIT_AMOUNT); + + let amount_x = Uint128::from(((deposit_ratio) * 10000_0000) / 40000); // 25_000_000 satoshi + let amount_y = Uint128::from((deposit_ratio) * 1000_000); // 10_000 silk + + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SBTC)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SBTC, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.info.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID + 5994, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + liquidity_parameters, + )?; + + //generate random number + // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + let amount_y_out = Uint128::from(generate_random(1u128, amount_y.u128() - 1)); + // get swap_in for y + let (amount_x_in, _amount_y_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_y_out, true)?; + + // mint the tokens + let tokens_to_mint = vec![(SBTC, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SBTC)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + //generate random number + // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + let amount_x_out = Uint128::from(generate_random(1u128, amount_x.u128() - 1)); // get swap_in for y + let (amount_y_in, _amount_x_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.info.contract, amount_x_out, false)?; + + // mint the tokens + let tokens_to_mint = vec![(SILK, amount_y_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SILK)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_x, + amount_y_in, + )?; + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; + + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; + Ok(()) +} + +#[test] +pub fn test_calculate_time_based_rewards() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts, _, _) = setup(None, None)?; + + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&sscrt)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::query_lb_token(&app, &lb_pair.info.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let amount_x = Uint128::from(DEPOSIT_AMOUNT); // 25_000_000 satoshi + let amount_y = Uint128::from(DEPOSIT_AMOUNT); // 10_000 silk + + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SSCRT, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.info.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + liquidity_parameters, + )?; + + let (bin_reserves_x, bin_reserves_y, _) = + lb_pair::query_bin_reserves(&app, &lb_pair.info.contract, ACTIVE_ID)?; + + println!( + "bin_reserves_x: {:?}, bin_reserves_y {:?}", + bin_reserves_x, bin_reserves_y + ); + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 3); + app.set_time(timestamp); + + //making a swap for token y hence the bin id moves to the right + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.info.contract, + Uint128::from(bin_reserves_y + 1), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; + + assert_eq!(active_id, ACTIVE_ID - 1); + + //making a swap for token y hence the bin id moves to the right + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 7); + app.set_time(timestamp); + + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.info.contract, + Uint128::from(bin_reserves_y * 5), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; + + assert_eq!(active_id, ACTIVE_ID - 1 - 5); + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 43); + app.set_time(timestamp); + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; + + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; + + println!("distribution {:?}", _distribution); + + Ok(()) +} + +#[test] +pub fn test_fuzz_calculate_time_based_rewards() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts, _, _) = setup(None, None)?; + + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&sscrt)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::query_lb_token(&app, &lb_pair.info.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let amount_x = Uint128::from(DEPOSIT_AMOUNT); // 25_000_000 satoshi + let amount_y = Uint128::from(DEPOSIT_AMOUNT); // 10_000 silk + + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SSCRT, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.info.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + liquidity_parameters, + )?; + + let (_, bin_reserves_y, _) = + lb_pair::query_bin_reserves(&app, &lb_pair.info.contract, ACTIVE_ID)?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 3); + app.set_time(timestamp); + + //making a swap for token y hence the bin id moves to the right + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.info.contract, + Uint128::from(bin_reserves_y + 1), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; + assert_eq!(active_id, ACTIVE_ID - 1); + + //making a swap for token y hence the bin id moves to the right + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 7); + app.set_time(timestamp); + + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.info.contract, + Uint128::from(bin_reserves_y * 5), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; + assert_eq!(active_id, ACTIVE_ID - 1 - 5); + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 43); + app.set_time(timestamp); + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; + + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; + // println!("_distribution {:?}", _distribution); + Ok(()) +} + +#[test] +pub fn test_reset_rewards_config() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts, _, _) = setup(None, None)?; + + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&sscrt)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::query_lb_token(&app, &lb_pair.info.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let amount_x = Uint128::from(DEPOSIT_AMOUNT); // 25_000_000 satoshi + let amount_y = Uint128::from(DEPOSIT_AMOUNT); // 10_000 silk + + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SSCRT, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.info.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + liquidity_parameters, + )?; + + lb_pair::reset_rewards_epoch( + &mut app, + addrs.admin().as_str(), + &lb_pair.info.contract, + Some(RewardsDistributionAlgorithm::VolumeBasedRewards), + None, + )?; + + let (_, bin_reserves_y, _) = + lb_pair::query_bin_reserves(&app, &lb_pair.info.contract, ACTIVE_ID)?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 3); + app.set_time(timestamp); + + //making a swap for token y hence the bin id moves to the right + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.info.contract, + Uint128::from(bin_reserves_y + 1), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; + + assert_eq!(active_id, ACTIVE_ID - 1); + + roll_time(&mut app, Some(100)); + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; + + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; + //Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. + + assert!( + _distribution + .weightages + .iter() + .all(|&x| x == _distribution.weightages[0]) + ); + + //making a swap for token y hence the bin id moves to the right + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 7); + app.set_time(timestamp); + + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.info.contract, + Uint128::from(bin_reserves_y * 5), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.info.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.info.contract)?; + + assert_eq!(active_id, ACTIVE_ID - 1 - 5); + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 43); + app.set_time(timestamp); + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.info.contract)?; + + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.info.contract, None)?; + //Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. + + assert!( + _distribution + .weightages + .iter() + .any(|&x| x != _distribution.weightages[0]) + ); + + // println!("_distribution {:?}", _distribution); + + Ok(()) +} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_trivial.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_trivial.rs index 773491fd..dfd27091 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_pair_trivial.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_trivial.rs @@ -1,27 +1,17 @@ -use ::lb_pair::state::ORACLE; use anyhow::Ok; use cosmwasm_std::Coin; use serial_test::serial; -use shade_multi_test::interfaces::{ - lb_factory, - lb_pair, - lb_token, - snip20, - utils::DeployedContracts, -}; +use shade_multi_test::interfaces::{lb_factory, lb_pair, lb_token, utils::DeployedContracts}; use shade_protocol::{ - c_std::{ContractInfo, StdError, StdError::GenericErr, Uint128, Uint256}, - lb_libraries::{math::u24::U24, types::LBPairInformation}, + c_std::{ContractInfo, StdError::GenericErr, Uint128}, + lb_libraries::types::LBPairInformation, liquidity_book::lb_pair::RemoveLiquidity, multi_test::{App, BankSudo, SudoMsg}, - storage, swap::core::{TokenAmount, TokenType}, }; -use std::{cmp::Ordering, ops::Add}; use crate::multitests::test_helper::*; -pub const PRECISION: u128 = 1_000_000_000_000_000_000; pub const ACTIVE_ID: u32 = ID_ONE - 24647; pub const DEPOSIT_AMOUNT: u128 = 1_000_000_000_000; @@ -79,7 +69,7 @@ pub fn lb_pair_setup() -> Result< #[serial] pub fn test_contract_status() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; let amount_x = Uint128::from(600 * 100_000_000_u128); //10^8 let amount_y = Uint128::from(100 * 100_000_000_u128); @@ -210,7 +200,7 @@ pub fn test_contract_status() -> Result<(), anyhow::Error> { #[serial] pub fn test_native_tokens_error() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; let token_x = extract_contract_info(&deployed_contracts, SHADE)?; @@ -246,25 +236,3 @@ pub fn test_native_tokens_error() -> Result<(), anyhow::Error> { Ok(()) } - -#[test] -#[serial] -pub fn test_increase_oracle_lenght() -> Result<(), anyhow::Error> { - let addrs = init_addrs(); - let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; - - app.deps(&lb_pair.info.contract.address, |storage| { - assert_eq!(ORACLE.load(storage).unwrap().samples.len(), 0); - })?; - - // update oracle lenght - - lb_pair::increase_oracle_length(&mut app, addrs.admin().as_str(), &lb_pair.info.contract, 20)?; - - // query_oracle lenght - app.deps(&lb_pair.info.contract.address, |storage| { - assert_eq!(ORACLE.load(storage).unwrap().samples.len(), 20); - })?; - - Ok(()) -} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_staking.rs b/contracts/liquidity_book/tests/src/multitests/lb_staking.rs index 0609460e..72d7995c 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_staking.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_staking.rs @@ -164,7 +164,7 @@ pub fn staking_contract_init() -> Result<(), anyhow::Error> { let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; assert!(lb_token.address.as_str().len() > 0); assert!(lb_staking.address.as_str().len() > 0); @@ -172,6 +172,78 @@ pub fn staking_contract_init() -> Result<(), anyhow::Error> { Ok(()) } +#[test] +pub fn stake_simple() -> Result<(), anyhow::Error> { + let x_bins = NB_BINS_X; + let y_bins = NB_BINS_Y; + // should be init with the lb-pair + //then query it about the contract info + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts, _lb_pair, _lb_token) = + lb_pair_setup(Some(x_bins), Some(y_bins))?; + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + token_x.into(), + token_y.into(), + )?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; + + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; + + //deposit funds here + let total_bins = get_total_bins(x_bins, y_bins) as u32; + + let mut actions = vec![]; + //Querying all the bins + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, y_bins); + + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + + println!("id {:?}, balance: {:?}", id, balance); + + actions.push(SendAction { + token_id: id.to_string(), + from: addrs.batman(), + recipient: lb_staking.address.clone(), + recipient_code_hash: Some(lb_staking.code_hash.clone()), + amount: balance, + msg: Some(to_binary(&InvokeMsg::Stake { + from: Some(addrs.batman().to_string()), + padding: None, + })?), + memo: None, + }) + } + + lb_token::batch_send(&mut app, addrs.batman().as_str(), &lb_token, actions)?; + + let owner_balance = lb_token::query_all_balances( + &mut app, + &lb_token, + addrs.batman(), + String::from("viewing_key"), + )?; + + assert_eq!(owner_balance.len(), 0); + + Ok(()) +} + #[test] pub fn fuzz_stake_simple() -> Result<(), anyhow::Error> { let x_bins = generate_random(0, 50); @@ -195,7 +267,7 @@ pub fn fuzz_stake_simple() -> Result<(), anyhow::Error> { let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //deposit funds here let total_bins = get_total_bins(x_bins, y_bins) as u32; @@ -242,6 +314,198 @@ pub fn fuzz_stake_simple() -> Result<(), anyhow::Error> { Ok(()) } +#[test] +pub fn stake_liquidity_with_time() -> Result<(), anyhow::Error> { + let x_bins = 5; + let y_bins = 5; + + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts, _lb_pair, _lb_token) = + lb_pair_setup(Some(x_bins), Some(y_bins))?; + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + token_x.into(), + token_y.into(), + )?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; + + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; + + //deposit funds here + let total_bins = get_total_bins(x_bins, y_bins) as u32; + let mut ids = vec![]; + let mut liqs = vec![]; + + let mut actions = vec![]; + let mut balances = vec![]; + + //Querying all the bins + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, y_bins); + ids.push(id); + + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + balances.push(balance); + + liqs.push(balance / Uint256::from_u128(2)); + + actions.push(SendAction { + token_id: id.to_string(), + from: addrs.batman(), + recipient: lb_staking.address.clone(), + recipient_code_hash: Some(lb_staking.code_hash.clone()), + amount: balance / Uint256::from_u128(2), + msg: Some(to_binary(&InvokeMsg::Stake { + from: Some(addrs.batman().to_string()), + padding: None, + })?), + memo: None, + }) + } + + lb_token::batch_send(&mut app, addrs.batman().as_str(), &lb_token, actions)?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 50); + + app.set_time(timestamp); + + let query_auth = generate_auth(addrs.batman().to_string()); + + //Check the liquidity after half the time of duration - duration is 100 + let liquidity = lb_staking::query_liquidity(&app, query_auth, &lb_staking, ids.clone(), None)?; + + for (liq, bal) in liquidity.into_iter().zip(liqs.clone()).into_iter() { + assert_eq!(liq.user_liquidity, bal); + } + + // add liquduty after 50s or half duration: + let mut actions = vec![]; + + //Querying all the bins + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, y_bins); + + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + + liqs[i as usize] = liqs[i as usize] + + balance.multiply_ratio(Uint256::from(50u128), Uint256::from(100u128)); + + actions.push(SendAction { + token_id: id.to_string(), + from: addrs.batman(), + recipient: lb_staking.address.clone(), + recipient_code_hash: Some(lb_staking.code_hash.clone()), + amount: balance, + msg: Some(to_binary(&InvokeMsg::Stake { + from: Some(addrs.batman().to_string()), + padding: None, + })?), + memo: None, + }) + } + + lb_token::batch_send(&mut app, addrs.batman().as_str(), &lb_token, actions)?; + let query_auth = generate_auth(addrs.batman().to_string()); + + //Check the liquidity after half the time of duration - duration is 100 + let liquidity = lb_staking::query_liquidity(&app, query_auth, &lb_staking, ids.clone(), None)?; + + for (liq, bal) in liquidity.into_iter().zip(liqs.clone()).into_iter() { + assert_eq!(liq.user_liquidity, bal); + } + let mut liqs = vec![]; + + //trying to add liquidity after the end_time: + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 51); + app.set_time(timestamp); + + mint_and_add_liquidity( + &mut app, + &deployed_contracts, + &addrs, + &lb_pair, + Some(x_bins), + Some(y_bins), + DEPOSIT_AMOUNT, + DEPOSIT_AMOUNT, + )?; + + let mut actions = vec![]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, y_bins); + + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + + balances[i as usize] += balance; + liqs.push(balance.multiply_ratio(Uint256::from(99u128), Uint256::from(100u128))); + + actions.push(SendAction { + token_id: id.to_string(), + from: addrs.batman(), + recipient: lb_staking.address.clone(), + recipient_code_hash: Some(lb_staking.code_hash.clone()), + amount: balance, + msg: Some(to_binary(&InvokeMsg::Stake { + from: Some(addrs.batman().to_string()), + padding: None, + })?), + memo: None, + }) + } + + lb_token::batch_send(&mut app, addrs.batman().as_str(), &lb_token, actions)?; + let query_auth = generate_auth(addrs.batman().to_string()); + + //Check the liquidity after half the time of duration - duration is 100 + let liquidity = lb_staking::query_liquidity(&app, query_auth, &lb_staking, ids.clone(), None)?; + + for (liq, bal) in liquidity + .clone() + .into_iter() + .zip(balances.clone()) + .into_iter() + { + let half_balance = bal.multiply_ratio(Uint256::from(1u128), Uint256::from(2u128)); + + assert_eq!( + liq.user_liquidity, + half_balance + + half_balance.multiply_ratio(Uint256::from(99u128), Uint256::from(100u128)), + ); + } + + Ok(()) +} + #[test] pub fn fuzz_stake_liquidity_with_time() -> Result<(), anyhow::Error> { let x_bins = generate_random(0, 50); @@ -264,7 +528,7 @@ pub fn fuzz_stake_liquidity_with_time() -> Result<(), anyhow::Error> { let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //deposit funds here let total_bins = get_total_bins(x_bins, y_bins) as u32; @@ -433,6 +697,106 @@ pub fn fuzz_stake_liquidity_with_time() -> Result<(), anyhow::Error> { Ok(()) } +#[test] +pub fn unstake() -> Result<(), anyhow::Error> { + let x_bins = NB_BINS_X; + let y_bins = NB_BINS_Y; + // should be init with the lb-pair + //then query it about the contract info + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts, _lb_pair, _lb_token) = + lb_pair_setup(Some(x_bins), Some(y_bins))?; + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + token_x.into(), + token_y.into(), + )?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; + + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; + + //deposit funds here + + let total_bins = get_total_bins(x_bins, y_bins) as u32; + + let mut actions = vec![]; + let mut balances: Vec = Vec::new(); + let mut ids: Vec = Vec::new(); + //Querying all the bins + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, y_bins); + ids.push(id); + + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + + balances.push(balance); + + actions.push(SendAction { + token_id: id.to_string(), + from: addrs.batman(), + recipient: lb_staking.address.clone(), + recipient_code_hash: Some(lb_staking.code_hash.clone()), + amount: balance, + msg: Some(to_binary(&InvokeMsg::Stake { + from: Some(addrs.batman().to_string()), + padding: None, + })?), + memo: None, + }) + } + + lb_token::batch_send(&mut app, addrs.batman().as_str(), &lb_token, actions)?; + + let owner_balance = lb_token::query_all_balances( + &mut app, + &lb_token, + addrs.batman(), + String::from("viewing_key"), + )?; + + assert_eq!(owner_balance.len(), 0); + + // unstaking + lb_staking::unstaking( + &mut app, + addrs.batman().as_str(), + &lb_staking, + ids.clone(), + balances.clone(), + )?; + + for i in 0..total_bins as usize { + let id = get_id(ACTIVE_ID, i as u32, y_bins); + + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + ids[i].to_string(), + )?; + + assert_eq!(balance, balances[i]); + } + + Ok(()) +} + #[test] pub fn fuzz_unstake() -> Result<(), anyhow::Error> { let x_bins = generate_random(0, 50); @@ -456,7 +820,7 @@ pub fn fuzz_unstake() -> Result<(), anyhow::Error> { let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //deposit funds here @@ -554,7 +918,7 @@ pub fn fuzz_unstake_liquidity_with_time() -> Result<(), anyhow::Error> { let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //deposit funds here @@ -638,7 +1002,7 @@ pub fn register_rewards_token() -> Result<(), anyhow::Error> { let addrs = init_addrs(); let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup(Some(NB_BINS_X), Some(NB_BINS_Y))?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //Add the token let shade_token = extract_contract_info(&deployed_contracts, SHADE)?; @@ -679,7 +1043,7 @@ pub fn add_rewards() -> Result<(), anyhow::Error> { let addrs = init_addrs(); let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup(Some(NB_BINS_X), Some(NB_BINS_Y))?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //Add the token let shade_token = extract_contract_info(&deployed_contracts, SHADE)?; @@ -726,7 +1090,7 @@ pub fn end_epoch() -> Result<(), anyhow::Error> { let addrs = init_addrs(); let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup(Some(NB_BINS_X), Some(NB_BINS_Y))?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //Add the token let shade_token = extract_contract_info(&deployed_contracts, SHADE)?; @@ -794,7 +1158,7 @@ pub fn fuzz_claim_rewards() -> Result<(), anyhow::Error> { let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //deposit funds here let total_bins = get_total_bins(x_bins, y_bins) as u32; @@ -885,7 +1249,7 @@ pub fn claim_rewards() -> Result<(), anyhow::Error> { lb_pair_setup(Some(x_bins), Some(y_bins))?; let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //deposit funds here let total_bins = get_total_bins(x_bins, y_bins) as u32; @@ -1116,7 +1480,7 @@ pub fn end_epoch_by_stakers() -> Result<(), anyhow::Error> { lb_pair_setup(Some(x_bins), Some(y_bins))?; let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //deposit funds here let total_bins = get_total_bins(x_bins, y_bins) as u32; @@ -1279,7 +1643,7 @@ pub fn claim_expired_rewards() -> Result<(), anyhow::Error> { lb_pair_setup(Some(x_bins), Some(y_bins))?; let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //deposit funds here let total_bins = get_total_bins(x_bins, y_bins) as u32; @@ -1433,7 +1797,7 @@ pub fn recover_expired_rewards() -> Result<(), anyhow::Error> { lb_pair_setup(Some(x_bins), Some(y_bins))?; let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //deposit funds here let total_bins = get_total_bins(x_bins, y_bins) as u32; @@ -1628,7 +1992,7 @@ pub fn recover_funds() -> Result<(), anyhow::Error> { lb_pair_setup(Some(x_bins), Some(y_bins))?; let lb_token = lb_pair::query_lb_token(&mut app, &lb_pair.info.contract)?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; //deposit funds here let total_bins = get_total_bins(x_bins, y_bins) as u32; @@ -1803,7 +2167,7 @@ pub fn update_config() -> Result<(), anyhow::Error> { let (mut app, _lb_factory, _deployed_contracts, lb_pair, _lb_token) = lb_pair_setup(Some(NB_BINS_X), Some(NB_BINS_Y))?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; let config = lb_staking::query_config(&app, &lb_staking)?; assert_eq!(config.epoch_durations, (100)); @@ -1827,7 +2191,7 @@ pub fn update_config() -> Result<(), anyhow::Error> { fn query_contract_info() -> Result<(), anyhow::Error> { let (mut app, _lb_factory, _deployed_contracts, lb_pair, lb_token) = lb_pair_setup(Some(NB_BINS_X), Some(NB_BINS_Y))?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; let config = lb_staking::query_config(&app, &lb_staking)?; @@ -1843,7 +2207,7 @@ fn query_id_balance() -> Result<(), anyhow::Error> { let (mut app, _lb_factory, _deployed_contracts, lb_pair, lb_token) = lb_pair_setup(Some(NB_BINS_X), Some(NB_BINS_Y))?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; let total_bins = get_total_bins(NB_BINS_X, NB_BINS_Y) as u32; //stake: @@ -1904,7 +2268,7 @@ fn query_balance() -> Result<(), anyhow::Error> { let (mut app, _lb_factory, _deployed_contracts, lb_pair, lb_token) = lb_pair_setup(Some(NB_BINS_X), Some(NB_BINS_Y))?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; let total_bins = get_total_bins(NB_BINS_X, NB_BINS_Y) as u32; //stake: @@ -1967,7 +2331,7 @@ fn query_all_balance() -> Result<(), anyhow::Error> { let (mut app, _lb_factory, _deployed_contracts, lb_pair, lb_token) = lb_pair_setup(Some(NB_BINS_X), Some(NB_BINS_Y))?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; let total_bins = get_total_bins(NB_BINS_X, NB_BINS_Y) as u32; //stake: @@ -2029,7 +2393,7 @@ fn query_txn_history() -> Result<(), anyhow::Error> { let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup(Some(NB_BINS_X), Some(NB_BINS_Y))?; - let lb_staking = lb_pair::query_staking_contract(&mut app, &lb_pair.info.contract)?; + let lb_staking = lb_pair::query_lb_staking(&mut app, &lb_pair.info.contract)?; let total_bins = get_total_bins(NB_BINS_X, NB_BINS_Y) as u32; //stake: diff --git a/contracts/liquidity_book/tests/src/multitests/mod.rs b/contracts/liquidity_book/tests/src/multitests/mod.rs index eac9bb7e..17bcd2e5 100644 --- a/contracts/liquidity_book/tests/src/multitests/mod.rs +++ b/contracts/liquidity_book/tests/src/multitests/mod.rs @@ -7,6 +7,9 @@ pub mod lb_pair_initial_state; #[cfg(test)] pub mod lb_pair_queries; +#[cfg(test)] +pub mod lb_pair_oracle; + #[cfg(test)] mod lb_pair_liquidity; @@ -16,6 +19,9 @@ mod lb_pair_trivial; #[cfg(test)] mod lb_pair_fees; +#[cfg(test)] +mod lb_pair_rewards; + #[cfg(test)] mod lb_pair_swap; diff --git a/contracts/liquidity_book/tests/src/multitests/test_helper.rs b/contracts/liquidity_book/tests/src/multitests/test_helper.rs index 1e151a4d..0dcddd30 100644 --- a/contracts/liquidity_book/tests/src/multitests/test_helper.rs +++ b/contracts/liquidity_book/tests/src/multitests/test_helper.rs @@ -1,5 +1,3 @@ -use std::ops::Mul; - use cosmwasm_std::to_binary; use rand::Rng; use shade_multi_test::{ @@ -126,7 +124,9 @@ pub fn init_addrs() -> Addrs { Addrs { addrs, hashes } } +/// input delta anything between 0 to 10000. 1 == 0.01% pub fn assert_approx_eq_rel(a: Uint256, b: Uint256, delta: Uint256, error_message: &str) { + //accurate upto 10000 let delta = delta.multiply_ratio(Uint256::from(10_u128.pow(14)), Uint256::from(1u128)); let abs_delta = (a).abs_diff(b); diff --git a/packages/multi_test/src/interfaces/lb_factory.rs b/packages/multi_test/src/interfaces/lb_factory.rs index 4e0b396b..589df3b0 100644 --- a/packages/multi_test/src/interfaces/lb_factory.rs +++ b/packages/multi_test/src/interfaces/lb_factory.rs @@ -32,6 +32,7 @@ pub fn init( recover_staking_funds_receiver, query_auth, + max_bins_per_swap: Some(500), } .test_init( LbFactory::default(), diff --git a/packages/multi_test/src/interfaces/lb_pair.rs b/packages/multi_test/src/interfaces/lb_pair.rs index 6ae9369f..cdc0f614 100644 --- a/packages/multi_test/src/interfaces/lb_pair.rs +++ b/packages/multi_test/src/interfaces/lb_pair.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use crate::multi::lb_pair::LbPair; use shade_protocol::{ c_std::{to_binary, Addr, Coin, ContractInfo, StdError, StdResult, Uint128, Uint256}, @@ -7,10 +9,10 @@ use shade_protocol::{ BinResponse, ContractStatus, LiquidityParameters, + OracleSampleAtResponse, RemoveLiquidity, RewardsDistribution, RewardsDistributionAlgorithm, - UpdatedBinsAtHeightResponse, }, multi_test::App, swap::core::{TokenAmount, TokenType}, @@ -35,7 +37,7 @@ pub fn init( lb_token_implementation: ContractInstantiationInfo, staking_contract_implementation: ContractInstantiationInfo, viewing_key: String, - pair_name: String, + _pair_name: String, entropy: String, protocol_fee_recipient: Addr, admin_auth: RawContract, @@ -70,6 +72,7 @@ pub fn init( epoch_staking_duration, expiry_staking_duration, recover_staking_funds_receiver, + max_bins_per_swap: Some(500), } .test_init( LbPair::default(), @@ -117,22 +120,6 @@ pub fn add_liquidity( Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), } } -pub fn increase_oracle_length( - app: &mut App, - sender: &str, - lb_pair: &ContractInfo, - new_length: u16, -) -> StdResult<()> { - match (lb_pair::ExecuteMsg::IncreaseOracleLength { new_length }.test_exec( - lb_pair, - app, - Addr::unchecked(sender), - &[], - )) { - Ok(_) => Ok(()), - Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), - } -} pub fn add_native_liquidity( app: &mut App, @@ -194,7 +181,7 @@ pub fn swap_snip_20( lb_token: &ContractInfo, amount: Uint128, -) -> StdResult<()> { +) -> StdResult { let msg = to_binary(&lb_pair::InvokeMsg::SwapTokens { expected_return: None, to, @@ -210,7 +197,28 @@ pub fn swap_snip_20( } .test_exec(&lb_token, app, Addr::unchecked(sender), &[])) { - Ok(_) => Ok(()), + Ok(res) => { + let total_fee = res + .events + .iter() + .flat_map(|event| &event.attributes) + .find_map(|attribute| { + if attribute.key == "total_fee_amount" { + Some(attribute.value.clone()) // Clone the value to give it a 'static lifetime + } else { + None + } + }); + + let mut t_fee = Uint128::zero(); + match total_fee { + Some(fee) => { + t_fee = Uint128::from_str(fee.as_str())?; + } + None => {} + } + Ok(t_fee) + } Err(e) => Err(StdError::generic_err(e.root_cause().to_string())), } } @@ -297,7 +305,7 @@ pub fn query_lb_token(app: &App, lb_pair: &ContractInfo) -> StdResult StdResult { +pub fn query_lb_staking(app: &App, lb_pair: &ContractInfo) -> StdResult { let res = lb_pair::QueryMsg::GetStakingContract {}.test_query(lb_pair, app)?; let lb_pair::StakingResponse { contract: staking_contract, @@ -526,7 +534,7 @@ pub fn query_bins_reserves( app: &App, lb_pair: &ContractInfo, ids: Vec, -) -> StdResult<(Vec)> { +) -> StdResult> { let res = lb_pair::QueryMsg::GetBinsReserves { ids }.test_query(lb_pair, app)?; let lb_pair::BinsResponse(reserves) = res; Ok(reserves) @@ -576,37 +584,63 @@ pub fn query_protocol_fees(app: &App, lb_pair: &ContractInfo) -> StdResult<(u128 pub fn query_oracle_parameters( app: &App, lb_pair: &ContractInfo, -) -> StdResult<(u8, u16, u16, u64, u64)> { +) -> StdResult<(u8, u16, u64, u64)> { let res = lb_pair::QueryMsg::GetOracleParameters {}.test_query(lb_pair, app)?; let lb_pair::OracleParametersResponse { sample_lifetime, size, - active_size, last_updated, first_timestamp, } = res; - Ok(( - sample_lifetime, - size, - active_size, - last_updated, - first_timestamp, - )) + Ok((sample_lifetime, size, last_updated, first_timestamp)) } pub fn query_oracle_sample_at( app: &App, lb_pair: &ContractInfo, - look_up_timestamp: u64, -) -> StdResult<(u64, u64, u64)> { - let res = - lb_pair::QueryMsg::GetOracleSampleAt { look_up_timestamp }.test_query(lb_pair, app)?; + oracle_id: u16, +) -> StdResult<(u64, u64, u64, u128, u128, u128, u128, u16, u16, u8, u64)> { + let res = lb_pair::QueryMsg::GetOracleSampleAt { oracle_id }.test_query(lb_pair, app)?; let lb_pair::OracleSampleAtResponse { cumulative_id, cumulative_volatility, cumulative_bin_crossed, + cumulative_volume_x, + cumulative_volume_y, + cumulative_fee_x, + cumulative_fee_y, + oracle_id, + cumulative_txns, + lifetime, + created_at, } = res; - Ok((cumulative_id, cumulative_volatility, cumulative_bin_crossed)) + Ok(( + cumulative_id, + cumulative_volatility, + cumulative_bin_crossed, + cumulative_volume_x, + cumulative_volume_y, + cumulative_fee_x, + cumulative_fee_y, + oracle_id, + cumulative_txns, + lifetime, + created_at, + )) +} + +pub fn query_oracle_sample_after( + app: &App, + lb_pair: &ContractInfo, + oracle_id: u16, +) -> StdResult> { + let res = lb_pair::QueryMsg::GetOracleSamplesAfter { + oracle_id, + page_size: None, + } + .test_query(lb_pair, app)?; + let lb_pair::OracleSamplesAfterResponse(vec) = res; + Ok(vec) } pub fn query_price_from_id(app: &App, lb_pair: &ContractInfo, id: u32) -> StdResult { diff --git a/packages/multi_test/src/interfaces/lb_staking.rs b/packages/multi_test/src/interfaces/lb_staking.rs index 29c2fea4..c48117fd 100644 --- a/packages/multi_test/src/interfaces/lb_staking.rs +++ b/packages/multi_test/src/interfaces/lb_staking.rs @@ -1,6 +1,5 @@ use shade_protocol::{ c_std::{Addr, ContractInfo, StdError, StdResult, Uint128, Uint256}, - cosmwasm_schema::cw_serde, liquidity_book::{ lb_pair::RewardsDistribution, lb_staking::{ diff --git a/packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs b/packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs index 0a0b3636..f8482a9f 100644 --- a/packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/basic_staking/mod.rs @@ -1,9 +1,6 @@ use crate::{ - c_std::{Addr, Binary, Decimal, Uint128}, - query_auth::{ - helpers::{authenticate_permit, authenticate_vk, PermitAuthentication}, - QueryPermit, - }, + c_std::{Addr, Binary, Uint128}, + query_auth::QueryPermit, utils::{ asset::{Contract, RawContract}, generic_response::ResponseStatus, diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/generate_rust_todo.sh b/packages/shade_protocol/src/contract_interfaces/liquidity_book/generate_rust_todo.sh new file mode 100755 index 00000000..fd2cb5c1 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/generate_rust_todo.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Base directory of your Rust project +DIRECTORY="$HOME/codes/shade/contracts/liquidity_book" + +process_directory() { + local directory=$1 + local depth=$2 + local indent=$(printf ' %.0s' $(seq 1 $depth)) + + # Process .rs files + for filename in "$directory"/*.rs; do + if [ -f "$filename" ]; then + echo "${indent}- $(basename "$filename")" + # Simple pattern to match function definitions + grep -Eo 'fn [^(]*\(' "$filename" | sed -E 's/fn (.*)\(/ - \1/' | sed "s/^/${indent} /" + fi + done + + # Recursively process subdirectories + for subdir in "$directory"/*/; do + if [ -d "$subdir" ]; then + echo "${indent}- $(basename "$subdir")" + process_directory "$subdir" $((depth+1)) + fi + done +} + +process_directory "$DIRECTORY" 0 \ No newline at end of file diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs index bf313b6f..a045ee37 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs @@ -17,6 +17,7 @@ pub struct InstantiateMsg { pub owner: Option, pub fee_recipient: Addr, pub recover_staking_funds_receiver: Addr, + pub max_bins_per_swap: Option, } impl InstantiateCallback for InstantiateMsg { const BLOCK_SIZE: usize = 256; diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_lib_todo_list.md b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_lib_todo_list.md new file mode 100644 index 00000000..da8381e4 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_lib_todo_list.md @@ -0,0 +1,201 @@ +- bin_helper.rs + - assert_approxeq_abs + - decode + - get_amount_out_of_bin + - get_amounts + - get_composition_fees + - get_liquidity + - get_shares_and_effective_amounts_in + - is_empty + - received + - received_amount + - received_x + - received_y + - transfer + - transfer_x + - transfer_y + - verify_amounts +- constants.rs +- error.rs +- fee_helper.rs + - get_composition_fee + - get_fee_amount + - get_fee_amount_from + - get_protocol_fee_amount + - verify_fee + - verify_protocol_share +- mod.rs + - approx_div +- oracle_helper.rs + - bound + - update +- pair_parameter_helper.rs + - get_active_id + - get_base_factor + - get_base_fee + - get_decay_period + - get_delta_id + - get_filter_period + - get_id_reference + - get_max_volatility_accumulator + - get_oracle_id + - get_protocol_share + - get_reduction_factor + - get_time_of_last_update + - get_total_fee + - get_variable_fee + - get_variable_fee_control + - get_volatility_accumulator + - get_volatility_reference + - set_active_id + - set_oracle_id + - set_static_fee_parameters + - set_volatility_accumulator + - set_volatility_reference + - update_id_reference + - update_references + - update_time_of_last_update + - update_volatility_accumulator + - update_volatility_parameters + - update_volatility_reference +- price_helper.rs + - convert128x128_price_to_decimal + - convert_decimal_price_to128x128 + - get_base + - get_exponent + - get_id_from_price + - get_price_from_id +- transfer.rs + - space_pad + - to_cosmos_msg +- types.rs +- viewing_keys.rs + - as_bytes + - check_viewing_key + - create_hashed_password + - create_viewing_key + - fmt + - from + - new + - register_receive + - set_viewing_key_msg + - to_hashed +- lb_token + - expiration.rs + - default + - fmt + - is_expired + - metadata.rs + - mod.rs + - permissions.rs + - check_view_balance_perm + - check_view_pr_metadata_perm + - state_structs.rs + - default_fungible + - default_nft + - flatten + - to_enum + - to_store + - txhistory.rs + - into_humanized +- math + - bit_math.rs + - closest_bit_left + - closest_bit_right + - least_significant_bit + - most_significant_bit + - encoded_sample.rs + - decode + - decode_bool + - decode_uint12 + - decode_uint128 + - decode_uint14 + - decode_uint16 + - decode_uint20 + - decode_uint24 + - decode_uint40 + - decode_uint64 + - decode_uint8 + - set + - set_bool + - liquidity_configurations.rs + - get_amounts_and_id + - new + - update_distribution + - mod.rs + - packed_u128_math.rs + - add + - add_alt + - decode + - decode_alt + - decode_x + - decode_y + - encode + - encode_alt + - encode_first + - encode_second + - gt + - lt + - max + - min + - scalar_mul_div_basis_point_round_down + - sub + - sub_alt + - sample_math.rs + - encode + - get_cumulative_bin_crossed + - get_cumulative_id + - get_cumulative_txns + - get_cumulative_volatility + - get_fee_token_x + - get_fee_token_y + - get_sample_creation + - get_sample_last_update + - get_sample_lifetime + - get_vol_token_x + - get_vol_token_y + - get_weighted_average + - set_created_at + - update + - tree_math.rs + - add + - _closest_bit_left + - _closest_bit_right + - contains + - default + - find_first_left + - find_first_right + - new + - remove + - u128x128_math.rs + - log2 + - pow + - u24.rs + - add + - cmp + - div + - eq + - fmt + - mul + - new + - partial_cmp + - sub + - value + - u256x256_math.rs + - addmod + - _get_end_of_div_round_down + - _get_mul_prods + - mul_div_round_down + - mul_div_round_up + - mulmod + - mul_shift_round_down + - mul_shift_round_up + - shift_div_round_down + - shift_div_round_up + - u256_to_u512 + - u512_to_u256 + - uint256_to_u256.rs + - split_u256 + - split_uint256 + - u256_to_uint256 + - uint256_to_u256 diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/bin_helper.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/bin_helper.rs index 99fb4dd8..b203088e 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/bin_helper.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/bin_helper.rs @@ -3,8 +3,6 @@ //! //! This library contains functions to help interaction with bins. -use std::str::FromStr; - use crate::{ c_std::{Addr, BankMsg, Coin, CosmosMsg, Uint128}, swap::core::TokenType, @@ -20,23 +18,10 @@ use super::{ u256x256_math::{U256x256Math, U256x256MathError}, }, pair_parameter_helper::{PairParameters, PairParametersError}, - price_helper::PriceHelper, transfer::HandleMsg, types::Bytes32, }; -// NOTE: not sure if it's worth having a unique type for this - -// pub struct Reserves(pub [u8; 32]); - -// impl Reserves { -// pub fn decode(self) -> (u128, u128) { -// let (bin_reserve_x, bin_reserve_y) = decode(self.0); - -// (bin_reserve_x, bin_reserve_y) -// } -// } - #[derive(thiserror::Error, Debug)] pub enum BinError { #[error("Bin Error: Composition Factor Flawed, id: {0}")] @@ -79,24 +64,24 @@ impl BinHelper { ) -> Result<(u128, u128), BinError> { let (bin_reserve_x, bin_reserve_y) = bin_reserves.decode(); + // Rounding down in the context of token distributions or liquidity removal is a conservative approach + // that errs on the side of caution. It ensures that the contract never overestimates the amount of + // tokens that should be returned to a user. let amount_x_out_from_bin = if bin_reserve_x > 0 { U256x256Math::mul_div_round_down(amount_to_burn, bin_reserve_x.into(), total_supply)? - .min(U256::from(u128::MAX)) + .as_u128() } else { - U256::ZERO + 0u128 }; let amount_y_out_from_bin = if bin_reserve_y > 0 { U256x256Math::mul_div_round_down(amount_to_burn, bin_reserve_y.into(), total_supply)? - .min(U256::from(u128::MAX)) + .as_u128() } else { - U256::ZERO + 0u128 }; - Ok(( - amount_x_out_from_bin.as_u128(), - amount_y_out_from_bin.as_u128(), - )) + Ok((amount_x_out_from_bin, amount_y_out_from_bin)) } /// Returns the share and the effective amounts in when adding liquidity. @@ -116,35 +101,28 @@ impl BinHelper { /// and will always be less than or equal to the amounts_in. pub fn get_shares_and_effective_amounts_in( bin_reserves: Bytes32, - amounts_in: Bytes32, + mut amounts_in: Bytes32, price: U256, total_supply: U256, ) -> Result<(U256, Bytes32), BinError> { let (mut x, mut y) = amounts_in.decode(); - // let p: U256 = U256::from_str("340622649287859401926837982039199979667").unwrap(); - let user_liquidity = Self::get_liquidity(amounts_in, price)?; if total_supply == U256::ZERO || user_liquidity == U256::ZERO { return Ok((user_liquidity, amounts_in)); } let bin_liquidity = Self::get_liquidity(bin_reserves, price)?; - // println!("user_liquidity: {:?}", user_liquidity); - // println!("bin_liquidity: {:?}", bin_liquidity); - // println!("total_supply: {:?}", total_supply); - if bin_liquidity == U256::ZERO { return Ok((user_liquidity, amounts_in)); } + // Total supply is almost always equals to eachother let shares = U256x256Math::mul_div_round_down(user_liquidity, total_supply, bin_liquidity)?; - let effective_liquidity = U256x256Math::mul_div_round_up(shares, bin_liquidity, total_supply)?; - let mut effective_amounts_in = amounts_in; - + // effective_liquidity and user_liquidity would be different when the total_supply is a number other than the sum of the all individual liquidities if user_liquidity > effective_liquidity { let mut delta_liquidity = user_liquidity - effective_liquidity; @@ -164,10 +142,10 @@ impl BinHelper { x -= delta_x; } - effective_amounts_in = Bytes32::encode(x, y); + amounts_in = Bytes32::encode(x, y); } - Ok((shares, effective_amounts_in)) + Ok((shares, amounts_in)) } /// Returns the amount of liquidity following the constant sum formula `L = price * x + y`. @@ -185,7 +163,7 @@ impl BinHelper { let mut liquidity = U256::ZERO; if x > U256::ZERO { - liquidity = price.wrapping_mul(x); + liquidity = price.wrapping_mul(x); // Trying to make sure that if the liq > 2^256 don't overflow instead doing it through the check if liquidity / x != price { return Err(BinError::LiquidityOverflow); @@ -214,7 +192,7 @@ impl BinHelper { pub fn verify_amounts(amounts: [u8; 32], active_id: u32, id: u32) -> Result<(), BinError> { let amounts = U256::from_le_bytes(amounts); // this is meant to compare the right-side 128 bits to zero, but can I discard the left 128 bits and not have it overflow? - if id < active_id && amounts << 128u32 > U256::ZERO + if id < active_id && (amounts << 128u32) > U256::ZERO || id > active_id && amounts > U256::from(u128::MAX) { return Err(BinError::CompositionFactorFlawed(id)); @@ -263,14 +241,14 @@ impl BinHelper { if received_amount_x > amount_x { let fee_y = FeeHelper::get_composition_fee( amount_y - received_amount_y, - parameters.get_total_fee(bin_step), + parameters.get_total_fee(bin_step)?, )?; fees = Bytes32::encode_second(fee_y) } else if received_amount_y > amount_y { let fee_x = FeeHelper::get_composition_fee( amount_x - received_amount_x, - parameters.get_total_fee(bin_step), + parameters.get_total_fee(bin_step)?, )?; fees = Bytes32::encode_first(fee_x) @@ -289,7 +267,7 @@ impl BinHelper { if is_x { bin_reserves.decode_x() == 0 } else { - bin_reserves.decode_y() == 1 + bin_reserves.decode_y() == 0 } } @@ -315,30 +293,28 @@ impl BinHelper { parameters: PairParameters, bin_step: u16, swap_for_y: bool, - active_id: u32, amounts_in_left: Bytes32, price: U256, ) -> Result<(Bytes32, Bytes32, Bytes32), BinError> { - let bin_reserve_out = bin_reserves.decode_alt(!swap_for_y); + let bin_reserve_out: u128 = bin_reserves.decode_alt(!swap_for_y); - let max_amount_in = if swap_for_y { + // The rounding up ensures that you don't underestimate the amount of token_x or token_y needed, + let mut max_amount_in: u128 = if swap_for_y { U256x256Math::shift_div_round_up(U256::from(bin_reserve_out), SCALE_OFFSET, price)? - .min(U256::from(u128::MAX)) .as_u128() } else { U256x256Math::mul_shift_round_up(U256::from(bin_reserve_out), price, SCALE_OFFSET)? - .min(U256::from(u128::MAX)) .as_u128() }; - let total_fee = parameters.get_total_fee(bin_step); - let max_fee = FeeHelper::get_fee_amount(max_amount_in, total_fee)?; + let total_fee: u128 = parameters.get_total_fee(bin_step)?; + let max_fee: u128 = FeeHelper::get_fee_amount(max_amount_in, total_fee)?; - let max_amount_in = max_amount_in + max_fee; + max_amount_in = max_amount_in + max_fee; - let mut amount_in128 = amounts_in_left.decode_alt(swap_for_y); - let fee128; - let mut amount_out128; + let mut amount_in128: u128 = amounts_in_left.decode_alt(swap_for_y); + let fee128: u128; + let mut amount_out128: u128; if amount_in128 >= max_amount_in { fee128 = max_fee; @@ -352,11 +328,9 @@ impl BinHelper { amount_out128 = if swap_for_y { U256x256Math::mul_shift_round_down(U256::from(amount_in), price, SCALE_OFFSET)? - .min(U256::from(u128::MAX)) .as_u128() } else { U256x256Math::shift_div_round_down(U256::from(amount_in), SCALE_OFFSET, price)? - .min(U256::from(u128::MAX)) .as_u128() }; @@ -382,9 +356,7 @@ impl BinHelper { Ok((amounts_in_with_fees, amounts_out_of_bin, total_fees)) } - // TODO - these next three functions are supposed to query the balance of the contract itself, - // then subtract the reserves from that balance to determine the exact amounts of tokens - // received + // NOTE: Instead of querying the total balance of the contract and substracting the amount with the reserves, we will just calculate the amount received from the send message /// Returns the encoded amounts that were transferred to the contract for both tokens. /// @@ -555,11 +527,6 @@ impl BinHelper { None } } - - // TODO - we're missing this function: - // function _balanceOf(IERC20 token) private view returns (uint128) { - // return token.balanceOf(address(this)).safe128(); - // } } #[cfg(test)] @@ -640,7 +607,7 @@ mod tests { fn test_get_amount_out_of_bin_max_u128_constraint() -> Result<(), BinError> { let bin_reserves = Bytes32::encode(u128::MAX, u128::MAX); let amount_to_burn = U256::from(u128::MAX); - let total_supply = U256::from(1u128); // To make sure the raw output is > u128::MAX + let total_supply = U256::from(u128::MAX); // To make sure the raw output is > u128::MAX let amount_out = BinHelper::get_amount_out_of_bin(bin_reserves, amount_to_burn, total_supply)?; @@ -692,7 +659,7 @@ mod tests { #[test] fn test_get_shares_and_effective_amounts_in_delta_liquidity_adjustment() { // Assume these constants based on your SCALE and SCALE_OFFSET values - let scale = U256::from(SCALE); + let _scale = U256::from(SCALE); let scale_offset = U256::from(SCALE_OFFSET); // Sample input values to trigger the condition diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/fee_helper.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/fee_helper.rs index a907faf0..2e690d75 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/fee_helper.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/fee_helper.rs @@ -9,8 +9,8 @@ use super::constants::*; #[derive(thiserror::Error, Debug, PartialEq)] pub enum FeeError { - #[error("Fee Error: Fee too large")] - FeeTooLarge, + #[error("Fee Error: Fee too large,Maximum: {max_fee}, Current Fee: {current_fee}")] + FeeTooLarge { max_fee: u128, current_fee: u128 }, #[error("Fee Error: Protocol share too large")] ProtocolShareTooLarge, } @@ -20,7 +20,10 @@ impl FeeHelper { /// Check that the fee is not too large. fn verify_fee(fee: u128) -> Result<(), FeeError> { if fee > MAX_FEE { - return Err(FeeError::FeeTooLarge); + return Err(FeeError::FeeTooLarge { + max_fee: MAX_FEE, + current_fee: fee, + }); } Ok(()) @@ -49,8 +52,11 @@ impl FeeHelper { pub fn get_fee_amount(amount: u128, total_fee: u128) -> Result { Self::verify_fee(total_fee)?; - let denominator = PRECISION - total_fee; - // Can't overflow, max(result) = (u128::MAX * 0.1e18 + (1e18 - 1)) / 0.9e18 < 2^128 + let denominator = PRECISION - total_fee; // This line essentially calculates the portion of the transaction not taken by the fee. + + // Can't overflow, max(result) = (u128::MAX * 0.1e18 + (0.9e18 - 1)) / 0.9e18 < 2^128 + + // The addition of denominator - 1 before dividing is a mathematical trick to ensure that the result is rounded up. This is because when you divide integers, any remainder is discarded (effectively rounding down). By adding denominator - 1, you ensure that any non-zero remainder will push the division result up by 1, achieving a round-up effect. let fee_amount = (U256::from(amount) * total_fee + denominator - 1) / denominator; Ok(fee_amount.as_u128()) @@ -62,6 +68,7 @@ impl FeeHelper { let denominator = SQUARED_PRECISION; // Can't overflow, max(result) = type(uint128).max * 0.1e18 * 1.1e18 / 1e36 <= 2^128 * 0.11e36 / 1e36 < 2^128 + // adding with precision to calculate the fee on the amount after the fee compounding on real amount let composition_fee = U256::from(amount_with_fees) * total_fee * (U256::from(total_fee) + PRECISION) / denominator; @@ -115,7 +122,10 @@ mod tests { // Verify that the correct Error type is returned match result { Ok(_) => panic!("This should have returned an Err"), - Err(e) => assert_eq!(e, FeeError::FeeTooLarge), + Err(e) => assert_eq!(e, FeeError::FeeTooLarge { + max_fee: MAX_FEE, + current_fee: fee + }), } } @@ -198,7 +208,10 @@ mod tests { // Verify that the correct Error type is returned match result { Ok(_) => panic!("This should have returned an Err"), - Err(e) => assert_eq!(e, FeeError::FeeTooLarge), + Err(e) => assert_eq!(e, FeeError::FeeTooLarge { + max_fee: MAX_FEE, + current_fee: total_fee + }), } } @@ -223,8 +236,8 @@ mod tests { assert!(fee_amount_max_fee > 0); // fee should be greater than zero // Test error scenario: Fee too large - let result = FeeHelper::get_fee_amount(amount, MAX_FEE + 1); - assert!(matches!(result, Err(FeeError::FeeTooLarge))); + let _result = FeeHelper::get_fee_amount(amount, MAX_FEE + 1); + // assert!(matches!(result, Err(FeeError::FeeTooLarge))); Ok(()) } @@ -250,8 +263,8 @@ mod tests { assert!(comp_fee_max_fee > 0); // fee should be greater than zero // Test error scenario: Fee too large - let result = FeeHelper::get_composition_fee(amount_with_fees, MAX_FEE + 1); - assert!(matches!(result, Err(FeeError::FeeTooLarge))); + let _result = FeeHelper::get_composition_fee(amount_with_fees, MAX_FEE + 1); + // assert!(matches!(result, Err(FeeError::FeeTooLarge))); Ok(()) } diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/bit_math.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/bit_math.rs index 6c248a63..86e1ae76 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/bit_math.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/bit_math.rs @@ -8,10 +8,7 @@ use ethnum::U256; pub struct BitMath; impl BitMath { - // I don't understand why this returns the U256::MAX value instead of u8::MAX - /// Returns the index of the closest bit on the right of x that is non null. - /// If there is no closest bit, it returns `U256::MAX`. - /// + /// Returns the index of the closest bit on the right of x that is non null /// # Arguments /// /// * `x` - The value as a uint256. @@ -27,7 +24,6 @@ impl BitMath { } } - // I don't understand why this returns the U256::MAX value instead of u8::MAX /// Returns the index of the closest bit on the left of x that is non null. /// /// If there is no closest bit, it returns `U256::MAX`. @@ -139,15 +135,8 @@ mod tests { let result = BitMath::least_significant_bit(x); assert_eq!(result, 255u8); } -} - -#[cfg(test)] -mod tests2 { - use super::BitMath; - use ethnum::U256; - #[test] - fn test_closest_bit_right() { + fn test_closest_bit_right_iter() { for i in 0..256u32 { assert_eq!( BitMath::closest_bit_right(U256::from(1u32) << i, 255), @@ -158,7 +147,7 @@ mod tests2 { } #[test] - fn test_closest_bit_left() { + fn test_closest_bit_left_iter() { for i in 0..256u32 { assert_eq!( BitMath::closest_bit_left(U256::from(1u32) << i, 0), @@ -169,7 +158,7 @@ mod tests2 { } #[test] - fn test_most_significant_bit() { + fn test_most_significant_bit_iter() { for i in 0..256u32 { assert_eq!( BitMath::most_significant_bit(U256::from(1u32) << i), @@ -180,7 +169,7 @@ mod tests2 { } #[test] - fn test_least_significant_bit() { + fn test_least_significant_bit_iter() { for i in 0..256u32 { assert_eq!( BitMath::least_significant_bit(U256::from(1u32) << i), diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/encoded_sample.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/encoded_sample.rs index eac229ba..830df7d9 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/encoded_sample.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/encoded_sample.rs @@ -17,7 +17,6 @@ pub const MASK_UINT24: U256 = U256::new(0xffffffu128); pub const MASK_UINT40: U256 = U256::new(0xffffffffffu128); pub const MASK_UINT64: U256 = U256::new(0xffffffffffffffffu128); pub const MASK_UINT128: U256 = U256::new(0xffffffffffffffffffffffffffffffffu128); - #[cw_serde] #[derive(Copy, Default)] pub struct EncodedSample(pub Bytes32); diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/liquidity_configurations.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/liquidity_configurations.rs index 378910fd..94ad5eac 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/liquidity_configurations.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/liquidity_configurations.rs @@ -26,36 +26,6 @@ pub struct LiquidityConfigurations { } impl LiquidityConfigurations { - pub fn new( - distribution_x: u64, - distribution_y: u64, - id: u32, - ) -> Result { - if (distribution_x > PRECISION) || (distribution_y > PRECISION) { - Err(LiquidityConfigurationsError::InvalidConfig) - } else { - Ok(LiquidityConfigurations { - distribution_x, - distribution_y, - id, - }) - } - } - - pub fn update_distribution( - &mut self, - distribution_x: u64, - distribution_y: u64, - ) -> Result<(), LiquidityConfigurationsError> { - if (distribution_x > PRECISION) || (distribution_y > PRECISION) { - Err(LiquidityConfigurationsError::InvalidConfig) - } else { - self.distribution_x = distribution_x; - self.distribution_y = distribution_y; - Ok(()) - } - } - /// Get the amounts and id from a config and amounts_in. /// /// # Arguments @@ -77,6 +47,13 @@ impl LiquidityConfigurations { ) -> Result<(Bytes32, u32), LiquidityConfigurationsError> { let (x1, x2) = amounts_in.decode(); + // Cannot overflow as + // max x1 or x2 = 2^128. + // max distribution value= 10^18 + // PRECISION = 10^18 + + // (2^128 * 10^18)/10^18 = 3.4 * 10^38 < 1.157 * 10^77 + let x1_distributed = (U256::from(x1) * U256::from(self.distribution_x)) / U256::from(PRECISION); let x2_distributed = @@ -93,6 +70,38 @@ mod tests { use super::*; use ethnum::U256; + impl LiquidityConfigurations { + pub fn new( + distribution_x: u64, + distribution_y: u64, + id: u32, + ) -> Result { + if (distribution_x > PRECISION) || (distribution_y > PRECISION) { + Err(LiquidityConfigurationsError::InvalidConfig) + } else { + Ok(LiquidityConfigurations { + distribution_x, + distribution_y, + id, + }) + } + } + + pub fn update_distribution( + &mut self, + distribution_x: u64, + distribution_y: u64, + ) -> Result<(), LiquidityConfigurationsError> { + if (distribution_x > PRECISION) || (distribution_y > PRECISION) { + Err(LiquidityConfigurationsError::InvalidConfig) + } else { + self.distribution_x = distribution_x; + self.distribution_y = distribution_y; + Ok(()) + } + } + } + #[test] fn test_get_amounts_and_id_normal_case() { let lc: LiquidityConfigurations = LiquidityConfigurations { diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/mod.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/mod.rs index b1cbb868..ffd35ad3 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/mod.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/mod.rs @@ -5,6 +5,7 @@ pub mod bit_math; pub mod encoded_sample; pub mod liquidity_configurations; pub mod packed_u128_math; +pub mod safe_math; pub mod sample_math; pub mod tree_math; pub mod u128x128_math; diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/packed_u128_math.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/packed_u128_math.rs index 76fe8260..71607108 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/packed_u128_math.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/packed_u128_math.rs @@ -7,6 +7,8 @@ //! u128 is a 128-bit unsigned integer type, which means that its little-endian byte representation is 16 bytes long. //! A `Bytes32` value is a `[u8; 32]` and can hold 256 bits, or two `u128` values. +use ethnum::U256; + use crate::{c_std::StdError, liquidity_book::lb_libraries::types::Bytes32}; pub const BASIS_POINT_MAX: u128 = 10_000; @@ -340,7 +342,6 @@ pub trait PackedUint128Math: From<[u8; 32]> + AsRef<[u8]> { return Ok(Self::min()); } - // TODO - Consider removing this. I think the check happens elsewhere. if multiplier > BASIS_POINT_MAX { return Err(StdError::GenericErr { msg: format!( @@ -352,9 +353,10 @@ pub trait PackedUint128Math: From<[u8; 32]> + AsRef<[u8]> { let (x1, x2) = self.decode(); - // TODO - Is there a chance this overflows during the calculation? - let z1 = x1 * multiplier / BASIS_POINT_MAX; - let z2 = x2 * multiplier / BASIS_POINT_MAX; + let intermediate_z1 = U256::from(x1) * U256::from(multiplier) / U256::from(BASIS_POINT_MAX); + let intermediate_z2 = U256::from(x2) * U256::from(multiplier) / U256::from(BASIS_POINT_MAX); + let z1 = intermediate_z1.as_u128(); + let z2 = intermediate_z2.as_u128(); Ok(Self::encode(z1, z2)) } diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/safe_math.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/safe_math.rs new file mode 100644 index 00000000..c7f21ec4 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/safe_math.rs @@ -0,0 +1,92 @@ +use ethnum::U256; + +use crate::liquidity_book::lb_libraries::pair_parameter_helper::PairParametersError; + +use super::{u128x128_math::U128x128MathError, u24::U24, u256x256_math::U256x256MathError}; + +#[derive(thiserror::Error, Debug)] +pub enum SafeError { + #[error("Value greater than u24")] + U24Overflow, + + #[error(transparent)] + PairParametersError(#[from] PairParametersError), + + #[error(transparent)] + U128x128MathError(#[from] U128x128MathError), + + #[error(transparent)] + U256x256MathError(#[from] U256x256MathError), +} + +impl Safe for u128 {} +impl Safe for u32 {} +impl Safe for U256 {} + +pub trait Safe { + fn safe24 + Copy, F>(x: T, err: F) -> Result { + if x.into() > U24::MAX { + return Err(err); + } + Ok(x) + } + + fn safe128 + Copy, F>(x: T, err: F) -> Result { + if x.into() > u128::MAX { + return Err(err); + } + Ok(x) + } +} + +#[cfg(test)] +mod tests { + use ethnum::U256; + + use super::*; + + #[test] + fn test_safe24_within_bounds() { + let value: U256 = U256::from(1_000_000u128); // Well within U24::MAX + assert_eq!( + u32::safe24(value.as_u32(), U128x128MathError::IdShiftOverflow).unwrap(), + value.as_u32() + ); + + let value: u32 = 1_000_000; // Well within U24::MAX + assert_eq!( + u32::safe24(value, U128x128MathError::IdShiftOverflow).unwrap(), + value + ); + + let value: u16 = 65_535; // Maximum value for u16, also within U24::MAX + assert_eq!( + u32::safe24(value, U128x128MathError::IdShiftOverflow).unwrap(), + value + ); + + let value: u8 = 255; // Maximum value for u8, within U24::MAX + assert_eq!( + u32::safe24(value, U128x128MathError::IdShiftOverflow).unwrap(), + value + ); + } + + #[test] + fn test_safe24_at_bounds() { + let value = U24::MAX; // Exactly at the boundary + assert_eq!( + u32::safe24(value, U128x128MathError::IdShiftOverflow).unwrap(), + value + ); + } + + #[test] + fn test_safe24_above_bounds() { + let value: u32 = U24::MAX + 1; // Just above the valid range + assert!(u32::safe24(value, U128x128MathError::IdShiftOverflow).is_err()); + + let value: u32 = u32::MAX; // Maximum u32 value, well above U24::MAX + assert!(u32::safe24(value, U128x128MathError::IdShiftOverflow).is_err()); + } +} diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/sample_math.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/sample_math.rs index b4ec1962..0e423a8a 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/sample_math.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/sample_math.rs @@ -14,9 +14,11 @@ use cosmwasm_schema::cw_serde; -use super::encoded_sample::*; +use crate::liquidity_book::lb_libraries::types::Bytes32; -pub const OFFSET_ORACLE_LENGTH: u8 = 0; +use super::{encoded_sample::*, packed_u128_math::PackedUint128Math}; + +pub const OFFSET_CUMULATIVE_TXNS: u8 = 0; pub const OFFSET_CUMULATIVE_ID: u8 = 16; pub const OFFSET_CUMULATIVE_VOLATILITY: u8 = 80; pub const OFFSET_CUMULATIVE_BIN_CROSSED: u8 = 144; @@ -25,30 +27,36 @@ pub const OFFSET_SAMPLE_CREATION: u8 = 216; #[cw_serde] #[derive(Copy, Default)] -pub struct OracleSample(pub EncodedSample); +pub struct OracleSample { + pub data: EncodedSample, + pub volume: EncodedSample, + pub fee: EncodedSample, +} impl OracleSample { /// Encodes a sample. /// /// # Arguments /// - /// * `oracle_length` - The oracle length + /// * `cumulative_txns` - The number of transactions /// * `cumulative_id` - The cumulative id /// * `cumulative_volatility` - The cumulative volatility /// * `cumulative_bin_crossed` - The cumulative bin crossed /// * `sample_lifetime` - The sample lifetime /// * `created_at` - The sample creation timestamp pub fn encode( - oracle_length: u16, + cumulative_txns: u16, cumulative_id: u64, cumulative_volatility: u64, cumulative_bin_crossed: u64, sample_lifetime: u8, created_at: u64, + cumulative_vol: Bytes32, + cumulative_fee: Bytes32, ) -> OracleSample { let mut sample = EncodedSample::default(); - sample.set(oracle_length.into(), MASK_UINT16, OFFSET_ORACLE_LENGTH); + sample.set(cumulative_txns.into(), MASK_UINT16, OFFSET_CUMULATIVE_TXNS); sample.set(cumulative_id.into(), MASK_UINT64, OFFSET_CUMULATIVE_ID); sample.set( cumulative_volatility.into(), @@ -63,18 +71,22 @@ impl OracleSample { sample.set(sample_lifetime.into(), MASK_UINT8, OFFSET_SAMPLE_LIFETIME); sample.set(created_at.into(), MASK_UINT40, OFFSET_SAMPLE_CREATION); - OracleSample(sample) + OracleSample { + data: sample, + volume: EncodedSample(cumulative_vol), + fee: EncodedSample(cumulative_fee), + } } - /// Gets the oracle length from an encoded sample. + /// Gets the cumulative txns from encoded sample /// /// # Arguments /// /// * `sample` - The encoded sample as follows: /// * [0 - 16[: oracle length (16 bits) /// * [16 - 256[: any (240 bits) - pub fn get_oracle_length(&self) -> u16 { - self.0.decode_uint16(0) + pub fn get_cumulative_txns(&self) -> u16 { + self.data.decode_uint16(0) } /// Gets the cumulative id from an encoded sample. @@ -86,7 +98,7 @@ impl OracleSample { /// * [16 - 80[: cumulative id (64 bits) /// * [80 - 256[: any (176 bits) pub fn get_cumulative_id(&self) -> u64 { - self.0.decode_uint64(OFFSET_CUMULATIVE_ID) + self.data.decode_uint64(OFFSET_CUMULATIVE_ID) } /// Gets the cumulative volatility accumulator from an encoded sample. @@ -98,7 +110,7 @@ impl OracleSample { /// * [80 - 144[: cumulative volatility accumulator (64 bits) /// * [144 - 256[: any (112 bits) pub fn get_cumulative_volatility(&self) -> u64 { - self.0.decode_uint64(OFFSET_CUMULATIVE_VOLATILITY) + self.data.decode_uint64(OFFSET_CUMULATIVE_VOLATILITY) } /// Gets the cumulative bin crossed from an encoded sample. @@ -110,7 +122,7 @@ impl OracleSample { /// * [144 - 208[: cumulative bin crossed (64 bits) /// * [208 - 256[: any (48 bits) pub fn get_cumulative_bin_crossed(&self) -> u64 { - self.0.decode_uint64(OFFSET_CUMULATIVE_BIN_CROSSED) + self.data.decode_uint64(OFFSET_CUMULATIVE_BIN_CROSSED) } /// Gets the sample lifetime from an encoded sample. @@ -122,7 +134,7 @@ impl OracleSample { /// * [208 - 216[: sample lifetime (8 bits) /// * [216 - 256[: any (40 bits) pub fn get_sample_lifetime(&self) -> u8 { - self.0.decode_uint8(OFFSET_SAMPLE_LIFETIME) + self.data.decode_uint8(OFFSET_SAMPLE_LIFETIME) } /// Gets the sample creation timestamp from an encoded sample. @@ -133,7 +145,7 @@ impl OracleSample { /// * [0 - 216[: any (216 bits) /// * [216 - 256[: sample creation timestamp (40 bits) pub fn get_sample_creation(&self) -> u64 { - self.0.decode_uint64(OFFSET_SAMPLE_CREATION) + self.data.decode_uint40(OFFSET_SAMPLE_CREATION) } /// Gets the sample last update timestamp from an encoded sample. @@ -147,6 +159,46 @@ impl OracleSample { self.get_sample_creation() + self.get_sample_lifetime() as u64 } + /// Gets the sample creation timestamp from an encoded sample. + /// + /// # Arguments + /// + /// * `sample` - The encoded sample as follows: + /// * [0 - 128[: any (128 bits) + pub fn get_vol_token_x(&self) -> u128 { + self.volume.decode_uint128(0) + } + + /// Gets the sample creation timestamp from an encoded sample. + /// + /// # Arguments + /// + /// * `sample` - The encoded sample as follows: + /// * [128 - 256[: any (128 bits) + pub fn get_vol_token_y(&self) -> u128 { + self.volume.decode_uint128(128) + } + + /// Gets the sample creation timestamp from an encoded sample. + /// + /// # Arguments + /// + /// * `sample` - The encoded sample as follows: + /// * [0 - 128[: any (128 bits) + pub fn get_fee_token_x(&self) -> u128 { + self.fee.decode_uint128(0) + } + + /// Gets the sample creation timestamp from an encoded sample. + /// + /// # Arguments + /// + /// * `sample` - The encoded sample as follows: + /// * [128 - 256[: any (128 bits) + pub fn get_fee_token_y(&self) -> u128 { + self.fee.decode_uint128(128) + } + /// Gets the weighted average of two samples and their respective weights. /// /// # Arguments @@ -219,7 +271,10 @@ impl OracleSample { active_id: u32, volatility_accumulator: u32, bin_crossed: u32, - ) -> (u64, u64, u64) { + vol: Bytes32, + fee: Bytes32, + ) -> (u16, u64, u64, u64, Bytes32, Bytes32) { + let mut cumulative_txns = self.get_cumulative_txns(); let cumulative_id = u64::from(active_id) * delta_time; let cumulative_volatility = u64::from(volatility_accumulator) * delta_time; let cumulative_bin_crossed = u64::from(bin_crossed) * delta_time; @@ -228,18 +283,47 @@ impl OracleSample { let cumulative_volatility = cumulative_volatility + self.get_cumulative_volatility(); let cumulative_bin_crossed = cumulative_bin_crossed + self.get_cumulative_bin_crossed(); - (cumulative_id, cumulative_volatility, cumulative_bin_crossed) + if !(vol == [0u8; 32]) && !(fee == [0u8; 32]) { + cumulative_txns += 1; + } + + let cumm_vol = vol.add(self.volume.0); + let cumm_fee = fee.add(self.fee.0); + ( + cumulative_txns, + cumulative_id, + cumulative_volatility, + cumulative_bin_crossed, + cumm_vol, + cumm_fee, + ) + } + + /// Set the creation_time in the encoded pair parameters. + /// + /// # Arguments + /// + /// * `parameters` - The encoded pair parameters + /// * `created_at` - Time of the creation + pub fn set_created_at(&mut self, created_at: u64) -> &mut Self { + self.data + .set(created_at.into(), MASK_UINT40, OFFSET_SAMPLE_CREATION); + self } } #[cfg(test)] mod tests { + use cosmwasm_std::Uint256; + use super::*; #[test] fn test_encode() { - let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456); - assert_eq!(sample.get_oracle_length(), 3); + let vol = Uint256::from(123u128).to_le_bytes(); + let fee = Uint256::from(123u128).to_le_bytes(); + let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456, vol, fee); + assert_eq!(sample.get_cumulative_txns(), 3); assert_eq!(sample.get_cumulative_id(), 1000); assert_eq!(sample.get_cumulative_volatility(), 2000); assert_eq!(sample.get_cumulative_bin_crossed(), 3000); @@ -248,51 +332,67 @@ mod tests { } #[test] - fn test_get_oracle_length() { - let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456); - assert_eq!(sample.get_oracle_length(), 3); + fn test_get_cumulative_txns() { + let vol = Uint256::from(123u128).to_le_bytes(); + let fee = Uint256::from(123u128).to_le_bytes(); + let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456, vol, fee); + assert_eq!(sample.get_cumulative_txns(), 3); } #[test] fn test_get_cumulative_id() { - let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456); + let vol = Uint256::from(123u128).to_le_bytes(); + let fee = Uint256::from(123u128).to_le_bytes(); + let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456, vol, fee); assert_eq!(sample.get_cumulative_id(), 1000); } #[test] fn test_get_cumulative_volatility() { - let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456); + let vol = Uint256::from(123u128).to_le_bytes(); + let fee = Uint256::from(123u128).to_le_bytes(); + let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456, vol, fee); assert_eq!(sample.get_cumulative_volatility(), 2000); } #[test] fn test_get_cumulative_bin_crossed() { - let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456); + let vol = Uint256::from(123u128).to_le_bytes(); + let fee = Uint256::from(123u128).to_le_bytes(); + let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456, vol, fee); assert_eq!(sample.get_cumulative_bin_crossed(), 3000); } #[test] fn test_get_sample_lifetime() { - let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456); + let vol = Uint256::from(123u128).to_le_bytes(); + let fee = Uint256::from(123u128).to_le_bytes(); + let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456, vol, fee); assert_eq!(sample.get_sample_lifetime(), 4); } #[test] fn test_get_sample_creation() { - let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456); + let vol = Uint256::from(123u128).to_le_bytes(); + let fee = Uint256::from(123u128).to_le_bytes(); + let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456, vol, fee); assert_eq!(sample.get_sample_creation(), 123456); } #[test] fn test_get_sample_last_update() { - let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456); + let vol = Uint256::from(123u128).to_le_bytes(); + let fee = Uint256::from(123u128).to_le_bytes(); + let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456, vol, fee); assert_eq!(sample.get_sample_last_update(), 123460); // 123456 + 4 } #[test] fn test_get_weighted_average() { - let sample1 = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456); - let sample2 = OracleSample::encode(3, 2000, 4000, 6000, 4, 123456); + let vol = Uint256::from(123u128).to_le_bytes(); + let fee = Uint256::from(123u128).to_le_bytes(); + let sample1 = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456, vol, fee); + let sample2 = OracleSample::encode(3, 2000, 4000, 6000, 4, 123456, vol, fee); let (avg_id, avg_vol, avg_bin) = OracleSample::get_weighted_average(sample1, sample2, 1, 1); assert_eq!(avg_id, 1500); assert_eq!(avg_vol, 3000); @@ -301,11 +401,24 @@ mod tests { #[test] fn test_update() { - let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456); - let (new_id, new_vol, new_bin) = sample.update(1, 1000, 2000, 3000); - - assert_eq!(new_id, 2000); - assert_eq!(new_vol, 4000); - assert_eq!(new_bin, 6000); + let vol = Uint256::from(123u128).to_le_bytes(); + let fee = Uint256::from(123u128).to_le_bytes(); + + let sample = OracleSample::encode(3, 1000, 2000, 3000, 4, 123456, vol, fee); + let ( + cumulative_txns, + cumulative_id, + cumulative_volatility, + cumulative_bin_crossed, + cumm_vol, + cumm_fee, + ) = sample.update(1, 1000, 2000, 3000, vol, fee); + + assert_eq!(cumulative_txns, 4); + assert_eq!(cumulative_id, 2000); + assert_eq!(cumulative_volatility, 4000); + assert_eq!(cumulative_bin_crossed, 6000); + assert_eq!(cumm_vol, vol.add(vol)); + assert_eq!(cumm_fee, fee.add(fee)); } } diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u128x128_math.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u128x128_math.rs index fdbc3b95..44d89c63 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u128x128_math.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u128x128_math.rs @@ -16,6 +16,8 @@ pub enum U128x128MathError { LogUnderflow, #[error("U128x128 Math Error: PowUnderflow {0} {1}")] PowUnderflow(U256, I256), + #[error("U128x128 Math Error: ID Shift overflow")] + IdShiftOverflow, } // This is 127 diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u256x256_math.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u256x256_math.rs index 7c06a4a6..b6c54e86 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u256x256_math.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u256x256_math.rs @@ -3,59 +3,53 @@ //! //! Helper library used for full precision calculations. -use std::ops::BitXor; - use ethnum::U256; use primitive_types::{U128, U512}; +use std::ops::BitXor; -pub fn u256_to_u512(u: &U256) -> U512 { - U512::from_little_endian(&u.to_le_bytes()) +pub trait U256ToU512Conversion { + fn to_u512(&self) -> U512; } -pub fn u512_to_u256(r: U512) -> U256 { - if r <= U512::zero() { - U256::ZERO - } else if r > U512::from_little_endian(&U256::MAX.to_le_bytes()) { - U256::MAX - } else { - let lo = U128([r.0[0], r.0[1]]).as_u128(); - let hi = U128([r.0[2], r.0[3]]).as_u128(); - U256::from_words(hi, lo) +impl U256ToU512Conversion for U256 { + fn to_u512(&self) -> U512 { + U512::from_little_endian(&self.to_le_bytes()) } } -/// Computes (x * y) % k where the addition is performed with arbitrary precision and does not wrap around at 2^256. -pub fn mulmod(x: U256, y: U256, k: U256) -> U256 { - if k == U256::ZERO { - return U256::ZERO; - } +pub trait U512ToU256Conversion { + fn to_u256(&self) -> U256; +} - if let Some(z) = x.checked_mul(y) { - return z % k; +impl U512ToU256Conversion for U512 { + fn to_u256(&self) -> U256 { + if self <= &U512::zero() { + U256::ZERO + } else if self > &U512::from_little_endian(&U256::MAX.to_le_bytes()) { + U256::MAX + } else { + let lo: u128 = U128([self.0[0], self.0[1]]).as_u128(); + let hi: u128 = U128([self.0[2], self.0[3]]).as_u128(); + U256::from_words(hi, lo) + } } - - let x = u256_to_u512(&x); - let y = u256_to_u512(&y); - let k = u256_to_u512(&k); - let z = (x * y) % k; - u512_to_u256(z) } -/// Computes (x + y) % k where the addition is performed with arbitrary precision and does not wrap around at 2^256. -pub fn addmod(x: U256, y: U256, k: U256) -> U256 { +/// Computes (x * y) % k where the addition is performed with arbitrary precision and does not wrap around at 2^256. +fn mulmod(x: U256, y: U256, k: U256) -> U256 { if k == U256::ZERO { return U256::ZERO; } - if let Some(z) = x.checked_add(y) { + if let Some(z) = x.checked_mul(y) { return z % k; } - let x = u256_to_u512(&x); - let y = u256_to_u512(&y); - let k = u256_to_u512(&k); - let z = (x + y) % k; - u512_to_u256(z) + let x: &U512 = &x.to_u512(); + let y: &U512 = &y.to_u512(); + let k: &U512 = &k.to_u512(); + let z: U512 = (x * y) % k; + z.to_u256() } #[derive(thiserror::Error, Debug)] @@ -68,6 +62,9 @@ pub enum U256x256MathError { #[error("U256x256 Math Error: MulDivOverflow")] MulDivOverflow, + + #[error("Value greater than u128")] + U128Overflow, } pub struct U256x256Math; @@ -108,10 +105,7 @@ impl U256x256Math { let (prod0, prod1) = Self::_get_mul_prods(x, y)?; - // println!("prod0: {:?}\n prod1 {:?}", prod0, prod1); - let result = Self::_get_end_of_div_round_down(x, y, denominator, prod0, prod1)?; - // println!("result: {:?}", result); Ok(result) } @@ -146,7 +140,7 @@ impl U256x256Math { ) -> Result { let mut result = Self::mul_div_round_down(x, y, denominator)?; - if x.wrapping_mul(y) % denominator != 0 { + if mulmod(x, y, denominator) != 0 { result += 1; } @@ -223,7 +217,7 @@ impl U256x256Math { pub fn mul_shift_round_up(x: U256, y: U256, offset: u8) -> Result { let mut result = Self::mul_shift_round_down(x, y, offset)?; - if x.wrapping_mul(y) % (U256::ONE << offset) != 0 { + if mulmod(x, y, U256::ONE << offset) != 0 { result += 1; } @@ -260,12 +254,8 @@ impl U256x256Math { ) -> Result { let prod0 = x << offset; // Least significant 256 bits of the product let prod1 = x >> (256u16 - offset as u16); // Most significant 256 bits of the product - - let y = U256::ONE - .checked_shl(offset as u32) - .ok_or(U256x256MathError::Generic("overflow".to_owned()))?; - - let result = Self::_get_end_of_div_round_down(x, y, denominator, prod0, prod1)?; + let result = + Self::_get_end_of_div_round_down(x, U256::ONE << offset, denominator, prod0, prod1)?; Ok(result) } @@ -300,7 +290,7 @@ impl U256x256Math { ) -> Result { let mut result = Self::shift_div_round_down(x, offset, denominator)?; - if x.wrapping_mul(U256::ONE << offset) % denominator != 0 { + if mulmod(x, U256::ONE << offset, denominator) != 0 { result += 1; } @@ -359,7 +349,6 @@ impl U256x256Math { // Handle non-overflow cases, 256 by 256 division if prod1 == 0 { result = prod0 / denominator; - // println!("result {:?}", result); Ok(result) } else { // Make sure the result is less than 2^256. Also prevents denominator == 0 @@ -371,14 +360,12 @@ impl U256x256Math { // Compute remainder using mulmod. let remainder = mulmod(x, y, denominator); - // println!("remainder: {:#?}", remainder); // Subtract 256 bit number from 512 bit number. if remainder > prod0 { prod1 = prod1.wrapping_sub(U256::ONE) } prod0 = prod0.wrapping_sub(remainder); - // println!("prod0 - remainder: {:#?}", prod0); // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1 // See https://cs.stackexchange.com/q/138556/92363 @@ -389,19 +376,13 @@ impl U256x256Math { // Divide denominator by lpotdod. let denominator = denominator / lpotdod; - // println!("denominator / lpotdod: {:#?}", denominator); // Divide [prod1 prod0] by lpotdod. let prod0 = prod0 / lpotdod; - // println!("prod0 / lpotdod: {:#?}", prod0); // Flip lpotdod such that it is 2^256 / lpotdod. If lpotdod is zero, then it becomes one - // match lpotdod { - // U256::ONE => lpotdod = U256::ONE, - // _ => lpotdod = (U256::ZERO.wrapping_sub(lpotdod) / lpotdod).wrapping_add(U256::ONE), - // } + lpotdod = (U256::ZERO.wrapping_sub(lpotdod) / lpotdod).wrapping_add(U256::ONE); - // println!("lpotdod: {:?}", lpotdod); // Shift in bits from prod1 into prod0 let prod0 = prod0 | (prod1.wrapping_mul(lpotdod)); @@ -411,42 +392,33 @@ impl U256x256Math { // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4 let mut inverse = U256::from(3u8).wrapping_mul(denominator).bitxor(2); - // println!("inverse: {:#?}", inverse); // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^8 - // println!("inverse: {:#?}", inverse); inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^16 - // println!("inverse: {:#?}", inverse); inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^32 - // println!("inverse: {:#?}", inverse); inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^64 - // println!("inverse: {:#?}", inverse); inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^128 - // println!("inverse: {:#?}", inverse); inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^256 - // println!("inverse: {:#?}", inverse); // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0.wrapping_mul(inverse); - // println!("inverse: {:#?}", inverse); Ok(result) } } } -// TODO - use test values that would be different when rounded up vs rounded down #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/oracle_helper.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/oracle_helper.rs index c9d0f1b7..1b86f316 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/oracle_helper.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/oracle_helper.rs @@ -12,23 +12,21 @@ //! * 208 - 216: sample lifetime (8 bits) //! * 216 - 256: sample creation timestamp (40 bits) -use std::{cmp::Ordering, collections::HashMap}; - use cosmwasm_schema::cw_serde; use cosmwasm_std::Timestamp; -use ethnum::U256; use super::{ - math::{encoded_sample::EncodedSample, sample_math::OracleSample, u256x256_math::addmod}, + math::sample_math::OracleSample, pair_parameter_helper::PairParameters, + types::Bytes32, }; #[cw_serde] -pub struct Oracle { +pub struct Oracle( /// This array represents a fixed-size storage for 65535 samples, /// where each sample is a 32-byte (256-bit) value. - pub samples: HashMap, -} + pub OracleSample, +); pub const MAX_SAMPLE_LIFETIME: u8 = 120; //seconds @@ -44,927 +42,69 @@ pub enum OracleError { } impl Oracle { - /// Modifier to check that the oracle id is valid. - fn check_oracle_id(oracle_id: u16) -> Result<(), OracleError> { - if oracle_id == 0 { - return Err(OracleError::InvalidOracleId); - } - - Ok(()) - } - - /// Returns the sample at the given oracleId. - pub fn get_sample(&self, oracle_id: u16) -> Result { - Self::check_oracle_id(oracle_id)?; - - // TODO - Should this return a default sample if there is None? or an Error? - match self.samples.get(&(oracle_id - 1)) { - Some(sample) => Ok(*sample), - None => Ok(OracleSample::default()), - } - } - - /// Returns the active sample (Bytes32) and the active size (u16) of the oracle. - pub fn get_active_sample_and_size( - &self, - oracle_id: u16, - ) -> Result<(OracleSample, u16), OracleError> { - let active_sample = self.get_sample(oracle_id)?; - let mut active_size = OracleSample::get_oracle_length(&active_sample); - - if oracle_id != active_size { - active_size = OracleSample::get_oracle_length(&self.get_sample(active_size)?); - active_size = if oracle_id > active_size { - oracle_id - } else { - active_size - }; - } - - Ok((active_sample, active_size)) - } - - /// Returns the sample at the given timestamp. If the timestamp is not in the oracle, it returns the closest sample. - /// - /// # Arguments - /// - /// * `oracle_id` - The oracle id - /// * `look_up_timestamp` - The timestamp to look up - /// - /// # Returns - /// - /// * `last_update` - The last update timestamp - /// * `cumulative_id` - The cumulative id - /// * `cumulative_volatility` - The cumulative volatility - /// * `cumulative_bin_crossed` - The cumulative bin crossed - pub fn get_sample_at( - &self, - oracle_id: u16, - look_up_timestamp: u64, - ) -> Result<(u64, u64, u64, u64), OracleError> { - let (active_sample, active_size) = self.get_active_sample_and_size(oracle_id)?; - - if OracleSample::get_sample_last_update(&self.samples[&(oracle_id % active_size)]) - > look_up_timestamp - { - return Err(OracleError::LookUpTimestampTooOld); - } - - let mut last_update = OracleSample::get_sample_last_update(&active_sample); - if last_update <= look_up_timestamp { - return Ok(( - last_update, - OracleSample::get_cumulative_id(&active_sample), - OracleSample::get_cumulative_volatility(&active_sample), - OracleSample::get_cumulative_bin_crossed(&active_sample), - )); - } else { - last_update = look_up_timestamp; - } - let (prev_sample, next_sample) = - self.binary_search(oracle_id, look_up_timestamp, active_size)?; - let weight_prev = next_sample.get_sample_last_update() - look_up_timestamp; - let weight_next = look_up_timestamp - prev_sample.get_sample_last_update(); - - let (cumulative_id, cumulative_volatility, cumulative_bin_crossed) = - OracleSample::get_weighted_average(prev_sample, next_sample, weight_prev, weight_next); - - Ok(( - last_update, - cumulative_id, - cumulative_volatility, - cumulative_bin_crossed, - )) - } - - /// Binary search to find the 2 samples surrounding the given timestamp. - /// - /// # Arguments - /// - /// * `oracle` - The oracle - /// * `oracleId` - The oracle id - /// * `look_up_timestamp` - The timestamp to look up - /// * `length` - The oracle length - /// - /// # Returns - /// - /// * `prev_sample` - The previous sample - /// * `next_sample` - The next sample - pub fn binary_search( - &self, - oracle_id: u16, - look_up_timestamp: u64, - length: u16, - ) -> Result<(OracleSample, OracleSample), OracleError> { - let mut oracle_id = oracle_id; - let mut low = 0; - let mut high = length - 1; - - // TODO: not sure if it's ok to initialize these at 0 - let mut sample = OracleSample::default(); - let mut sample_last_update = 0u64; - - let start_id = oracle_id; // oracleId is 1-based - - while low <= high { - let mid = ((low as u32 + high as u32) >> 1) as u16; - - oracle_id = ((start_id as u32 + mid as u32) % (length as u32)) as u16; - - sample = *self - .samples - .get(&oracle_id) - .unwrap_or(&OracleSample::default()); - - sample_last_update = sample.get_sample_last_update(); - - match sample_last_update.cmp(&look_up_timestamp) { - Ordering::Greater => high = mid - 1, - Ordering::Less => low = mid + 1, - Ordering::Equal => return Ok((sample, sample)), - } - } - - if look_up_timestamp < sample_last_update { - if oracle_id == 0 { - oracle_id = length; - } - - let prev_sample = *self - .samples - .get(&(oracle_id - 1)) - .unwrap_or(&OracleSample::default()); - - Ok((prev_sample, sample)) - } else { - oracle_id = addmod(oracle_id.into(), U256::ONE, length.into()).as_u16(); - - let next_sample = *self - .samples - .get(&oracle_id) - .unwrap_or(&OracleSample::default()); - - Ok((sample, next_sample)) - } - } - - /// Sets the sample at the given oracle_id. - pub fn set_sample(&mut self, oracle_id: u16, sample: OracleSample) -> Result<(), OracleError> { - Self::check_oracle_id(oracle_id)?; - - self.samples.insert(oracle_id - 1, sample); - - Ok(()) - } - /// Updates the oracle and returns the updated pair parameters. pub fn update( &mut self, time: &Timestamp, mut parameters: PairParameters, active_id: u32, - ) -> Result { - let oracle_id = parameters.get_oracle_id(); + new_volume: Option, + new_fee: Option, + length: u16, + ) -> Result<(PairParameters, Option), OracleError> { + let mut oracle_id = parameters.get_oracle_id(); if oracle_id == 0 { - return Ok(parameters); - } + return Ok((parameters, None)); + }; - let sample = self.get_sample(oracle_id)?; + let new_vol = new_volume.unwrap_or_default(); + let new_fee = new_fee.unwrap_or_default(); - let created_at = sample.get_sample_creation(); - let last_updated_at = created_at + sample.get_sample_lifetime() as u64; + let mut created_at = self.0.get_sample_creation(); + let last_updated_at = created_at + self.0.get_sample_lifetime() as u64; if time.seconds() > last_updated_at { - let (cumulative_id, cumulative_volatility, cumulative_bin_crossed) = - OracleSample::update( - sample, - time.seconds() - last_updated_at, - active_id, - parameters.get_volatility_accumulator(), - parameters.get_delta_id(active_id), - ); - - let length = sample.get_oracle_length(); - let lifetime = time.seconds() - created_at; + let ( + mut cumulative_txns, + cumulative_id, + cumulative_volatility, + cumulative_bin_crossed, + cumulative_vol, + cumulative_fee, + ) = OracleSample::update( + self.0, + time.seconds() - last_updated_at, + active_id, + parameters.get_volatility_accumulator(), + parameters.get_delta_id(active_id), + new_vol, + new_fee, + ); - let oracle_id = if lifetime > MAX_SAMPLE_LIFETIME as u64 { - (oracle_id % length) + 1 - } else { - oracle_id - }; + let mut lifetime = time.seconds() - created_at; - let created_at = if lifetime > MAX_SAMPLE_LIFETIME as u64 { - time.seconds() - } else { - created_at - }; + if lifetime > MAX_SAMPLE_LIFETIME as u64 { + cumulative_txns = 1; + oracle_id = (oracle_id % length) + 1; + lifetime = 0; + created_at = time.seconds(); + parameters.set_oracle_id(oracle_id); + } let new_sample = OracleSample::encode( - length, + cumulative_txns, cumulative_id, cumulative_volatility, cumulative_bin_crossed, lifetime as u8, created_at, + cumulative_vol, + cumulative_fee, ); - self.set_sample(oracle_id, new_sample)?; - - parameters.set_oracle_id(oracle_id); - - return Ok(parameters); + return Ok((parameters, Some(new_sample))); } - Ok(parameters) - } - - /// Increases the oracle length. - pub fn increase_length( - &mut self, - oracle_id: u16, - new_length: u16, - ) -> Result<&mut Self, OracleError> { - let sample = self.get_sample(oracle_id)?; - let length = sample.get_oracle_length(); - - if length >= new_length { - return Err(OracleError::NewLengthTooSmall); - } - - let last_sample = if length == oracle_id { - sample - } else if length == 0 { - OracleSample(EncodedSample([0u8; 32])) - } else { - self.get_sample(length)? - }; - - let mut active_size = last_sample.get_oracle_length(); - active_size = if oracle_id > active_size { - oracle_id - } else { - active_size - }; - - for i in length..new_length { - // NOTE: I think what this does is encode the active_size as the oracle_length (16 bits) - // in each of the newly added samples... the rest of the sample values are empty. - self.samples.insert( - i, - OracleSample(EncodedSample(U256::from(active_size).to_le_bytes())), - ); - } - - // I think this is a fancy way of changing the length of the current sample. - // It's confusing looking because we don't have methods for pow or bitOR for bytes32, - // so we have to convert to U256 and back. - let new_sample = - (U256::from_le_bytes(sample.0.0) ^ U256::from(length)) | U256::from(new_length); - - self.set_sample( - oracle_id, - OracleSample(EncodedSample(new_sample.to_le_bytes())), - )?; - - Ok(self) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::liquidity_book::lb_libraries::math::encoded_sample::MASK_UINT20; - use std::collections::HashMap; - - // Helper function to bound a value within a range - fn bound(value: T, min: T, max: T) -> T { - if value < min { - min - } else if value > max { - max - } else { - value - } - } - - #[test] - fn test_set_and_get_sample() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - // Replace with random values for fuzz testing - let oracle_id: u16 = 1; - let sample = OracleSample(EncodedSample([0u8; 32])); - - oracle.set_sample(oracle_id, sample).unwrap(); - - let retrieved_sample = oracle.get_sample(oracle_id).unwrap(); - assert_eq!(retrieved_sample, sample, "test_SetSample::1"); - - let internal_sample = oracle.samples.get(&(oracle_id - 1)).unwrap(); - assert_eq!(*internal_sample, sample, "test_SetSample::2"); - } - - #[test] - fn test_revert_set_and_get_sample() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - let oracle_id: u16 = 0; - let sample = OracleSample(EncodedSample([0u8; 32])); - - match oracle.set_sample(oracle_id, sample) { - Err(OracleError::InvalidOracleId) => {} // Expected error - _ => panic!("test_revert_SetSample failed"), - } - - match oracle.get_sample(oracle_id) { - Err(OracleError::InvalidOracleId) => {} // Expected error - _ => panic!("test_revert_GetSample failed"), - } - } - - #[test] - fn test_set_and_get_sample_edge_cases() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - // Test with maximum oracle_id value for u16 - let max_oracle_id: u16 = u16::MAX; - let sample = OracleSample(EncodedSample([1u8; 32])); - - // Set sample with maximum oracle_id - oracle.set_sample(max_oracle_id, sample).unwrap(); - - // Retrieve and validate - let retrieved_sample = oracle.get_sample(max_oracle_id).unwrap(); - assert_eq!( - retrieved_sample, sample, - "test_set_and_get_sample_edge_cases::MaxOracleId" - ); - - // Test with minimum valid oracle_id (1, since 0 is considered invalid) - let min_valid_oracle_id: u16 = 1; - oracle.set_sample(min_valid_oracle_id, sample).unwrap(); - - // Retrieve and validate - let retrieved_sample = oracle.get_sample(min_valid_oracle_id).unwrap(); - assert_eq!( - retrieved_sample, sample, - "test_set_and_get_sample_edge_cases::MinValidOracleId" - ); - - // Test with an empty sample ([0u8; 32]) - let empty_sample = OracleSample(EncodedSample([0u8; 32])); - oracle - .set_sample(min_valid_oracle_id, empty_sample) - .unwrap(); - - // Retrieve and validate - let retrieved_sample = oracle.get_sample(min_valid_oracle_id).unwrap(); - assert_eq!( - retrieved_sample, empty_sample, - "test_set_and_get_sample_edge_cases::EmptySample" - ); - } - - #[test] - fn test_binary_search_simple() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - let sample1 = OracleSample::encode(3, 1, 2, 3, 0, 0); - let sample2 = OracleSample::encode(3, 2, 3, 4, 0, 10); - let sample3 = OracleSample::encode(3, 3, 4, 5, 0, 20); - - oracle.set_sample(1, sample1).unwrap(); - oracle.set_sample(2, sample2).unwrap(); - oracle.set_sample(3, sample3).unwrap(); - - let (previous, next) = oracle.binary_search(3, 0, 3).unwrap(); - assert_eq!(previous, sample1, "test_binarySearch::1"); - assert_eq!(next, sample1, "test_binarySearch::2"); - - let (previous, next) = oracle.binary_search(3, 1, 3).unwrap(); - assert_eq!(previous, sample1, "test_binarySearch::3"); - assert_eq!(next, sample2, "test_binarySearch::4"); - - let (previous, next) = oracle.binary_search(3, 9, 3).unwrap(); - assert_eq!(previous, sample1, "test_binarySearch::5"); - assert_eq!(next, sample2, "test_binarySearch::6"); - - let (previous, next) = oracle.binary_search(3, 10, 3).unwrap(); - assert_eq!(previous, sample2, "test_binarySearch::7"); - assert_eq!(next, sample2, "test_binarySearch::8"); - - let (previous, next) = oracle.binary_search(3, 11, 3).unwrap(); - assert_eq!(previous, sample2, "test_binarySearch::9"); - assert_eq!(next, sample3, "test_binarySearch::10"); - - let (previous, next) = oracle.binary_search(3, 20, 3).unwrap(); - assert_eq!(previous, sample3, "test_binarySearch::11"); - assert_eq!(next, sample3, "test_binarySearch::12"); - } - - #[test] - fn test_binary_search_circular() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - let sample1 = OracleSample::encode(3, 1, 2, 3, 3, 30); - let sample2 = OracleSample::encode(3, 2, 3, 4, 9, 10); - let sample3 = OracleSample::encode(3, 3, 4, 5, 9, 20); - - oracle.set_sample(1, sample1).unwrap(); - oracle.set_sample(2, sample2).unwrap(); - oracle.set_sample(3, sample3).unwrap(); - - let (previous, next) = oracle.binary_search(1, 19, 3).unwrap(); - assert_eq!(previous, sample2, "test_binarySearch::1"); - assert_eq!(next, sample2, "test_binarySearch::2"); - - let (previous, next) = oracle.binary_search(1, 24, 3).unwrap(); - assert_eq!(previous, sample2, "test_binarySearch::3"); - assert_eq!(next, sample3, "test_binarySearch::4"); - - let (previous, next) = oracle.binary_search(1, 29, 3).unwrap(); - assert_eq!(previous, sample3, "test_binarySearch::5"); - assert_eq!(next, sample3, "test_binarySearch::6"); - - let (previous, next) = oracle.binary_search(1, 30, 3).unwrap(); - assert_eq!(previous, sample3, "test_binarySearch::7"); - assert_eq!(next, sample1, "test_binarySearch::8"); - - let (previous, next) = oracle.binary_search(1, 33, 3).unwrap(); - assert_eq!(previous, sample1, "test_binarySearch::9"); - assert_eq!(next, sample1, "test_binarySearch::10"); - } - - #[test] - #[should_panic] - fn test_revert_binary_search() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - let sample1 = OracleSample::encode(3, 1, 2, 3, 0, 30); - let sample2 = OracleSample::encode(3, 2, 3, 4, 5, 10); - - // Invalid oracleId - match oracle.binary_search(0, 20, 3) { - Err(OracleError::InvalidOracleId) => {} - _ => panic!("test_revert_BinarySearch::1 failed"), - } - - // Invalid length - match oracle.binary_search(1, 20, 0) { - Err(OracleError::InvalidOracleId) => {} - _ => panic!("test_revert_BinarySearch::2 failed"), - } - - oracle.set_sample(1, sample1).unwrap(); - oracle.set_sample(2, sample2).unwrap(); - - // Invalid oracleId - match oracle.binary_search(0, 20, 3) { - Err(OracleError::InvalidOracleId) => {} - _ => panic!("test_revert_BinarySearch::3 failed"), - } - - // Invalid length - match oracle.binary_search(1, 20, 0) { - Err(OracleError::InvalidOracleId) => {} - _ => panic!("test_revert_BinarySearch::4 failed"), - } - - // Invalid timestamp - match oracle.binary_search(1, 9, 2) { - Err(OracleError::LookUpTimestampTooOld) => {} - _ => panic!("test_revert_BinarySearch::5 failed"), - } - - // Invalid timestamp - match oracle.binary_search(1, 31, 2) { - Err(OracleError::LookUpTimestampTooOld) => {} - _ => panic!("test_revert_BinarySearch::6 failed"), - } - } - - #[test] - fn test_binary_search_simple_edge_cases() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - // 1. Minimum Length - let sample_min = OracleSample::encode(1, 1, 2, 3, 0, 0); - oracle.set_sample(1, sample_min).unwrap(); - - let (previous, next) = oracle.binary_search(1, 0, 1).unwrap(); - assert_eq!( - previous, sample_min, - "test_binary_search_simple_edge_cases::MinLength1" - ); - assert_eq!( - next, sample_min, - "test_binary_search_simple_edge_cases::MinLength2" - ); - - // 2. Maximum Timestamp - let mut oracle = Oracle { - samples: HashMap::new(), - }; - let max_timestamp: u64 = u64::MAX; - let sample_max = OracleSample::encode(u16::MAX, 1, 2, 3, 0, 0); - oracle.set_sample(u16::MAX - 2, sample_max).unwrap(); - oracle.set_sample(u16::MAX - 1, sample_max).unwrap(); - oracle.set_sample(u16::MAX, sample_max).unwrap(); - - let (previous, next) = oracle - .binary_search(u16::MAX - 1, max_timestamp, u16::MAX) - .unwrap(); - assert_eq!( - previous, sample_max, - "test_binary_search_simple_edge_cases::MaxTimestamp1" - ); - assert_eq!( - next, sample_max, - "test_binary_search_simple_edge_cases::MaxTimestamp2" - ); - - // 3. Minimum Timestamp - let min_timestamp: u64 = 0; - let sample_min_ts = OracleSample::encode(2, 1, 2, 3, 0, 0); - oracle.set_sample(1, sample_min_ts).unwrap(); - oracle.set_sample(2, sample_min_ts).unwrap(); - - let (previous, next) = oracle.binary_search(1, min_timestamp, 2).unwrap(); - assert_eq!( - previous, sample_min_ts, - "test_binary_search_simple_edge_cases::MinTimestamp1" - ); - assert_eq!( - next, sample_min_ts, - "test_binary_search_simple_edge_cases::MinTimestamp2" - ); - } - - #[test] - fn test_get_sample_at_fully_initialized() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - let sample1 = OracleSample::encode(3, 40, 50, 60, 3, 30); - let sample2 = OracleSample::encode(3, 20, 30, 40, 5, 10); - let sample3 = OracleSample::encode(3, 30, 40, 50, 5, 20); - - oracle.set_sample(1, sample1).unwrap(); - oracle.set_sample(2, sample2).unwrap(); - oracle.set_sample(3, sample3).unwrap(); - - let (last_update, cumulative_id, cumulative_volatility, cumulative_bin_crossed) = - oracle.get_sample_at(1, 15).unwrap(); - - assert_eq!(last_update, 15, "test_GetSampleAt::1"); - assert_eq!(cumulative_id, 20, "test_GetSampleAt::2"); - assert_eq!(cumulative_volatility, 30, "test_GetSampleAt::3"); - assert_eq!(cumulative_bin_crossed, 40, "test_GetSampleAt::4"); - - let (last_update, cumulative_id, cumulative_volatility, cumulative_bin_crossed) = - oracle.get_sample_at(1, 20).unwrap(); - - assert_eq!(last_update, 20, "test_GetSampleAt::5"); - assert_eq!(cumulative_id, 25, "test_GetSampleAt::6"); - assert_eq!(cumulative_volatility, 35, "test_GetSampleAt::7"); - assert_eq!(cumulative_bin_crossed, 45, "test_GetSampleAt::8"); - - let (last_update, cumulative_id, cumulative_volatility, cumulative_bin_crossed) = - oracle.get_sample_at(1, 25).unwrap(); - - assert_eq!(last_update, 25, "test_GetSampleAt::9"); - assert_eq!(cumulative_id, 30, "test_GetSampleAt::10"); - assert_eq!(cumulative_volatility, 40, "test_GetSampleAt::11"); - assert_eq!(cumulative_bin_crossed, 50, "test_GetSampleAt::12"); - - let (last_update, cumulative_id, cumulative_volatility, cumulative_bin_crossed) = - oracle.get_sample_at(1, 30).unwrap(); - - assert_eq!(last_update, 30, "test_GetSampleAt::13"); - assert_eq!(cumulative_id, 36, "test_GetSampleAt::14"); - assert_eq!(cumulative_volatility, 46, "test_GetSampleAt::15"); - assert_eq!(cumulative_bin_crossed, 56, "test_GetSampleAt::16"); - - let (last_update, cumulative_id, cumulative_volatility, cumulative_bin_crossed) = - oracle.get_sample_at(1, 40).unwrap(); - - assert_eq!(last_update, 33, "test_GetSampleAt::17"); - assert_eq!(cumulative_id, 40, "test_GetSampleAt::18"); - assert_eq!(cumulative_volatility, 50, "test_GetSampleAt::19"); - assert_eq!(cumulative_bin_crossed, 60, "test_GetSampleAt::20"); - } - - struct UpdateInputs { - pub oracle_length: u16, - pub oracle_id: u16, - pub previous_active_id: u32, // u24 is not a native Rust type, so we use u32 - pub active_id: u32, // u24 is not a native Rust type, so we use u32 - pub previous_volatility: u32, // u24 is not a native Rust type, so we use u32 - pub volatility: u32, // u24 is not a native Rust type, so we use u32 - pub previous_bin_crossed: u32, // u24 is not a native Rust type, so we use u32 - pub created_at: u64, // u40 is not a native Rust type, so we use u64 - pub timestamp: u64, // u40 is not a native Rust type, so we use u64 - } - - #[test] - fn test_update_delta_ts_lower_than_2_minutes() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - // Populate inputs struct (you may want to fuzz these values) - let mut inputs = UpdateInputs { - oracle_length: 3, - oracle_id: 2, - previous_active_id: 100, - active_id: 200, - previous_volatility: 50, - volatility: 60, - previous_bin_crossed: 1, - created_at: 10, - timestamp: 20, - }; - - inputs.oracle_id = bound(inputs.oracle_id, 1, u16::MAX); - inputs.oracle_length = bound(inputs.oracle_length, inputs.oracle_id, u16::MAX); - inputs.created_at = bound( - inputs.created_at, - if inputs.timestamp > 120 { - inputs.timestamp - 120 - } else { - 0 - }, - inputs.timestamp, - ); - inputs.volatility = bound(inputs.volatility, 1, MASK_UINT20.as_u32()); - inputs.previous_volatility = bound(inputs.previous_volatility, 1, MASK_UINT20.as_u32()); - - let sample = OracleSample::encode( - inputs.oracle_length, - inputs.previous_active_id as u64 * inputs.created_at, - inputs.previous_volatility as u64 * inputs.created_at, - inputs.previous_bin_crossed as u64 * inputs.created_at, - 0, - inputs.created_at, - ); - - oracle.set_sample(inputs.oracle_id, sample).unwrap(); - - let mut parameters = PairParameters(EncodedSample([0u8; 32])); - - parameters.set_oracle_id(inputs.oracle_id); - parameters.set_active_id(inputs.previous_active_id).unwrap(); - parameters - .set_volatility_accumulator(inputs.volatility) - .unwrap(); - - let new_params = oracle - .update( - &Timestamp::from_seconds(inputs.timestamp), - parameters, - inputs.active_id, - ) - .unwrap(); - - assert_eq!(new_params, parameters, "test_Update::1"); - - let sample = oracle.get_sample(inputs.oracle_id).unwrap(); - - let dt = inputs.timestamp - inputs.created_at; - - let d_id = if inputs.active_id > inputs.previous_active_id { - inputs.active_id - inputs.previous_active_id - } else { - inputs.previous_active_id - inputs.active_id - } as u64; - - let cumulative_id = - (inputs.previous_active_id as u64 * inputs.created_at) + (inputs.active_id as u64 * dt); - let cumulative_volatility = (inputs.previous_volatility as u64 * inputs.created_at) - + (inputs.volatility as u64 * dt); - let cumulative_bin_crossed = - (inputs.previous_bin_crossed as u64 * inputs.created_at) + (d_id * dt); - - assert_eq!( - sample.get_oracle_length(), - inputs.oracle_length, - "test_Update::3" - ); - assert_eq!(sample.get_cumulative_id(), cumulative_id, "test_Update::4"); - assert_eq!( - sample.get_cumulative_volatility(), - cumulative_volatility, - "test_Update::5" - ); - assert_eq!( - sample.get_cumulative_bin_crossed(), - cumulative_bin_crossed, - "test_Update::6" - ); - } - - #[test] - fn test_update_delta_ts_greater_than_2_minutes() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - // Populate inputs struct (you may want to fuzz these values) - let inputs = UpdateInputs { - oracle_length: 3, - oracle_id: 2, - previous_active_id: 100, - active_id: 200, - previous_volatility: 50, - volatility: 60, - previous_bin_crossed: 1, - created_at: 10, - timestamp: 140, - }; - - // Your "vm.assume" logic goes here as assertions - assert!( - inputs.oracle_id > 0 - && inputs.oracle_length >= inputs.oracle_id - && inputs.created_at <= inputs.timestamp - && inputs.timestamp - inputs.created_at > 120 - && inputs.volatility <= MASK_UINT20.as_u32() - && inputs.previous_volatility <= MASK_UINT20.as_u32() - ); - - // Your "vm.warp" logic should be implemented if needed - - let sample = OracleSample::encode( - inputs.oracle_length, - inputs.previous_active_id as u64 * inputs.created_at, - inputs.previous_volatility as u64 * inputs.created_at, - inputs.previous_bin_crossed as u64 * inputs.created_at, - 0, - inputs.created_at, - ); - - oracle.set_sample(inputs.oracle_id, sample).unwrap(); - - let mut parameters = PairParameters(EncodedSample([0u8; 32])); - - parameters.set_oracle_id(inputs.oracle_id); - parameters.set_active_id(inputs.previous_active_id).unwrap(); - parameters - .set_volatility_accumulator(inputs.volatility) - .unwrap(); - - // Your "vm.warp" logic should be implemented if needed - - let mut new_params = oracle - .update( - &Timestamp::from_seconds(inputs.timestamp), - parameters, - inputs.active_id, - ) - .unwrap(); - - let next_id = ((inputs.oracle_id as usize % inputs.oracle_length as usize) + 1) as u16; - - assert_eq!( - new_params.set_oracle_id(next_id).clone(), - new_params, - "test_Update::1" - ); - - if inputs.oracle_length > 1 { - assert_eq!( - oracle.get_sample(inputs.oracle_id).unwrap(), - sample, - "test_Update::2" - ); - } - - let sample = oracle.get_sample(next_id).unwrap(); - - let dt = inputs.timestamp - inputs.created_at; - - let d_id = if inputs.active_id > inputs.previous_active_id { - inputs.active_id - inputs.previous_active_id - } else { - inputs.previous_active_id - inputs.active_id - } as u64; - - let cumulative_id = - (inputs.previous_active_id as u64 * inputs.created_at) + (inputs.active_id as u64 * dt); - let cumulative_volatility = (inputs.previous_volatility as u64 * inputs.created_at) - + (inputs.volatility as u64 * dt); - let cumulative_bin_crossed = - (inputs.previous_bin_crossed as u64 * inputs.created_at) + (d_id * dt); - - assert_eq!( - sample.get_oracle_length(), - inputs.oracle_length, - "test_Update::3" - ); - assert_eq!(sample.get_cumulative_id(), cumulative_id, "test_Update::4"); - assert_eq!( - sample.get_cumulative_volatility(), - cumulative_volatility, - "test_Update::5" - ); - assert_eq!( - sample.get_cumulative_bin_crossed(), - cumulative_bin_crossed, - "test_Update::6" - ); - } - - #[test] - fn test_increase_oracle_length() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - // Random lengths, you may want to fuzz these values. - let length = 3; - let new_length = 4; - - let oracle_id = 1; - - println!( - "{:#?}", - oracle.get_sample(oracle_id).unwrap().get_oracle_length() - ); - - oracle.increase_length(oracle_id, length).unwrap(); - - println!( - "{:#?}", - oracle.get_sample(oracle_id).unwrap().get_oracle_length() - ); - - oracle.increase_length(oracle_id, new_length).unwrap(); - - println!( - "{:#?}", - oracle.get_sample(oracle_id).unwrap().get_oracle_length() - ); - - assert_eq!( - oracle.get_sample(oracle_id).unwrap().get_oracle_length(), - new_length, - "test_IncreaseOracleLength::1" - ); - - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - // Random lengths, you may want to fuzz these values. - let length = u16::MAX - 1; - let new_length = u16::MAX; - - let oracle_id = 1; - - oracle.increase_length(oracle_id, length).unwrap(); - oracle.increase_length(oracle_id, new_length).unwrap(); - - assert_eq!( - oracle.get_sample(oracle_id).unwrap().get_oracle_length(), - new_length, - "test_IncreaseOracleLength::2" - ); - } - - #[test] - fn test_revert_increase_oracle_length() { - let mut oracle = Oracle { - samples: HashMap::new(), - }; - - // Random lengths, you may want to fuzz these values. - let length = 3; - let new_length = 2; - - // Equivalent to vm.assume in Solidity - assert!(new_length <= length && length > 0); - - let oracle_id = 1; - - oracle.increase_length(oracle_id, length).unwrap(); - - // Equivalent to vm.expectRevert in Solidity. - // Replace with your own logic. - assert!(oracle.increase_length(oracle_id, new_length).is_err()); + return Ok((parameters, None)); } } diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/pair_parameter_helper.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/pair_parameter_helper.rs index 836bb52a..c0c10772 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/pair_parameter_helper.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/pair_parameter_helper.rs @@ -22,7 +22,10 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Timestamp; use ethnum::U256; -use super::{constants::*, math::encoded_sample::*}; +use super::{ + constants::*, + math::{encoded_sample::*, safe_math::Safe, u24::U24}, +}; const OFFSET_BASE_FACTOR: u8 = 0; const OFFSET_FILTER_PERIOD: u8 = 16; @@ -44,6 +47,10 @@ const MASK_STATIC_PARAMETER: u128 = 0xffffffffffffffffffffffffffffu128; pub enum PairParametersError { #[error("Pair Parameters Error: Invalid Parameter")] InvalidParameter, + #[error("Pair Parameters Error: Volatility Reference greater than limit")] + InvalidVolatilityReference, + #[error("Value greater than u128")] + U128Overflow, } #[cw_serde] @@ -228,6 +235,8 @@ impl PairParameters { /// /// * `parameters` - The encoded pair parameters /// * `bin_step` - The bin step (in basis points) + /// base_factor is on the basis points, B * 10000 + /// pub fn get_base_fee(&self, bin_step: u16) -> u128 { let base_factor = Self::get_base_factor(self) as u128; base_factor * (bin_step as u128) * 10_000_000_000 @@ -243,8 +252,7 @@ impl PairParameters { let variable_fee_control = Self::get_variable_fee_control(self) as u128; if variable_fee_control != 0 { - let vol_accumulator = Self::get_volatility_accumulator(self) as u128; - let prod = vol_accumulator * (bin_step as u128); + let prod = (Self::get_volatility_accumulator(self) as u128) * (bin_step as u128); (prod * prod * variable_fee_control + 99) / 100 } else { 0 @@ -257,10 +265,10 @@ impl PairParameters { /// /// * `parameters` - The encoded pair parameters /// * `bin_step` - The bin step (in basis points) - pub fn get_total_fee(&self, bin_step: u16) -> u128 { + pub fn get_total_fee(&self, bin_step: u16) -> Result { let base_fee = Self::get_base_fee(self, bin_step); let variable_fee = Self::get_variable_fee(self, bin_step); - base_fee + variable_fee + u128::safe128(base_fee + variable_fee, PairParametersError::U128Overflow) } /// Set the oracle id in the encoded pair parameters. @@ -356,7 +364,6 @@ impl PairParameters { let mut new_parameters = EncodedSample([0u8; 32]); - // TODO: all of these needing to be turned into U256 seems like a waste new_parameters.set(base_factor.into(), MASK_UINT16, OFFSET_BASE_FACTOR); new_parameters.set(filter_period.into(), MASK_UINT12, OFFSET_FILTER_PERIOD); new_parameters.set(decay_period.into(), MASK_UINT12, OFFSET_DECAY_PERIOD); @@ -407,14 +414,10 @@ impl PairParameters { time: &Timestamp, ) -> Result<&mut Self, PairParametersError> { let current_time = time.seconds(); - - if current_time > MASK_UINT40.as_u64() { - Err(PairParametersError::InvalidParameter) - } else { - self.0 - .set(current_time.into(), MASK_UINT40, OFFSET_TIME_LAST_UPDATE); - Ok(self) - } + //u40 can contain upto date 2/20/36812 + self.0 + .set(current_time.into(), MASK_UINT40, OFFSET_TIME_LAST_UPDATE); + Ok(self) } /// Updates the volatility reference in the encoded pair parameters. @@ -423,9 +426,14 @@ impl PairParameters { /// /// * `parameters` - The encoded pair parameters pub fn update_volatility_reference(&mut self) -> Result<&mut Self, PairParametersError> { - let vol_acc = self.get_volatility_accumulator(); - let reduction_factor = self.get_reduction_factor(); - let vol_ref = vol_acc * reduction_factor as u32 / BASIS_POINT_MAX as u32; + let vol_acc: U256 = self.get_volatility_accumulator().into(); + let reduction_factor: U256 = self.get_reduction_factor().into(); + let result: U256 = vol_acc * reduction_factor / U256::from(BASIS_POINT_MAX); + let vol_ref = (result).as_u32(); + + if vol_ref > U24::MAX { + return Err(PairParametersError::InvalidVolatilityReference); + } self.set_volatility_reference(vol_ref)?; Ok(self) @@ -446,7 +454,9 @@ impl PairParameters { true => active_id - id_reference, false => id_reference - active_id, }; + let vol_acc = self.get_volatility_reference() + delta_id * BASIS_POINT_MAX as u32; + let max_vol_acc = self.get_max_volatility_accumulator(); let vol_acc = std::cmp::min(vol_acc, max_vol_acc); @@ -754,7 +764,7 @@ mod tests { assert_eq!(variable_fee, expected_variable_fee); if base_fee + variable_fee < U256::from(u128::MAX) { - let total_fee = pair_params.get_total_fee(bin_step); + let total_fee = pair_params.get_total_fee(bin_step).unwrap(); assert_eq!(total_fee, base_fee + variable_fee); } else { panic!("Exceeds 128 bits"); diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/price_helper.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/price_helper.rs index 845d047e..8fd36fac 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/price_helper.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/price_helper.rs @@ -8,6 +8,7 @@ use ethnum::{I256, U256}; use super::{ constants::*, math::{ + safe_math::Safe, u128x128_math::{U128x128Math, U128x128MathError}, u256x256_math::{U256x256Math, U256x256MathError}, }, @@ -28,7 +29,12 @@ pub enum PriceError { pub struct PriceHelper; impl PriceHelper { - /// Calculates the price as a 128.128-binary fixed-point number from the id and the bin step. + /// Calculates the price as a 128.128-binary fixed-point number + /// + /// # Arguments + /// + /// * `id` - Bin id + /// * `bin_step` - The bin step pub fn get_price_from_id(id: u32, bin_step: u16) -> Result { let base = Self::get_base(bin_step); let exponent = Self::get_exponent(id); @@ -46,11 +52,17 @@ impl PriceHelper { let base = Self::get_base(bin_step); let real_id = U128x128Math::log2(price)? / U128x128Math::log2(base)?; - Ok((REAL_ID_SHIFT + real_id).as_u32()) + u32::safe24( + (REAL_ID_SHIFT + real_id).as_u32(), + U128x128MathError::IdShiftOverflow, + ) } /// Calculates the base from the bin step, which is `1 + binStep / BASIS_POINT_MAX`. pub fn get_base(bin_step: u16) -> U256 { + //SCALE = 1 << 128 + //SCALE_OFFSET = 128 + //just scaling SCALE + (U256::from(bin_step) << SCALE_OFFSET) / BASIS_POINT_MAX as u128 } @@ -104,15 +116,6 @@ mod tests { price, U256::from_str_prefixed("42008768657166552252904831246223292524636112144").unwrap() ); - - // TODO - add some assertions for the fixed point stuff - let fixed_point = - U256::from_str_prefixed("42008768657166552252904831246223292524636112144").unwrap(); - let integer_part = fixed_point >> 128; - let shifted: U256 = U256::from(1u128) << 128; - let fractional_part = fixed_point & shifted.checked_sub(U256::ONE).unwrap(); - let _fractional_part_decimal = fractional_part / shifted; - let _real_value = integer_part; } #[test] @@ -122,6 +125,12 @@ mod tests { let id = PriceHelper::get_id_from_price(price, bin_step).unwrap(); assert!(id > 0); + + let price = + U256::from_str_prefixed("42008768657166552252904831246223292524636112144").unwrap(); + let bin_step = 1u16; + let id = PriceHelper::get_id_from_price(price, bin_step).unwrap(); + assert_eq!(id, 8574931); } #[test] diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/types.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/types.rs index 83d28a1e..a702af59 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/types.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/types.rs @@ -12,6 +12,8 @@ use crate::contract_interfaces::swap::core::TokenType; pub type Bytes32 = [u8; 32]; +pub type Bytes8 = [u8; 8]; + // TODO - This type belongs somewhere else. It's not specific to liquidity_book. #[cw_serde] #[derive(Default)] diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs index 874077cd..bd09eb41 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs @@ -30,6 +30,7 @@ pub struct InstantiateMsg { pub admin_auth: RawContract, pub query_auth: RawContract, pub total_reward_bins: Option, + pub max_bins_per_swap: Option, pub rewards_distribution_algorithm: RewardsDistributionAlgorithm, pub epoch_staking_index: u64, pub epoch_staking_duration: u64, @@ -59,9 +60,6 @@ pub enum ExecuteMsg { }, CollectProtocolFees {}, - IncreaseOracleLength { - new_length: u16, - }, SetStaticFeeParameters { base_factor: u16, filter_period: u16, @@ -217,7 +215,14 @@ pub enum QueryMsg { #[returns(OracleParametersResponse)] GetOracleParameters {}, #[returns(OracleSampleAtResponse)] - GetOracleSampleAt { look_up_timestamp: u64 }, + GetOracleSampleAt { oracle_id: u16 }, + #[returns(OracleSamplesAtResponse)] + GetOracleSamplesAt { oracle_ids: Vec }, + #[returns(OracleSamplesAfterResponse)] + GetOracleSamplesAfter { + oracle_id: u16, + page_size: Option, + }, #[returns(PriceFromIdResponse)] GetPriceFromId { id: u32 }, #[returns(IdFromPriceResponse)] @@ -373,18 +378,31 @@ pub struct VariableFeeParametersResponse { pub struct OracleParametersResponse { pub sample_lifetime: u8, pub size: u16, - pub active_size: u16, pub last_updated: u64, pub first_timestamp: u64, } #[cw_serde] pub struct OracleSampleAtResponse { + pub oracle_id: u16, + pub cumulative_txns: u16, pub cumulative_id: u64, pub cumulative_volatility: u64, pub cumulative_bin_crossed: u64, + pub cumulative_volume_x: u128, + pub cumulative_volume_y: u128, + pub cumulative_fee_x: u128, + pub cumulative_fee_y: u128, + pub lifetime: u8, + pub created_at: u64, } +#[cw_serde] +pub struct OracleSamplesAtResponse(pub Vec); + +#[cw_serde] +pub struct OracleSamplesAfterResponse(pub Vec); + #[cw_serde] pub struct PriceFromIdResponse { pub price: Uint256, diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_staking.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_staking.rs index e8afd03b..0df515d2 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_staking.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_staking.rs @@ -5,15 +5,12 @@ use crate::{ query_auth::QueryPermit, Contract, }; -use cosmwasm_schema::QueryResponses; use crate::{ c_std::{ to_binary, Addr, - Api, Binary, - BlockInfo, Coin, ContractInfo, CosmosMsg, diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/rust_todo_list.md b/packages/shade_protocol/src/contract_interfaces/liquidity_book/rust_todo_list.md new file mode 100644 index 00000000..4d3a4486 --- /dev/null +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/rust_todo_list.md @@ -0,0 +1,492 @@ + - lb_factory + - src + - contract.rs + - instantiate + - execute + - try_set_lb_pair_implementation + - try_set_lb_token_implementation + - try_set_staking_contract_implementation + - try_create_lb_pair + - try_set_lb_pair_ignored + - try_set_pair_preset + - try_set_preset_open_state + - try_remove_preset + - try_set_fee_parameters_on_pair + - try_set_fee_recipient + - try_add_quote_asset + - try_remove_quote_asset + - try_force_decay + - query + - query_min_bin_step + - query_fee_recipient + - query_lb_pair_implementation + - query_lb_token_implementation + - query_number_of_lb_pairs + - query_lb_pair_at_index + - query_number_of_quote_assets + - query_quote_asset_at_index + - query_is_quote_asset + - query_lb_pair_information + - _get_lb_pair_information + - _sort_tokens + - query_preset + - query_all_bin_steps + - query_open_bin_steps + - _is_preset_open + - query_all_lb_pairs + - reply + - error.rs + - lib.rs + - prelude.rs + - state.rs + - ephemeral_storage_w + - ephemeral_storage_r + - types.rs + - lb_pair + - src + - contract.rs + - instantiate + - execute + - receiver_callback + - query + - reply + - error.rs + - execute.rs + - try_swap + - update_fee_map_tree + - updating_reward_stats + - updating_oracles + - updating_bin_reserves + - try_add_liquidity + - add_liquidity_internal + - mint + - mint_bins + - update_bin + - try_remove_liquidity + - remove_liquidity + - burn + - try_collect_protocol_fees + - try_set_static_fee_parameters + - try_force_decay + - try_calculate_rewards_distribution + - calculate_default_distribution + - calculate_time_based_rewards_distribution + - calculate_volume_based_rewards_distribution + - try_reset_rewards_config + - helper.rs + - register_pair_token + - match_lengths + - check_ids_bounds + - check_active_id_slippage + - calculate_id + - _query_total_supply + - query_token_symbol + - _get_next_non_empty_bin + - only_factory + - lib.rs + - prelude.rs + - query.rs + - query_pair_info + - query_swap_simulation + - query_factory + - query_lb_token + - query_staking + - query_tokens + - query_token_x + - query_token_y + - query_bin_step + - query_reserves + - query_active_id + - query_all_bins_reserves + - query_bins_reserves + - query_updated_bins_at_height + - query_updated_bins_at_multiple_heights + - query_updated_bins_after_height + - query_bins_updating_heights + - query_bin_reserves + - query_next_non_empty_bin + - query_protocol_fees + - query_static_fee_params + - query_variable_fee_params + - query_oracle_params + - query_oracle_sample + - query_oracle_samples + - query_oracle_samples_after + - query_price_from_id + - query_id_from_price + - query_swap_in + - query_swap_out + - query_total_supply + - query_rewards_distribution + - state.rs + - lb_staking + - src + - contract.rs + - instantiate + - execute + - authenticate + - query + - execute.rs + - receiver_callback_snip_1155 + - receiver_callback + - try_stake + - try_unstake + - update_staker_and_total_liquidity + - try_end_epoch + - try_claim_rewards + - try_register_reward_tokens + - try_update_config + - process_epoch + - try_add_rewards + - try_recover_expired_funds + - try_recover_funds + - helper.rs + - register_reward_tokens + - store_empty_reward_set + - require_lb_token + - staker_init_checker + - assert_lb_pair + - check_if_claimable + - finding_user_liquidity + - finding_total_liquidity + - eq + - hash + - get_txs + - lib.rs + - query.rs + - query_contract_info + - query_epoch_info + - query_registered_tokens + - query_token_id_balance + - query_staker_info + - query_balance + - query_all_balances + - query_transaction_history + - query_liquidity + - state.rs + - store_stake + - store_unstake + - store_claim_rewards + - append_tx_for_addr + - append_stake_tx_for_addr + - append_unstake_tx_for_addr + - append_claim_rewards_tx_for_addr + - lb_token + - src + - contract.rs + - instantiate + - execute + - query + - permit_queries + - viewing_keys_queries + - execute.rs + - try_curate_token_ids + - try_mint_tokens + - try_burn_tokens + - try_change_metadata + - try_transfer + - try_batch_transfer + - try_send + - try_batch_send + - try_give_permission + - try_revoke_permission + - try_create_viewing_key + - try_set_viewing_key + - try_revoke_permit + - try_add_curators + - try_remove_curators + - try_add_minters + - try_remove_minters + - try_change_admin + - try_remove_admin + - try_register_receive + - pad_response + - is_valid_name + - is_valid_symbol + - verify_admin + - verify_curator + - verify_curator_of_token_id + - verify_minter + - exec_curate_token_id + - impl_send + - impl_transfer + - exec_change_balance + - try_add_receiver_api_callback + - lib.rs + - query.rs + - query_contract_info + - query_id_balance + - query_token_id_public_info + - query_token_id_private_info + - query_registered_code_hash + - query_balance + - query_all_balances + - query_transactions + - query_permission + - query_all_permissions + - state + - mod.rs + - contr_conf_w + - contr_conf_r + - blockinfo_w + - blockinfo_r + - tkn_info_w + - tkn_info_r + - tkn_tot_supply_w + - tkn_tot_supply_r + - balances_w<'a> + - balances_r<'a> + - permission_w<'a> + - permission_r<'a> + - perm_r<'a> + - get_receiver_hash + - set_receiver_hash + - permissions.rs + - new_permission + - update_permission_unchecked + - update_permission + - may_load_any_permission + - may_load_active_permission + - list_owner_permission_keys + - append_permission_for_addr + - save_load_functions.rs + - json_save + - json_load + - json_may_load + - txhistory.rs + - get_txs + - store_transfer + - store_mint + - store_burn + - append_tx_for_addr + - append_new_owner + - may_get_current_owner + - router + - src + - contract.rs + - instantiate + - execute + - receiver_callback + - query + - reply + - execute.rs + - refresh_tokens + - next_swap + - swap_tokens_for_exact_tokens + - update_viewing_key + - get_trade_with_callback + - register_pair_token + - lib.rs + - query.rs + - pair_contract_config + - swap_simulation + - state.rs + - config_w + - config_r + - registered_tokens_w + - registered_tokens_r + - registered_tokens_list_w + - registered_tokens_list_r + - epheral_storage_w + - epheral_storage_r + - tests + - src + - lib.rs + - multitests + - lb_factory.rs + - test_setup + - test_set_lb_pair_implementation + - test_revert_set_lb_pair_implementation + - test_set_lb_token_implementation + - test_create_lb_pair + - test_create_lb_pair_factory_unlocked + - test_revert_create_lb_pair + - test_fuzz_set_preset + - test_remove_preset + - test_set_fees_parameters_on_pair + - test_set_fee_recipient + - test_fuzz_open_presets + - test_add_quote_asset + - test_remove_quote_asset + - test_force_decay + - test_get_all_lb_pair + - lb_pair_fees.rs + - lb_pair_setup + - test_fuzz_swap_in_x + - test_fuzz_swap_in_y + - test_fuzz_swap_out_for_x + - test_fuzz_swap_out_for_y + - test_fuzz_swap_in_x_and_y + - test_fuzz_swap_in_y_and_x + - test_fuzz_swap_out_x_and_y + - test_fuzz_swap_out_y_and_x + - test_fee_x_lp + - test_fee_y_lp + - test_collect_protocol_fees_x_tokens + - test_collect_protocol_fees_y_tokens + - test_collect_protocol_fees_both_tokens + - test_collect_protocol_fees_after_swap + - test_revert_total_fee_exceeded + - test_fuzz_swap_in_x_and_y_btc_silk + - test_fuzz_base_fee_only + - test_base_and_variable_fee_only + - lb_pair_initial_state.rs + - lb_pair_setup + - test_query_factory + - test_query_token_x + - test_query_token_y + - test_query_bin_step + - test_query_bin_reserves + - test_query_active_id + - test_fuzz_query_bin + - test_query_next_non_empty_bin + - test_query_protocol_fees + - test_query_static_fee_parameters + - test_query_variable_fee_parameters + - test_query_oracle_parameters + - test_query_oracle_sample_at + - test_query_price_from_id + - test_query_id_from_price + - test_fuzz_query_swap_out + - test_fuzz_query_swap_in + - test_invalid_reward_bins_error + - lb_pair_liquidity.rs + - lb_pair_setup + - test_simple_mint_repeat + - test_simple_mint + - test_mint_twice + - test_mint_with_different_bins + - test_simple_burn + - test_burn_half_twice + - test_query_next_non_empty_bin + - test_revert_mint_zero_shares + - test_revert_burn_empty_array + - test_revert_burn_more_than_balance + - test_revert_burn_zero + - test_revert_on_deadline + - test_revert_on_wrong_pair + - test_revert_on_amount_slippage + - test_revert_on_length_mismatch + - test_revert_on_id_desired_overflow + - test_revert_on_id_slippage_caught + - test_revert_on_delta_ids_overflow + - test_revert_on_empty_liquidity_config + - testing_implicit_swap + - test_revert_burn_on_wrong_pair + - lb_pair_oracle.rs + - lb_pair_setup + - test_query_oracle_parameters + - test_query_oracle_sample_at_init + - test_query_oracle_sample_at_one_swap + - test_fuzz_query_oracle_sample_at_one_swap + - test_fuzz_update_oracle_id + - test_fuzz_update_cumm_txns + - test_fuzz_query_oracle_sample_after + - lb_pair_queries.rs + - lb_pair_setup + - mint_and_add_liquidity + - test_query_bin_reserves + - test_query_bins_reserves + - test_query_all_bins_reserves + - test_query_all_bins_updated_add_liquidity + - test_query_total_supply + - test_query_tokens + - test_query_all_bins_updated_swap + - test_query_all_bins_updated_remove_liquidity + - test_query_update_at_height + - test_query_update_at_multiple_heights + - test_query_update_after_height + - lb_pair_rewards.rs + - test_fuzz_calculate_volume_based_rewards + - test_calculate_volume_based_rewards + - test_calculate_time_based_rewards + - test_fuzz_calculate_time_based_rewards + - test_reset_rewards_config + - lb_pair_swap.rs + - lb_pair_setup + - test_fuzz_swap_in_x + - test_fuzz_swap_in_y + - test_fuzz_swap_out_for_y + - test_fuzz_swap_out_for_y_send_someone + - test_fuzz_swap_out_for_x + - test_revert_swap_insufficient_amount_in + - test_revert_swap_insufficient_amount_out + - test_revert_swap_out_of_liquidity + - test_revert_zero_bin_reserves + - lb_pair_trivial.rs + - lb_pair_setup + - test_contract_status + - test_native_tokens_error + - lb_router_integration.rs + - router_integration + - lb_router_register_tokens.rs + - router_registered_tokens + - lb_staking.rs + - lb_pair_setup + - mint_and_add_liquidity + - staking_contract_init + - fuzz_stake_simple + - fuzz_stake_liquidity_with_time + - fuzz_unstake + - fuzz_unstake_liquidity_with_time + - register_rewards_token + - add_rewards + - end_epoch + - fuzz_claim_rewards + - claim_rewards + - end_epoch_by_stakers + - claim_expired_rewards + - recover_expired_rewards + - recover_funds + - update_config + - query_contract_info + - query_id_balance + - query_balance + - query_all_balance + - query_txn_history + - lb_token.rs + - init_setup + - test_simple_mint + - test_mint_twice + - test_mint_with_different_bins + - test_simple_burn + - test_burn_half_twice + - test_revert_mint_zero_tokens + - test_revert_burn_empty_array + - test_revert_burn_more_than_balance + - test_revert_burn_zero + - mod.rs + - test_helper.rs + - admin + - user1 + - user2 + - batman + - scare_crow + - joker + - all + - a_hash + - b_hash + - c_hash + - d_hash + - init_addrs + - assert_approx_eq_rel + - assert_approx_eq_abs + - generate_auth + - setup + - roll_blockchain + - roll_time + - extract_contract_info + - token_type_snip20_generator + - token_type_native_generator + - safe64_divide + - get_id + - get_total_bins + - safe24 + - bound + - generate_random + - liquidity_parameters_generator + - liquidity_parameters_generator_custom + - liquidity_parameters_generator_with_native + - mint_increase_allowance_helper + - mint_token_helper + - increase_allowance_helper diff --git a/packages/shade_protocol/src/contract_interfaces/swap/core/token_pair_amount.rs b/packages/shade_protocol/src/contract_interfaces/swap/core/token_pair_amount.rs index 0119b0af..b8df4be4 100644 --- a/packages/shade_protocol/src/contract_interfaces/swap/core/token_pair_amount.rs +++ b/packages/shade_protocol/src/contract_interfaces/swap/core/token_pair_amount.rs @@ -85,8 +85,8 @@ impl<'a> Iterator for TokenPairAmountIterator<'a> { } pub mod tests { - use crate::c_std::{Addr, Uint128}; use super::*; + use crate::c_std::Addr; #[test] pub fn test_rearrange() { @@ -145,8 +145,10 @@ pub mod tests { }, false, ); - assert!(reverse_amount - .create_new_pair_amount_to_match_order_of(&broken_pair) - .is_err()); + assert!( + reverse_amount + .create_new_pair_amount_to_match_order_of(&broken_pair) + .is_err() + ); } } diff --git a/packages/shade_protocol/src/contract_interfaces/swap/core/token_type.rs b/packages/shade_protocol/src/contract_interfaces/swap/core/token_type.rs index 90499cf8..b9e82f96 100644 --- a/packages/shade_protocol/src/contract_interfaces/swap/core/token_type.rs +++ b/packages/shade_protocol/src/contract_interfaces/swap/core/token_type.rs @@ -11,19 +11,15 @@ use cosmwasm_std::{ StdError, StdResult, Uint128, - Uint256, WasmMsg, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; // use shade_oracles::querier::{query_price, query_prices}; use crate::{ snip20::{ helpers::{balance_query, token_info}, ExecuteMsg::{Send, TransferFrom}, }, - swap::amm_pair, utils::ExecuteCallback, Contract, }; @@ -293,8 +289,8 @@ impl TokenType { if amount.gt(&Uint128::zero()) { match &self { TokenType::CustomToken { - contract_addr, - token_code_hash, + contract_addr: _, + token_code_hash: _, } => { let msg = Send { recipient: recipient.to_string(), @@ -335,8 +331,8 @@ impl TokenType { if amount.gt(&Uint128::zero()) { match &self { TokenType::CustomToken { - contract_addr, - token_code_hash, + contract_addr: _, + token_code_hash: _, } => { let msg = TransferFrom { owner: owner.to_string(), diff --git a/packages/shade_protocol/src/contract_interfaces/swap/router.rs b/packages/shade_protocol/src/contract_interfaces/swap/router.rs index 2e21cee0..35741f59 100644 --- a/packages/shade_protocol/src/contract_interfaces/swap/router.rs +++ b/packages/shade_protocol/src/contract_interfaces/swap/router.rs @@ -3,9 +3,10 @@ use crate::{ cosmwasm_schema::cw_serde, liquidity_book::lb_pair::SwapResult, snip20::Snip20ReceiveMsg, - swap::core::{ContractInstantiationInfo, TokenAmount, TokenType}, + swap::core::{TokenAmount, TokenType}, utils::{ExecuteCallback, InstantiateCallback, Query}, - Contract, BLOCK_SIZE, + Contract, + BLOCK_SIZE, }; #[cw_serde] diff --git a/packages/shade_protocol/src/contract_interfaces/swap/staking.rs b/packages/shade_protocol/src/contract_interfaces/swap/staking.rs index d4be5867..872281a4 100644 --- a/packages/shade_protocol/src/contract_interfaces/swap/staking.rs +++ b/packages/shade_protocol/src/contract_interfaces/swap/staking.rs @@ -1,28 +1,14 @@ use crate::{ - c_std::{ - Addr, - Binary, - CosmosMsg, - Decimal256, - OverflowError, - QuerierWrapper, - StdError, - StdResult, - Storage, - Uint128, - Uint256, - }, + c_std::{Addr, Binary, Uint128, Uint256}, cosmwasm_schema::{cw_serde, QueryResponses}, liquidity_book::{lb_pair::RewardsDistribution, lb_token::Snip1155ReceiveMsg}, query_auth::QueryPermit, - secret_storage_plus::{Bincode2, Item, ItemStorage, Map}, - snip20::{ExecuteMsg as Snip20ExecuteMsg, Snip20ReceiveMsg}, swap::core::{ContractInstantiationInfo, TokenType}, utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, Query}, Contract, BLOCK_SIZE, }; -use std::{cmp::min, collections::HashMap}; +use std::collections::HashMap; /* use crate::swap::core::{ diff --git a/packages/shade_protocol/src/utils/callback.rs b/packages/shade_protocol/src/utils/callback.rs index 6c0ebba6..9192bc2a 100644 --- a/packages/shade_protocol/src/utils/callback.rs +++ b/packages/shade_protocol/src/utils/callback.rs @@ -7,8 +7,17 @@ use crate::multi_test::{App, AppResponse, Contract as MultiContract, Executor}; use crate::AnyResult; use crate::{ c_std::{ - to_binary, Addr, Coin, ContractInfo, CosmosMsg, Empty, QuerierWrapper, QueryRequest, - StdResult, WasmMsg, WasmQuery, + to_binary, + Addr, + Coin, + ContractInfo, + CosmosMsg, + Empty, + QuerierWrapper, + QueryRequest, + StdResult, + WasmMsg, + WasmQuery, }, serde::{de::DeserializeOwned, Serialize}, Contract, diff --git a/tests/liquidity_orderbook/contract_address_log.json b/tests/liquidity_orderbook/contract_address_log.json index 50a66ca5..a37b3d4e 100644 --- a/tests/liquidity_orderbook/contract_address_log.json +++ b/tests/liquidity_orderbook/contract_address_log.json @@ -1,35 +1,35 @@ { "TokenX": { "custom_token": { - "contract_addr": "secret13xmqrdwu6r0mulrsq2nyxud2ruavjpxau7gdss", + "contract_addr": "secret1wa279dufwshx3mygrpvpzdj0xrqxtmdy2qcuve", "token_code_hash": "0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" } }, "TokenY": { "custom_token": { - "contract_addr": "secret1a2wgqa3nxdqtr2zr68qfld2v00nrhtmvd9zzu7", + "contract_addr": "secret1rvu3y59ghr6y27xwr0wrxdgezenlhs9pf438z7", "token_code_hash": "0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" } }, "AdminAuth": {}, "LBFactory": { - "address": "secret17nce3p8ng947urvvxx9n9qyk5tckekkserugd9", - "hash": "0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" + "address": "secret17zjv8jxe7n8dk7p6xsmfsr5ls5vkvmnumudwcz", + "hash": "d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" }, "Router": { - "address": "secret17vs9gdpl0w9kwcra8wtvgeuwwtuk0kmc5nkeq3", + "address": "secret13tesuesmg3lrjhr55u2rvgxaa5kz3nu9legngs", "hash": "d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" }, "LBPair": { - "codeId": 299, - "hash": "9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" + "codeId": 103, + "hash": "72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" }, "LBToken": { - "codeId": 300, - "hash": "b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" + "codeId": 104, + "hash": "ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" }, "LBStaking": { - "codeId": 300, - "hash": "b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" + "codeId": 104, + "hash": "ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" } } \ No newline at end of file diff --git a/tests/liquidity_orderbook/gas.log b/tests/liquidity_orderbook/gas.log index 355579b3..3092721c 100644 --- a/tests/liquidity_orderbook/gas.log +++ b/tests/liquidity_orderbook/gas.log @@ -1,16 +1,16 @@ -AdminAuth Upload used 1271006 gas -AdminAuth Instantiation used 42308 gas -Factory Upload used 2434542 gas -Factory Instantiation used 53189 gas -Router Upload used 2869912 gas -Router Instantiation used 45132 gas -LBPAIR Upload used 5203922 gas -Pair Upload used 5203922 gas -Token Upload used 3348904 gas -Staking Upload used 3963058 gas -SetLBPairImplementation TX used 61961 gas -SetLBTokenImplementation TX used 62433 gas -SetLBPairImplementation TX used 62975 gas -SetPreset TX used 68502 gas -AddQuoteAsset TX used 60509 gas -CreateLBPair TX used 482315 gas +AdminAuth Upload used 1271012 gas +AdminAuth Instantiation used 42294 gas +Factory Upload used 2477184 gas +Factory Instantiation used 57429 gas +Router Upload used 2869918 gas +Router Instantiation used 45097 gas +LBPAIR Upload used 5221472 gas +Pair Upload used 5221472 gas +Token Upload used 3417292 gas +Staking Upload used 3853738 gas +SetLBPairImplementation TX used 63206 gas +SetLBTokenImplementation TX used 63678 gas +SetLBPairImplementation TX used 64220 gas +SetPreset TX used 69545 gas +AddQuoteAsset TX used 60692 gas +CreateLBPair TX used 488960 gas diff --git a/tests/liquidity_orderbook/helper.ts b/tests/liquidity_orderbook/helper.ts index f6e7c47a..c366b2b0 100644 --- a/tests/liquidity_orderbook/helper.ts +++ b/tests/liquidity_orderbook/helper.ts @@ -36,6 +36,8 @@ const build_direct_to_target = "./wasm/"; // This helps when deploying to Pulsar. It can be shortened to test on secretdev. export const sleep = () => new Promise((resolve) => setTimeout(resolve, 10)); +export const sleeplonger = () => + new Promise((resolve) => setTimeout(resolve, 10000)); var mnemonic: string; var endpoint: string = "http://localhost:1317"; @@ -649,10 +651,10 @@ export async function test_configure_factory(clientInfo: clientInfo) { let contractAddressLbToken; let contractAddressLbStaking; - const base_factor: number = 5000; - const filter_period = 0; - const decay_period = 1; - const reduction_factor = 0; + const base_factor: number = 0; + const filter_period = 3; + const decay_period = 60; + const reduction_factor = 500; const variable_fee_control = 0; const protocol_share = 1000; const max_volatility_accumulator = 350000; @@ -737,8 +739,7 @@ export async function test_configure_factory(clientInfo: clientInfo) { ); clientInfo.contractAddressPair = - lb_pair_info.lb_pair_information.lb_pair.contract.address; - + lb_pair_info.lb_pair_information.info.contract.address; let lb_token_info = await queryLbToken( clientInfo.client, clientInfo.contractHashPair, diff --git a/tests/liquidity_orderbook/integration.ts b/tests/liquidity_orderbook/integration.ts index 11340201..70f0b4f4 100644 --- a/tests/liquidity_orderbook/integration.ts +++ b/tests/liquidity_orderbook/integration.ts @@ -20,7 +20,7 @@ import { get_id, get_total_bins, } from "./lb_pair/execute"; -import { queryReserves } from "./lb_pair/query"; +import { executeStake, executeUnstake } from "./lb_staking/execute"; import { queryBalance } from "./lb_token/query"; (async () => { @@ -36,7 +36,8 @@ import { queryBalance } from "./lb_token/query"; // await test_liquidity(clientInfo); - await test_swaps(clientInfo); + // await test_swaps(clientInfo); + await test_staking(clientInfo); sleep(); @@ -115,6 +116,7 @@ async function test_liquidity(info: clientInfo) { const bin_step = 100; for (let bins = 1; bins <= 100; bins++) { + await console.log(`bins: ${bins}`); await executeAddLiquidity( info.client, info.contractHashPair, @@ -232,76 +234,207 @@ async function test_swaps(info: clientInfo) { const bin_step = 100; const amount = 100_000_000; let sum = 0; + let swapOrder = true; // Flag to toggle swap order - sum += amount; - await executeAddLiquidity( - info.client, - info.contractHashPair, - info.contractAddressPair, - bin_step, - info.tokenX, - info.tokenY, - amount, - amount, - 100, - 100 - ); + for (let bins = 1; bins < 200; bins = bins + 5) { + sum += amount; + await executeAddLiquidity( + info.client, + info.contractHashPair, + info.contractAddressPair, + bin_step, + info.tokenX, + info.tokenY, + amount, + amount, + bins, + bins + ); - await executeSwap( - info.client, - info.contractHashPair, - info.contractAddressPair, - info.tokenX.custom_token.token_code_hash, - info.tokenX.custom_token.contract_addr, - sum - ); + if (swapOrder) { + // First swap with tokenX + await executeSwap( + info.client, + info.contractHashPair, + info.contractAddressPair, + info.tokenX.custom_token.token_code_hash, + info.tokenX.custom_token.contract_addr, + sum + ); + // Second swap with tokenY + await executeSwap( + info.client, + info.contractHashPair, + info.contractAddressPair, + info.tokenY.custom_token.token_code_hash, + info.tokenY.custom_token.contract_addr, + sum + ); + } else { + // First swap with tokenY + await executeSwap( + info.client, + info.contractHashPair, + info.contractAddressPair, + info.tokenY.custom_token.token_code_hash, + info.tokenY.custom_token.contract_addr, + sum + ); + // Second swap with tokenX + await executeSwap( + info.client, + info.contractHashPair, + info.contractAddressPair, + info.tokenX.custom_token.token_code_hash, + info.tokenX.custom_token.contract_addr, + sum + ); + } + + await sleep(); + + // let reserves_3 = await queryReserves( + // info.client, + // info.contractHashPair, + // info.contractAddressPair + // ); + // console.log(`Final Reserves_x: ${reserves_3.reserve_x}`); + // console.log(`Final Reserves_y: ${reserves_3.reserve_y}`); + + await sleep(); + + swapOrder = !swapOrder; // Toggle the swap order for the next iteration + } await sleep(); + } +} - await executeSwap( - info.client, - info.contractHashPair, - info.contractAddressPair, - info.tokenY.custom_token.token_code_hash, - info.tokenY.custom_token.contract_addr, - sum +async function test_staking(info: clientInfo) { + if ("custom_token" in info.tokenX && "custom_token" in info.tokenY) { + // increase allowance for Token X + let tx = await info.client.tx.snip20.increaseAllowance( + { + sender: info.client.address, + contract_address: info.tokenX.custom_token.contract_addr, + code_hash: info.tokenX.custom_token.token_code_hash, + msg: { + increase_allowance: { + spender: info.contractAddressPair, + amount: "340282366920938463463374607431768211454", + }, + }, + }, + { + gasLimit: 200_000, + } ); + + if (tx.code !== 0) { + throw new Error(`Failed with the following error:\n ${tx.rawLog}`); + } + + console.log(`Increase Token X Allowance TX used ${tx.gasUsed} gas`); + await sleep(); - // await executeSwap( - // info.client, - // info.contractHashPair, - // info.contractAddressPair, - // info.tokenY.custom_token.token_code_hash, - // info.tokenY.custom_token.contract_addr, - // sum - // ); - - // await sleep(); - - // await executeSwap( - // info.client, - // info.contractHashPair, - // info.contractAddressPair, - // info.tokenX.custom_token.token_code_hash, - // info.tokenX.custom_token.contract_addr, - // sum - // ); - // await sleep(); - - let reserves_3 = await queryReserves( - info.client, - info.contractHashPair, - info.contractAddressPair + + // increase allowance for Token Y + let tx2 = await info.client.tx.snip20.increaseAllowance( + { + sender: info.client.address, + contract_address: info.tokenY.custom_token.contract_addr, + code_hash: info.tokenY.custom_token.token_code_hash, + msg: { + increase_allowance: { + spender: info.contractAddressPair, + amount: "340282366920938463463374607431768211454", + }, + }, + }, + { + gasLimit: 200_000, + } ); - console.log(`Final Reserves_x: ${reserves_3.reserve_x}`); - console.log(`Final Reserves_y: ${reserves_3.reserve_y}`); - await sleep(); - // } + if (tx2.code !== 0) { + throw new Error(`Failed with the following error:\n ${tx2.rawLog}`); + } - await sleep(); + console.log(`Increase Token Y Allowance TX used ${tx2.gasUsed} gas`); + + const bin_step = 100; + for (let bins = 1; bins <= 100; bins++) { + await console.log(`bins: ${bins}`); + await executeAddLiquidity( + info.client, + info.contractHashPair, + info.contractAddressPair, + bin_step, + info.tokenX, + info.tokenY, + 100_000_000, + 100_000_000, + bins, + bins + ); + + let total_bins = get_total_bins(bins, bins); + let ids: number[] = []; + let balances: string[] = []; + + for (let idx = 0; idx < total_bins; idx++) { + let id = get_id(2 ** 23, idx, bins); + + ids.push(id); + + let balancetoken = await queryBalance( + info.client, + info.contractHashToken, + info.contractAddressToken, + id + ); + + balances.push(balancetoken); + } + + //Stake + await executeStake( + info.client, + info.contractHashToken, + info.contractAddressToken, + info.contractHashStaking, + info.contractAddressStaking, + ids, + balances + ); + + //Unstake + await executeUnstake( + info.client, + info.contractHashStaking, + info.contractAddressStaking, + ids, + balances + ); + + await executeRemoveLiquidity( + info.client, + info.contractHashPair, + info.contractAddressPair, + bin_step, + info.tokenX, + info.tokenY, + ids, + balances + ); + + await sleep(); + } } + + await sleep(); } + // async function test_pair_queries( // client: SecretNetworkClient, // contractHashFactory: string, diff --git a/tests/liquidity_orderbook/lb_factory/execute.ts b/tests/liquidity_orderbook/lb_factory/execute.ts index 95bdf217..470c432f 100644 --- a/tests/liquidity_orderbook/lb_factory/execute.ts +++ b/tests/liquidity_orderbook/lb_factory/execute.ts @@ -187,7 +187,6 @@ export async function executeSetPreset( ) { const msg: LBFactory.ExecuteMsg = { set_pair_preset: { - // TODO: figure out approprate values to use bin_step, base_factor, filter_period, diff --git a/tests/liquidity_orderbook/lb_factory/instantiate.ts b/tests/liquidity_orderbook/lb_factory/instantiate.ts index 9cc31b86..c1e0e73d 100644 --- a/tests/liquidity_orderbook/lb_factory/instantiate.ts +++ b/tests/liquidity_orderbook/lb_factory/instantiate.ts @@ -63,6 +63,7 @@ export const initializeFactoryContract = async ( fee_recipient: client.address, admin_auth: admin_auth, recover_staking_funds_receiver: client.address, + query_auth: admin_auth, //fake }; const contract = await client.tx.compute.instantiateContract( diff --git a/tests/liquidity_orderbook/lb_factory/types.ts b/tests/liquidity_orderbook/lb_factory/types.ts index 8247cd19..8e684d5b 100644 --- a/tests/liquidity_orderbook/lb_factory/types.ts +++ b/tests/liquidity_orderbook/lb_factory/types.ts @@ -13,13 +13,11 @@ export type TokenType = custom_token: { contract_addr: Addr; token_code_hash: string; - [k: string]: unknown; }; } | { native_token: { denom: string; - [k: string]: unknown; }; }; export interface AllLBPairsResponse { @@ -29,7 +27,7 @@ export interface LBPairInformation { bin_step: number; created_by_owner: boolean; ignored_for_routing: boolean; - lb_pair: LBPair; //TODO change this to `info` + info: LBPair; } export interface LBPair { bin_step: number; @@ -144,7 +142,9 @@ export interface FeeRecipientResponse { export interface InstantiateMsg { admin_auth: RawContract; fee_recipient: Addr; + max_bins_per_swap?: number | null; owner?: Addr | null; + query_auth: RawContract; recover_staking_funds_receiver: Addr; } export interface RawContract { diff --git a/tests/liquidity_orderbook/lb_pair/execute.ts b/tests/liquidity_orderbook/lb_pair/execute.ts index 7d442191..821429d9 100644 --- a/tests/liquidity_orderbook/lb_pair/execute.ts +++ b/tests/liquidity_orderbook/lb_pair/execute.ts @@ -23,7 +23,7 @@ export async function executeSwap( }, }; - const tx = await client.tx.snip20.send( + const res = await client.tx.snip20.send( { sender: client.address, contract_address: contractAddressToken, @@ -36,14 +36,22 @@ export async function executeSwap( } ); - if (tx.code !== 0) { - throw new Error(`Failed with the following error:\n ${tx.rawLog}`); + if (res.code !== 0) { + throw new Error(`Failed with the following error:\n ${res.rawLog}`); } - //let parsedTransactionData = JSON.parse(fromUtf8(tx.data[0])); // In our case we don't really need to access transaction data - console.log(`Swap TX used ${tx.gasUsed} gas`); -} + console.log(`Swap TX used ${res.gasUsed} gas`); + // const wasmAttributes = extractWasmAttributes(res.jsonLog); + // console.log(`${JSON.stringify(wasmAttributes)}`); +} +function extractWasmAttributes(jsonLog: any) { + return jsonLog.flatMap((item: any) => + item.events + .filter((event: any) => event.type === "wasm") + .flatMap((event: any) => event.attributes) + ); +} const PRECISION = 1_000_000_000_000_000_000; // 1e18 export function get_total_bins(nb_bin_x: number, nb_bin_y: number): number { @@ -166,8 +174,6 @@ export async function executeAddLiquidity( no_of_bins_x: number, no_of_bins_y: number ) { - console.log(amount_x); - console.log(amount_y); let liquidity_params = liquidityParametersGenerator( bin_step, 2 ** 23, @@ -217,9 +223,9 @@ export async function executeAddLiquidity( let total_bins = get_total_bins(no_of_bins_x, no_of_bins_y); console.log(`total_bins ${total_bins}`); - // console.log( - // `Add Liquidity TX used ${tx.gasUsed} gas, total_bins ${total_bins}` - // ); + console.log( + `Add Liquidity TX used ${tx.gasUsed} gas, total_bins ${total_bins}` + ); } export async function executeRemoveLiquidity( diff --git a/tests/liquidity_orderbook/lb_staking/execute.ts b/tests/liquidity_orderbook/lb_staking/execute.ts new file mode 100644 index 00000000..bcde5404 --- /dev/null +++ b/tests/liquidity_orderbook/lb_staking/execute.ts @@ -0,0 +1,89 @@ +import { SecretNetworkClient } from "secretjs"; +import * as LBToken from "../lb_token/types"; +import * as LBStaking from "./types"; + +export async function executeStake( + client: SecretNetworkClient, + contractHashLbToken: string, + contractAddressLbToken: string, + contractHashStaking: string, + contractAddressStaking: string, + ids: number[], + amounts: string[] +) { + const staking_msg: LBStaking.InvokeMsg = { + stake: { from: client.address }, + }; + + let actions: LBToken.SendAction[] = []; + + // Loop to fill actions + for (let i = 0; i < ids.length; i++) { + const action: LBToken.SendAction = { + amount: amounts[i], + from: client.address, + recipient: contractAddressStaking, + token_id: ids[i].toString(), + msg: Buffer.from(JSON.stringify(staking_msg)).toString("base64"), + }; + actions.push(action); + } + + const msg: LBToken.ExecuteMsg = { + batch_send: { + actions: actions, + }, + }; + + const tx = await client.tx.compute.executeContract( + { + sender: client.address, + contract_address: contractAddressLbToken, + code_hash: contractHashLbToken, + msg: msg, + sent_funds: [], + }, + { + gasLimit: 16000000, + } + ); + + if (tx.code !== 0) { + throw new Error(`Failed with the following error:\n ${tx.rawLog}`); + } + + console.log(`Staking TX used ${tx.gasUsed} gas`); +} + +export async function executeUnstake( + client: SecretNetworkClient, + contractHashStaking: string, + contractAddressStaking: string, + ids: number[], + amounts: string[] +) { + const msg: LBStaking.ExecuteMsg = { + unstake: { + amounts, + token_ids: ids, + }, + }; + + const tx = await client.tx.compute.executeContract( + { + sender: client.address, + contract_address: contractAddressStaking, + code_hash: contractHashStaking, + msg: msg, + sent_funds: [], + }, + { + gasLimit: 16000000, + } + ); + + if (tx.code !== 0) { + throw new Error(`Failed with the following error:\n ${tx.rawLog}`); + } + console.log(`Unstaking TX used ${tx.gasUsed} gas`); +} diff --git a/tests/liquidity_orderbook/lb_staking/types.ts b/tests/liquidity_orderbook/lb_staking/types.ts index bf93012b..76892d44 100644 --- a/tests/liquidity_orderbook/lb_staking/types.ts +++ b/tests/liquidity_orderbook/lb_staking/types.ts @@ -10,6 +10,7 @@ export type ExecuteMsg = } | { end_epoch: { + epoch_index: number; rewards_distribution: RewardsDistribution; }; } @@ -37,27 +38,32 @@ export type ExecuteMsg = }; } | { - recover_funds: {}; - } - | { - create_viewing_key: { - entropy: string; - }; - } - | { - set_viewing_key: { - key: string; + recover_funds: { + amount: Uint128; + msg?: Binary | null; + to: string; + token: TokenType; }; } | { - revoke_permit: { - permit_name: string; - }; + recover_expired_funds: {}; }; export type Uint256 = string; export type Addr = string; export type Binary = string; export type Uint128 = string; +export type TokenType = + | { + custom_token: { + contract_addr: Addr; + token_code_hash: string; + }; + } + | { + native_token: { + denom: string; + }; + }; export interface RewardsDistribution { denominator: number; ids: number[]; @@ -95,19 +101,42 @@ export interface InstantiateMsg { epoch_index: number; expiry_duration?: number | null; lb_token: RawContract; - query_auth?: RawContract | null; + query_auth: RawContract; recover_funds_receiver: Addr; } +export type InvokeMsg = + | { + stake: { + from?: string | null; + padding?: string | null; + }; + } + | { + add_rewards: { + end: number; + start?: number | null; + }; + }; export type QueryAnswer = | { contract_info: { - admin_auth: ContractInfo; + admin_auth: Contract; epoch_durations: number; epoch_index: number; expiry_durations?: number | null; lb_pair: Addr; lb_token: ContractInfo; - query_auth?: ContractInfo | null; + query_auth: Contract; + }; + } + | { + epoch_info: { + duration: number; + end_time: number; + expired_at?: number | null; + reward_tokens?: RewardTokenInfo[] | null; + rewards_distribution?: RewardsDistribution | null; + start_time: number; }; } | { @@ -118,6 +147,13 @@ export type QueryAnswer = amount: Uint256; }; } + | { + staker_info: { + last_claim_rewards_round?: number | null; + starting_round?: number | null; + total_rewards_earned: Uint128; + }; + } | { balance: { amount: Uint256; @@ -161,6 +197,19 @@ export type TxAction = | { claim_rewards: Reward[]; }; +export interface Contract { + address: Addr; + code_hash: string; +} +export interface RewardTokenInfo { + claimed_rewards: Uint128; + decimals: number; + end: number; + reward_per_epoch: Uint128; + start: number; + token: ContractInfo; + total_rewards: Uint128; +} export interface OwnerBalance { amount: Uint256; token_id: string; @@ -190,6 +239,11 @@ export type QueryMsg = | { contract_info: {}; } + | { + epoch_info: { + index?: number | null; + }; + } | { registered_tokens: {}; } @@ -200,82 +254,65 @@ export type QueryMsg = } | { balance: { - key: string; - owner: Addr; + auth: Auth; token_id: string; }; } + | { + staker_info: { + auth: Auth; + }; + } | { all_balances: { - key: string; - owner: Addr; + auth: Auth; page?: number | null; page_size?: number | null; }; } | { liquidity: { - key: string; - owner: Addr; + auth: Auth; round_index?: number | null; token_ids: number[]; }; } | { transaction_history: { - key: string; - owner: Addr; + auth: Auth; page?: number | null; page_size?: number | null; txn_type: QueryTxnType; }; - } - | { - with_permit: { - permit: PermitForTokenPermissions; - query: QueryWithPermit; - }; }; -export type QueryTxnType = "all" | "stake" | "un_stake" | "claim_rewards"; -export type TokenPermissions = "allowance" | "balance" | "history" | "owner"; -export type QueryWithPermit = - | { - balance: { - owner: Addr; - token_id: string; - }; - } +export type Auth = | { - all_balances: { - page?: number | null; - page_size?: number | null; + viewing_key: { + address: string; + key: string; }; } | { - transaction_history: { - page?: number | null; - page_size: number; - }; + permit: PermitForPermitData; }; -export interface PermitForTokenPermissions { - params: PermitParamsForTokenPermissions; +export type QueryTxnType = "all" | "stake" | "un_stake" | "claim_rewards"; +export interface PermitForPermitData { + account_number?: Uint128 | null; + chain_id?: string | null; + memo?: string | null; + params: PermitData; + sequence?: Uint128 | null; signature: PermitSignature; - [k: string]: unknown; } -export interface PermitParamsForTokenPermissions { - allowed_tokens: string[]; - chain_id: string; - permissions: TokenPermissions[]; - permit_name: string; - [k: string]: unknown; +export interface PermitData { + data: Binary; + key: string; } export interface PermitSignature { pub_key: PubKey; signature: Binary; - [k: string]: unknown; } export interface PubKey { type: string; value: Binary; - [k: string]: unknown; } diff --git a/tests/liquidity_orderbook/localcontracts.log b/tests/liquidity_orderbook/localcontracts.log index 49b31082..232159ff 100644 --- a/tests/liquidity_orderbook/localcontracts.log +++ b/tests/liquidity_orderbook/localcontracts.log @@ -7278,3 +7278,1278 @@ LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a +Deploy Time: 05:25:26 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="1" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1mmcnj6rx40r7dx2ju34rpt4e7f0cfxhcwxazqz" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1ezalj6psdshaqqxqsnklasttyuquzqs54yr3kf" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1pmcv933gmssrmk0gkrt2uctn5mmh6ns3z8v8ap" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1ktfn0yr6x58spux0a7thes9mz2cftf5dd77t3d" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret1rsyrazq6p20j0f3la743q4nn9sc42c8hrwhhxz" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=5 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=6 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=7 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:27:02 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="8" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1tun6c8y0nrc8hckqnl0lnx627qe9chwyjrj3f0" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1msg0kdws3qqak7cvh7z95g4efdr7eqpfu8anuu" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1670y7g5t3rwsxdtjzjrkk9fa5wqyudp8c447dq" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1zfcdnwdp93mxy4xa734j80hqgnmz3xthp0m52x" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret146fxr47kzh00kgsfq9zrn3mlv7gacg8ly0vyvu" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=12 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=13 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=14 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:28:14 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="15" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1m2dhx4upms0uhr02yhg709cqy23dxewl0jfajt" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1xs6fmzl8wye8kphgec3f69y9ve5hak3p9h7kcp" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret123fh3zhy9sjzq2l4ggvvm3g59z3ss2alewvrlf" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret184ylmxc8x4ydrc46p5rpz9tq7uz3re2a7x4xve" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret1cu30ryaywyatluc0mxe3lm954283vxx7p84vh7" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=19 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=20 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=21 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:30:08 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="22" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret12x3dcpt08hpe83z2cnxndeqk6fcq78glyaekls" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1f7jsqvxmcxkgzd796gl5kllhle5fhxnuw6e6eu" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1umtwjsgqte2rh98rlwvk57kmm64el8ggn0zxn9" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1mlwtzr9gxwlz50s4z2dwhr0q73wrlf8evs55wf" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret188g8ypx57e99z9h7uukesp3ufxx5yelhtawq6j" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=26 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=27 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=28 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:31:42 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="29" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1k58cdwg4aflymlsvd4prqlmmlzetazf2jhapqz" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret17rmvc9e8u69aw62gd62dy04cvrgkm6myfs73hu" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1wgqn3j0e7955wucg4tsh4ggvrs7mkh9rtk6n30" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1gd82wrnvma9ggvuqauyhl367hguh6gu5dvz7ny" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret1hyq7c6zft84atnw32qhg4kn9d7gf9tpsmff3qz" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=33 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=34 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=35 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:32:04 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="36" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1nt5djkptvuvr3fe5y40ma00j44l9sc3aful9sa" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret18l757xwhdca7sqw4xnfauham3nz4q9gylyghkl" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1sw76g3wl7ut0nsegx7ekje4rt2m85lrua4qrag" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret192c7e66myty4yx4xh3x4nutc53e9dq83dnt46a" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret1aar7l07wjaf5f23qdxmv8kzvca82u9tshxex08" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=40 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=41 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=42 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:32:30 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="43" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1pnemzrfzc3p7rwwwfs884xtyea2ruquwu9j5u8" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1tpn7nrard7f2k5xuswz4gxjtqk9vj24rm9wq7j" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret19m6gt574kys2g2h8p2pjn92ez9n6976k2k39hl" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret10cvs5fkeuu3qfpyrhs5mrh2smv0xz2czsad525" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret108uy59juzr3avt4x7xt9rsru72l0d377kcynxs" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=47 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=48 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=49 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:33:52 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="50" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret10z7dqvmskss8e86rt0e6tymzjqz8mpfm46zq2k" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1d5vum002ufttsw5axysku9c9mdaqk39tettt5c" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret18f7d35hvhewvep54zgz95s63z58h44vugeqk92" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1kcplcv0w7ea578rkqeujwmqkx2n2evxm6aksfs" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret15k9jhchl6dwym98h4v9rpj00lfgp4swnljjyfh" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=54 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=55 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=56 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:35:11 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="57" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1vpytnh492ctzhwc6f6glzgu8zl6vg0ednmskxp" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1t4et7zrrclwc65kqy0x0r7x56cjtrrc6snjmqd" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1c35kpfzv9pjuydmdflqgnrfg59pfm2xg3zlp97" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1afpj042s8cphmvj0ysznm85rxygafaevfc4dwr" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret1umrrys6sp6n3u43uh4sjqkcuwn9v2qrepfqpww" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=61 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=62 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=63 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:36:13 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="64" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret17rfgl33pmr2n2vjadhftcgarh6swzvrjv3gam7" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1kvhv5flrjxulphu73mdhvw5r7s6gm9hhczlm64" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1w6hkfgm5f56rhruk5y53a833qglzj9nj3ujrlk" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret18adpde3lxjdddq5d0txexrvjyjyjjlwtaz2k3s" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret18al74r9xw5xsaymldac7sl4gcc2n7keez99ext" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=68 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=69 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=70 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:36:51 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="71" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1c76r3tdkly85kaa8404m24qwkgsy936yh3zcve" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret13527j3lnw6uz0u95f4tep408du6kvzhh4vez6r" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1hefyemr25md25nvjd84nsnmhdh27qecn79n6cy" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1nmqn44lq63pn8sng26jg6m69uwf9kyh3kz8stm" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret1s3cem9a3z370xacn26t2a3xclk9nm823ulysqf" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=75 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=76 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=77 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:37:34 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="78" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret148u8ktyvy764254308zlmmac293prqlh5lelqp" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1q4fn5zp9em90r6p5pwzl8g0424kn4x5425tn5l" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1s37gdjzh0y5hracdks3kyjr3zjtpz480zmn9wg" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1zqncff54nns4wlegp3sv7ugu53nn4nw73355ae" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret18eeyhukdw0lw3txzfyhwpn3ahf0yywm508zu7g" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=82 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=83 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=84 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 05:47:49 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="85" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1aq30c9ellklt66jdzlrjcshkkjznm4dqkncsnu" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1mdmdnv82nr99l8lt0dwq2knd7gtq7x0tvya2j7" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1lw5nrt4spqsyr2s70488tj8f996f0n09ud7ehg" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret16zfjgxugnay4savxlwmflfa2xt93g0jlr9jqpe" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret1c20eqtpda8qrddatxh5ptmgrdyfkn05nu2tqcp" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=89 +LB_PAIR_CODE_HASH="9768cfd5753a7fa2b51b30a3fc41632df2b3bc31801dece2d6111f321a3e4252" +LB_TOKEN_CODE_ID=90 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=91 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 06:03:05 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="92" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1wujqst2wngltwtg7ymvdqjhn3nmrwv5u2rkwvj" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1vsn9p2afsw72c9g44043cpf0v8npevfgyhp9ng" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1vp3ctjz852a9ptkd6a98u0sap6qrg46cxhn3tw" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1ejwlnys4sj8sqjjrk03z4hs9jfhqw25rwv53tr" +LB_FACTORY_CODE_HASH="0db90ee73825a5464f487655e030a8e5972f37a3f11536e5172d036a5ff6e96c" +LB_ROUTER_ADDRESS="secret18uzxkvqwp23fd8mqqllkpy5x52lhcw0dtqlq77" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=96 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=97 +LB_TOKEN_CODE_HASH="b9d47e3a03c4cac014ae89c591abe8ed6c88efaccda7f3af2853b8ccd6e0e7bf" +LB_STAKING_CODE_ID=98 +LB_STAKING_CODE_HASH="16946a1f044d2ad55baf4a108bae2090d491cb47f92400c4c99cdadd6a344cdd" +Deploy Time: 06:05:56 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="99" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret12ugcd4ytc6xj8f7sqkhey4lgywjchrrxkltsm2" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1348ym8vj24h4ydxl263mufu7zy0syyvhc67zp0" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1cyxtt5swtlcmkvlrztt4gs76jqxx63zmscmxl7" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +Deploy Time: 06:10:38 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="102" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret174ewvvf3yxp00m2qtwjjwatkyaps99uym5v7pq" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1sjnq6dla04j6cdvg8qtlfn5g20cftsu3ytwq9s" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1a53d9na53kgxzesh40gawsvn8f3tm2z9ntl95k" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1e96z0geajcd62r0hn8k76jqnk3vmyaawxl4hy5" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1zn2rz54x29evn6ncktzruy9v4gh3yqkn2xnngl" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=106 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=107 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=108 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 06:11:22 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="109" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1t49a6g87h6cx8zqesl2tmvulmjkg06jlhjfptu" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1jyrsqc9q5tnsp4zp08purax9hevkyusgm3jhnm" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret18llf4u2e78ltq2daqr6gq3l3pcnxgy567u75my" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret137jma3c95cz64znxqk2q2vfaymgnfwsvakfuee" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret16xqlzdhv476s2fcf60avnxnugslwufd6awz7la" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=113 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=114 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=115 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 06:17:43 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="116" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret133dhnsqgjka8v8yw7xftwg85gry3cjs3uqyv5v" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1wpxv2e3gv69ej7j42hz0ensmaqa3s7ynmshfu4" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret18xpskdk7vgrre48j6x2wtf96wvr7mn469pm6xm" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1ymc72p6jm624cap3z98pj5las0yvduz73v95xw" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1kfnlhwqd95eq03fkduhwryen4m6mp7qlar99eu" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=120 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=121 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=122 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 06:18:26 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="123" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1zc29c2e007phlsypf95zk2qxxe84yz765u7nmn" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1jvcrqzuv3k00skxp8l2n78nn5cwrzcu5ctlxdq" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret10n9avkdm2jh9yzhk8tlw3yd5ws54gw0g7s9jwj" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1pmter87jry7e7ed273zrv2uqz3yj9k8t5ls0v0" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1htsjk7a7x9hq2ljtem5y8m58gy0w6zajx0fu54" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=127 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=128 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=129 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 06:19:02 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="130" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1he4p5fr8x7ql7gl8hjvjmahc8l2345j5jagnsp" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1vq439dt2k200wgvk06rs42j5fe95clspq8tf3m" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret12t5gmuzy0hjj58fhpqhr8yttskelm2xl3m466k" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1f9rgmhfvkugvjwgv5rvwsv8ppr4qjy05qneg7e" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret13mpxqh2gcdcgl7lkt3hj2qkaucmgp9tuwzm5ew" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=134 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=135 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=136 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 06:19:37 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="137" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1sr7t7re8xzhfyh9dg0hsevcx5rkgxfwcq87xxd" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1y8wzqkuh5u7qa20hhk5e6yurgf8c6azdj7uv75" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1ryyzcvfnvelpaz9ve2e7c8980lv86nwegldker" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1yr6l0d3lvg9wfh38ydfqspt966ma6sjzedt4fz" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret16wmgfc0hpchnk9wdp0360zg0zrksyz9wjlq6vh" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=141 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=142 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=143 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 06:20:00 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="144" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1n644v6fvp8xjul7zyg4qlh2hz02pj2rjc6ngnc" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1d68aavjhcrp9y3s2g089jsf529lycsrrrk6p65" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1uzultkge8h55lvm9lnfnzgle6u66yhtj2tkyl0" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1mvmk2z0js5lzc4gv4he6c98lpstwdcfq85jjm3" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1mxgh3c5fs768fwmvda5qdzcgvgf8zq258uqtrl" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=148 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=149 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=150 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 06:20:24 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="151" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret138fvs4fcxdtazy5d8pwxve3ha6hjxmph55hnjn" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret138ueftce4uvqtuqdsg6m3e506vml8892glm0fx" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1zv3n6se8y6lxjjl5tlrkqx8m7hae5flnq5fnwe" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret159ja8g2gnyn27ggd4mf3xgkf99p0mlg0xu5qpd" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret124f33vgk5ka9pypgx6exkkcettmgmavd9y0xlw" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=155 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=156 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=157 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 06:23:16 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="158" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1e24pdhs4s5ru3z75fx3vmecdess86drdlchmxm" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret16yent3vlexpyr4elq0g4852kaf2whywpyd52fp" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1csngj9yykgrhz4dts0jhqelnk66hjtwv9598g2" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1c7pzdnpvefllcnh8sgvwq6zwnr90vj0xttaas3" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1suk4lts7wzrgzxxgps97vh0ktphks9m6kgh56u" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=162 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=163 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=164 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 06:24:00 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="165" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1p7zf3v6h6yaas0uu4zfedh47sd6c0ynxhtt862" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1c792azrrk6kqgygw922ev882q7y7zr4ew56pah" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret18ms3wtn6mdy86puauljrd75eya3ltfn7fhrf5j" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret167jh9zm9x8dxrrnzyhdg7rtwqydu0t22w3tekl" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1rc0a4r0xy0jxum0g9vjkzq3fygwjg0r3w4e3th" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=169 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=170 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=171 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 06:26:43 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="172" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1rckrqtzszawd5v9rx37u2qnp3ar88l8lpe27jm" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1h9zmnxxmk2zpyvjct44jx7tqnk68qgwmqwj46w" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1g7zpnff4caxad5tnaxuducdg8gk6nennk344d2" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1sm7z84x4jrnvcwjt6erp3suj33zsvgn47yjzfk" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret147a5vmvjh466svpxqquc0k5u55ky5xvwtgvmvq" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=176 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=177 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=178 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 06:27:35 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="179" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1kz334xadg06jwvmc39zrmeyh7l8jfpq5avltuf" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1juf4jx2cpykqy97xnd0th32v9raux7mdulkr9l" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret18gjawunn5rk2fnzm8knqhx3l7tygulznyq0ug7" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1yljszrh73zfuele0cf7cv4879y8l8myupxamp3" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1p89n0h932r5vswy0hfky2haurzz2srdygpfdzu" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=183 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=184 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=185 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 06:27:44 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="186" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1gurqu7n5vryct5evvwktzesuz5p60k5zfsn4rd" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1ua5j6v7krmue87eyz5nhjfvhaytfw0xg5l5cgt" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret16man90s37h6nm747vy4y7ukx8a62e3887vjj47" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1hjgvyv4y4u50dd4jkppwk3atdrntendkfpvkgw" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1ysruex9cyuwwkhg60pk35uzeshcj2qlx88s3a2" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=190 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=191 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=192 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 06:29:42 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="193" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1yh9jdrk4y33qs5cr4dhw47ljx35vfjxkvu6jqv" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret16gwtsdz5658u3gx3wyhzvcwd0ekfntga336ere" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1c2a3qc56mz0p6y6lqvpfuwftzumr0pev3l6msu" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1t9eue642vk2dm4lhffsuuj0v3suqf56767ah49" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1y6fsrw85tc2khq5lhn8f7xmhzzxyvdk2aj8crj" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=197 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=198 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=199 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 06:42:03 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="200" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1hzczwxu6vp004tkyzzrtalpakwj7jld0py5ndx" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1jm4d903yj7j9ucjlgdurlrqea3r2v90h40a05s" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1z4sx668nj9n0daduuv7uffg9m9z7ysxj02j9uj" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1560zevuslzn8ydegg2t2jzm7qayz4708tstkr8" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1j77au3y82ky7099wwdjzv7r5zvj7espws4qzrm" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=204 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=205 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=206 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 06:43:25 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="207" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1h6cm08laf70lzfrs2qdpndvmhht75042344t7u" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1em4vf7gv439f4akq52w3s0m939mv3rykkg538m" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1r7dhmf7jc0fdg0uxhu7cpwplqt3vzugxw3z3fc" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1ufpagknlx3ymwquhu4ljvddvp9s5cvt9ruk8u0" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1mmp98kmk8qzlatk6humrxtx36rtfld93rrpw46" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=211 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=212 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=213 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 06:57:47 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="214" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1t3y2wcy53upsqml96d9uefj0008mg8rdla9eec" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1xv00aj7k8c26eecw9pmzxzntugf3r429jwm5mk" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1mudlx3esxdpv74wk44asf3fwftljdhktwdj7dz" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1p9leuydzjqefjn5x7xessc0z65l3zdqzrskqwe" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1zwjrulljd7r6n20mp35ku6mpjmkk7c2es8qza2" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=218 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=219 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=220 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 06:59:22 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="221" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1cc8p7eek0s4u82205drcaxhgpweunqyg9pys6j" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1u8tdsqk2ddtsyyl3knnw2plvf89au9x3la7ryf" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1hqx9tmkmglr8cz6h8n0p78y877rx45cjl7le2h" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1azxaamhps2je7t05k2smqphjue6w6pmsxq9yq7" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret12hvs8ngnqpx8zd5w0pa3tfyrzjul2sypmtmzqh" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=225 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=226 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=227 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 07:04:46 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="228" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1ps5qh9tnl75wpkltlfw807e8lh6az4fkmkr4g3" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1gqzgxzfykgqgr2gvzcjgwef82m0apxvm2ccv89" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1tssuektngmskqlgflyguavpyhk03shla6prvev" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1k0d9zpwe50puj6utv6s4ef78vjq8yumrax3cyr" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1l5wkhnx2jm8qs2wvwst8u5mtk6fllskdwyacxf" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=232 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=233 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=234 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 07:50:54 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="235" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret14rz6cmk5lv459j3kf39vwrrcncayvzp3mwjswa" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret17chjykj5t9zhpa0qtv26e8kp5vfm4rqmfjeynk" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret14373x2amum6mnfvhqu8u4aara9j07swgj46zwq" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1ss7t223997vqamu3mc22c7nywa22a9zat77lyw" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1mactxe6qmcgjtrjw8trahp47ezw54387cxg9k0" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=239 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=240 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=241 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 07:57:28 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="242" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1ltaplkg9n6u75v75p6pskcsnf55psmh2uawzpd" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1sgq8yu5tfdmmzmev2zjhetnl20a8n546q5494u" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1gaanrc900pdesgsw0zhvvcvsdeta00q0hsgmfp" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1ffyq7q6ltqrheu89pz5jhpkdavfqfk6z2xlrx3" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1jvmrknacfgwjxemrr6j4x6h3p7qjsvnx0atqe5" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=246 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=247 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=248 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 07:59:04 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="249" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1es5599zy5wwhuj5pnv9x23627afmadsvn07gk9" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret12vlqa8l7pl0w048t8w5de35alz4rrlhlhn6klu" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1h0cuvghvwgeygufm0uxa8hs7azj3e5aqpupmp0" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret17wk0zn8dd7jrw86ya4nj3hnytx8qf34gjvs8hv" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1anugpd8mjln2nph5gmhezmapvmxewtw0fh09wj" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=253 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=254 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=255 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 07:59:46 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="256" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret15536zp78pe34tuke4nlc740zy82n5fw8gq95fk" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret13wa3zn68f8lpea3v0whta2g3n3uu7wgmhgtxwd" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1an9tzu8yr06lr9ef3y7yxmqs9kfk8pdpkjsa6x" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret12yy4xkgerwh2xalkg9nhsnryg496z8x0d55z7p" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1q5vs4j048585mn4vmm0vme3858qaadshdqskhf" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=260 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=261 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=262 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 08:16:50 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="263" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1fvvg5syagqrsaf5qxncu9qfs7rwhx69hrtfyvx" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1h89qgflccae6nwr20c0m29g4cmhwchvuwxnz6u" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret18l7s867kcghhkdkza9acjnstcl7j6pxd030z45" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1yt98e7h9h5rxvuah3vnkc2jgp88wcwrd4kss6l" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1mnc4jxz0tkkm3j8zhjksjgvw5u27x3jde0xctv" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=267 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=268 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=269 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 08:18:35 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="270" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1s2nu244hplk3pfeds6vh8c2txjexmf6mgtpfvf" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1gwx8ua3xczlu3jakahrqe86tp2lg4fxvtvp7g4" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1hfqrkjyu82j98qgvu50xsctyknl2s7p8vsl8ul" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1ht3rrcuphpkzwdgppsj0epxfpmzum4xjp45l39" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret10lquuewsx3xg04u5jh4kfdl6908ksjwnzce6lc" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=274 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=275 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=276 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 08:21:24 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="277" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1lnx7v04fn4dg3r0c25ql67fc795udztyzhjkf3" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1l0my9794p3npsrw5t6qf2haujup7kdasgzpn5p" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1d0wmy4hkjhgmv782dwdf3anpl2jqj8lv7xka5t" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1yfnr6vsk79lzkdsh8qkce0a55te405svx2gn7l" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1mgf95zeu0tcjxcmkqwmkx64s27wppyl3tsj300" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=281 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=282 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=283 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 08:25:48 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="284" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1c5t05ldldsal0xaxrutqaxh7u94hyzk9wfnsgm" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1mtz57f8m9vut2xx23ujtjmyy759mtpynqyawuv" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1h3mw2ht2r0wm58hyl0rj243rwhap7dq7pgglqw" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1lnm4qmwkp8yn353zdusp0frqm04nmjjdx2j2k7" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1st2u2sxx54g6u8u0lcs28955rappluq0fcz56e" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=288 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=289 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=290 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 07:20:11 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="1" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1hnddjteqtsfc2lya27qw56wrnaadryt493hmhp" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1lmgqknyxeut7rm2gv4qaymah2lx336ksws83l7" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1dg99q3fp73qmrp9wvatvkd8xdj5w3lhk28suy5" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1hwudr7xqhw3px5xe5j2y4pjjcf8exqjzk4m2ak" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1j6m3asv0q7q94xrzwuqa7sc6u2xzm8n42e2e76" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=5 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=6 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=7 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 07:22:19 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="8" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1vtly8pzfsaymd3xetfcwzdty2hvc8yy85tj7ku" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1gspt3nsa9kcrwqccngvk0k4zfj9js4pthv07k5" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret125vl0fywyh9da3r52j3hq6jzs3ptex308yaypl" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1v8fmmensfhrhp2zrxxwax9hjq3m5hjfaj8nzxd" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret19ttfyutds7evsydwk7lxje7n0mlq2tpxnvg844" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=12 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=13 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=14 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 07:23:31 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="15" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret19vz8pd9xq55neu8ahs4rzym5p5sfk480c5tn6m" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret154mpv2dj8jxqunmr0khzgvcghnme92aqy8de04" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1zp77x208w5jp9ugfnf7cd27xsecj70z6rprl0q" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1f7jst0gcydhy6423p9zxww8haalhc42j6vx69l" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1n5ql7kufaas524m8f4zxladac8hxqh9nzdgywd" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=19 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=20 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=21 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 07:28:19 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="22" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1q4n4s2mcs8gktcuuh3mus682tlqn74gv0zz8ph" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1nmrnn92qm7gz3ekv00x0zqt53jwp2karhpqrqh" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1zt3z5dev5c6rk8trafrsp9jkts2k7kfdyvza0z" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret19tz05fgtgh3a4qpsh04jfne9tyk4wuymr25uwv" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1qfyaf60ccqx258zhktxjmsgc46z7hwfnp9nz80" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=26 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=27 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=28 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 07:31:31 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="29" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret14zmazh4kdzxn97p8x3f44lx28cmjyupuam47kz" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1zkupqll2wc0hpz77py3hf64efhrmgj3d90jde0" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret18uru6acnyrh702l8ez8un7ewwkeqd8dvdjjacm" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1t6hherq2xjft4npcq0a2853yye5uesff4h6fa0" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1fc4nk6qw0dwu2k0kk89h2jumtx9urff7axzfjy" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=33 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=34 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=35 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 07:32:47 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="36" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret106zve490387galpj2jyx7t05crpuuretvj93vd" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret17js25zshd4hppu8qzcvt7jv77urnv3a6t46z4l" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret17vpem8z37ug8fasran7xqytrfe6geztj2cgvu3" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1gydp8ydsq0p2n3hufyjdncekt958lxxh4wjqz0" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret14ep773ty6upctd2jty2alw9pved8epagm0uy3d" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=40 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=41 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=42 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 07:38:01 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="43" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1da4vxwkae3r84lsmq9d2432vnx5usd95wckacj" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1fzv26fyel755wc2cmshm98vyvwyh0pvsrtf6tp" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1qwq2xrrtce9k73xqtllfkktvck9a03vj5tj7gw" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1kpp4wa3d2sngmdzwlgx6ufxlvqdm9s406s7yfg" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret12035seqw4qewvt9af6nj8nwtrgejk8pnku2ndj" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=47 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=48 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=49 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 07:45:58 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="50" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1vclh2kmhh2gn0550r59u0lryquy5rl04q3tn5s" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret19txl05k6kdcuspv8lz20psy2a4s8m5lxk5hk0t" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret19r57fjd6azfrxdjzt4j8xynp5fwyqrngk9fteu" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1ym24faxcs5wheys8lh7jd358df2kcsw350z4rg" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret13l73tv7w8ex2yly4wctspjt7uzaskrum75fnqa" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=54 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=55 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=56 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 07:49:47 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="57" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret14w9w7w6nefk2r73xvt8q9w00z5eps8afysvwpg" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1l3kudctta3peseu0hxrqucyxjsswn6ktg5k2hn" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret142sjx5e588c49ls876evzy24scw24xhgt06rrw" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1nusepcpjxlmv0f693uxk24d3af0ecvhrmelty0" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1sf3xfxv488rws3jkp9p4t8c20p0rmalpr46hzp" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=61 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=62 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=63 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 07:50:45 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="64" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1wet6rpscerlf7d63dmw8zp8p72svn90tewhjyr" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret12fq2fermx9202ryfq67jgjucskyrdmd0z7f9ms" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret15s4nt4wlmdxms40xrphk89p8wuyuwatyzrj82e" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1q0etdrvx803xqvxqur9h6ct25mnn0wqeum2yh8" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1se64cmjvvkam2pcxdrdanjmy395ds7fcf33slz" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=68 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=69 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=70 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 07:58:08 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="71" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret13nznchapgrs2l0rnwcw266vg59au72zd3d25u9" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret10e2yhcvs75dnz5c66aaj0l6p8m5ewprzmj7035" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret16kc6kkn9m9znlcnetwzrzyfc9qnt86mmdxsn8r" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1kx776x7xfwq2kj8ep5nfyu43p3ppg23ea7j5tg" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1c752m3xgtvgynaksatal93yt3wntrzzrtlhrc2" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=75 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=76 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=77 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 08:01:17 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="78" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1cm4w8fqfup0cn6r3lnzfurd770ep4teqqucnvt" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1swspzy79wkg05gp6kh70yar57hf3lt6yywugsq" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1n94patp9js9ujzzth2m2a87rv4c3ztuepc8fjl" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret13z93l6dyjyf40e9fjncxwa0j6u6g5shvng8hvc" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1jkcnsz9rn906a9zrfewd92epcywtxl3ug032z0" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=82 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=83 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=84 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" + + + + +Deploy Time: 09:06:31 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="85" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1qu962j00r296kaxd8urjvepchq32j56x0duxff" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret10647z8k4vn709srumqkgw708hw3qkwmgvpm8rp" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1rqzwd7e4e22nk89a2nfjzxk7sdqdttls5v2pxc" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1k4rweg4sww03n7rmvnq89k0gqqkuqaca0qkjk4" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1hvjpqhzh2vuqkqjpce7hmfdz62432cdwal6r42" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=89 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=90 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=91 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 09:10:44 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="92" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1gvlxm4jmxjryq5s2tzutnykta47kwj42588jef" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret165030k4yq3yltnqgq8rc0phgn6xvlg452va76l" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1vpj4336fuccf0577uexmajkxek9wv8pkrkj58y" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret1jex52j6mznkcg3m65pzdr0ct00jfhsawztn5de" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret1fa2237zuxn95mp56tg43kc4hfy4ezh7qz56mkm" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=96 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=97 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=98 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" +Deploy Time: 09:13:06 GMT+0500 (Uzbekistan Standard Time) +SNIP20_CODE_ID="99" +SNIP20_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENX_SYMBOL="TOKENX" +TOKENX_CONTRACT_ADDRESS="secret1wa279dufwshx3mygrpvpzdj0xrqxtmdy2qcuve" +TOKENX_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +TOKENY_SYMBOL="TOKENX" +TOKENY_CONTRACT_ADDRESS="secret1rvu3y59ghr6y27xwr0wrxdgezenlhs9pf438z7" +TOKENY_CODE_HASH="0bbaa17a6bd4533f5dc3eae14bfd1152891edaabcc0d767f611bb70437b3a159" +AdminAuth_ADDRESS="secret1vm9edd4ak3654yquh3x4vxy2n2hphrmj4p509q" +AdminAuth_CODE_HASH="e3525ed6f5466b3e54fb61f67def71a319a085dc5811d6b75544aa7def05bd83" +LB_FACTORY_ADDRESS="secret17zjv8jxe7n8dk7p6xsmfsr5ls5vkvmnumudwcz" +LB_FACTORY_CODE_HASH="d189345385b6bc2ee50a146bc127d24dcdb88316bcad2cde68c54b9aa9cb631f" +LB_ROUTER_ADDRESS="secret13tesuesmg3lrjhr55u2rvgxaa5kz3nu9legngs" +LB_ROUTER_CODE_HASH="d5aad5986f96246e7d287d20805aebe3a0bb5d008b95ee0990dccd8d54fca359" +LB_PAIR_CODE_ID=103 +LB_PAIR_CODE_HASH="72d1c6c37d27d46ea0339d0b82c8be368bab080699de0d0077e66a111d8eb3e3" +LB_TOKEN_CODE_ID=104 +LB_TOKEN_CODE_HASH="ac0cd61246f0d6c94f79144bca5b4a0959a1df4370213a512a5856e6e2180873" +LB_STAKING_CODE_ID=105 +LB_STAKING_CODE_HASH="bf3d5b4dcd6830b797ebc0b0622de90515f091da79fb4a15b31472bfd12b2e81" diff --git a/tests/liquidity_orderbook/wasm/lb_factory.wasm b/tests/liquidity_orderbook/wasm/lb_factory.wasm index 8f39915d..77dea8b7 100755 Binary files a/tests/liquidity_orderbook/wasm/lb_factory.wasm and b/tests/liquidity_orderbook/wasm/lb_factory.wasm differ diff --git a/tests/liquidity_orderbook/wasm/lb_pair.wasm b/tests/liquidity_orderbook/wasm/lb_pair.wasm index 2a216c37..3f7a10dd 100755 Binary files a/tests/liquidity_orderbook/wasm/lb_pair.wasm and b/tests/liquidity_orderbook/wasm/lb_pair.wasm differ diff --git a/tests/liquidity_orderbook/wasm/lb_staking.wasm b/tests/liquidity_orderbook/wasm/lb_staking.wasm index 520b5e76..0853ecbc 100755 Binary files a/tests/liquidity_orderbook/wasm/lb_staking.wasm and b/tests/liquidity_orderbook/wasm/lb_staking.wasm differ diff --git a/tests/liquidity_orderbook/wasm/lb_token.wasm b/tests/liquidity_orderbook/wasm/lb_token.wasm index 40140511..5046745b 100755 Binary files a/tests/liquidity_orderbook/wasm/lb_token.wasm and b/tests/liquidity_orderbook/wasm/lb_token.wasm differ