Skip to content

Commit

Permalink
Merge pull request #35 from hadronlabs-org/feat/exchange_rate
Browse files Browse the repository at this point in the history
feat: exchange rate
  • Loading branch information
oldremez authored Feb 15, 2024
2 parents 7db72b3 + 46a7fcf commit 866e4ba
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 145 deletions.
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ compile_arm64:
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
--platform linux/arm64 \
cosmwasm/workspace-optimizer-arm64:0.15.0
@sudo chown -R $(shell id -u):$(shell id -g) artifacts
@cd artifacts && for file in *-aarch64.wasm; do cp -f "$$file" "$${file%-aarch64.wasm}.wasm"; done

check_contracts:
Expand Down
161 changes: 120 additions & 41 deletions contracts/core/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::error::{ContractError, ContractResult};
use cosmwasm_std::{
attr, ensure, ensure_eq, ensure_ne, entry_point, to_json_binary, Attribute, Binary, CosmosMsg,
CustomQuery, Decimal, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Timestamp, Uint128,
WasmMsg,
attr, ensure, ensure_eq, ensure_ne, entry_point, to_json_binary, Attribute, BankQuery, Binary,
CosmosMsg, CustomQuery, Decimal, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
Timestamp, Uint128, WasmMsg,
};
use cw2::set_contract_version;

