@dhedge/v2-sdk 1.9.8 → 1.9.9

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.
package/src/config.ts CHANGED
@@ -138,3 +138,22 @@ export const UNISWAPV3_QUOTER_ADDRESS =
138
138
 
139
139
  export const SYNTHETIX_TRACKING_CODE =
140
140
  "0x4448454447450000000000000000000000000000000000000000000000000000";
141
+
142
+ export const flatMoneyContractAddresses: Readonly<Partial<
143
+ Record<
144
+ Network,
145
+ {
146
+ DelayedOrder: string;
147
+ FlatcoinVault: string;
148
+ StableModule: string;
149
+ RETH: string;
150
+ }
151
+ >
152
+ >> = {
153
+ [Network.BASE]: {
154
+ DelayedOrder: "0x6D857e9D24a7566bB72a3FB0847A3E0e4E1c2879",
155
+ FlatcoinVault: "0x95Fa1ddc9a78273f795e67AbE8f1Cd2Cd39831fF",
156
+ StableModule: "0xb95fB324b8A2fAF8ec4f76e3dF46C718402736e2",
157
+ RETH: "0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c"
158
+ }
159
+ };
@@ -64,6 +64,11 @@ import {
64
64
  getExitVestTxData
65
65
  } from "../services/ramses/vesting";
66
66
  import { getPoolTxOrGasEstimate } from "../utils/contract";
67
+ import {
68
+ cancelOrderViaFlatMoney,
69
+ mintUnitViaFlatMoney,
70
+ redeemUnitViaFlatMoney
71
+ } from "../services/flatmoney/stableLp";
67
72
 
68
73
  export class Pool {
69
74
  public readonly poolLogic: Contract;
@@ -1629,4 +1634,66 @@ export class Pool {
1629
1634
  );
1630
1635
  return tx;
1631
1636
  }
1637
+
1638
+ /** deposit rETH to mint UNIT via the Flat Money protocol
1639
+ *
1640
+ * @param { BigNumber | string } depositAmount Amount of rETH to deposit
1641
+ * @param { number } slippage slippage, 0.5 represents 0.5%
1642
+ * @param { number | null } maxKeeperFeeInUsd 5 represents $5; null will skip the maxKeeperFee check
1643
+ * @param {any} options Transaction options
1644
+ * @param {boolean} estimateGas Simulate/estimate gas
1645
+ * @returns {Promise<any>} Transaction
1646
+ */
1647
+ async mintUnitViaFlatMoney(
1648
+ depositAmount: ethers.BigNumber | string,
1649
+ slippage = 0.5,
1650
+ maxKeeperFeeInUsd: number | null,
1651
+ options: any = null,
1652
+ estimateGas = false
1653
+ ): Promise<any> {
1654
+ const tx = await mintUnitViaFlatMoney(
1655
+ this,
1656
+ depositAmount,
1657
+ slippage,
1658
+ maxKeeperFeeInUsd,
1659
+ options,
1660
+ estimateGas
1661
+ );
1662
+ return tx;
1663
+ }
1664
+
1665
+ /** redeem UNIT via the Flat Money protocol
1666
+ *
1667
+ * @param { BigNumber | string } depositAmount Amount of UNIT to withdraw
1668
+ * @param { number } slippage slippage, 0.5 represents 0.5%
1669
+ * @param { number | null } maxKeeperFeeInUsd 5 represents $5; null will skip the maxKeeperFee check
1670
+ * @param {any} options Transaction options
1671
+ * @param {boolean} estimateGas Simulate/estimate gas
1672
+ * @returns {Promise<any>} Transaction
1673
+ */
1674
+ async redeemUnitViaFlatMoney(
1675
+ withdrawAmount: ethers.BigNumber | string,
1676
+ slippage = 0.5,
1677
+ maxKeeperFeeInUsd: number | null,
1678
+ options: any = null,
1679
+ estimateGas = false
1680
+ ): Promise<any> {
1681
+ const tx = await redeemUnitViaFlatMoney(
1682
+ this,
1683
+ withdrawAmount,
1684
+ slippage,
1685
+ maxKeeperFeeInUsd,
1686
+ options,
1687
+ estimateGas
1688
+ );
1689
+ return tx;
1690
+ }
1691
+
1692
+ async cancelOrderViaFlatMoney(
1693
+ options: any = null,
1694
+ estimateGas = false
1695
+ ): Promise<any> {
1696
+ const tx = await cancelOrderViaFlatMoney(this, options, estimateGas);
1697
+ return tx;
1698
+ }
1632
1699
  }
