Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/empty-ants-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@delvtech/hyperdrive-js-core": minor
---

Add `getShortBondsGivenDeposit` method to `ReadHyperdrive`
5 changes: 5 additions & 0 deletions .changeset/khaki-cougars-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@delvtech/hyperdrive-wasm": patch
---

Add `shortBondsGivenDeposit` function
5 changes: 5 additions & 0 deletions .changeset/sixty-dogs-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@delvtech/fixed-point-wasm": patch
---

Add `absDiff` method
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/delv-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ thiserror = "1.0"
wasm-bindgen = "0.2.92"

# internal
fixedpointmath = "0.17.1"
fixedpointmath = "0.18.1"
4 changes: 2 additions & 2 deletions crates/fixed-point-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "fixed-point-wasm"
# This version will be overwritten by the build script to match the version in
# the crate's package.json which is managed by changesets.
version = "0.0.5"
version = "0.0.6"
edition = "2021"
license = "AGPL-3.0"

Expand All @@ -25,7 +25,7 @@ getrandom = { version = "0.2", features = ["js"] }
rand = "0.8.5"

# internal
fixedpointmath = "0.17.1"
fixedpointmath = "0.18.1"

# workspace
ts-macro = { path = "../ts-macro" }
Expand Down
17 changes: 17 additions & 0 deletions crates/fixed-point-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@ impl WasmFixedPoint {
})
}

/// Get the absolute difference between this number and another.
#[wasm_bindgen(skip_jsdoc, js_name = absDiff)]
pub fn abs_diff(
&self,
other: Numberish,
decimals: Option<u8>,
) -> Result<WasmFixedPoint, Error> {
Ok(WasmFixedPoint {
inner: self
.inner
.abs_diff(WasmFixedPoint::new(other, decimals)?.inner)
.change_type()
.to_result()?,
decimals: self.decimals,
})
}

