Skip to content

Commit 8f42fc5

Browse files
committed
add script calcMinDepositReturn.js
1 parent 944a874 commit 8f42fc5

1 file changed

Lines changed: 135 additions & 0 deletions

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const Web3 = require("web3");
4+
const BN = require("bn.js");
5+
6+
// Usage: node calcMinDepositReturn.js <config.json> <data.json> <amount1> <amount2> ... <node_url>
7+
// e.g. node calcMinDepositReturn.js addBOS_mainnet.json data_mainnet.json 1 233656500 https://mainnet-dev.sovryn.app/rpc
8+
const [, , CONFIG_FILE, DATA_FILE, ...args] = process.argv;
9+
const NODE_URL = args.pop();
10+
const AMOUNT_ARGS = args;
11+
12+
if (!CONFIG_FILE || !DATA_FILE || AMOUNT_ARGS.length < 2 || !NODE_URL) {
13+
console.error("Usage: node getLPDepositMinReturn.js <config.json> <data.json> <amount1> <amount2> ... <node_url>");
14+
process.exit(1);
15+
}
16+
17+
const ARTIFACTS_DIR = path.resolve(__dirname, "../build/contracts");
18+
19+
function getConfig() {
20+
return JSON.parse(fs.readFileSync(path.join(__dirname, CONFIG_FILE), "utf8"));
21+
}
22+
function getData() {
23+
return JSON.parse(fs.readFileSync(path.join(__dirname, DATA_FILE), "utf8"));
24+
}
25+
26+
async function main() {
27+
const config = getConfig();
28+
const data = getData();
29+
const web3 = new Web3(NODE_URL);
30+
31+
// Find the V1 converter config
32+
const converter = config.converters.find((c) => c.type === 1);
33+
if (!converter) {
34+
console.error("No V1 converter found in config.");
35+
process.exit(1);
36+
}
37+
38+
// Get token addresses and decimals
39+
const reserves = converter.reserves;
40+
const tokens = reserves.map((r) => {
41+
let addr = r.address;
42+
if (!addr) {
43+
// Try top-level reserves array
44+
const topReserve = config.reserves.find((x) => x.symbol === r.symbol);
45+
if (topReserve && topReserve.address) addr = topReserve.address;
46+
}
47+
if (!addr && config[r.symbol] && config[r.symbol].addr) {
48+
addr = config[r.symbol].addr;
49+
}
50+
if (!addr) {
51+
throw new Error(`Missing address for token ${r.symbol}`);
52+
}
53+
return addr;
54+
});
55+
const decimals = reserves.map((r) => r.decimals || 18);
56+
57+
// Scale input amounts to token decimals
58+
const amounts = AMOUNT_ARGS.map((amt, i) => {
59+
// Use BN for precision, avoid floating point errors
60+
const [whole, fraction = ""] = amt.split(".");
61+
const padded = (whole + fraction.padEnd(decimals[i], "0")).replace(/^0+/, "");
62+
return padded === "" ? "0" : padded;
63+
});
64+
65+
// Get converter contract address
66+
const converterAddr = config["newLiquidityPoolV1Converter"]?.addr;
67+
if (!converterAddr) {
68+
console.error("No deployed V1 converter found in config.");
69+
process.exit(1);
70+
}
71+
72+
// Load ABIs
73+
const poolAbi = JSON.parse(fs.readFileSync(path.join(ARTIFACTS_DIR, "LiquidityPoolV1Converter.json"), "utf8")).abi;
74+
const anchorAbi = JSON.parse(fs.readFileSync(path.join(ARTIFACTS_DIR, "SmartToken.json"), "utf8")).abi;
75+
const formulaAbi = JSON.parse(fs.readFileSync(path.join(ARTIFACTS_DIR, "SovrynSwapFormula.json"), "utf8")).abi;
76+
77+
// Get contract instances
78+
const pool = new web3.eth.Contract(poolAbi, converterAddr);
79+
80+
// Get anchor address and total supply
81+
const anchorAddr = await pool.methods.anchor().call();
82+
const anchor = new web3.eth.Contract(anchorAbi, anchorAddr);
83+
const totalSupply = await anchor.methods.totalSupply().call();
84+
85+
// Get reserve balances
86+
const reserveBalances = [];
87+
for (let i = 0; i < tokens.length; i++) {
88+
const reserve = await pool.methods.reserves(tokens[i]).call();
89+
reserveBalances[i] = reserve.balance;
90+
}
91+
92+
// Get reserveRatio
93+
const reserveRatio = await pool.methods.reserveRatio().call();
94+
95+
// Get SovrynSwapFormula contract
96+
const formulaAddr = data.sovrynSwapFormula.addr;
97+
const formula = new web3.eth.Contract(formulaAbi, formulaAddr);
98+
99+
// Find limiting reserve (minimum proportional share)
100+
let minIndex = 0;
101+
for (let i = 1; i < amounts.length; i++) {
102+
const left = new BN(amounts[i]).mul(new BN(reserveBalances[minIndex]));
103+
const right = new BN(amounts[minIndex]).mul(new BN(reserveBalances[i]));
104+
if (left.lt(right)) minIndex = i;
105+
}
106+
107+
// Print proportional shares for debugging
108+
console.log("Proportional shares for each reserve:");
109+
for (let i = 0; i < amounts.length; i++) {
110+
const share = Number(amounts[i]) / Number(reserveBalances[i]);
111+
console.log(` Reserve ${i}: depositAmount = ${amounts[i]}, reserveBalance = ${reserveBalances[i]}, proportionalShare = ${share}`);
112+
}
113+
console.log(`Limiting reserve index: ${minIndex}\n`);
114+
115+
// Call formula contract
116+
const expectedLP = await formula.methods.fundSupplyAmount(totalSupply, reserveBalances[minIndex], reserveRatio, amounts[minIndex]).call();
117+
118+
// Get pool token decimals
119+
const poolTokenDecimals = await anchor.methods.decimals().call();
120+
121+
// Convert from wei to tokens for output
122+
const scaledLP = Number(expectedLP) / Math.pow(10, poolTokenDecimals);
123+
124+
// Calculate minReturn
125+
const minReturnRaw = Math.floor(Number(expectedLP) * 0.99);
126+
const minReturnScaled = scaledLP * 0.99;
127+
128+
console.log(`Expected LP tokens (raw): ${expectedLP}`);
129+
console.log(`Expected LP tokens (scaled): ${scaledLP}`);
130+
console.log(`Recommended minReturn (99%) raw: ${minReturnRaw}`);
131+
console.log(`Recommended minReturn (99%) scaled: ${minReturnScaled}`);
132+
console.log("Use this value for addLiquidity(..., ..., minReturn)");
133+
}
134+
135+
main().catch(console.error);

0 commit comments

Comments
 (0)