Skip to content

Commit b41cd6e

Browse files
Nour/derivative constituent testing (#1708)
* slot staleness checks * update aum ix to use constituent oracles * constituent test works when adjusting derivative index * constituent depeg kill switch works * works with multiple derivatives on the same parent
1 parent bd615ea commit b41cd6e

File tree

3 files changed

+261
-9
lines changed

3 files changed

+261
-9
lines changed

programs/drift/src/instructions/lp_pool.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,25 @@ pub fn handle_update_lp_pool_aum<'c: 'info, 'info>(
414414
let mut derivative_weights_sum = 0;
415415
for constituent_index in constituent_indexes {
416416
let constituent = constituent_map.get_ref(constituent_index)?;
417+
if constituent.last_oracle_price
418+
< parent_constituent
419+
.last_oracle_price
420+
.safe_mul(9)?
421+
.safe_div(10)?
422+
{
423+
msg!(
424+
"Constituent {} last oracle price {} is too low compared to parent constituent {} last oracle price {}. Assuming depegging and setting target base to 0.",
425+
constituent.constituent_index,
426+
constituent.last_oracle_price,
427+
parent_constituent.constituent_index,
428+
parent_constituent.last_oracle_price
429+
);
430+
constituent_target_base
431+
.get_mut(*constituent_index as u32)
432+
.target_base = 0_i64;
433+
continue;
434+
}
435+
417436
derivative_weights_sum += constituent.derivative_weight;
418437

419438
let target_weight = target_parent_weight

tests/lpPool.ts

Lines changed: 240 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
overWriteMintAccount,
5656
overWritePerpMarket,
5757
overWriteSpotMarket,
58+
setFeedPriceNoProgram,
5859
} from './testHelpers';
5960
import { startAnchor } from 'solana-bankrun';
6061
import { TestBulkAccountLoader } from '../sdk/src/accounts/testBulkAccountLoader';
@@ -88,6 +89,7 @@ describe('LP Pool', () => {
8889
let usdcMint: Keypair;
8990
let spotTokenMint: Keypair;
9091
let spotMarketOracle: PublicKey;
92+
let spotMarketOracle2: PublicKey;
9193

9294
const mantissaSqrtScale = new BN(Math.sqrt(PRICE_PRECISION.toNumber()));
9395
const ammInitialQuoteAssetReserve = new anchor.BN(100 * 10 ** 13).mul(
@@ -137,6 +139,7 @@ describe('LP Pool', () => {
137139
usdcMint = await mockUSDCMint(bankrunContextWrapper);
138140
spotTokenMint = await mockUSDCMint(bankrunContextWrapper);
139141
spotMarketOracle = await mockOracleNoProgram(bankrunContextWrapper, 200);
142+
spotMarketOracle2 = await mockOracleNoProgram(bankrunContextWrapper, 200);
140143

141144
const keypair = new Keypair();
142145
await bankrunContextWrapper.fundKeypair(keypair, 10 ** 9);
@@ -243,7 +246,21 @@ describe('LP Pool', () => {
243246
optimalUtilization,
244247
optimalRate,
245248
maxRate,
246-
spotMarketOracle,
249+
spotMarketOracle2,
250+
OracleSource.PYTH,
251+
initialAssetWeight,
252+
maintenanceAssetWeight,
253+
initialLiabilityWeight,
254+
maintenanceLiabilityWeight,
255+
imfFactor
256+
);
257+
258+
await adminClient.initializeSpotMarket(
259+
spotTokenMint.publicKey,
260+
optimalUtilization,
261+
optimalRate,
262+
maxRate,
263+
spotMarketOracle2,
247264
OracleSource.PYTH,
248265
initialAssetWeight,
249266
maintenanceAssetWeight,
@@ -671,7 +688,7 @@ describe('LP Pool', () => {
671688
);
672689
});
673690

674-
it('can add constituent to LP Pool thats a derivative and get half of the target weight', async () => {
691+
it('can add constituent to LP Pool thats a derivative and behave correctly', async () => {
675692
const lpPool = (await adminClient.program.account.lpPool.fetch(
676693
lpPoolKey
677694
)) as LPPoolAccount;
@@ -723,7 +740,7 @@ describe('LP Pool', () => {
723740
program.programId,
724741
lpPoolKey
725742
);
726-
const constituentTargetBase =
743+
let constituentTargetBase =
727744
(await adminClient.program.account.constituentTargetBase.fetch(
728745
constituentTargetBasePublicKey
729746
)) as ConstituentTargetBase;
@@ -733,11 +750,88 @@ describe('LP Pool', () => {
733750
'constituentTargetBase.targets',
734751
constituentTargetBase.targets.map((x) => x.targetBase.toString())
735752
);
736-
assert(
737-
constituentTargetBase.targets[1].targetBase
738-
.sub(constituentTargetBase.targets[2].targetBase)
739-
.lt(constituentTargetBase.targets[1].targetBase.divn(1000))
753+
expect(
754+
constituentTargetBase.targets[1].targetBase.toNumber()
755+
).to.be.approximately(
756+
constituentTargetBase.targets[2].targetBase.toNumber(),
757+
10
758+
);
759+
760+
// Move the oracle price to be double, so it should have half of the target base
761+
const derivativeBalanceBefore = constituentTargetBase.targets[2].targetBase;
762+
const derivative = (await adminClient.program.account.constituent.fetch(
763+
getConstituentPublicKey(program.programId, lpPoolKey, 2)
764+
)) as ConstituentAccount;
765+
await setFeedPriceNoProgram(bankrunContextWrapper, 400, spotMarketOracle2);
766+
await adminClient.updateConstituentOracleInfo(derivative);
767+
const tx2 = new Transaction();
768+
tx2
769+
.add(await adminClient.getUpdateAmmCacheIx([0, 1, 2]))
770+
.add(
771+
await adminClient.getUpdateLpConstituentTargetBaseIx(
772+
encodeName(lpPoolName),
773+
[
774+
getConstituentPublicKey(program.programId, lpPoolKey, 0),
775+
getConstituentPublicKey(program.programId, lpPoolKey, 1),
776+
getConstituentPublicKey(program.programId, lpPoolKey, 2),
777+
]
778+
)
779+
);
780+
await adminClient.sendTransaction(tx2);
781+
await adminClient.updateLpPoolAum(lpPool, [0, 1, 2]);
782+
783+
constituentTargetBase =
784+
(await adminClient.program.account.constituentTargetBase.fetch(
785+
constituentTargetBasePublicKey
786+
)) as ConstituentTargetBase;
787+
const derivativeBalanceAfter = constituentTargetBase.targets[2].targetBase;
788+
789+
console.log(
790+
'constituentTargetBase.targets',
791+
constituentTargetBase.targets.map((x) => x.targetBase.toString())
792+
);
793+
794+
expect(derivativeBalanceAfter.toNumber()).to.be.approximately(
795+
derivativeBalanceBefore.toNumber() / 2,
796+
20
740797
);
798+
799+
// Move the oracle price to be half, so its target base should go to zero
800+
const parentBalanceBefore = constituentTargetBase.targets[1].targetBase;
801+
await setFeedPriceNoProgram(bankrunContextWrapper, 100, spotMarketOracle2);
802+
await adminClient.updateConstituentOracleInfo(derivative);
803+
const tx3 = new Transaction();
804+
tx3
805+
.add(await adminClient.getUpdateAmmCacheIx([0, 1, 2]))
806+
.add(
807+
await adminClient.getUpdateLpConstituentTargetBaseIx(
808+
encodeName(lpPoolName),
809+
[
810+
getConstituentPublicKey(program.programId, lpPoolKey, 0),
811+
getConstituentPublicKey(program.programId, lpPoolKey, 1),
812+
getConstituentPublicKey(program.programId, lpPoolKey, 2),
813+
]
814+
)
815+
);
816+
await adminClient.sendTransaction(tx3);
817+
await adminClient.updateLpPoolAum(lpPool, [0, 1, 2]);
818+
819+
constituentTargetBase =
820+
(await adminClient.program.account.constituentTargetBase.fetch(
821+
constituentTargetBasePublicKey
822+
)) as ConstituentTargetBase;
823+
const parentBalanceAfter = constituentTargetBase.targets[1].targetBase;
824+
825+
console.log(
826+
'constituentTargetBase.targets',
827+
constituentTargetBase.targets.map((x) => x.targetBase.toString())
828+
);
829+
expect(parentBalanceAfter.toNumber()).to.be.approximately(
830+
parentBalanceBefore.toNumber() * 2,
831+
10
832+
);
833+
await setFeedPriceNoProgram(bankrunContextWrapper, 200, spotMarketOracle2);
834+
await adminClient.updateConstituentOracleInfo(derivative);
741835
});
742836

743837
it('can settle pnl from perp markets into the usdc account', async () => {
@@ -1015,7 +1109,6 @@ describe('LP Pool', () => {
10151109
BigInt(lpPool.lastAum.toNumber())
10161110
);
10171111

1018-
console.log('here!');
10191112
const tx = new Transaction();
10201113
tx.add(await adminClient.getUpdateLpPoolAumIxs(lpPool, [0, 1, 2]));
10211114
tx.add(
@@ -1103,4 +1196,143 @@ describe('LP Pool', () => {
11031196
assert(constituent.tokenBalance.eq(balanceBefore.add(owedAmount)));
11041197
assert(lpPool.lastAum.eq(aumBefore.add(owedAmount)));
11051198
});
1199+
1200+
it('can work with multiple derivatives on the same parent', async () => {
1201+
const lpPool = (await adminClient.program.account.lpPool.fetch(
1202+
lpPoolKey
1203+
)) as LPPoolAccount;
1204+
1205+
await adminClient.initializeConstituent(lpPool.name, {
1206+
spotMarketIndex: 3,
1207+
decimals: 6,
1208+
maxWeightDeviation: new BN(10).mul(PERCENTAGE_PRECISION),
1209+
swapFeeMin: new BN(1).mul(PERCENTAGE_PRECISION),
1210+
swapFeeMax: new BN(2).mul(PERCENTAGE_PRECISION),
1211+
oracleStalenessThreshold: new BN(400),
1212+
costToTrade: 1,
1213+
derivativeWeight: PERCENTAGE_PRECISION.divn(4),
1214+
volatility: new BN(10).mul(PERCENTAGE_PRECISION),
1215+
constituentCorrelations: [
1216+
ZERO,
1217+
PERCENTAGE_PRECISION.muln(87).divn(100),
1218+
PERCENTAGE_PRECISION,
1219+
],
1220+
constituentDerivativeIndex: 1,
1221+
});
1222+
1223+
await adminClient.updateConstituentParams(
1224+
lpPool.name,
1225+
getConstituentPublicKey(program.programId, lpPoolKey, 2),
1226+
{
1227+
derivativeWeight: PERCENTAGE_PRECISION.divn(4),
1228+
}
1229+
);
1230+
1231+
await adminClient.updateAmmCache([0, 1, 2]);
1232+
1233+
let constituent = (await adminClient.program.account.constituent.fetch(
1234+
getConstituentPublicKey(program.programId, lpPoolKey, 3)
1235+
)) as ConstituentAccount;
1236+
1237+
await adminClient.updateConstituentOracleInfo(constituent);
1238+
1239+
constituent = (await adminClient.program.account.constituent.fetch(
1240+
getConstituentPublicKey(program.programId, lpPoolKey, 3)
1241+
)) as ConstituentAccount;
1242+
assert(!constituent.lastOraclePrice.eq(ZERO));
1243+
await adminClient.updateLpPoolAum(lpPool, [0, 1, 2, 3]);
1244+
1245+
const tx = new Transaction();
1246+
tx.add(await adminClient.getUpdateAmmCacheIx([0, 1, 2])).add(
1247+
await adminClient.getUpdateLpConstituentTargetBaseIx(
1248+
encodeName(lpPoolName),
1249+
[
1250+
getConstituentPublicKey(program.programId, lpPoolKey, 0),
1251+
getConstituentPublicKey(program.programId, lpPoolKey, 1),
1252+
getConstituentPublicKey(program.programId, lpPoolKey, 2),
1253+
getConstituentPublicKey(program.programId, lpPoolKey, 3),
1254+
]
1255+
)
1256+
);
1257+
await adminClient.sendTransaction(tx);
1258+
await adminClient.updateLpPoolAum(lpPool, [0, 1, 2, 3]);
1259+
1260+
const constituentTargetBasePublicKey = getConstituentTargetBasePublicKey(
1261+
program.programId,
1262+
lpPoolKey
1263+
);
1264+
let constituentTargetBase =
1265+
(await adminClient.program.account.constituentTargetBase.fetch(
1266+
constituentTargetBasePublicKey
1267+
)) as ConstituentTargetBase;
1268+
1269+
expect(constituentTargetBase).to.not.be.null;
1270+
console.log(
1271+
'constituentTargetBase.targets',
1272+
constituentTargetBase.targets.map((x) => x.targetBase.toString())
1273+
);
1274+
expect(
1275+
constituentTargetBase.targets[2].targetBase.toNumber()
1276+
).to.be.approximately(
1277+
constituentTargetBase.targets[3].targetBase.toNumber(),
1278+
10
1279+
);
1280+
expect(
1281+
constituentTargetBase.targets[3].targetBase.toNumber()
1282+
).to.be.approximately(
1283+
constituentTargetBase.targets[1].targetBase.toNumber() / 2,
1284+
10
1285+
);
1286+
1287+
// Set the derivative weights to 0
1288+
await adminClient.updateConstituentParams(
1289+
lpPool.name,
1290+
getConstituentPublicKey(program.programId, lpPoolKey, 2),
1291+
{
1292+
derivativeWeight: ZERO,
1293+
}
1294+
);
1295+
1296+
await adminClient.updateConstituentParams(
1297+
lpPool.name,
1298+
getConstituentPublicKey(program.programId, lpPoolKey, 3),
1299+
{
1300+
derivativeWeight: ZERO,
1301+
}
1302+
);
1303+
1304+
const parentTargetBaseBefore = constituentTargetBase.targets[1].targetBase;
1305+
const tx2 = new Transaction();
1306+
tx2
1307+
.add(await adminClient.getUpdateAmmCacheIx([0, 1, 2]))
1308+
.add(
1309+
await adminClient.getUpdateLpConstituentTargetBaseIx(
1310+
encodeName(lpPoolName),
1311+
[
1312+
getConstituentPublicKey(program.programId, lpPoolKey, 0),
1313+
getConstituentPublicKey(program.programId, lpPoolKey, 1),
1314+
getConstituentPublicKey(program.programId, lpPoolKey, 2),
1315+
getConstituentPublicKey(program.programId, lpPoolKey, 3),
1316+
]
1317+
)
1318+
);
1319+
await adminClient.sendTransaction(tx2);
1320+
await adminClient.updateLpPoolAum(lpPool, [0, 1, 2, 3]);
1321+
1322+
constituentTargetBase =
1323+
(await adminClient.program.account.constituentTargetBase.fetch(
1324+
constituentTargetBasePublicKey
1325+
)) as ConstituentTargetBase;
1326+
console.log(
1327+
'constituentTargetBase.targets',
1328+
constituentTargetBase.targets.map((x) => x.targetBase.toString())
1329+
);
1330+
1331+
const parentTargetBaseAfter = constituentTargetBase.targets[1].targetBase;
1332+
1333+
expect(parentTargetBaseAfter.toNumber()).to.be.approximately(
1334+
parentTargetBaseBefore.toNumber() * 2,
1335+
10
1336+
);
1337+
});
11061338
});

tests/testHelpers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
OracleInfo,
4242
PerpMarketAccount,
4343
ConstituentAccount,
44+
SpotMarketAccount,
4445
} from '../sdk';
4546
import {
4647
TestClient,
@@ -1207,7 +1208,7 @@ export async function overWriteSpotMarket(
12071208
driftClient: TestClient,
12081209
bankrunContextWrapper: BankrunContextWrapper,
12091210
spotMarketKey: PublicKey,
1210-
spotMarket: PerpMarketAccount
1211+
spotMarket: SpotMarketAccount
12111212
) {
12121213
bankrunContextWrapper.context.setAccount(spotMarketKey, {
12131214
executable: false,

0 commit comments

Comments
 (0)