@@ -0,0 +1,84 @@
1
+ import { Contract, ethers } from "ethers";
2
+ import { Pool } from "../../entities";
3
+ import IFlatcoinVaultAbi from "../../abi/flatmoney/IFlatcoinVault.json";
4
+ import KeeperFeeAbi from "../../abi/flatmoney/KeeperFee.json";
5
+ import { flatMoneyContractAddresses } from "../../config";
6
+ import BigNumber from "bignumber.js";
7
+
8
+ export const getKeeperFeeContract = async (pool: Pool): Promise<Contract> => {
9
+ const flatMoneyContracts = flatMoneyContractAddresses[pool.network];
10
+ if (!flatMoneyContracts) {
11
+ throw new Error(
12
+ `getStableModuleContract: network of ${pool.network} not supported`
13
+ );
14
+ }
15
+ const flatcoinVaultContract = new Contract(
16
+ flatMoneyContracts.FlatcoinVault,
17
+ IFlatcoinVaultAbi,
18
+ pool.signer
19
+ );
20
+ const key = ethers.utils.formatBytes32String("keeperFee");
21
+ const keeperFeeContractAddress: string = await flatcoinVaultContract.callStatic.moduleAddress(
22
+ key
23
+ );
24
+
25
+ const keeperFeeContract = new ethers.Contract(
26
+ keeperFeeContractAddress,
27
+ KeeperFeeAbi,
28
+ pool.signer
29
+ );
30
+ return keeperFeeContract;
31
+ };
32
+
33
+ export const getKeeperFee = async (
34
+ pool: Pool,
35
+ maxKeeperFeeInUsd: number | null
36
+ ): Promise<ethers.BigNumber> => {
37
+ const keeperFeeContract = await getKeeperFeeContract(pool);
38
+ const gasPrice = await pool.signer.provider.getGasPrice();
39
+
40
+ let keeperfee: ethers.BigNumber;
41
+ if (gasPrice) {
42
+ keeperfee = await keeperFeeContract["getKeeperFee(uint256)"](
43
+ new BigNumber(gasPrice.toString()).times(1.2).toFixed(0)
44
+ );
45
+ } else {
46
+ keeperfee = await keeperFeeContract["getKeeperFee()"]();
47
+ }
48
+
49
+ const keeperFeeInUsd = await getKeeperFeeInUsd(pool, keeperfee);
50
+
51
+ if (
52
+ Number.isFinite(maxKeeperFeeInUsd) &&
53
+ keeperFeeInUsd.gt(ethers.BigNumber.from(maxKeeperFeeInUsd).toString())
54
+ ) {
55
+ throw new Error("mintUnitViaFlatMoney: keeperFee too large");
56
+ }
57
+
58
+ return keeperfee;
59
+ };
60
+
61
+ export const getKeeperFeeInUsd = async (
62
+ pool: Pool,
63
+ keeperFee: ethers.BigNumber
64
+ ): Promise<BigNumber> => {
65
+ const flatMoneyContracts = flatMoneyContractAddresses[pool.network];
66
+ if (!flatMoneyContracts) {
67
+ throw new Error(
68
+ `getKeeperFeeInUsd: network of ${pool.network} not supported`
69
+ );
70
+ }
71
+ const fundComposition = await pool.getComposition();
72
+ const filteredFc = fundComposition.filter(
73
+ fc =>
74
+ fc.asset.toLocaleLowerCase() ===
75
+ flatMoneyContracts.RETH.toLocaleLowerCase()
76
+ );
77
+
78
+ if (!filteredFc[0])
79
+ throw new Error(`getKeeperFeeInUsd: required asset not enabled yet`);
80
+
81
+ const rateD1 = new BigNumber(filteredFc[0].rate.toString()).div(1e18);
82
+
83
+ return rateD1.times(keeperFee.toString()).div(1e18);
84
+ };
@@ -0,0 +1,135 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import BigNumber from "bignumber.js";
4
+ import { Pool, ethers } from "../..";
5
+ import DelayedOrderAbi from "../../abi/flatmoney/DelayedOrder.json";
6
+ import { flatMoneyContractAddresses } from "../../config";
7
+ import { getPoolTxOrGasEstimate } from "../../utils/contract";
8
+ import { getStableDepositQuote, getStableWithdrawQuote } from "./stableModule";
9
+ import { getKeeperFee } from "./keeperFee";
10
+
11
+ export function getAnnounceStableDepositTxData(
12
+ depositAmount: ethers.BigNumber | string,
13
+ minAmountOut: ethers.BigNumber | string,
14
+ keeperFee: ethers.BigNumber | string
15
+ ): string {
16
+ return new ethers.utils.Interface(
17
+ DelayedOrderAbi
18
+ ).encodeFunctionData("announceStableDeposit", [
19
+ depositAmount,
20
+ minAmountOut,
21
+ keeperFee
22
+ ]);
23
+ }
24
+
25
+ export function getAnnounceStableWithdrawTxData(
26
+ withdrawAmount: ethers.BigNumber | string,
27
+ minAmountOut: ethers.BigNumber | string,
28
+ keeperFee: ethers.BigNumber | string
29
+ ): string {
30
+ return new ethers.utils.Interface(
31
+ DelayedOrderAbi
32
+ ).encodeFunctionData("announceStableWithdraw", [
33
+ withdrawAmount,
34
+ minAmountOut,
35
+ keeperFee
36
+ ]);
37
+ }
38
+
39
+ export function getCancelExistingOrderTxData(account: string): string {
40
+ return new ethers.utils.Interface(
41
+ DelayedOrderAbi
42
+ ).encodeFunctionData("cancelExistingOrder", [account]);
43
+ }
44
+
45
+ export async function mintUnitViaFlatMoney(
46
+ pool: Pool,
47
+ depositAmount: ethers.BigNumber | string,
48
+ slippage: number, // 0.5 means 0.5%
49
+ maxKeeperFeeInUsd: number | null,
50
+ options: any = null,
51
+ estimateGas = false
52
+ ): Promise<any> {
53
+ const flatMoneyContracts = flatMoneyContractAddresses[pool.network];
54
+ if (!flatMoneyContracts) {
55
+ throw new Error("mintUnitViaFlatMoney: network not supported");
56
+ }
57
+
58
+ const keeperfee = await getKeeperFee(pool, maxKeeperFeeInUsd); // in RETH
59
+
60
+ const adjustedDepositAmount = new BigNumber(depositAmount.toString()).minus(
61
+ keeperfee.toString() // keeper fee deducted from amountIn
62
+ );
63
+
64
+ const amountOut = await getStableDepositQuote(
65
+ pool,
66
+ adjustedDepositAmount.toFixed(0)
67
+ );
68
+ const minAmountOut = new BigNumber(amountOut.toString())
69
+ .times(1 - slippage / 100)
70
+ .toFixed(0);
71
+
72
+ const mintUnitTxData = await getAnnounceStableDepositTxData(
73
+ adjustedDepositAmount.toFixed(0),
74
+ minAmountOut,
75
+ keeperfee
76
+ );
77
+
78
+ const tx = await getPoolTxOrGasEstimate(
79
+ pool,
80
+ [flatMoneyContracts.DelayedOrder, mintUnitTxData, options],
81
+ estimateGas
82
+ );
83
+ return tx;
84
+ }
85
+
86
+ export async function redeemUnitViaFlatMoney(
87
+ pool: Pool,
88
+ withdrawAmount: ethers.BigNumber | string,
89
+ slippage: number, // 0.5 means 0.5%
90
+ maxKeeperFeeInUsd: number | null,
91
+ options: any = null,
92
+ estimateGas = false
93
+ ): Promise<any> {
94
+ const flatMoneyContracts = flatMoneyContractAddresses[pool.network];
95
+ if (!flatMoneyContracts) {
96
+ throw new Error("redeemUnitViaFlatMoney: network not supported");
97
+ }
98
+ const keeperfee = await getKeeperFee(pool, maxKeeperFeeInUsd); // in RETH
99
+
100
+ const amountOut = await getStableWithdrawQuote(pool, withdrawAmount);
101
+ const minAmountOut = new BigNumber(amountOut.toString())
102
+ .times(1 - slippage / 100)
103
+ .minus(keeperfee.toString()) // keeper fee deducted from amountOut
104
+ .toFixed(0);
105
+
106
+ const redeemUnitTxData = await getAnnounceStableWithdrawTxData(
107
+ withdrawAmount,
108
+ minAmountOut,
109
+ keeperfee
110
+ );
111
+ const tx = await getPoolTxOrGasEstimate(
112
+ pool,
113
+ [flatMoneyContracts.DelayedOrder, redeemUnitTxData, options],
114
+ estimateGas
115
+ );
116
+ return tx;
117
+ }
118
+
119
+ export async function cancelOrderViaFlatMoney(
120
+ pool: Pool,
121
+ options: any = null,
122
+ estimateGas = false
123
+ ): Promise<any> {
124
+ const flatMoneyContracts = flatMoneyContractAddresses[pool.network];
125
+ if (!flatMoneyContracts) {
126
+ throw new Error("cancelOrderViaFlatMoney: network not supported");
127
+ }
128
+ const cancelOrderTxData = await getCancelExistingOrderTxData(pool.address);
129
+ const tx = await getPoolTxOrGasEstimate(
130
+ pool,
131
+ [flatMoneyContracts.DelayedOrder, cancelOrderTxData, options],
132
+ estimateGas
133
+ );
134
+ return tx;
135
+ }
@@ -0,0 +1,43 @@
1
+ import { BigNumber, Contract } from "ethers";
2
+ import { flatMoneyContractAddresses } from "../../config";
3
+ import { Pool } from "../../entities";
4
+ import StableModuleAbi from "../../abi/flatmoney/StableModule.json";
5
+
6
+ const getStableModuleContract = (pool: Pool): Contract => {
7
+ const flatMoneyContracts = flatMoneyContractAddresses[pool.network];
8
+ if (!flatMoneyContracts) {
9
+ throw new Error(
10
+ `getStableModuleContract: network of ${pool.network} not supported`
11
+ );
12
+ }
13
+ const stableModuleContract = new Contract(
14
+ flatMoneyContracts.StableModule,
15
+ StableModuleAbi,
16
+ pool.signer
17
+ );
18
+ return stableModuleContract;
19
+ };
20
+
21
+ /// @notice Quoter function for getting the stable deposit amount out.
22
+ export const getStableDepositQuote = async (
23
+ pool: Pool,
24
+ depositAmount: string | BigNumber
25
+ ): Promise<BigNumber> => {
26
+ const stableModuleContract = getStableModuleContract(pool);
27
+ const amountOut: BigNumber = await stableModuleContract.stableDepositQuote(
28
+ depositAmount.toString()
29
+ );
30
+ return amountOut;
31
+ };
32
+
33
+ /// @notice Quoter function for getting the stable withdraw amount out.
34
+ export const getStableWithdrawQuote = async (
35
+ pool: Pool,
36
+ withdrawAmount: string | BigNumber
37
+ ): Promise<BigNumber> => {
38
+ const stableModuleContract = getStableModuleContract(pool);
39
+ const amountOut: BigNumber = await stableModuleContract.stableWithdrawQuote(
40
+ withdrawAmount.toString()
41
+ );
42
+ return amountOut;
43
+ };
@@ -0,0 +1,164 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import BigNumber from "bignumber.js";
3
+ import { Dhedge, Pool } from "../entities";
4
+ import { AssetEnabled, Network } from "../types";
5
+ import {
6
+ TestingRunParams,
7
+ setTokenAmount,
8
+ testingHelper
9
+ } from "./utils/testingHelper";
10
+ import { Contract, ethers } from "ethers";
11
+ import { CONTRACT_ADDRESS, MAX_AMOUNT, TEST_POOL } from "./constants";
12
+ import { flatMoneyContractAddresses } from "../config";
13
+ import DelayedOrderAbi from "../abi/flatmoney/DelayedOrder.json";
14
+ import { allowanceDelta } from "./utils/token";
15
+ import { getKeeperFee } from "../services/flatmoney/keeperFee";
16
+
17
+ const RETH = "0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c";
18
+ const RETH_SLOT = 0;
19
+ const UNIT = "0xb95fB324b8A2fAF8ec4f76e3dF46C718402736e2";
20
+ // https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/token/ERC20/ERC20Upgradeable.sol#L31
21
+ // https://eips.ethereum.org/EIPS/eip-7201
22
+ const UNIT_SLOT =
23
+ "0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00";
24
+
25
+ const testFlatMoney = ({
26
+ wallet,
27
+ network,
28
+ provider,
29
+ rpcUrl
30
+ }: TestingRunParams) => {
31
+ let dhedge: Dhedge;
32
+ let pool: Pool;
33
+ let delayOrderContract: Contract;
34
+ jest.setTimeout(200000);
35
+ describe(`flatmoney on ${network}`, () => {
36
+ beforeAll(async () => {
37
+ dhedge = new Dhedge(wallet, network);
38
+ pool = await dhedge.loadPool(TEST_POOL[network]);
39
+
40
+ const flatMoneyContracts = flatMoneyContractAddresses[pool.network];
41
+ if (!flatMoneyContracts) {
42
+ throw new Error("testFlatMoney: network not supported");
43
+ }
44
+ delayOrderContract = new Contract(
45
+ flatMoneyContracts.DelayedOrder,
46
+ DelayedOrderAbi,
47
+ pool.signer
48
+ );
49
+
50
+ // top up gas
51
+ await provider.send("hardhat_setBalance", [
52
+ wallet.address,
53
+ "0x10000000000000000"
54
+ ]);
55
+ await provider.send("evm_mine", []);
56
+
57
+ await setTokenAmount({
58
+ amount: new BigNumber(100).times(1e18).toString(),
59
+ provider,
60
+ tokenAddress: RETH,
61
+ slot: RETH_SLOT,
62
+ userAddress: pool.address
63
+ });
64
+ await setTokenAmount({
65
+ amount: new BigNumber(100).times(1e18).toString(),
66
+ provider,
67
+ tokenAddress: UNIT,
68
+ slot: UNIT_SLOT,
69
+ userAddress: pool.address
70
+ });
71
+
72
+ const currentAssets: any[] = await pool.managerLogic.getSupportedAssets();
73
+ const exisitingAssets = currentAssets.map(item => {
74
+ return {
75
+ asset: item[0],
76
+ isDeposit: item[1]
77
+ };
78
+ });
79
+
80
+ const newAssets: AssetEnabled[] = [
81
+ ...exisitingAssets,
82
+ { asset: CONTRACT_ADDRESS[network].USDC, isDeposit: true },
83
+ { asset: CONTRACT_ADDRESS[network].WETH, isDeposit: true },
84
+ {
85
+ asset: UNIT,
86
+ isDeposit: false
87
+ },
88
+ {
89
+ asset: RETH,
90
+ isDeposit: false
91
+ }
92
+ ];
93
+ await pool.changeAssets(newAssets);
94
+ });
95
+
96
+ it("mint UNIT", async () => {
97
+ //approve
98
+ await pool.approveSpender(delayOrderContract.address, RETH, MAX_AMOUNT);
99
+ const collateralAllowanceDelta = await allowanceDelta(
100
+ pool.address,
101
+ RETH,
102
+ delayOrderContract.address,
103
+ pool.signer
104
+ );
105
+ await expect(collateralAllowanceDelta.gt(0));
106
+
107
+ const depositAmountStr = new BigNumber(1).times(1e18).toString();
108
+ const tx = await pool.mintUnitViaFlatMoney(
109
+ depositAmountStr,
110
+ 0.5,
111
+ 10, // set higher to tolerate high gasPrice returned by forked local chain
112
+ null,
113
+ false
114
+ );
115
+ expect(tx).not.toBe(null);
116
+ const existingOrder = await delayOrderContract.getAnnouncedOrder(
117
+ pool.address
118
+ );
119
+ expect(existingOrder.orderType).toBe(1);
120
+ });
121
+
122
+ it("cancel order", async () => {
123
+ await provider.send("evm_increaseTime", [60 * 2]); // more than 1 min
124
+ await pool.cancelOrderViaFlatMoney();
125
+ const existingOrder = await delayOrderContract.getAnnouncedOrder(
126
+ pool.address
127
+ );
128
+ expect(existingOrder.orderType).toBe(0);
129
+ });
130
+
131
+ it("redeem UNIT", async () => {
132
+ const withdrawAmountStr = new BigNumber(2).times(1e18).toString();
133
+ const tx = await pool.redeemUnitViaFlatMoney(
134
+ withdrawAmountStr,
135
+ 0.5,
136
+ 10, // set higher to tolerate high gasPrice returned by forked local chain
137
+ null,
138
+ false
139
+ );
140
+ expect(tx).not.toBe(null);
141
+ const existingOrder = await delayOrderContract.getAnnouncedOrder(
142
+ pool.address
143
+ );
144
+ expect(existingOrder.orderType).toBe(2);
145
+
146
+ await provider.send("evm_increaseTime", [60 * 2]); // more than 1 min
147
+ await pool.cancelOrderViaFlatMoney();
148
+ });
149
+
150
+ it("keeperFee is small", async () => {
151
+ const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
152
+ const walletOnChain = wallet.connect(provider);
153
+ const dhedge = new Dhedge(walletOnChain, network);
154
+ const pool = await dhedge.loadPool(TEST_POOL[network]);
155
+ const keeperFee = await getKeeperFee(pool, 3);
156
+ expect(keeperFee).toBeTruthy();
157
+ });
158
+ });
159
+ };
160
+
161
+ testingHelper({
162
+ network: Network.BASE,
163
+ testingRun: testFlatMoney
164
+ });
@@ -7,6 +7,7 @@ export type TestingRunParams = {
7
7
  network: Network;
8
8
  wallet: ethers.Wallet;
9
9
  provider: ethers.providers.JsonRpcProvider;
10
+ rpcUrl: string;
10
11
  };