Expand All @@ -11,7 +11,7 @@ use lido_puppeteer_base::msg::TransferReadyBatchMsg;
use lido_staking_base::state::core::{
Config, ConfigOptional, ContractState, UnbondBatch, UnbondBatchStatus, UnbondItem, CONFIG,
FAILED_BATCH_ID, FSM, LAST_ICA_BALANCE_CHANGE_HEIGHT, LAST_PUPPETEER_RESPONSE,
PRE_UNBONDING_BALANCE, UNBOND_BATCHES, UNBOND_BATCH_ID,
PENDING_TRANSFER, PRE_UNBONDING_BALANCE, UNBOND_BATCHES, UNBOND_BATCH_ID,
};
use lido_staking_base::state::validatorset::ValidatorInfo;
use lido_staking_base::state::withdrawal_voucher::{Metadata, Trait};
Expand Down Expand Up @@ -58,7 +58,7 @@ pub fn instantiate(
pub fn query(deps: Deps<NeutronQuery>, env: Env, msg: QueryMsg) -> ContractResult<Binary> {
Ok(match msg {
QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?)?,
QueryMsg::ExchangeRate {} => to_json_binary(&query_exchange_rate(deps, env)?)?,
QueryMsg::ExchangeRate {} => to_json_binary(&query_exchange_rate(deps, env, None)?)?,
QueryMsg::UnbondBatch { batch_id } => query_unbond_batch(deps, batch_id)?,
QueryMsg::ContractState {} => to_json_binary(&FSM.get_current_state(deps.storage)?)?,
QueryMsg::LastPuppeteerResponse {} => {
Expand All @@ -67,8 +67,73 @@ pub fn query(deps: Deps<NeutronQuery>, env: Env, msg: QueryMsg) -> ContractResul
})
}

fn query_exchange_rate(_deps: Deps<NeutronQuery>, _env: Env) -> StdResult<Decimal> {
Decimal::from_str("1.01")
fn query_exchange_rate(
deps: Deps<NeutronQuery>,
env: Env,
current_stake: Option<Uint128>,
) -> ContractResult<Decimal> {
let config = CONFIG.load(deps.storage)?;
let ld_denom = config.ld_denom.ok_or(ContractError::LDDenomIsNotSet {})?;
let ld_total_supply: cosmwasm_std::SupplyResponse =
deps.querier
.query(&cosmwasm_std::QueryRequest::Bank(BankQuery::Supply {
denom: ld_denom,
}))?;
let ld_total_amount = ld_total_supply.amount.amount;
if ld_total_amount.is_zero() {
return Ok(Decimal::one());
}
let delegations = deps
.querier
.query_wasm_smart::<lido_staking_base::msg::puppeteer::DelegationsResponse>(
config.puppeteer_contract.to_string(),
&lido_puppeteer_base::msg::QueryMsg::Extention {
msg: lido_staking_base::msg::puppeteer::QueryExtMsg::Delegations {},
},
)?;
let delegations_amount: Uint128 = delegations
.0
.delegations
.iter()
.map(|d| d.amount.amount)
.sum();
let mut batch_id = UNBOND_BATCH_ID.load(deps.storage)?;
let mut unprocessed_unbonded_amount = Uint128::zero();
let batch = UNBOND_BATCHES.load(deps.storage, batch_id)?;
if batch.status == UnbondBatchStatus::New {
unprocessed_unbonded_amount += batch.total_amount;
}
if batch_id > 0 {
batch_id -= 1;
let batch = UNBOND_BATCHES.load(deps.storage, batch_id)?;
if batch.status == UnbondBatchStatus::UnbondRequested {
unprocessed_unbonded_amount += batch.total_amount;
}
}
let failed_batch_id = FAILED_BATCH_ID.may_load(deps.storage)?;
if let Some(failed_batch_id) = failed_batch_id {
let failed_batch = UNBOND_BATCHES.load(deps.storage, failed_batch_id)?;
unprocessed_unbonded_amount += failed_batch.total_amount;
}
let core_balance = deps
.querier
.query_balance(env.contract.address.to_string(), config.base_denom)?
.amount;
let extra_amount = match FSM.get_current_state(deps.storage)? {
ContractState::Transfering => PENDING_TRANSFER.load(deps.storage),
ContractState::Staking => {
let (ica_balance, _) =
get_ica_balance_by_denom(deps, &config.puppeteer_contract, &config.remote_denom)?;
Ok(ica_balance)
}
_ => Ok(Uint128::zero()),
}?;
Ok(Decimal::from_ratio(
delegations_amount + core_balance + extra_amount
- current_stake.unwrap_or(Uint128::zero())
- unprocessed_unbonded_amount,
ld_total_amount,
)) // arithmetic operations order is important here as we don't want to overflow
}

fn query_unbond_batch(deps: Deps<NeutronQuery>, batch_id: Uint128) -> StdResult<Binary> {
Expand Down Expand Up @@ -205,10 +270,11 @@ fn execute_tick_idle(
FSM.go_to(deps.storage, ContractState::Claiming)?;
if validators_to_claim.is_empty() {
attrs.push(attr("validators_to_claim", "empty"));
if let Some(transfer_msg) =
transfer_pending_balance(deps.as_ref(), &env, config, info.funds.clone())?
if let Some((transfer_msg, pending_amount)) =
get_transfer_pending_balance(deps.as_ref(), &env, config, info.funds.clone())?
{
FSM.go_to(deps.storage, ContractState::Transfering)?;
PENDING_TRANSFER.save(deps.storage, &pending_amount)?;
messages.push(transfer_msg);
} else {
messages.push(get_stake_msg(deps.as_ref(), &env, config, info.funds)?);
Expand Down Expand Up @@ -271,10 +337,11 @@ fn execute_tick_claiming(
attrs.push(attr("error_on_claiming", format!("{:?}", err)));
}
}
if let Some(transfer_msg) =
transfer_pending_balance(deps.as_ref(), &env, config, info.funds.clone())?
if let Some((transfer_msg, pending_amount)) =
get_transfer_pending_balance(deps.as_ref(), &env, config, info.funds.clone())?
{
FSM.go_to(deps.storage, ContractState::Transfering)?;
PENDING_TRANSFER.save(deps.storage, &pending_amount)?;
messages.push(transfer_msg);
} else {
messages.push(get_stake_msg(deps.as_ref(), &env, config, info.funds)?);
Expand Down Expand Up @@ -472,7 +539,7 @@ fn execute_bond(
let denom = funds[0].denom.to_string();
check_denom(denom)?;

let exchange_rate = query_exchange_rate(deps.as_ref(), env)?;
let exchange_rate = query_exchange_rate(deps.as_ref(), env, Some(amount))?;
attrs.push(attr("exchange_rate", exchange_rate.to_string()));

let issue_amount = amount * (Decimal::one() / exchange_rate);
Expand Down Expand Up @@ -618,7 +685,7 @@ fn execute_unbond(
}
);
let mut unbond_batch = UNBOND_BATCHES.load(deps.storage, unbond_batch_id)?;
let exchange_rate = query_exchange_rate(deps.as_ref(), env)?;
let exchange_rate = query_exchange_rate(deps.as_ref(), env, None)?;
attrs.push(attr("exchange_rate", exchange_rate.to_string()));
let expected_amount = amount * exchange_rate;
unbond_batch.unbond_items.push(UnbondItem {
Expand Down Expand Up @@ -660,22 +727,31 @@ fn execute_unbond(
},
]),
});
let msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: config.withdrawal_voucher_contract,
msg: to_json_binary(&VoucherExecuteMsg::Mint {
owner: info.sender.to_string(),
token_id: unbond_batch_id.to_string()
+ "_"
+ info.sender.to_string().as_str()
+ "_"
+ &unbond_batch.unbond_items.len().to_string(),
token_uri: None,
extension,
})?,
funds: vec![],
});

Ok(response("execute-unbond", CONTRACT_NAME, attrs).add_message(msg))
let msgs = vec![
CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: config.withdrawal_voucher_contract,
msg: to_json_binary(&VoucherExecuteMsg::Mint {
owner: info.sender.to_string(),
token_id: unbond_batch_id.to_string()
+ "_"
+ info.sender.to_string().as_str()
+ "_"
+ &unbond_batch.unbond_items.len().to_string(),
token_uri: None,
extension,
})?,
funds: vec![],
}),
CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: config.token_contract,
msg: to_json_binary(&TokenExecuteMsg::Burn {})?,
funds: vec![cosmwasm_std::Coin {
denom: ld_denom,
amount,
}],
}),
];
Ok(response("execute-unbond", CONTRACT_NAME, attrs).add_messages(msgs))
}

