@dhedge/v2-sdk 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,241 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+
3
+ import BigNumber from "bignumber.js";
4
+ import { BigNumber as EthersBigNumber, Contract, providers } from "ethers";
5
+ import { Dhedge, Pool } from "..";
6
+ import { Dapp, Network } from "../types";
7
+ import { CONTRACT_ADDRESS, MAX_AMOUNT } from "./constants";
8
+ import {
9
+ TestingRunParams,
10
+ runWithImpersonateAccount,
11
+ setTokenAmount,
12
+ setUSDCAmount,
13
+ testingHelper
14
+ } from "./utils/testingHelper";
15
+ import { balanceDelta } from "./utils/token";
16
+ import { routerAddress } from "../config";
17
+
18
+ const officeAbi = [
19
+ "function getMarketConfig(uint88 market) view returns (address marketConfig)"
20
+ ];
21
+ const marketConfigAbi = ["function hooks() view returns (address hooks)"];
22
+ const borrowerWhitelistAbi = [
23
+ "function owner() view returns (address)",
24
+ "function setAddressWhitelist(address accountOwner, bool allowed)"
25
+ ];
26
+ const erc20AllowanceAbi = [
27
+ "function allowance(address owner, address spender) view returns (uint256)"
28
+ ];
29
+
30
+ const DYTM_MARKET_ID = 1;
31
+ // ReserveKey: (marketId uint88 << 160) | asset address — DYTM src/types/ReserveKey.sol
32
+ const toReserveKey = (marketId: number, asset: string): EthersBigNumber =>
33
+ EthersBigNumber.from(marketId)
34
+ .shl(160)
35
+ .or(asset);
36
+ // full tokenId: (TokenType << 248) | reserveKey — DYTM src/libraries/TokenHelpers.sol
37
+ // TokenType: ESCROW = 1, LEND = 2, DEBT = 3
38
+ const toLentId = (key: EthersBigNumber): EthersBigNumber =>
39
+ EthersBigNumber.from(2)
40
+ .shl(248)
41
+ .or(key);
42
+ const toEscrowId = (key: EthersBigNumber): EthersBigNumber =>
43
+ EthersBigNumber.from(1)
44
+ .shl(248)
45
+ .or(key);
46
+
47
+ // SPYon (Ondo) collateral on mainnet market 1
48
+ const SPYON_MAINNET = "0xFeDC5f4a6c38211c1338aa411018DFAf26612c08";
49
+ const SPYON_BALANCEOF_SLOT = 51; // OZ ERC20Upgradeable layout
50
+
51
+ // market 1 borrows are gated by the BorrowerWhitelist hook — impersonate its
52
+ // owner and whitelist the pool
53
+ const whitelistPoolAsBorrower = async ({
54
+ pool,
55
+ network,
56
+ provider
57
+ }: {
58
+ pool: Pool;
59
+ network: Network;
60
+ provider: providers.JsonRpcProvider;
61
+ }): Promise<void> => {
62
+ const office = new Contract(
63
+ routerAddress[network][Dapp.DYTM]!,
64
+ officeAbi,
65
+ provider
66
+ );
67
+ const marketConfigAddress = await office.getMarketConfig(DYTM_MARKET_ID);
68
+ const hookAddress = await new Contract(
69
+ marketConfigAddress,
70
+ marketConfigAbi,
71
+ provider
72
+ ).hooks();
73
+ const hook = new Contract(hookAddress, borrowerWhitelistAbi, provider);
74
+ const hookOwner = await hook.owner();
75
+ await runWithImpersonateAccount(
76
+ { account: hookOwner, provider },
77
+ async ({ signer }) => {
78
+ await hook.connect(signer).setAddressWhitelist(pool.address, true);
79
+ }
80
+ );
81
+ };
82
+
83
+ const testDytm = ({ network, wallet, provider }: TestingRunParams) => {
84
+ const USDC = CONTRACT_ADDRESS[network].USDC;
85
+ const usdcKey = toReserveKey(DYTM_MARKET_ID, USDC);
86
+ // supply/withdraw take the full LEND tokenId; borrow/repay take the bare ReserveKey
87
+ const USDC_DEPOSIT_TOKEN = toLentId(usdcKey).toString();
88
+ const USDC_BORROW_TOKEN = usdcKey.toString();
89
+
90
+ const USDC_FUNDING_AMOUNT = new BigNumber(100).shiftedBy(6).toFixed(0);
91
+ const USDC_LIQUIDITY_AMOUNT = new BigNumber(50).shiftedBy(6).toFixed(0);
92
+
93
+ // the asset backing the borrow differs per network: on arbitrum lent USDC
94
+ // counts as collateral, on mainnet USDC has lend weight 0 and SPYon escrow
95
+ // collateral (weight 0.75) is required instead
96
+ const collateral =
97
+ network === Network.ETHEREUM
98
+ ? {
99
+ asset: SPYON_MAINNET,
100
+ tokenId: toEscrowId(
101
+ toReserveKey(DYTM_MARKET_ID, SPYON_MAINNET)
102
+ ).toString(),
103
+ supplyAmount: new BigNumber(1).shiftedBy(18).toFixed(0),
104
+ // SPYon isn't covered by the USDC funding — deal it via its balanceOf slot
105
+ funding: {
106
+ slot: SPYON_BALANCEOF_SLOT,
107
+ amount: new BigNumber(10).shiftedBy(18).toFixed(0)
108
+ }
109
+ }
110
+ : {
111
+ asset: USDC,
112
+ tokenId: USDC_DEPOSIT_TOKEN,
113
+ supplyAmount: USDC_LIQUIDITY_AMOUNT,
114
+ funding: null // covered by the USDC funded in beforeAll
115
+ };
116
+
117
+ let dhedge: Dhedge;
118
+ let pool: Pool;
119
+ jest.setTimeout(100000);
120
+
121
+ describe(`[${network}] DYTM tests`, () => {
122
+ beforeAll(async () => {
123
+ // top up ETH (gas)
124
+ await provider.send("hardhat_setBalance", [
125
+ wallet.address,
126
+ "0x100000000000000"
127
+ ]);
128
+ // mine a local block so eth_calls run under hardhat's own hardfork —
129
+ // the remote fork-point block executes as london (hardforkHistory) and
130
+ // lacks the cancun opcodes DYTM needs (transient storage)
131
+ await provider.send("evm_mine", []);
132
+ dhedge = new Dhedge(wallet, network);
133
+ pool = await dhedge.loadPool(wallet.address, false);
134
+
135
+ await whitelistPoolAsBorrower({ pool, network, provider });
136
+
137
+ // fund USDC and supply half of it as market liquidity to be borrowed
138
+ await setUSDCAmount({
139
+ amount: USDC_FUNDING_AMOUNT,
140
+ userAddress: pool.address,
141
+ network,
142
+ provider
143
+ });
144
+ await pool.approve(Dapp.DYTM, USDC, MAX_AMOUNT);
145
+ await pool.lend(Dapp.DYTM, USDC_DEPOSIT_TOKEN, USDC_LIQUIDITY_AMOUNT);
146
+
147
+ // fund the collateral asset for the supply tests
148
+ if (collateral.funding) {
149
+ await setTokenAmount({
150
+ amount: collateral.funding.amount,
151
+ userAddress: pool.address,
152
+ tokenAddress: collateral.asset,
153
+ slot: collateral.funding.slot,
154
+ provider
155
+ });
156
+ }
157
+ });
158
+
159
+ it("approves unlimited collateral for DYTM", async () => {
160
+ await pool.approve(Dapp.DYTM, collateral.asset, MAX_AMOUNT);
161
+ const allowance = await new Contract(
162
+ collateral.asset,
163
+ erc20AllowanceAbi,
164
+ provider
165
+ ).allowance(pool.address, routerAddress[network][Dapp.DYTM]!);
166
+ expect(allowance.eq(MAX_AMOUNT)).toBe(true);
167
+ });
168
+
169
+ it("estimates supplying collateral to DYTM market", async () => {
170
+ const result = await pool.lend(
171
+ Dapp.DYTM,
172
+ collateral.tokenId,
173
+ collateral.supplyAmount,
174
+ 0,
175
+ null,
176
+ true
177
+ );
178
+ expect(result.gas.gt(0)).toBe(true);
179
+ });
180
+
181
+ it("supplies collateral to DYTM market", async () => {
182
+ await pool.lend(Dapp.DYTM, collateral.tokenId, collateral.supplyAmount);
183
+
184
+ const collateralDelta = await balanceDelta(
185
+ pool.address,
186
+ collateral.asset,
187
+ pool.signer
188
+ );
189
+ expect(collateralDelta.lt(0)).toBe(true);
190
+ });
191
+
192
+ it("borrows USDC from DYTM market", async () => {
193
+ // borrow value must clear the market's minDebtAmountUSD ($1 on arbitrum);
194
+ // borrow 3 USDC so the ~2 USDC debt left after the repay test stays above it
195
+ await pool.borrow(Dapp.DYTM, USDC_BORROW_TOKEN, (3 * 1e6).toString());
196
+
197
+ const usdcTokenDelta = await balanceDelta(
198
+ pool.address,
199
+ USDC,
200
+ pool.signer
201
+ );
202
+ expect(usdcTokenDelta.gt(0)).toBe(true);
203
+ });
204
+
205
+ it("repays USDC to DYTM market", async () => {
206
+ await pool.repay(Dapp.DYTM, USDC_BORROW_TOKEN, (1 * 1e6).toString());
207
+
208
+ const usdcTokenDelta = await balanceDelta(
209
+ pool.address,
210
+ USDC,
211
+ pool.signer
212
+ );
213
+ expect(usdcTokenDelta.lt(0)).toBe(true);
214
+ });
215
+
216
+ it("withdraws USDC from DYTM market", async () => {
217
+ await pool.withdrawDeposit(
218
+ Dapp.DYTM,
219
+ USDC_DEPOSIT_TOKEN,
220
+ (5 * 1e6).toString()
221
+ );
222
+
223
+ const usdcTokenDelta = await balanceDelta(
224
+ pool.address,
225
+ USDC,
226
+ pool.signer
227
+ );
228
+ expect(usdcTokenDelta.gt(0)).toBe(true);
229
+ });
230
+ });
231
+ };
232
+
233
+ testingHelper({
234
+ network: Network.ARBITRUM,
235
+ testingRun: testDytm
236
+ });
237
+
238
+ testingHelper({
239
+ network: Network.ETHEREUM,
240
+ testingRun: testDytm
241
+ });
package/src/types.ts CHANGED
@@ -36,6 +36,7 @@ export enum Dapp {
36
36
  ODOS = "odos",
37
37
  PENDLE = "pendle",
38
38
  KYBERSWAP = "kyberswap",
39
+ DYTM = "dytm",
39
40
  HYPERLIQUID = "hyperliquid",
40
41
  COWSWAP = "cowswap",
41
42
  ONDO = "ondo"