/// Add a fixed-point number to this one.
#[wasm_bindgen(skip_jsdoc)]
pub fn add(&self, other: Numberish, decimals: Option<u8>) -> Result<WasmFixedPoint, Error> {
Expand Down
6 changes: 3 additions & 3 deletions crates/hyperdrive-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ wasm-bindgen = "0.2.92"
getrandom = { version = "0.2", features = ["js"] }

# internal
fixedpointmath = "0.17.1"
hyperdrive-math = "0.17.1"
hyperdrive-wrappers = "0.17.1"
fixedpointmath = "0.18.1"
hyperdrive-math = "0.18.1"
hyperdrive-wrappers = "0.18.1"

# workspace
delv-core = { path = "../delv-core" }
Expand Down
41 changes: 41 additions & 0 deletions crates/hyperdrive-wasm/src/short/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,44 @@ pub fn calcImpliedRate(params: IImpliedRateParams) -> Result<BigInt, Error> {

result_fp.to_bigint()
}

#[ts(extends = IStateParams)]
struct ShortBondsGivenDepositParams {
/// The target base deposit amount.
target_base_amount: BigInt,
/// The vault share price at the start of the checkpoint.
open_vault_share_price: BigInt,
/// The pool's absolute maximum bond amount at the time of opening.
absolute_max_bond_amount: BigInt,
/// The maximum difference between the target and actual base amount.
///
/// @default 1e9
maybe_tolerance: Option<BigInt>,
/// The maximum number of iterations to run the Newton's method for.
///
/// @default 500
maybe_max_iterations: Option<u16>,
}

/// Calculates the amount of bonds that will be shorted given a target base
/// deposit amount.
#[wasm_bindgen(skip_jsdoc)]
pub fn shortBondsGivenDeposit(params: IShortBondsGivenDepositParams) -> Result<BigInt, Error> {
params
.to_state()?
.calculate_short_bonds_given_deposit(
params.target_base_amount().to_u256()?.fixed(),
params.open_vault_share_price().to_u256()?.fixed(),
params.absolute_max_bond_amount().to_u256()?.fixed(),
match params.maybe_tolerance() {
Some(tolerance) => Some(tolerance.to_u256()?.fixed()),
None => None,
},
match params.maybe_max_iterations() {
Some(max_iterations) => Some(max_iterations as usize),
None => None,
},
)
.to_result()?
.to_bigint()
}
9 changes: 7 additions & 2 deletions packages/fixed-point-wasm/fixed_point_wasm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,10 @@ export class FixedPoint {
*/
abs(): FixedPoint;
/**
* Get the absolute difference between this number and another.
*/
absDiff(other: Numberish, decimals?: number): FixedPoint;
/**
* Add a fixed-point number to this one.
*/
add(other: Numberish, decimals?: number): FixedPoint;
Expand Down Expand Up @@ -435,12 +439,15 @@ export interface InitOutput {
readonly fixedpoint_random: (a: number, b: number) => void;
readonly fixedpoint_bigint: (a: number, b: number) => void;
readonly fixedpoint_abs: (a: number, b: number) => void;
readonly fixedpoint_absDiff: (a: number, b: number, c: number, d: number) => void;
readonly fixedpoint_add: (a: number, b: number, c: number, d: number) => void;
readonly fixedpoint_sub: (a: number, b: number, c: number, d: number) => void;
readonly fixedpoint_mulDivDown: (a: number, b: number, c: number, d: number, e: number) => void;
readonly fixedpoint_mulDivUp: (a: number, b: number, c: number, d: number, e: number) => void;
readonly fixedpoint_mulDown: (a: number, b: number, c: number, d: number) => void;
readonly fixedpoint_mulUp: (a: number, b: number, c: number, d: number) => void;
readonly fixedpoint_mul: (a: number, b: number, c: number, d: number) => void;
readonly fixedpoint_divDown: (a: number, b: number, c: number, d: number) => void;
readonly fixedpoint_divUp: (a: number, b: number, c: number, d: number) => void;
readonly fixedpoint_div: (a: number, b: number, c: number, d: number) => void;
readonly fixedpoint_pow: (a: number, b: number, c: number, d: number) => void;
Expand All @@ -459,8 +466,6 @@ export interface InitOutput {
readonly fixedpoint_toFixed: (a: number, b: number) => number;
readonly initialize: () => void;
readonly fixedpoint_valueOf: (a: number, b: number) => void;
readonly fixedpoint_mulDown: (a: number, b: number, c: number, d: number) => void;
readonly fixedpoint_divDown: (a: number, b: number, c: number, d: number) => void;
readonly getVersion: (a: number) => void;
readonly fixed: (a: number, b: number, c: number) => void;
readonly parseFixed: (a: number, b: number, c: number) => void;
Expand Down
54 changes: 36 additions & 18 deletions packages/fixed-point-wasm/fixed_point_wasm.js

Large diffs are not rendered by default.

23 changes: 12 additions & 11 deletions packages/hyperdrive-js-core/src/base/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type Prettify<T> = {
*/
export type Constructor<
TInstanceType = any,
TArgs extends any[] = any[]
TArgs extends any[] = any[],
> = new (...args: TArgs) => TInstanceType;

/**
Expand Down Expand Up @@ -85,13 +85,14 @@ type UnionToIntersection<T> = (
* // }
* ```
*/
export type MergeKeys<T> = UnionToIntersection<T> extends infer I
? {
// Each key of the intersection is first checked against the union type,
// T. If it exists in every member of T, then T[K] will be a union of
// the value types. Otherwise, I[K] is used. I[K] is the value type of
// the key in the intersection which will be `never` for keys with
// conflicting value types.
[K in keyof I]: K extends keyof T ? T[K] : I[K];
}
: never;
export type MergeKeys<T> =
UnionToIntersection<T> extends infer I
? {
// Each key of the intersection is first checked against the union type,
// T. If it exists in every member of T, then T[K] will be a union of
// the value types. Otherwise, I[K] is used. I[K] is the value type of
// the key in the intersection which will be `never` for keys with
// conflicting value types.
[K in keyof I]: K extends keyof T ? T[K] : I[K];
}
: never;
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { ALICE, BOB } from "src/base/testing/accounts";
import { CheckpointEvent } from "src/checkpoint/types";
import { parseFixed } from "src/fixed-point";
import { fixed, parseFixed } from "src/fixed-point";
import { setupReadHyperdrive } from "src/hyperdrive/base/testing/setupReadHyperdrive";
import { decodeAssetFromTransferSingleEventData } from "src/pool/decodeAssetFromTransferSingleEventData";
import {
simplePoolConfig30Days,
simplePoolConfig7Days,
} from "src/pool/testing/PoolConfig";
import { simplePoolInfo } from "src/pool/testing/PoolInfo";
import { expect, test } from "vitest";
import { assert, expect, test } from "vitest";

test("getVersion should return the parsed version of the contract", async () => {
const { contract, readHyperdrive } = setupReadHyperdrive();
Expand Down Expand Up @@ -1393,6 +1393,50 @@ test("getOpenShorts should handle when user fully closes then re-opens a positio
]);
});

test("getShortBondsGivenDeposit & previewOpenShort should align within a given tolerance", async () => {
const { contract, network, readHyperdrive } = setupReadHyperdrive();
contract.stubRead({
functionName: "getPoolConfig",
value: simplePoolConfig30Days,
});
contract.stubRead({
functionName: "getPoolInfo",
value: simplePoolInfo,
});
contract.stubRead({
functionName: "getCheckpointExposure",
value: 0n,
});
contract.stubRead({
functionName: "getCheckpoint",
value: {
vaultSharePrice: parseFixed(1.05).bigint,
weightedSpotPrice: 0n,
lastWeightedSpotPriceUpdateTime: 0n,
},
});
network.stubGetBlock({
value: {
timestamp: 123456789n,
blockNumber: 1n,
},
});

const targetDeposit = parseFixed(1.123);
const tolerance = fixed(1e9);
const amountOfBondsToShort = await readHyperdrive.getShortBondsGivenDeposit({
amountIn: targetDeposit.bigint,
asBase: true,
tolerance: tolerance.bigint,
});
const { traderDeposit } = await readHyperdrive.previewOpenShort({
amountOfBondsToShort,
asBase: true,
});

assert(targetDeposit.absDiff(traderDeposit).lte(tolerance));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

});

test("getClosedShorts should account for shorts closed to base", async () => {
// Description:
// Bob completely closes his position, redeeming 100 shorted bonds for 2 base.
Expand Down
49 changes: 49 additions & 0 deletions packages/hyperdrive-js-core/src/hyperdrive/base/ReadHyperdrive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1663,6 +1663,55 @@ export class ReadHyperdrive extends ReadModel {
};
}

/**
* Predicts the amount of bonds that can be shorted given a target deposit
* amount in either base or shares.
*/
async getShortBondsGivenDeposit({
amountIn,
asBase,
tolerance,
options,
}: {
amountIn: bigint;
asBase: boolean;
/**
* The maximum difference between the target and actual base amount.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1e9 is decent for 18 decimal numbers, but for usdc (6 decimals) we'll definitely want to set a smaller tolerance. Should we capture this in the TokenConfig, HyperdriveConfig, or just inline a bit of logic in our hook to look at the token decimals and map that to a known good tolerance?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also just make the default tolerance in ReadHyperdrive dynamic based on hyperdrive.decimals. 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I'd confirm with @dpaiton on how far this tolerance can go before it stops working, but if the tolerance a pool uses is data based on other data (e.g., the token's decimals) then I'd inline it to keep the config data more normalized.

*
* @default 1e9
*/
tolerance?: bigint;
options?: ContractReadOptions;
}): Promise<bigint> {
const poolConfig = await this.getPoolConfig(options);
const poolInfo = await this.getPoolInfo(options);
const latestCheckpoint = await this.getCheckpoint({ options });
const checkpointExposure = await this.getCheckpointExposure({ options });

let targetBaseAmount = amountIn;
if (!asBase) {
targetBaseAmount = await this.convertToBase({
sharesAmount: amountIn,
options,
});
}

const absoluteMaxBondAmount = hyperwasm.absoluteMaxShort({
poolInfo,
poolConfig,
checkpointExposure,
});

return hyperwasm.shortBondsGivenDeposit({
poolInfo,
poolConfig,
targetBaseAmount,
absoluteMaxBondAmount,
openVaultSharePrice: latestCheckpoint.vaultSharePrice,
maybeTolerance: tolerance,
});
}

/**
* Predicts the amount of base asset a user will receive when closing a long.
*/
Expand Down
Loading