fn get_unbonded_batch(deps: Deps<NeutronQuery>) -> ContractResult<Option<(u128, UnbondBatch)>> {
Expand All @@ -690,12 +766,12 @@ fn get_unbonded_batch(deps: Deps<NeutronQuery>) -> ContractResult<Option<(u128,
Ok(None)
}

fn transfer_pending_balance<T>(
fn get_transfer_pending_balance<T>(
deps: Deps<NeutronQuery>,
env: &Env,
config: &Config,
funds: Vec<cosmwasm_std::Coin>,
) -> ContractResult<Option<CosmosMsg<T>>> {
) -> ContractResult<Option<(CosmosMsg<T>, Uint128)>> {
let pending_amount = deps
.querier
.query_balance(
Expand All @@ -712,16 +788,19 @@ fn transfer_pending_balance<T>(
}];
all_funds.extend(funds);

Ok(Some(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: config.puppeteer_contract.to_string(),
msg: to_json_binary(
&lido_staking_base::msg::puppeteer::ExecuteMsg::IBCTransfer {
timeout: config.puppeteer_timeout,
reply_to: env.contract.address.to_string(),
},
)?,
funds: all_funds,
})))
Ok(Some((
CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: config.puppeteer_contract.to_string(),
msg: to_json_binary(
&lido_staking_base::msg::puppeteer::ExecuteMsg::IBCTransfer {
timeout: config.puppeteer_timeout,
reply_to: env.contract.address.to_string(),
},
)?,
funds: all_funds,
}),
pending_amount,
)))
}

