Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 5 additions & 5 deletions docs/ACTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ runner works.

Cron times are UTC. Enable state is managed in the database, not here.

The mainnet `setPrices*` actions use `--amount` as the DEX swap amount when
fetching the reference price quote. This is separate from `--buy-amount` and
The mainnet `setPrices*` actions use `--amount` as an explicit override for the
DEX swap amount when fetching the reference price quote. This is separate from `--buy-amount` and
`--sell-amount`, which set the buy-side liquidity-asset and sell-side base-asset
liquidity remaining on the Ethena, USDC, and WETH ARMs. If omitted, each limit is
set to the maximum `uint128` value. Liquidity amounts are integer native token
units (for example, `100000000` is 100 tokens for an asset with 6 decimals).
When `--amount` is omitted, the DEX quote amount is the smaller of the
withdrawable ARM/market reserves and the corresponding price liquidity limit.
An explicit `--amount` is used unchanged.

`--buy-price` and `--sell-price` bypass DEX-derived pricing and set an exact
pair. Both must be supplied together; `--amount` is not used in this mode.
When the Ethena or USDC action derives `--amount` from withdrawable liquidity,
it rounds the available amount up to the minimum DEX quote size of 1,000 USDe
or 1,000 USDC respectively; an explicit `--amount` override is used as supplied.

`setPricesWETH` uses the Lido pricing profile and 1Inch for `STETH,WSTETH`, and
the EtherFi pricing profile and Kyber for `EETH,WEETH`. It processes all four
Expand Down
14 changes: 1 addition & 13 deletions src/js/tasks/actions/setPricesEthena.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { types } from "hardhat/config";
import { action } from "../lib/action";
import { setPrices } from "../armPrices";
import { setPricesForBases } from "../../utils/priceActionUtils";
import { resolveEthenaAggregatorAmount } from "../../utils/ethenaPricing";
import { mainnet } from "../../utils/addresses";
const ethenaARMAbi = require("../../../abis/EthenaARM.json");

