actions.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. const {
  2. checkTokenBalance,
  3. amountLDtoSD,
  4. amountSDtoLD,
  5. getRoundingDust,
  6. getFeesFromFeeLibraryForPool,
  7. ZERO_ADDRESS,
  8. getDefaultLzTxParams,
  9. } = require("./helpers")
  10. const { getPoolState, getChainPaths } = require("./poolStateHelpers")
  11. const { ethers } = require("hardhat")
  12. const { expect } = require("chai")
  13. const { BigNumber } = require("ethers")
  14. const verbose = false
  15. addLiquidity = async (srcPoolObj, user, amountLD) => {
  16. const { router, pool, token } = srcPoolObj
  17. const amountSD = amountLDtoSD(BigNumber.from(amountLD), srcPoolObj)
  18. // when converting between shared and local decimals, the rounding error just stays inside the users wallet
  19. const unspentSrcToken = getRoundingDust(amountLD, srcPoolObj)
  20. // pools
  21. const mintFeeSD = amountSD.mul(await pool.mintFeeBP()).div(await pool.BP_DENOMINATOR())
  22. const amountSDMinusMint = amountSD.sub(mintFeeSD)
  23. const mintFeeBal = await pool.mintFeeBalance()
  24. const totalLiquidity = await pool.totalLiquidity()
  25. const totalSupply = await pool.totalSupply()
  26. const lpBalUser = await pool.balanceOf(user.address)
  27. const amountLpTokens = totalSupply.toString() !== "0" ? amountSDMinusMint.mul(totalSupply).div(totalLiquidity) : amountSDMinusMint
  28. // tokens
  29. const srcUserTokenBal = await token.balanceOf(user.address)
  30. const srcPoolTokenBal = await token.balanceOf(pool.address)
  31. await token.mint(user.address, amountLD)
  32. await token.connect(user).increaseAllowance(router.address, amountLD)
  33. await callAddLiquidity(srcPoolObj, user, amountLD)
  34. // pools
  35. expect(await pool.mintFeeBalance()).to.equal(mintFeeBal.add(mintFeeSD))
  36. expect(await pool.totalLiquidity()).to.equal(totalLiquidity.add(amountSDMinusMint))
  37. // tokens
  38. await checkTokenBalance(token, user.address, srcUserTokenBal.add(unspentSrcToken))
  39. await checkTokenBalance(token, pool.address, srcPoolTokenBal.add(amountLD.sub(unspentSrcToken)))
  40. await checkTokenBalance(pool, user.address, lpBalUser.add(amountLpTokens))
  41. }
  42. // Local Remote
  43. // ------- ---------
  44. // instantRedeemLocal(amount)
  45. removeLiquidityInstant = async (poolObj, user, amountLP) => {
  46. const { pool, token } = poolObj
  47. // pools
  48. const { totalLiquidity, totalSupply } = await getPoolState(poolObj)
  49. const amountSD = totalLiquidity.eq(0) || totalSupply.eq(0) ? BigNumber.from(0) : amountLP.mul(totalLiquidity).div(totalSupply)
  50. // const amountLD = amountSDtoLD(amountSD, poolObj)
  51. // tokens
  52. const tokenUser = await token.balanceOf(user.address)
  53. const tokenPool = await token.balanceOf(pool.address)
  54. const lpTokenUser = await pool.balanceOf(user.address)
  55. const redeemResult = await callRedeemInstant(poolObj, user, amountLP, amountSD)
  56. // pools
  57. expect(await pool.totalLiquidity()).to.equal(totalLiquidity.sub(redeemResult.redeemSD))
  58. // tokens
  59. const redeemLD = amountSDtoLD(redeemResult.redeemSD, poolObj)
  60. await checkTokenBalance(token, user.address, tokenUser.add(redeemLD))
  61. await checkTokenBalance(token, pool.address, tokenPool.sub(redeemLD))
  62. await checkTokenBalance(pool, user.address, lpTokenUser.sub(redeemResult.redeemLP))
  63. }
  64. // Local Remote
  65. // ------- ---------
  66. // redeemLocal(amount) -> withdrawRemote
  67. // redeemLocalCallback <-
  68. removeLiquidityLocal = async (srcPoolObj, dstPoolObj, user, amountLP, lzTxParams = {}, endpoints = [], pools = [], delta = true) => {
  69. const { pool: srcPool, token: srcToken, router: srcRouter } = srcPoolObj
  70. const { pool: dstPool } = dstPoolObj
  71. lzTxParams = getDefaultLzTxParams(lzTxParams)
  72. // call delta ahead of state check, so we do not need to worry about this calculation
  73. if (delta) await srcRouter.callDelta(srcPoolObj.id, false)
  74. // pools
  75. const { totalLiquidity: srcTotalLiquidity, totalSupply: srcTotalSupply, deltaCredit: srcDeltaCredit } = await getPoolState(srcPoolObj)
  76. // const { totalLiquidity: dstTotalLiquidity } = await getPoolState(dstPoolObj)
  77. const srcAmountSD = srcTotalLiquidity.eq(0) || srcTotalSupply.eq(0) ? amountLP : amountLP.mul(srcTotalLiquidity).div(srcTotalSupply)
  78. // chain paths
  79. const { srcChainPath, dstChainPath } = await getChainPaths(srcPoolObj, dstPoolObj)
  80. // const srcIdealBalance = srcTotalLiquidity.sub(srcAmountSD).mul(srcChainPath.weight).div(await srcPool.totalWeight())
  81. // misc
  82. const srcLpTokenUser = await srcPool.balanceOf(user.address)
  83. // const srcTokenUser = await srcToken.balanceOf(user.address)
  84. await callRedeemLocal(srcPoolObj, dstPoolObj, user, amountLP, lzTxParams)
  85. // call audit before we go back from dst -> src
  86. // try {
  87. // await audit(endpoints, pools)
  88. // } catch(e) {
  89. // console.log('\n Audit failed before we went back dst -> src on redeemLocal')
  90. // throw(e)
  91. // }
  92. // pools
  93. const { totalLiquidity: _srcTotalLiquidity, totalSupply: _srcTotalSupply } = await getPoolState(srcPoolObj)
  94. // const _dstDeltaCredit = await dstPool.deltaCredit()
  95. const _srcLpTokenUser = await srcPool.balanceOf(user.address)
  96. expect(_srcTotalLiquidity).to.equal(srcTotalLiquidity.sub(srcAmountSD))
  97. expect(_srcTotalSupply).to.equal(srcTotalSupply.sub(amountLP))
  98. expect(_srcLpTokenUser).to.equal(srcLpTokenUser.sub(amountLP))
  99. // chain paths
  100. const { dstChainPath: _dstChainPath } = await getChainPaths(srcPoolObj, dstPoolObj)
  101. // misc
  102. // let dstMintAmountSD = BigNumber.from(0)
  103. // let dstSwapAmountSD = BigNumber.from(0)
  104. // instant redeem, should only sendCredits
  105. if (srcAmountSD !== BigNumber.from(0)) {
  106. if (srcAmountSD.gt(dstChainPath.balance.add(srcChainPath.credits))) {
  107. // dstMintAmountSD = srcAmountSD.sub(dstChainPath.balance)
  108. // dstSwapAmountSD = dstChainPath.balance
  109. expect(_dstChainPath.balance).to.equal(0)
  110. } else {
  111. // dstSwapAmountSD = srcAmountSD
  112. // dstMintAmount = 0
  113. }
  114. }
  115. // const userTokensSD = (await srcToken.balanceOf(user.address)).sub(srcTokenUser)
  116. await callRevertRedeemLocal(srcPoolObj, dstPoolObj, user, lzTxParams)
  117. }
  118. // Local Remote
  119. // ------- ---------
  120. // swap -> swapRemote
  121. removeLiquidityRemote = async (srcPoolObj, dstPoolObj, user, amountLP, lzTxParams = {}) => {
  122. const { pool: srcPool } = srcPoolObj
  123. const { pool: dstPool, token: dstToken } = dstPoolObj
  124. lzTxParams = getDefaultLzTxParams(lzTxParams)
  125. // call delta ahead of state checks, so we do not need to worry about this calculation
  126. // await callDelta(srcPoolObj, dstPoolObj, await srcPool.defaultLPMode())
  127. // pools
  128. const { totalLiquidity: srcTotalLiquidity, eqFeePool: srcEqFeePool, totalSupply: srcTotalSupply } = await getPoolState(srcPoolObj)
  129. const {
  130. totalLiquidity: dstTotalLiquidity,
  131. eqFeePool: dstEqFeePool,
  132. protocolFeeBalance: dstProtocolFeeBalance,
  133. } = await getPoolState(dstPoolObj)
  134. const srcAmountSD = srcTotalLiquidity.eq(0) || srcTotalSupply.eq(0) ? amountLP : amountLP.mul(srcTotalLiquidity).div(srcTotalSupply)
  135. // fees. to assert the view function return == the actual amount sent over chain and got applied to the ledger.
  136. const fees = await getFeesFromFeeLibraryForPool(srcPoolObj, dstPoolObj, user, srcAmountSD)
  137. const { eqFee: srcEqFee, protocolFee: srcProtocolFee, lpFee: srcLpFee, eqReward: srcEqReward } = fees
  138. // const logString = `redeemRemote Fee : eq ${srcEqFee} | protocol ${srcProtocolFee} | lp ${srcLpFee} | reward ${srcEqReward}`
  139. // require('fs').writeSync(process.stdout.fd, ` ${logString} \n`);
  140. const srcAmountToReceive = srcAmountSD.sub(srcEqFee).sub(srcProtocolFee).sub(srcLpFee)
  141. // tokens
  142. const dstTokenBalance = await dstToken.balanceOf(user.address)
  143. await callRedeemRemote(srcPoolObj, dstPoolObj, user, amountLP, srcAmountToReceive, lzTxParams)
  144. // pools
  145. await checkSrcGlobals(srcPool, srcTotalLiquidity.sub(srcAmountSD), srcEqFeePool.sub(srcEqReward))
  146. await checkDstGlobals(dstPool, dstTotalLiquidity.add(srcLpFee), dstEqFeePool.add(srcEqFee), dstProtocolFeeBalance.add(srcProtocolFee))
  147. // tokens
  148. const dstAmountLD = amountSDtoLD(srcAmountToReceive.add(srcEqReward), dstPoolObj)
  149. await checkTokenBalance(dstToken, user.address, dstTokenBalance.add(dstAmountLD))
  150. }
  151. // internal function used in mintSwapSafe
  152. mintAndSwap = async (srcPoolObj, dstPoolObj, user, amountLD, lzTxParams = {}, delta = true) => {
  153. const { pool: srcPool, token: srcToken, router: srcRouter } = srcPoolObj
  154. const { pool: dstPool, token: dstToken } = dstPoolObj
  155. lzTxParams = getDefaultLzTxParams(lzTxParams)
  156. // call delta ahead of state checks so we dont need to worry about this calculation
  157. if (delta) await callDelta(srcPoolObj, dstPoolObj, await srcPool.defaultSwapMode())
  158. // misc
  159. const minAmountLD = BigNumber.from("1") //
  160. const srcAmountSD = amountLDtoSD(amountLD, srcPoolObj)
  161. // when converting between shared and local decimals, the rounding error just stays inside the users wallet
  162. const unspentSrcToken = getRoundingDust(amountLD, srcPoolObj)
  163. // pools
  164. const { totalLiquidity: srcTotalLiquidity, eqFeePool: srcEqFeePool } = await getPoolState(srcPoolObj)
  165. const {
  166. totalLiquidity: dstTotalLiquidity,
  167. eqFeePool: dstEqFeePool,
  168. protocolFeeBalance: dstProtocolFeeBalance,
  169. } = await getPoolState(dstPoolObj)
  170. // fees
  171. const {
  172. eqFee: srcEqFee,
  173. protocolFee: srcProtocolFee,
  174. lpFee: srcLpFee,
  175. eqReward: srcEqReward,
  176. } = await getFeesFromFeeLibraryForPool(srcPoolObj, dstPoolObj, user, srcAmountSD)
  177. const srcAmountToReceiveSD = srcAmountSD.sub(srcEqFee).sub(srcProtocolFee).sub(srcLpFee).add(srcEqReward)
  178. // tokens
  179. const srcUserTokenBal = await srcToken.balanceOf(user.address)
  180. const dstUserTokenBal = await dstToken.balanceOf(user.address)
  181. const srcPoolTokenBal = await srcToken.balanceOf(srcPool.address)
  182. await srcToken.mint(user.address, amountLD) // so that the user has money to swap
  183. await srcToken.connect(user).increaseAllowance(srcRouter.address, amountLD)
  184. const tx = await callSwap(srcPoolObj, dstPoolObj, user, amountLD, minAmountLD, lzTxParams)
  185. // pools
  186. await checkSrcGlobals(srcPool, srcTotalLiquidity, srcEqFeePool.sub(srcEqReward))
  187. await checkDstGlobals(dstPool, dstTotalLiquidity.add(srcLpFee), dstEqFeePool.add(srcEqFee), dstProtocolFeeBalance.add(srcProtocolFee))
  188. // tokens
  189. const dstAmountToReceive = amountSDtoLD(srcAmountToReceiveSD, dstPoolObj)
  190. await checkTokenBalance(srcToken, user.address, srcUserTokenBal.add(unspentSrcToken))
  191. await checkTokenBalance(dstToken, user.address, dstUserTokenBal.add(dstAmountToReceive))
  192. await checkTokenBalance(srcToken, srcPool.address, srcPoolTokenBal.add(amountLD.sub(unspentSrcToken)))
  193. return tx
  194. }
  195. callDelta = async (srcPoolObj, dstPoolObj, fullMode) => {
  196. await dstPoolObj.router.callDelta(dstPoolObj.id, fullMode)
  197. await dstPoolObj.router.callDelta(srcPoolObj.id, fullMode)
  198. await srcPoolObj.router.callDelta(dstPoolObj.id, fullMode)
  199. await srcPoolObj.router.callDelta(srcPoolObj.id, fullMode)
  200. }
  201. callAddLiquidity = async (poolObj, user, amountLd) => {
  202. await poolObj.router.connect(user).addLiquidity(poolObj.id, amountLd, user.address)
  203. }
  204. callRedeemInstant = async (poolObj, user, amountLP, amountSD) => {
  205. const { totalSupply, totalLiquidity, deltaCredit } = await getPoolState(poolObj)
  206. let redeemLP
  207. let redeemSD
  208. // may not redeem in full
  209. if (totalSupply.eq(0) || totalLiquidity.eq(0)) {
  210. redeemLP = BigNumber.from(0)
  211. redeemSD = BigNumber.from(0)
  212. } else {
  213. const capAmountLP = deltaCredit.mul(totalSupply).div(totalLiquidity)
  214. redeemLP = amountLP.gt(capAmountLP) ? capAmountLP : amountLP
  215. redeemSD = redeemLP.mul(totalLiquidity).div(totalSupply)
  216. }
  217. await expect(poolObj.router.connect(user).instantRedeemLocal(poolObj.id, amountLP, user.address))
  218. .to.emit(poolObj.pool, "InstantRedeemLocal")
  219. .withArgs(user.address, redeemLP, redeemSD, user.address)
  220. // return the actual amount got redeemed
  221. return { redeemSD, redeemLP }
  222. }
  223. callRedeemLocal = async (srcPoolObj, dstPoolObj, user, amount, lzTxParams) => {
  224. // due to async race conditions, when figuring out the nonce to pass to revertRedeemLocal,
  225. // should always query it via the event thats emitted on redeemLocal via the bridge contract
  226. await expect(
  227. srcPoolObj.router
  228. .connect(user)
  229. .redeemLocal(dstPoolObj.chainId, srcPoolObj.id, dstPoolObj.id, user.address, amount, user.address, lzTxParams)
  230. ).to.emit(dstPoolObj.router, "RevertRedeemLocal")
  231. // .withArgs(srcPoolObj.chainId, srcPoolObj.id, dstPoolObj.id, user.address.toLowerCase(), redeemAmount, mintBackAmount)
  232. await callDelta(srcPoolObj, dstPoolObj, false)
  233. }
  234. callRevertRedeemLocal = async (srcPoolObj, dstPoolObj, user, lzTxParams) => {
  235. // due to async race conditions, when figuring out the nonce to pass to revertRedeemLocal,
  236. // should always query it via the event thats emitted on redeemLocal via the bridge contract
  237. const nonce = await srcPoolObj.lzEndpoint.outboundNonce(dstPoolObj.chainId, srcPoolObj.bridge.address)
  238. await expect(
  239. dstPoolObj.router.connect(user).revertRedeemLocal(srcPoolObj.chainId, srcPoolObj.bridge.address, nonce, user.address, lzTxParams)
  240. ).to.emit(srcPoolObj.pool, "RedeemLocalCallback")
  241. // check the revert is consumed
  242. expect(await dstPoolObj.router.revertLookup(srcPoolObj.chainId, srcPoolObj.bridge.address, nonce)).to.equal("0x")
  243. }
  244. callRedeemRemote = async (srcPoolObj, dstPoolObj, user, amount, srcAmountToReceive, lzTxParams) => {
  245. await expect(
  246. srcPoolObj.router
  247. .connect(user)
  248. .redeemRemote(dstPoolObj.chainId, srcPoolObj.id, dstPoolObj.id, user.address, amount, 1, user.address, lzTxParams)
  249. ).to.emit(srcPoolObj.pool, "Swap")
  250. }
  251. callSwap = async (srcPoolObj, dstPoolObj, user, amountLD, minAmountLD, lzTxParams) => {
  252. return await srcPoolObj.router
  253. .connect(user)
  254. .swap(dstPoolObj.chainId, srcPoolObj.id, dstPoolObj.id, user.address, amountLD, minAmountLD, lzTxParams, user.address, "0x")
  255. }
  256. getRandomNumberFromBigNum = (amountBN) => {
  257. const bp = 100000000
  258. const numerator = Math.floor(Math.random() * bp)
  259. const randomVal = amountBN.mul(numerator).div(bp).toString()
  260. const index = Math.floor(Math.random() * randomVal.length)
  261. return BigNumber.from(randomVal.substring(0, index === 0 ? 1 : index))
  262. }
  263. withdrawFees = async (endpoints, user) => {
  264. // const logString = `withdrawFees() Endpoints: ${endpoints.map(x => x.name)} User: ${user.name}`
  265. // require('fs').writeSync(process.stdout.fd, ` ${logString} \n`);
  266. // console.log(
  267. // `withdrawFees() `,
  268. // `Endpoints: ${endpoints.map(x => x.name)} `,
  269. // `User: ${user.name}`,
  270. // )
  271. for (const endpoint of Object.values(endpoints)) {
  272. for (const pool of Object.values(endpoint.pools)) {
  273. await endpoint.router.setMintFeeOwner(user.address)
  274. await endpoint.router.connect(user).withdrawMintFee(pool.id, user.address)
  275. await endpoint.router.setProtocolFeeOwner(user.address)
  276. await endpoint.router.connect(user).withdrawProtocolFee(pool.id, user.address)
  277. }
  278. }
  279. }
  280. equalize = async (endpoints, user = { address: ZERO_ADDRESS }, print = false) => {
  281. if (print) {
  282. const logString = `sendCredits() Endpoints: ${endpoints.map((x) => x.name)} User: ${user.name}`
  283. require("fs").writeSync(process.stdout.fd, ` ${logString} \n`)
  284. // console.log(
  285. // `sendCredits() `,
  286. // `Endpoints: ${endpoints.map(x => x.name)} `,
  287. // `User: ${user.name}`,
  288. // )
  289. }
  290. for (const endpoint of Object.values(endpoints)) {
  291. for (const pool of Object.values(endpoint.pools)) {
  292. for (const [dstChainId, chainPaths] of Object.entries(pool.chainPaths)) {
  293. for (const dstPoolId of Object.keys(chainPaths)) {
  294. await endpoint.router.sendCredits(dstChainId, pool.id, dstPoolId, user.address)
  295. }
  296. }
  297. }
  298. }
  299. }
  300. addLiquiditySafe = async (srcPoolObj, user) => {
  301. const decimals = await srcPoolObj.token.decimals()
  302. const amountLd = getRandomNumberFromBigNum(ethers.utils.parseUnits("100", decimals))
  303. // const logString = `addLiquidity() Pool: ${srcPoolObj.name} User: ${user.name} Amount: ${amountLd.toString()}`
  304. // require('fs').writeSync(process.stdout.fd, ` ${logString} \n`);
  305. // console.log(
  306. // `addLiquidity() `,
  307. // `Pool: ${srcPoolObj.name} `,
  308. // `User: ${user.name} `,
  309. // `Amount: ${amountLd.toString()}`,
  310. // )
  311. await addLiquidity(srcPoolObj, user, amountLd)
  312. }
  313. removeLiquidityInstantSafe = async (poolObj, user) => {
  314. const randomAmountLp = getRandomNumberFromBigNum(await poolObj.pool.deltaCredit())
  315. // console.log(
  316. // `redeemLiquidityInstant() `,
  317. // `Pool: ${poolObj.name} `,
  318. // `User: ${user.name} `,
  319. // `Amount: ${randomAmountLp.toString()}`,
  320. // )
  321. try {
  322. await removeLiquidityInstant(poolObj, user, randomAmountLp)
  323. } catch (e) {
  324. const { totalLiquidity, totalSupply } = await getPoolState(poolObj)
  325. const noLiquidity = totalLiquidity.eq(0)
  326. const noSupply = totalSupply.eq(0)
  327. const notEnoughLp = randomAmountLp.eq(0)
  328. const notEnoughLpToBurn = (await poolObj.pool.balanceOf(user.address)).lte(randomAmountLp)
  329. if (notEnoughLp && e.message.includes("Stargate: not enough lp to redeem")) {
  330. if (verbose) console.log(` => cannot redeem 0 lp, failed as intended\n`)
  331. } else if (notEnoughLpToBurn && e.message.includes("Stargate: not enough LP tokens to burn")) {
  332. if (verbose) console.log(` => cannot burn more lp than user has, failed as intended\n`)
  333. } else if (noLiquidity && e.message.includes("Stargate: cant convert SDtoLP when totalLiq == 0")) {
  334. if (verbose) console.log(` => cannot convert SDtoLP when no totalLiquidity, failed as intended\n`)
  335. } else if (noSupply && e.message.includes("Stargate: cant burn when totalSupply == 0")) {
  336. if (verbose) console.log(` => cannot burn when no totalSupply, failed as intended\n`)
  337. } else {
  338. const logString = `redeemLiquidityInstant() Pool: ${poolObj.name} User: ${user.name} Amount: ${randomAmountLp.toString()}`
  339. require("fs").writeSync(process.stdout.fd, ` ${logString} \n`)
  340. throw e
  341. }
  342. }
  343. }
  344. removeLiquidityLocalSafe = async (srcPoolObj, dstPoolObj, user, endpoints, pools) => {
  345. const randomAmountLp = getRandomNumberFromBigNum(await srcPoolObj.pool.balanceOf(user.address))
  346. // console.log(
  347. // `redeemLiquidityLocal() `,
  348. // `Pools: ${srcPoolObj.name} -> ${dstPoolObj.name} `,
  349. // `User: ${user.name} `,
  350. // `Amount: ${randomAmountLp.toString()}`,
  351. // )
  352. const notEnoughLp = randomAmountLp.eq(0)
  353. try {
  354. await removeLiquidityLocal(srcPoolObj, dstPoolObj, user, randomAmountLp, {}, endpoints, pools)
  355. } catch (e) {
  356. if (notEnoughLp && e.message.includes("Stargate: not enough lp to redeem")) {
  357. if (verbose) console.log(` => cannot redeem 0 lp, failed as intended\n`)
  358. } else {
  359. const logString = `redeemLiquidityLocal() Pools: ${srcPoolObj.name} -> ${dstPoolObj.name} User: ${
  360. user.name
  361. } Amount: ${randomAmountLp.toString()}`
  362. require("fs").writeSync(process.stdout.fd, ` ${logString} \n`)
  363. throw e
  364. }
  365. }
  366. }
  367. removeLiquidityRemoteSafe = async (srcPoolObj, dstPoolObj, user) => {
  368. const randomAmountLp = getRandomNumberFromBigNum(await srcPoolObj.pool.balanceOf(user.address))
  369. // console.log(
  370. // `redeemLiquidityRemote() `,
  371. // `Pools: ${srcPoolObj.name} -> ${dstPoolObj.name} `,
  372. // `User: ${user.name} `,
  373. // `Amount: ${randomAmountLp.toString()}`,
  374. // )
  375. const { totalSupply } = await getPoolState(srcPoolObj)
  376. // const noLiquidity = totalLiquidity.eq(0)
  377. const noSupply = totalSupply.eq(0)
  378. let dstBalanceTooLow = false
  379. let feeTooHigh = false
  380. try {
  381. const { totalLiquidity, totalSupply } = await getPoolState(srcPoolObj)
  382. const srcAmountSD = totalLiquidity.eq(0) || totalSupply.eq(0) ? randomAmountLp : randomAmountLp.mul(totalLiquidity).div(totalSupply)
  383. const { protocolFee, eqFee, lpFee, eqReward } = await getFeesFromFeeLibraryForPool(srcPoolObj, dstPoolObj, user, srcAmountSD)
  384. // swap amount < total Fee
  385. if (eqFee.add(protocolFee).add(lpFee).gt(srcAmountSD)) {
  386. feeTooHigh = true
  387. }
  388. const srcLkbRemove = srcAmountSD.sub(lpFee).add(eqReward)
  389. const { srcChainPath } = await getChainPaths(srcPoolObj, dstPoolObj)
  390. dstBalanceTooLow = srcChainPath.balance.lt(srcLkbRemove)
  391. } catch (e) {
  392. if (e.message.includes("Stargate: not enough balance")) {
  393. dstBalanceTooLow = true
  394. } else {
  395. throw e
  396. }
  397. }
  398. try {
  399. await removeLiquidityRemote(srcPoolObj, dstPoolObj, user, randomAmountLp, {})
  400. } catch (e) {
  401. if (dstBalanceTooLow && e.message.includes("Stargate: not enough balance")) {
  402. if (verbose) console.log(` => cp.balance and fees are not high enough, failed as intended\n`)
  403. return
  404. } else if (dstBalanceTooLow && e.message.includes("Stargate: dst balance too low")) {
  405. if (verbose) console.log(` => cp.balance was not high enough, failed as intended\n`)
  406. return
  407. } else if (noSupply && e.message.includes("Stargate: cant convert LPtoSD when totalSupply == 0")) {
  408. if (verbose) console.log(` => cant convert LPtoSD when totalSupply == 0, failed as intended\n`)
  409. return
  410. } else if (randomAmountLp.eq(0) && e.message.includes("Stargate: not enough lp to redeemRemote")) {
  411. if (verbose) console.log(` => amount of lp to redeem was 0, failed as intended\n`)
  412. return
  413. } else if (feeTooHigh && e.message.includes("SafeMath: subtraction overflow")) {
  414. if (verbose) console.log(" fee > total swap amount. failed as intended")
  415. return
  416. } else {
  417. const logString = `redeemLiquidityRemote() Pools: ${srcPoolObj.name} -> ${dstPoolObj.name} User: ${
  418. user.name
  419. } Amount: ${randomAmountLp.toString()}`
  420. require("fs").writeSync(process.stdout.fd, ` ${logString} \n`)
  421. throw e
  422. }
  423. }
  424. if (dstBalanceTooLow) throw "Tx should have failed with: dst balance too low"
  425. }
  426. mintAndSwapSafe = async (srcPoolObj, dstPoolObj, user) => {
  427. const decimals = await srcPoolObj.token.decimals()
  428. const amountLd = getRandomNumberFromBigNum(ethers.utils.parseUnits("100", decimals))
  429. // console.log(
  430. // `swap() `,
  431. // `Pools: ${srcPoolObj.name} -> ${dstPoolObj.name} `,
  432. // `User: ${user.name} `,
  433. // `Amount: ${amountLd.toString()}`,
  434. // )
  435. let feeTooHigh = false
  436. let dstBalanceTooLow = false
  437. const srcAmountSD = amountLDtoSD(amountLd, srcPoolObj)
  438. try {
  439. const { lpFee, eqFee, eqReward, protocolFee } = await getFeesFromFeeLibraryForPool(srcPoolObj, dstPoolObj, user, srcAmountSD)
  440. if (eqFee.add(protocolFee).add(lpFee).gt(srcAmountSD)) {
  441. feeTooHigh = true
  442. }
  443. const srcLkbRemove = srcAmountSD.sub(lpFee).add(eqReward)
  444. const { srcChainPath } = await getChainPaths(srcPoolObj, dstPoolObj)
  445. dstBalanceTooLow = srcChainPath.balance.lt(srcLkbRemove)
  446. } catch (e) {
  447. if (e.message.includes("Stargate: not enough balance")) {
  448. dstBalanceTooLow = true
  449. } else {
  450. throw e
  451. }
  452. }
  453. try {
  454. await mintAndSwap(srcPoolObj, dstPoolObj, user, amountLd, {})
  455. } catch (e) {
  456. if (dstBalanceTooLow && e.message.includes("Stargate: not enough balance")) {
  457. if (verbose) console.log(` => cp.balance and fees are not high enough, failed as intended\n`)
  458. return
  459. } else if (dstBalanceTooLow && e.message.includes("Stargate: dst balance too low")) {
  460. if (verbose) console.log(` => cp.balance is not high enough, failed as intended\n`)
  461. return
  462. } else if (feeTooHigh && e.message.includes("SafeMath: subtraction overflow")) {
  463. if (verbose) console.log(" fee > total swap amount. failed as intended")
  464. return
  465. } else {
  466. const logString = `swap() Pools: ${srcPoolObj.name} -> ${dstPoolObj.name} User: ${user.name} Amount: ${amountLd.toString()}`
  467. require("fs").writeSync(process.stdout.fd, ` ${logString} \n`)
  468. throw e
  469. }
  470. }
  471. if (dstBalanceTooLow) throw "Tx should have failed with: dst balance too low"
  472. }
  473. mintAndSwapUnsafe = async (srcPoolObj, dstPoolObj, user, amountSD, lzTxParams) => {
  474. await srcPoolObj.token.mint(user.address, amountSD)
  475. await srcPoolObj.token.connect(user).increaseAllowance(srcPoolObj.router.address, amountSD)
  476. await callSwap(srcPoolObj, dstPoolObj, user, amountSD, 0, lzTxParams)
  477. }
  478. executeAction = async (users, pools, endpoints) => {
  479. const user = getRandomUser(users)
  480. const [srcPoolObj, dstPoolObj] = getRandomSrcAndDstPool(pools)
  481. const [actionName, action] = getRandomAction()
  482. switch (actionName) {
  483. case "addLiquidity":
  484. await action(srcPoolObj, user)
  485. break
  486. case "removeLiquidityInstant":
  487. await action(srcPoolObj, user)
  488. break
  489. case "removeLiquidityLocal":
  490. await action(srcPoolObj, dstPoolObj, user, endpoints, pools) // special params because we audit mid tx
  491. break
  492. case "removeLiquidityRemote":
  493. await action(srcPoolObj, dstPoolObj, user)
  494. break
  495. case "swap":
  496. await action(srcPoolObj, dstPoolObj, user)
  497. break
  498. case "equalize":
  499. await action(endpoints, user)
  500. break
  501. case "withdrawFees":
  502. await action(endpoints, user)
  503. break
  504. }
  505. // await audit(endpoints, pools)
  506. }
  507. getRandomFromList = (list) => {
  508. return list[Math.floor(Math.random() * list.length)]
  509. }
  510. getRandomUser = (users) => {
  511. return getRandomFromList(users)
  512. }
  513. getRandomEndpoint = (endpoints) => {
  514. return getRandomFromList(endpoints)
  515. }
  516. getRandomAction = function () {
  517. const funcName = getRandomFromList(Object.keys(actions))
  518. const func = actions[funcName]
  519. return [funcName, func]
  520. }
  521. getRandomSrcAndDstPool = (pools) => {
  522. const shuffled = pools.sort(() => 0.5 - Math.random())
  523. const [src, dst] = shuffled.slice(0, 2)
  524. // try again if we get a local chain
  525. if (src.chainId === dst.chainId) {
  526. return getRandomSrcAndDstPool(pools)
  527. } else {
  528. return [src, dst]
  529. }
  530. }
  531. actions = {
  532. addLiquidity: addLiquiditySafe,
  533. removeLiquidityInstant: removeLiquidityInstantSafe,
  534. removeLiquidityLocal: removeLiquidityLocalSafe,
  535. removeLiquidityRemote: removeLiquidityRemoteSafe,
  536. swap: mintAndSwapSafe,
  537. equalize: equalize,
  538. withdrawFees: withdrawFees,
  539. }
  540. checkSrcGlobals = async (srcPool, expectedLiq, expectedEqFee) => {
  541. expect(await srcPool.totalLiquidity()).to.equal(expectedLiq)
  542. expect(await srcPool.eqFeePool()).to.equal(expectedEqFee)
  543. }
  544. checkDstGlobals = async (dstPool, expectedLiq, expectedEqFee, expectedProtocolFee) => {
  545. expect(await dstPool.totalLiquidity()).to.equal(expectedLiq)
  546. expect(await dstPool.eqFeePool()).to.equal(expectedEqFee)
  547. expect(await dstPool.protocolFeeBalance()).to.equal(expectedProtocolFee)
  548. }
  549. module.exports = {
  550. mintAndSwap,
  551. mintAndSwapUnsafe,
  552. addLiquidity,
  553. removeLiquidityInstant,
  554. removeLiquidityLocal,
  555. removeLiquidityRemote,
  556. withdrawFees,
  557. equalize,
  558. executeAction,
  559. callSwap,
  560. callRedeemLocal,
  561. }