11
12
 
12
13
  type TestHelperParams = {
@@ -17,8 +18,8 @@ export const testingHelper = ({
17
18
  network,
18
19
  testingRun
19
20
  }: TestHelperParams): void => {
20
- const { wallet, provider } = getWalletData(network);
21
- testingRun({ network, wallet, provider });
21
+ const { wallet, provider, rpcUrl } = getWalletData(network);
22
+ testingRun({ network, wallet, provider, rpcUrl });
22
23
  };
23
24
 
24
25
  export const beforeAfterReset = ({
@@ -52,7 +53,7 @@ export const setTokenAmount = async ({
52
53
  amount: string;
53
54
  userAddress: string;
54
55
  tokenAddress: string;
55
- slot: number;
56
+ slot: number | string;
56
57
  provider: ethers.providers.JsonRpcProvider;
57
58
  }): Promise<void> => {
58
59
  const toBytes32 = (bn: string) => {
@@ -38,12 +38,15 @@ export const getWalletData = (
38
38
  ): {
39
39
  wallet: ethers.Wallet;
40
40
  provider: ethers.providers.JsonRpcProvider;
41
+ rpcUrl: string;
41
42
  } => {
42
43
  const provider = new ethers.providers.JsonRpcProvider(
43
44
  `http://127.0.0.1:${networkPortMap[network]}/`
44
45
  );
46
+ const rpcUrl = process.env[`${network.toUpperCase()}_URL`] || "";
45
47
  return {
46
48
  wallet: new ethers.Wallet(process.env.PRIVATE_KEY as string, provider),
47
- provider
49
+ provider,
50
+ rpcUrl
48
51
  };
49
52
  };