Expand Down Expand Up @@ -118,17 +117,6 @@ action({
const arm = new ethers.Contract(mainnet.ethenaARM, ethenaARMAbi, signer);

log.info("Setting prices for Ethena ARM");
const exactPrices =
args.buyPrice !== undefined && args.sellPrice !== undefined;
let amount = args.amount;
if (!exactPrices && amount === undefined) {
amount = await resolveEthenaAggregatorAmount({
arm,
log,
blockTag: "latest",
});
}

await setPricesForBases({
setPrices,
bases: ["SUSDE"],
Expand All @@ -146,7 +134,7 @@ action({
minBuyPrice: args.minBuyPrice,
kyber: args.kyber,
inch: args.inch,
amount,
amount: args.amount,
tolerance: args.tolerance,
fee: args.fee,
offset: args.offset,
Expand Down
4 changes: 2 additions & 2 deletions src/js/tasks/actions/setPricesEtherFi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ action({
)
.addOptionalParam(
"amount",
"DEX swap amount used to fetch the reference price quote.",
20,
"Override the automatically detected DEX swap amount used to fetch the reference price quote.",
undefined,
types.float,
)
.addOptionalParam(
Expand Down
4 changes: 2 additions & 2 deletions src/js/tasks/actions/setPricesLido.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ action({
)
.addOptionalParam(
"amount",
"DEX swap amount used to fetch the reference price quote.",
20,
"Override the automatically detected DEX swap amount used to fetch the reference price quote.",
undefined,
types.float,
)
.addOptionalParam(
Expand Down
4 changes: 2 additions & 2 deletions src/js/tasks/actions/setPricesOETH.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ action({
)
.addOptionalParam(
"amount",
"DEX swap amount used to fetch the reference price quote.",
10,
"Override the automatically detected DEX swap amount used to fetch the reference price quote.",
undefined,
types.float,
)
.addOptionalParam(
Expand Down
14 changes: 1 addition & 13 deletions src/js/tasks/actions/setPricesUSDC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { types } from "hardhat/config";
import { action } from "../lib/action";
import { setPrices } from "../armPrices";
import { setPricesForBases } from "../../utils/priceActionUtils";
import { resolveUsdAggregatorAmount } from "../../utils/usdPricing";
import { mainnet } from "../../utils/addresses";
const multiAssetARMAbi = require("../../../abis/MultiAssetARM.json");

Expand Down Expand Up @@ -122,17 +121,6 @@ action({
const arm = new ethers.Contract(mainnet.usdcARM, multiAssetARMAbi, signer);

log.info("Setting prices for USDC ARM");
const exactPrices =
args.buyPrice !== undefined && args.sellPrice !== undefined;
let amount = args.amount;
if (!exactPrices && amount === undefined) {
amount = await resolveUsdAggregatorAmount({
arm,
log,
blockTag: "latest",
});
}

await setPricesForBases({
setPrices,
bases: String(args.bases).split(","),
Expand All @@ -150,7 +138,7 @@ action({
minBuyPrice: args.minBuyPrice,
kyber: args.kyber,
inch: args.inch,
amount,
amount: args.amount,
tolerance: args.tolerance,
fee: args.fee,
offset: args.offset,
Expand Down
2 changes: 1 addition & 1 deletion src/js/tasks/actions/setPricesWETH.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ action({
minBuyPrice: args.minBuyPrice ?? defaults.minBuyPrice,
kyber: aggregatorOverridden ? Boolean(args.kyber) : defaults.kyber,
inch: aggregatorOverridden ? Boolean(args.inch) : defaults.inch,
amount: args.amount ?? 20,
amount: args.amount,
tolerance: args.tolerance ?? defaults.tolerance,
fee: args.fee,
offset: args.offset,
Expand Down
41 changes: 36 additions & 5 deletions src/js/tasks/armPrices.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ const {
rangeSellPrice,
rangeBuyPrice,
} = require("../utils/pricing");
const { haveSwapCapsChanged } = require("../utils/priceUpdate");
const {
haveSwapCapsChanged,
resolveDexQuoteAmount,
} = require("../utils/priceUpdate");

const log = require("../utils/logger")("task:prices");

Expand Down Expand Up @@ -133,11 +136,38 @@ const setPrices = async (options) => {
if (curve && options.armName !== "Lido")
throw new Error(`Curve prices only available for Lido`);

let reserves;
if (options.amount === undefined || options.amount === null) {
if (baseContext.version !== "multiBase") {
throw new Error(
`--amount is required when pricing a legacy ${options.armName} ARM`,
);
}
reserves = await baseContext.arm.getReserves(baseAddress, {
blockTag: options.blockTag ?? "latest",
});
}

const dexAmount = resolveDexQuoteAmount({
amount: options.amount,
liquidityAssets: reserves?.liquidityAssets ?? reserves?.[0],
baseAssetReserve: reserves?.baseAssetReserve ?? reserves?.[1],
buyLiquidity: parseSwapCap(buyAmount),
sellLiquidity: parseSwapCap(sellAmount),
liquidityDecimals,
baseDecimals,
});
if (options.amount === undefined || options.amount === null) {
log(
`Using ${dexAmount} as the DEX quote amount based on available reserves and price liquidity`,
);
}

// 2.1 Get latest market prices if no midPrice is provided
referencePrices = inch
? // 2.1.b Otherwise, get prices from 1Inch
await get1InchPrices(
options.amount,
dexAmount,
assets,
inchFee,
1,
Expand All @@ -147,25 +177,26 @@ const setPrices = async (options) => {
: kyber
? // 2.1.c Or from Kyber if specified
await getKyberPrices(
options.amount,
dexAmount,
assets,
baseDecimals,
liquidityDecimals,
)
: // 2.1.d Or from Curve if specified
await getCurvePrices({
...options,
amount: dexAmount,
poolAddress: addresses.mainnet.CurveNgStEthPool,
});

// Adjust price down if a wrapped asset like sUSDe or wstETH
if (shouldAdjustWrapped) {
const amountIn = parseUnits(options.amount.toString(), baseDecimals);
const amountIn = parseUnits(dexAmount, baseDecimals);
// The legacy convertToAsset path returns 18 decimals while the adapter
// converts a base decimals input to liquidity decimals
const convertedAssets =
config.adapter === ZeroAddress
? await convertToAsset(baseAddress, options.amount, signer)
? await convertToAsset(baseAddress, dexAmount, signer)
: await (
await adapterContract(config.adapter, signer)
).convertToAssets(amountIn);
Expand Down
44 changes: 44 additions & 0 deletions src/js/utils/priceUpdate.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,52 @@
const { formatUnits, parseUnits } = require("ethers");

const capDexAmountBySwapLiquidity = ({
amount,
buyLiquidity,
sellLiquidity,
liquidityDecimals,
baseDecimals,
}) => {
let cappedAmount = amount.toString();

if (parseUnits(cappedAmount, liquidityDecimals) > buyLiquidity) {
cappedAmount = formatUnits(buyLiquidity, liquidityDecimals);
}
if (parseUnits(cappedAmount, baseDecimals) > sellLiquidity) {
cappedAmount = formatUnits(sellLiquidity, baseDecimals);
}

return cappedAmount;
};

const resolveDexQuoteAmount = ({
amount,
liquidityAssets,
baseAssetReserve,
buyLiquidity,
sellLiquidity,
liquidityDecimals,
baseDecimals,
}) => {
if (amount !== undefined && amount !== null) return amount.toString();

return capDexAmountBySwapLiquidity({
amount: formatUnits(liquidityAssets, liquidityDecimals),
buyLiquidity,
sellLiquidity:
baseAssetReserve < sellLiquidity ? baseAssetReserve : sellLiquidity,
liquidityDecimals,
baseDecimals,
});
};

const haveSwapCapsChanged = (baseContext, buyAmount, sellAmount) =>
baseContext.version === "multiBase" &&
(buyAmount !== baseContext.config.buyLiquidityRemaining ||
sellAmount !== baseContext.config.sellLiquidityRemaining);

module.exports = {
capDexAmountBySwapLiquidity,
haveSwapCapsChanged,
resolveDexQuoteAmount,
};
81 changes: 80 additions & 1 deletion test/js/armPrices.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
const assert = require("assert");

const { haveSwapCapsChanged } = require("../../src/js/utils/priceUpdate");
const {
capDexAmountBySwapLiquidity,
haveSwapCapsChanged,
resolveDexQuoteAmount,
} = require("../../src/js/utils/priceUpdate");

const multiBaseContext = (buyLiquidityRemaining, sellLiquidityRemaining) => ({
version: "multiBase",
Expand Down Expand Up @@ -34,4 +38,79 @@ assert.strictEqual(
"legacy ARMs do not support buy and sell amounts",
);

assert.strictEqual(
capDexAmountBySwapLiquidity({
amount: 100,
buyLiquidity: 50000000n,
sellLiquidity: (1n << 128n) - 1n,
liquidityDecimals: 6,
baseDecimals: 18,
}),
"50.0",
"buy quote amount should not exceed buy liquidity",
);

assert.strictEqual(
capDexAmountBySwapLiquidity({
amount: 100,
buyLiquidity: (1n << 128n) - 1n,
sellLiquidity: 25000000000000000000n,
liquidityDecimals: 6,
baseDecimals: 18,
}),
"25.0",
"sell quote amount should not exceed sell liquidity",
);

assert.strictEqual(
capDexAmountBySwapLiquidity({
amount: 20,
buyLiquidity: 30000000n,
sellLiquidity: 10000000000000000000n,
liquidityDecimals: 6,
baseDecimals: 18,
}),
"10.0",
"quote amount should use the lower of buy and sell liquidity",
);

assert.strictEqual(
capDexAmountBySwapLiquidity({
amount: 20,
buyLiquidity: 30000000n,
sellLiquidity: 40000000000000000000n,
liquidityDecimals: 6,
baseDecimals: 18,
}),
"20",
"quote amount within both liquidity limits should be unchanged",
);

assert.strictEqual(
resolveDexQuoteAmount({
amount: 100,
liquidityAssets: 50000000n,
baseAssetReserve: 25000000000000000000n,
buyLiquidity: 30000000n,
sellLiquidity: 10000000000000000000n,
liquidityDecimals: 6,
baseDecimals: 18,
}),
"100",
"an explicit quote amount should override reserves and price liquidity",
);

assert.strictEqual(
resolveDexQuoteAmount({
liquidityAssets: 50000000n,
baseAssetReserve: 40000000000000000000n,
buyLiquidity: 30000000n,
sellLiquidity: 20000000000000000000n,
liquidityDecimals: 6,
baseDecimals: 18,
}),
"20.0",
"an automatic quote amount should use the smallest reserve or price liquidity limit",
);

console.log("ARM price tests passed");
Loading