PoolState.test.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. const { expect } = require("chai")
  2. const { ethers } = require("hardhat")
  3. const { BigNumber } = require("ethers")
  4. const { setup } = require("./util/setup")
  5. const {
  6. encodePackedParams,
  7. getAddr,
  8. deployNew,
  9. callAsContract,
  10. encodeParams,
  11. amountSDtoLD,
  12. amountLDtoSD,
  13. getPoolFromFactory,
  14. } = require("./util/helpers")
  15. const {
  16. ZERO_ADDRESS,
  17. USDC,
  18. DAI,
  19. ETHEREUM,
  20. AVAX,
  21. TYPE_REDEEM_LOCAL_RESPONSE,
  22. TYPE_REDEEM_LOCAL_CALLBACK_RETRY,
  23. TYPE_SWAP_REMOTE_RETRY,
  24. } = require("./util/constants")
  25. const { addLiquidity, equalize, mintAndSwap, removeLiquidityLocal, removeLiquidityRemote, removeLiquidityInstant } = require("./util/actions")
  26. const { audit, getPoolState } = require("./util/poolStateHelpers")
  27. describe("Pool State: ", function () {
  28. this.timeout(600000000)
  29. let eth_endpoint, avax_endpoint, endpoints, tokens, pools
  30. let eth_usdc_pool, eth_dai_pool, avax_usdc_pool, avax_dai_pool
  31. let alice, bob, badUser1, fakeContract, emptyLzTxObj, defaultSwapObj
  32. before(async function () {
  33. ;({ alice, bob, badUser1, fakeContract } = await getAddr(ethers))
  34. })
  35. beforeEach(async function () {
  36. endpoints = await setup(2, 2)
  37. eth_endpoint = endpoints[ETHEREUM]
  38. avax_endpoint = endpoints[AVAX]
  39. ;({ [DAI]: eth_dai_pool, [USDC]: eth_usdc_pool } = eth_endpoint.pools)
  40. ;({ [DAI]: avax_dai_pool, [USDC]: avax_usdc_pool } = avax_endpoint.pools)
  41. endpoints = [eth_endpoint, avax_endpoint]
  42. pools = [eth_dai_pool, avax_dai_pool, eth_usdc_pool, avax_usdc_pool]
  43. tokens = [DAI]
  44. emptyLzTxObj = { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" }
  45. defaultSwapObj = { amount: 1, eqFee: 1, eqReward: 1, lpFee: 1, protocolFee: 1, lkbRemove: 1 }
  46. })
  47. it("swap() - lzTxParams transfers extra gas", async function () {
  48. await addLiquidity(avax_dai_pool, alice, BigNumber.from("1000"))
  49. await equalize(endpoints, alice, false)
  50. const nativeAmt = 453
  51. const encodedDstNativeAddr = encodePackedParams(["address"], [alice.address])
  52. const lzTxParams = { dstGasForCall: 0, dstNativeAmount: nativeAmt, dstNativeAddr: encodedDstNativeAddr }
  53. // mock is design to throw if this does not pass
  54. await expect(mintAndSwap(eth_dai_pool, avax_dai_pool, bob, BigNumber.from("500"), lzTxParams)).to.be.revertedWith(
  55. "NativeGasParams check"
  56. )
  57. })
  58. it("swap() - reverts with 0 amount", async function () {
  59. const lzTxParams = { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" }
  60. await expect(
  61. eth_dai_pool.router
  62. .connect(alice)
  63. .swap(avax_dai_pool.chainId, eth_dai_pool.id, avax_dai_pool.id, alice.address, 0, 0, lzTxParams, alice.address, "0x")
  64. ).to.revertedWith("Stargate: cannot swap 0")
  65. })
  66. it("swap() - reverts when cp balance is not high enough for swap", async function () {
  67. await addLiquidity(avax_dai_pool, alice, BigNumber.from("10000000"), {})
  68. await addLiquidity(eth_dai_pool, alice, BigNumber.from("10000000"), {})
  69. await equalize(endpoints, bob, false)
  70. // amount sd and ld are the same for this state
  71. const amountSD = BigNumber.from("2500000")
  72. const lzTxParams = { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" }
  73. // mint and swap enough that it makes eqReward larger than the lp fee
  74. await mintAndSwap(avax_dai_pool, eth_dai_pool, bob, amountSD, lzTxParams)
  75. await mintAndSwap(avax_dai_pool, eth_dai_pool, bob, amountSD, lzTxParams)
  76. // when we try to swap the other way the total amount of the chain path, it tries to deduct more than the amountSD
  77. await expect(mintAndSwap(eth_dai_pool, avax_dai_pool, bob, amountSD.mul(3), lzTxParams)).to.revertedWith("Stargate: dst balance too low")
  78. })
  79. it("swap() - reverts when cp balance is not high enough for swap", async function () {
  80. await addLiquidity(avax_dai_pool, alice, BigNumber.from("10000000"), {})
  81. await addLiquidity(eth_dai_pool, alice, BigNumber.from("10000000"), {})
  82. await equalize(endpoints, bob, false)
  83. // amount sd and ld are the same for this state
  84. const amountSD = BigNumber.from("2500000")
  85. const lzTxParams = { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" }
  86. // mint and swap enough that it makes eqReward larger than the lp fee
  87. await mintAndSwap(avax_dai_pool, eth_dai_pool, bob, amountSD, lzTxParams)
  88. await mintAndSwap(avax_dai_pool, eth_dai_pool, bob, amountSD, lzTxParams)
  89. // when we try to swap the other way the total amount of the chain path, it tries to deduct more than the amountSD
  90. await expect(mintAndSwap(eth_dai_pool, avax_dai_pool, bob, amountSD.mul(3), lzTxParams)).to.revertedWith("Stargate: dst balance too low")
  91. })
  92. it("swap() - using loop back mock, revert on sgReceive when paused", async function () {
  93. const minAmountLD = BigNumber.from("1")
  94. const amountLD = BigNumber.from("66")
  95. const loopBackMock = await deployNew("LoopBackMock", [avax_dai_pool.router.address])
  96. const encodedDstNativeAddr = encodePackedParams(["address"], [loopBackMock.address])
  97. const lzTxParams = { dstGasForCall: 500000, dstNativeAmount: 123400, dstNativeAddr: encodedDstNativeAddr }
  98. const payload = encodeParams(["uint256", "uint256"], [avax_dai_pool.id, eth_dai_pool.id])
  99. // give the loop back some ether to transfer via lzTxParams
  100. await alice.sendTransaction({
  101. to: loopBackMock.address,
  102. value: 1000000,
  103. })
  104. // ensure liquidity in pools for swap
  105. await addLiquidity(eth_dai_pool, alice, BigNumber.from("1000000"))
  106. await addLiquidity(avax_dai_pool, alice, BigNumber.from("1000000"))
  107. // update the chain path balances
  108. await equalize(endpoints, bob, false)
  109. // check the loop back has no tokens yet
  110. expect(await avax_dai_pool.token.balanceOf(loopBackMock.address)).to.equal(0)
  111. // give alice tokens to spend
  112. await eth_dai_pool.token.mint(alice.address, amountLD)
  113. // allow the eth router to spend them
  114. await eth_dai_pool.token.connect(alice).increaseAllowance(eth_dai_pool.router.address, amountLD)
  115. expect(await eth_dai_pool.token.balanceOf(alice.address)).to.equal(amountLD)
  116. // allow the loopback mock to send back half of the original amount back
  117. await callAsContract(avax_dai_pool.token, loopBackMock.address, "increaseAllowance(address,uint256)", [
  118. avax_dai_pool.router.address,
  119. amountLD.div(2),
  120. ])
  121. // pause the loop back contract
  122. await loopBackMock.pause(true)
  123. // increase the nonce by 1 because it is the next one
  124. const nonce = (await eth_endpoint.lzEndpoint.inboundNonce(avax_endpoint.chainId, eth_dai_pool.bridge.address)).add(1)
  125. await expect(
  126. eth_dai_pool.router
  127. .connect(alice)
  128. .swap(
  129. avax_dai_pool.chainId,
  130. eth_dai_pool.id,
  131. avax_dai_pool.id,
  132. alice.address,
  133. amountLD,
  134. minAmountLD,
  135. lzTxParams,
  136. loopBackMock.address,
  137. payload
  138. )
  139. ).to.emit(avax_dai_pool.router, "CachedSwapSaved")
  140. // can not clear the swap because it is paused
  141. await expect(avax_dai_pool.router.clearCachedSwap(eth_dai_pool.chainId, eth_dai_pool.bridge.address, nonce)).to.revertedWith(
  142. "Failed sgReceive due to pause"
  143. )
  144. // unpause
  145. await loopBackMock.pause(false)
  146. // unpaused, should clear
  147. await avax_dai_pool.router.clearCachedSwap(eth_dai_pool.chainId, eth_dai_pool.bridge.address, nonce)
  148. expect(await avax_dai_pool.token.balanceOf(loopBackMock.address)).to.equal(amountLD.div(2))
  149. // _srcAddress is where the tokens pass back to after LoopBackMock.sol
  150. expect(await eth_dai_pool.token.balanceOf(eth_dai_pool.bridge.address)).to.equal(amountLD.div(2))
  151. })
  152. it("swap() - using loop back mock, can send on sgReceive", async function () {
  153. const minAmountLD = BigNumber.from("1")
  154. const amountLD = BigNumber.from("66")
  155. const loopBackMock = await deployNew("LoopBackMock", [avax_dai_pool.router.address])
  156. const encodedDstNativeAddr = encodePackedParams(["address"], [loopBackMock.address])
  157. const lzTxParams = { dstGasForCall: 500000, dstNativeAmount: 123400, dstNativeAddr: encodedDstNativeAddr }
  158. const payload = encodeParams(["uint256", "uint256"], [avax_dai_pool.id, eth_dai_pool.id])
  159. // give the loop back some ether to transfer via lzTxParams
  160. await alice.sendTransaction({
  161. to: loopBackMock.address,
  162. value: 1000000,
  163. })
  164. // ensure liquidity in pools for swap
  165. await addLiquidity(eth_dai_pool, alice, BigNumber.from("1000000"))
  166. await addLiquidity(avax_dai_pool, alice, BigNumber.from("1000000"))
  167. // update the chain path balances
  168. await equalize(endpoints, bob, false)
  169. // check the loop back has no tokens yet
  170. expect(await avax_dai_pool.token.balanceOf(loopBackMock.address)).to.equal(0)
  171. // give alice tokens to spend
  172. await eth_dai_pool.token.mint(alice.address, amountLD)
  173. // allow the eth router to spend them
  174. await eth_dai_pool.token.connect(alice).increaseAllowance(eth_dai_pool.router.address, amountLD)
  175. expect(await eth_dai_pool.token.balanceOf(alice.address)).to.equal(amountLD)
  176. // allow the loopback mock to send back half of the original amount back
  177. await callAsContract(avax_dai_pool.token, loopBackMock.address, "increaseAllowance(address,uint256)", [
  178. avax_dai_pool.router.address,
  179. amountLD.div(2),
  180. ])
  181. // initial swap tx can call send again and loop back in the same tx
  182. await expect(
  183. eth_dai_pool.router
  184. .connect(alice)
  185. .swap(
  186. avax_dai_pool.chainId,
  187. eth_dai_pool.id,
  188. avax_dai_pool.id,
  189. alice.address,
  190. amountLD,
  191. minAmountLD,
  192. lzTxParams,
  193. loopBackMock.address,
  194. payload
  195. )
  196. )
  197. .to.emit(loopBackMock, "LoopBack")
  198. .withArgs(eth_dai_pool.bridge.address.toLowerCase(), avax_dai_pool.id, eth_dai_pool.id, amountLD.div(2))
  199. expect(await avax_dai_pool.token.balanceOf(loopBackMock.address)).to.equal(amountLD.div(2))
  200. // _srcAddress is where the tokens pass back to after LoopBackMock.sol
  201. expect(await eth_dai_pool.token.balanceOf(eth_dai_pool.bridge.address)).to.equal(amountLD.div(2))
  202. })
  203. describe("LP pools are filled and fees set:", async function () {
  204. const amount = BigNumber.from("100000")
  205. beforeEach(async function () {
  206. // setting these fees allow delta credit to accumulate
  207. avax_endpoint.router.setFees(avax_dai_pool.id, 2)
  208. eth_endpoint.router.setFees(eth_dai_pool.id, 2)
  209. avax_endpoint.router.setDeltaParam(
  210. avax_dai_pool.id,
  211. true,
  212. 500, // 5%
  213. 500, // 5%
  214. true, //default
  215. true //default
  216. )
  217. eth_endpoint.router.setDeltaParam(
  218. eth_dai_pool.id,
  219. true,
  220. 500, // 5%
  221. 500, // 5%
  222. true, //default
  223. true //default
  224. )
  225. await addLiquidity(avax_dai_pool, alice, amount)
  226. await addLiquidity(eth_dai_pool, alice, amount)
  227. await addLiquidity(avax_dai_pool, bob, amount)
  228. await addLiquidity(avax_dai_pool, bob, amount)
  229. await equalize(endpoints, bob, false)
  230. await mintAndSwap(avax_dai_pool, eth_dai_pool, bob, BigNumber.from(123), {}, false)
  231. })
  232. it("delta() - run a series of tests NOT in fullMode and ensure audit still works", async function () {
  233. const bigAmount = BigNumber.from("100000000000000")
  234. // turn full mode off
  235. avax_endpoint.router.setDeltaParam(
  236. avax_dai_pool.id,
  237. true,
  238. 500, // 5%
  239. 500, // 5%
  240. false, // non-default
  241. false // non-default
  242. )
  243. eth_endpoint.router.setDeltaParam(
  244. eth_dai_pool.id,
  245. true,
  246. 500, // 5%
  247. 500, // 5%
  248. false, // non-default
  249. false // non-default
  250. )
  251. // arbitrary set of actions
  252. await addLiquidity(avax_dai_pool, alice, bigAmount)
  253. await addLiquidity(eth_dai_pool, alice, bigAmount)
  254. await addLiquidity(avax_dai_pool, bob, bigAmount)
  255. await addLiquidity(eth_dai_pool, bob, bigAmount)
  256. await addLiquidity(avax_usdc_pool, bob, bigAmount)
  257. await addLiquidity(eth_usdc_pool, bob, bigAmount)
  258. await equalize(endpoints, bob, false)
  259. await mintAndSwap(avax_dai_pool, eth_dai_pool, bob, BigNumber.from(10000000000000), {})
  260. await removeLiquidityRemote(eth_dai_pool, avax_dai_pool, alice, BigNumber.from(100000000000))
  261. await mintAndSwap(eth_dai_pool, avax_dai_pool, alice, BigNumber.from(10000000000000), {})
  262. await mintAndSwap(avax_dai_pool, eth_dai_pool, bob, BigNumber.from(10000000000000), {})
  263. await mintAndSwap(avax_dai_pool, eth_usdc_pool, bob, BigNumber.from(10000000000000), {})
  264. await removeLiquidityRemote(eth_dai_pool, avax_usdc_pool, alice, BigNumber.from(100000000000))
  265. await audit(endpoints, pools)
  266. })
  267. it("redeemRemote() - add eqReward to the deltaCredits", async function () {
  268. const bigAmount = BigNumber.from("100000000000000")
  269. await addLiquidity(avax_dai_pool, alice, bigAmount)
  270. await addLiquidity(eth_dai_pool, alice, bigAmount)
  271. await equalize(endpoints, bob, false)
  272. // create an eq fee reward deficit so reward is generated on remove liquidity remote
  273. await mintAndSwap(avax_dai_pool, eth_dai_pool, bob, BigNumber.from(10000000000000), {}, true)
  274. const deltaCredit = await eth_dai_pool.pool.deltaCredit()
  275. await removeLiquidityRemote(eth_dai_pool, avax_dai_pool, alice, BigNumber.from(100000000000))
  276. // delta credits increases because the eq reward is added to it
  277. expect((await eth_dai_pool.pool.deltaCredit()).gt(deltaCredit)).to.equal(true)
  278. })
  279. it("redeemRemote() - nativeGasParams blocks", async function () {
  280. const nativeAmt = 453
  281. const encodedDstNativeAddr = encodePackedParams(["address"], [alice.address])
  282. const lzTxParams = { dstGasForCall: 0, dstNativeAmount: nativeAmt, dstNativeAddr: encodedDstNativeAddr }
  283. const dstChainId = eth_endpoint.chainId
  284. // remove gas object so it gets stored properly, Passing ZERO_ADDRESS causes it to revert
  285. await expect(
  286. avax_endpoint.router.connect(bob).redeemRemote(dstChainId, DAI, DAI, bob.address, 1000, 1, ZERO_ADDRESS, lzTxParams)
  287. ).to.be.revertedWith("NativeGasParams check")
  288. })
  289. it("redeemRemote() - calls delta when lpDeltaBP is 0", async function () {
  290. avax_endpoint.router.setDeltaParam(
  291. avax_dai_pool.id,
  292. true,
  293. 0, // 0%
  294. 0, // 0%
  295. true, //default
  296. true //default
  297. )
  298. const deltaCredit = await avax_dai_pool.pool.deltaCredit()
  299. await removeLiquidityRemote(avax_dai_pool, eth_dai_pool, bob, BigNumber.from(1))
  300. expect((await avax_dai_pool.pool.deltaCredit()).lt(deltaCredit)).to.equal(true)
  301. })
  302. it("redeemRemote() - reverts when not enough lp", async function () {
  303. await expect(
  304. callAsContract(avax_dai_pool.pool, avax_endpoint.router.address, "redeemRemote(uint16,uint256,address,uint256)", [
  305. eth_dai_pool.chainId,
  306. eth_dai_pool.id,
  307. fakeContract.address,
  308. amount.add(1),
  309. ])
  310. ).to.revertedWith("Stargate: not enough LP tokens to burn")
  311. })
  312. it("redeemRemote() - lzTxParams transfers extra gas", async function () {
  313. const nativeAmt = 453
  314. const encodedDstNativeAddr = encodePackedParams(["address"], [alice.address])
  315. const lzTxParams = { dstGasForCall: 0, dstNativeAmount: nativeAmt, dstNativeAddr: encodedDstNativeAddr }
  316. await expect(removeLiquidityRemote(avax_dai_pool, eth_dai_pool, bob, BigNumber.from("50"), lzTxParams)).to.be.revertedWith(
  317. "NativeGasParams check"
  318. )
  319. })
  320. it("redeemLocal()", async function () {
  321. const encodedDstNativeAddr = encodePackedParams(["address"], [alice.address])
  322. const lzTxParams = { dstGasForCall: 1000000, dstNativeAmount: 0, dstNativeAddr: encodedDstNativeAddr }
  323. await removeLiquidityLocal(avax_dai_pool, eth_dai_pool, alice, BigNumber.from("200"), lzTxParams, [], [], false)
  324. })
  325. it("redeemLocal() - lzTxParams transfers extra gas", async function () {
  326. const nativeAmt = 453
  327. const encodedDstNativeAddr = encodePackedParams(["address"], [alice.address])
  328. const lzTxParams = { dstGasForCall: 0, dstNativeAmount: nativeAmt, dstNativeAddr: encodedDstNativeAddr }
  329. const srcChainId = avax_endpoint.chainId
  330. const dstChainId = eth_endpoint.chainId
  331. await expect(
  332. avax_endpoint.router.connect(bob).redeemLocal(dstChainId, DAI, DAI, bob.address, 1000, bob.address, lzTxParams)
  333. ).to.be.revertedWith("NativeGasParams check")
  334. // nonce is actually the next one, hence +1
  335. const expectedNonce = (await avax_dai_pool.lzEndpoint.outboundNonce(dstChainId, avax_dai_pool.bridge.address)).add(1)
  336. // remove gas object so it gets stored properly, then try to send it back the other way and hopefully get a revert
  337. await expect(avax_endpoint.router.connect(bob).redeemLocal(dstChainId, DAI, DAI, bob.address, 1000, bob.address, emptyLzTxObj))
  338. .to.emit(eth_endpoint.router, "RevertRedeemLocal")
  339. .withArgs(srcChainId, DAI, DAI, bob.address.toLowerCase(), 1000, 0, expectedNonce, avax_dai_pool.bridge.address.toLowerCase())
  340. await expect(
  341. eth_endpoint.router
  342. .connect(bob)
  343. .revertRedeemLocal(srcChainId, avax_endpoint.bridge.address, expectedNonce, bob.address, lzTxParams)
  344. ).to.be.revertedWith("NativeGasParams check")
  345. })
  346. it("retryRevert() - reverts when you try to send an invalid function", async function () {
  347. const srcChainId = avax_endpoint.chainId
  348. const dstChainId = eth_endpoint.chainId
  349. // remove gas object so it gets stored properly, then try to send it back the other way and hopefully get a revert
  350. await expect(avax_endpoint.router.connect(bob).redeemLocal(dstChainId, DAI, DAI, bob.address, 1000, bob.address, emptyLzTxObj))
  351. // nonce is actually the next one, hence +1
  352. const expectedNonce = (await avax_dai_pool.lzEndpoint.outboundNonce(dstChainId, avax_dai_pool.bridge.address)).add(1)
  353. await expect(
  354. eth_endpoint.router.connect(bob).retryRevert(srcChainId, avax_endpoint.bridge.address, expectedNonce)
  355. ).to.be.revertedWith("Stargate: invalid function type")
  356. })
  357. it("addLiquidity() - reverts when the safeTransferFrom fails", async function () {
  358. // pause transfers
  359. await avax_dai_pool.token.pauseTransfers(true)
  360. await expect(avax_dai_pool.router.addLiquidity(avax_dai_pool.id, 1, bob.address)).to.revertedWith("Stargate: TRANSFER_FROM_FAILED")
  361. })
  362. it("redeemLocalCheckOnRemote() - stores a payload on failed msg, then clears upon pool creation", async function () {
  363. const nonce = 1
  364. const srcChainId = 5
  365. const srcPoolId = 55
  366. const amountSD = BigNumber.from(69) // always keep it
  367. // simulate a situation where the user has already burned these lp tokens on the source side, and we are trying to complete the cycle on the other
  368. await expect(
  369. callAsContract(
  370. avax_dai_pool.router,
  371. avax_endpoint.bridge.address,
  372. "redeemLocalCheckOnRemote(uint16,bytes,uint256,uint256,uint256,uint256,bytes)",
  373. [srcChainId, eth_dai_pool.bridge.address, nonce, srcPoolId, avax_dai_pool.id, amountSD, bob.address]
  374. )
  375. )
  376. .to.emit(avax_dai_pool.router, "Revert")
  377. .withArgs(TYPE_REDEEM_LOCAL_RESPONSE, srcChainId, eth_dai_pool.bridge.address.toLowerCase(), nonce)
  378. // create and setup a pool for this to be sent to now
  379. await avax_endpoint.router.createChainPath(avax_dai_pool.id, srcChainId, srcPoolId, 1)
  380. await avax_endpoint.router.activateChainPath(avax_dai_pool.id, srcChainId, srcPoolId)
  381. await avax_endpoint.bridge.setBridge(srcChainId, eth_endpoint.bridge.address)
  382. await eth_endpoint.router.createPool(srcPoolId, eth_dai_pool.token.address, 18, 18, "x", "xx")
  383. await eth_endpoint.router.createChainPath(srcPoolId, avax_dai_pool.chainId, avax_dai_pool.id, 1)
  384. await eth_endpoint.router.activateChainPath(srcPoolId, avax_dai_pool.chainId, avax_dai_pool.id)
  385. const srcPool = await getPoolFromFactory(eth_endpoint.factory, srcPoolId)
  386. await expect(avax_dai_pool.router.retryRevert(srcChainId, eth_endpoint.bridge.address, nonce)).to.revertedWith(
  387. "Stargate: invalid function type"
  388. )
  389. // is a stored revert
  390. expect(await avax_dai_pool.router.revertLookup(srcChainId, eth_endpoint.bridge.address, nonce)).to.not.equal("0x")
  391. const userBalance = await srcPool.balanceOf(bob.address)
  392. // revert - the swap amount has been set to 0, because the deduction of chain path balances on the remote side failed
  393. // the full amount of lp that was attempted to burn, is minted back to the user, as if nothing happened
  394. await expect(
  395. avax_dai_pool.router.revertRedeemLocal(srcChainId, eth_endpoint.bridge.address, nonce, fakeContract.address, emptyLzTxObj)
  396. )
  397. .to.emit(srcPool, "RedeemLocalCallback")
  398. .withArgs(bob.address, 0, amountSD)
  399. // user gets minted back the full amount upon completion
  400. expect(await srcPool.balanceOf(bob.address)).to.equal(userBalance.add(amountSD))
  401. // make sure the payload is cleared
  402. expect(await avax_dai_pool.router.revertLookup(srcChainId, eth_endpoint.bridge.address, nonce)).to.equal("0x")
  403. // can not send another one
  404. await expect(avax_dai_pool.router.retryRevert(srcChainId, eth_endpoint.bridge.address, nonce)).to.revertedWith(
  405. "Stargate: no retry revert"
  406. )
  407. })
  408. it("redeemLocalCallback() - stores a payload on failed msg", async function () {
  409. const nonce = 1
  410. // pause transfers
  411. await avax_dai_pool.token.pauseTransfers(true)
  412. await expect(
  413. callAsContract(
  414. avax_dai_pool.router,
  415. avax_endpoint.bridge.address,
  416. "redeemLocalCallback(uint16,bytes,uint256,uint256,uint256,address,uint256,uint256)",
  417. [eth_dai_pool.chainId, eth_dai_pool.bridge.address, nonce, eth_dai_pool.id, avax_dai_pool.id, bob.address, 1, 1]
  418. )
  419. )
  420. .to.emit(avax_dai_pool.router, "Revert")
  421. .withArgs(TYPE_REDEEM_LOCAL_CALLBACK_RETRY, eth_dai_pool.chainId, eth_dai_pool.bridge.address.toLowerCase(), nonce)
  422. await expect(
  423. avax_dai_pool.router.revertRedeemLocal(
  424. eth_dai_pool.chainId,
  425. eth_endpoint.bridge.address,
  426. nonce,
  427. fakeContract.address,
  428. emptyLzTxObj
  429. )
  430. ).to.revertedWith("Stargate: invalid function type")
  431. // unpause transfers and revert
  432. await avax_dai_pool.token.pauseTransfers(false)
  433. await avax_dai_pool.router.retryRevert(eth_dai_pool.chainId, eth_endpoint.bridge.address, nonce)
  434. // make sure the payload is cleared
  435. expect(await avax_dai_pool.router.revertLookup(eth_dai_pool.chainId, eth_endpoint.bridge.address, nonce)).to.equal("0x")
  436. // can not send another one
  437. await expect(avax_dai_pool.router.retryRevert(eth_dai_pool.chainId, eth_endpoint.bridge.address, nonce)).to.revertedWith(
  438. "Stargate: no retry revert"
  439. )
  440. })
  441. it("swapRemote() - stores a payload on failed msg", async function () {
  442. const nonce = 1
  443. // pause transfers
  444. await avax_dai_pool.token.pauseTransfers(true)
  445. await expect(
  446. callAsContract(
  447. avax_dai_pool.router,
  448. avax_endpoint.bridge.address,
  449. "swapRemote(uint16,bytes,uint256,uint256,uint256,uint256,address,(uint256,uint256,uint256,uint256,uint256,uint256),bytes)",
  450. [
  451. avax_dai_pool.chainId,
  452. avax_dai_pool.bridge.address,
  453. nonce,
  454. avax_dai_pool.id,
  455. eth_dai_pool.id,
  456. 0,
  457. bob.address,
  458. defaultSwapObj,
  459. "0x",
  460. ]
  461. )
  462. )
  463. .to.emit(avax_dai_pool.router, "Revert")
  464. .withArgs(TYPE_SWAP_REMOTE_RETRY, avax_dai_pool.chainId, avax_dai_pool.bridge.address.toLowerCase(), nonce)
  465. await expect(
  466. avax_dai_pool.router.revertRedeemLocal(
  467. avax_dai_pool.chainId,
  468. avax_endpoint.bridge.address,
  469. nonce,
  470. fakeContract.address,
  471. emptyLzTxObj
  472. )
  473. ).to.revertedWith("Stargate: invalid function type")
  474. // unpause transfers and revert
  475. await avax_dai_pool.token.pauseTransfers(false)
  476. await avax_dai_pool.router.retryRevert(avax_dai_pool.chainId, avax_endpoint.bridge.address, nonce)
  477. // make sure the payload is cleared
  478. expect(await avax_dai_pool.router.revertLookup(avax_dai_pool.chainId, avax_endpoint.bridge.address, nonce)).to.equal("0x")
  479. // can not send another one
  480. await expect(avax_dai_pool.router.retryRevert(avax_dai_pool.chainId, avax_endpoint.bridge.address, nonce)).to.revertedWith(
  481. "Stargate: no retry revert"
  482. )
  483. })
  484. it("instantRedeemLocal() - redeems less than the cap", async function () {
  485. const deltaCredit = await avax_dai_pool.pool.deltaCredit()
  486. const userBal = await avax_dai_pool.token.balanceOf(bob.address)
  487. await removeLiquidityInstant(avax_dai_pool, bob, deltaCredit.sub(1))
  488. expect(await avax_dai_pool.token.balanceOf(bob.address)).to.equal(userBal.add(deltaCredit.sub(1)))
  489. })
  490. it("instantRedeemLocal() - only burns/redeems the cap", async function () {
  491. const deltaCredit = await avax_dai_pool.pool.deltaCredit()
  492. const userBal = await avax_dai_pool.token.balanceOf(bob.address)
  493. // try to redeem 1 more than delta credits, should only get the max amount od delta credits back
  494. await removeLiquidityInstant(avax_dai_pool, bob, deltaCredit.add(1))
  495. expect(await avax_dai_pool.token.balanceOf(bob.address)).to.equal(userBal.add(deltaCredit))
  496. })
  497. it("instantRedeemLocal() - reverts when from address is 0x0", async function () {
  498. await addLiquidity(avax_dai_pool, alice, BigNumber.from(100))
  499. await expect(
  500. callAsContract(avax_dai_pool.pool, avax_dai_pool.router.address, "instantRedeemLocal(address,uint256,address)", [
  501. ZERO_ADDRESS,
  502. 1,
  503. alice.address,
  504. ])
  505. ).to.revertedWith("Stargate: _from cannot be 0x0")
  506. })
  507. it("redeemLocalCallback() - mints to user", async function () {
  508. const amountSD = BigNumber.from(1000)
  509. const amountToMintSD = BigNumber.from(435)
  510. let amountLP = amountLDtoSD(amountSDtoLD(amountToMintSD, avax_dai_pool), avax_dai_pool)
  511. const { totalSupply, totalLiquidity } = await getPoolState(avax_dai_pool)
  512. if (totalSupply.gt(0)) amountLP = amountLP.mul(totalSupply).div(totalLiquidity)
  513. const userLpBalance = await avax_dai_pool.pool.balanceOf(bob.address)
  514. await expect(
  515. callAsContract(avax_dai_pool.pool, avax_endpoint.router.address, "redeemLocalCallback(uint16,uint256,address,uint256,uint256)", [
  516. eth_dai_pool.chainId,
  517. eth_dai_pool.id,
  518. bob.address,
  519. amountSD,
  520. amountToMintSD,
  521. ])
  522. )
  523. .to.emit(avax_dai_pool.pool, "RedeemLocalCallback")
  524. .withArgs(bob.address, amountSD, amountToMintSD)
  525. // make sure the user got the expected amount of lp
  526. expect(await avax_dai_pool.pool.balanceOf(bob.address)).to.equal(userLpBalance.add(amountLP))
  527. })
  528. it("redeemLocal() - reverts when amountSD is 0", async function () {
  529. const storageLocationTotalLiquidity = "0xe" // position in contract storage
  530. const setTotalLiquidityValue = "0x0000000000000000000000000000000000000000000000000000000000000001" // totalLiquidity = 1
  531. // set totalLiquidity to 1
  532. await network.provider.send("hardhat_setStorageAt", [
  533. avax_dai_pool.pool.address,
  534. storageLocationTotalLiquidity,
  535. setTotalLiquidityValue,
  536. ])
  537. await expect(callRedeemLocal(avax_dai_pool, eth_dai_pool, bob, 1000, emptyLzTxObj)).to.revertedWith(
  538. "Stargate: not enough lp to redeem with amountSD"
  539. )
  540. })
  541. })
  542. it("redeemLocal() - reverts when lp isnt enough", async function () {
  543. const nativeAmt = 100
  544. const encodedDstNativeAddr = encodePackedParams(["address"], [alice.address])
  545. const lzTxParams = { dstGasForCall: 0, dstNativeAmount: nativeAmt, dstNativeAddr: encodedDstNativeAddr }
  546. await addLiquidity(avax_dai_pool, alice, BigNumber.from("1"))
  547. await expect(removeLiquidityLocal(avax_dai_pool, eth_dai_pool, alice, BigNumber.from("0"), lzTxParams)).to.be.revertedWith(
  548. "Stargate: not enough lp to redeem"
  549. )
  550. })
  551. it("instantRedeemLocal() - reverts when totalLiquidity is 0", async function () {
  552. await expect(avax_dai_pool.router.connect(alice).instantRedeemLocal(avax_dai_pool.id, 1, ZERO_ADDRESS)).to.revertedWith(
  553. "Stargate: cant convert SDtoLP when totalLiq == 0'"
  554. )
  555. })
  556. })