fn get_stake_msg<T>(
Expand Down
12 changes: 8 additions & 4 deletions contracts/withdrawal-manager/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use cosmwasm_std::{
attr, ensure_eq, entry_point, from_json, to_json_binary, Attribute, BankMsg, Binary, Coin,
CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128,
};
use cw2::set_contract_version;
use cw721::NftInfoResponse;
Expand Down Expand Up @@ -152,10 +152,14 @@ fn execute_receive_nft_withdraw(
UnbondBatchStatus::Unbonded,
ContractError::BatchIsNotUnbonded {}
);
let payout_amount = unbond_batch
let slashing_effect = unbond_batch
.slashing_effect
.ok_or(ContractError::BatchSlashingEffectIsEmpty {})?
* voucher_extention.expected_amount;
.ok_or(ContractError::BatchSlashingEffectIsEmpty {})?;

let payout_amount = Uint128::min(
slashing_effect * voucher_extention.expected_amount,
voucher_extention.expected_amount,
); //just in case

let to_address = receiver.unwrap_or(sender);
attrs.push(attr("batch_id", batch_id.to_string()));
Expand Down
4 changes: 2 additions & 2 deletions integration_tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"test": "vitest --run",
"test:poc-stargate": "vitest --run poc-stargate --bail 1",
"test:core": "vitest --run core --bail 1",
"test:core": "vitest --run core.test.ts --bail 1",
"test:core:fsm": "vitest --run core.fsm --bail 1",
"test:pump": "vitest --run pump --bail 1",
"test:pump-multi": "vitest --run pump-multi --bail 1",
Expand All @@ -15,7 +15,7 @@
"test:validators-stats": "vitest --run validators-stats.test --bail 1",
"test:validator-set": "vitest --run validator-set.test --bail 1",
"test:distribution": "vitest --run distribution.test --bail 1",
"test:auto-withdrawer": "vitest --run auto-withdrawer.test --bail 1",
"test:auto-withdrawer": "vitest --run auto-withdrawer.test --bail 1",
"watch": "vitest",
"build-ts-client": "ts-node ./src/rebuild-client.ts",
"build-lsm-image": "./dockerfiles/lsm/build.sh",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,37 +34,11 @@ export type Addr = string;
export type Uint128 = string;
export type IcaState = "none" | "in_progress" | "registered" | "timeout";
export type ArrayOfTransfer = Transfer[];
/**
* A point in time in nanosecond precision.
*
* This type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.
*
* ## Examples
*
* ``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);
*
* let ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```
*/
export type Timestamp = Uint64;
/**
* A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.
*
* # Examples
*
* Use `from` to create instances of this and `u64` to get the value out:
*
* ``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);
*
* let b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```
*/
export type Uint64 = string;
export type ArrayOfUnbondingDelegation = UnbondingDelegation[];

export interface LidoPuppeteerAuthzSchema {
responses: Config | DelegationsResponse | State | ArrayOfTransfer | ArrayOfUnbondingDelegation;
responses: Config | DelegationsResponse | State | ArrayOfTransfer;
execute:
| RegisterDelegatorDelegationsQueryArgs
| RegisterDelegatorUnbondingDelegationsQueryArgs
| SetFeesArgs
| DelegateArgs
| UndelegateArgs
Expand Down Expand Up @@ -119,25 +93,9 @@ export interface Transfer {
recipient: string;
sender: string;
}
export interface UnbondingDelegation {
last_updated_height: number;
query_id: number;
unbonding_delegations: UnbondingEntry[];
validator_address: string;
}
export interface UnbondingEntry {
balance: Uint128;
completion_time?: Timestamp | null;
creation_height: number;
initial_balance: Uint128;
[k: string]: unknown;
}
export interface RegisterDelegatorDelegationsQueryArgs {
validators: string[];
}
export interface RegisterDelegatorUnbondingDelegationsQueryArgs {
validators: string[];
}
export interface SetFeesArgs {
ack_fee: Uint128;
recv_fee: Uint128;
Expand Down Expand Up @@ -220,9 +178,6 @@ export class Client {
queryDelegations = async(): Promise<DelegationsResponse> => {
return this.client.queryContractSmart(this.contractAddress, { delegations: {} });
}
queryUnbondingDelegations = async(): Promise<ArrayOfUnbondingDelegation> => {
return this.client.queryContractSmart(this.contractAddress, { unbonding_delegations: {} });
}
registerICA = async(sender: string, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise<ExecuteResult> => {
if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); }
return this.client.execute(sender, this.contractAddress, { register_i_c_a: {} }, fee || "auto", memo, funds);
Expand All @@ -235,10 +190,6 @@ export class Client {
if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); }
return this.client.execute(sender, this.contractAddress, { register_delegator_delegations_query: args }, fee || "auto", memo, funds);
}
registerDelegatorUnbondingDelegationsQuery = async(sender:string, args: RegisterDelegatorUnbondingDelegationsQueryArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise<ExecuteResult> => {
if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); }
return this.client.execute(sender, this.contractAddress, { register_delegator_unbonding_delegations_query: args }, fee || "auto", memo, funds);
}
setFees = async(sender:string, args: SetFeesArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise<ExecuteResult> => {
if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); }
return this.client.execute(sender, this.contractAddress, { set_fees: args }, fee || "auto", memo, funds);
Expand Down
2 changes: 1 addition & 1 deletion integration_tests/src/testcases/auto-withdrawer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ describe('Auto withdrawer', () => {
neutronUserAddress,
{ denom: neutronIBCDenom },
);
expect(parseInt(balance.data.balance.amount) - balanceBefore).toBe(2512);
expect(parseInt(balance.data.balance.amount) - balanceBefore).toBe(2000);

const bondings = await autoWithdrawerContractClient.queryBondings({
user: neutronUserAddress,
Expand Down
Loading

0 comments on commit 866e4ba

Please sign in to comment.