|
| 1 | +import { expect } from "chai"; |
| 2 | +import { Contract } from "ethers"; |
| 3 | +import { ethers } from "hardhat"; |
| 4 | +import { SignerWithAddress, toBNWei, seedWallet, toWei } from "./utils"; |
| 5 | +import * as consts from "./constants"; |
| 6 | +import { hubPoolFixture, enableTokensForLP } from "./HubPool.Fixture"; |
| 7 | +import { buildPoolRebalanceTree, buildPoolRebalanceLeafs } from "./MerkleLib.utils"; |
| 8 | + |
| 9 | +let hubPool: Contract, weth: Contract, timer: Contract; |
| 10 | +let owner: SignerWithAddress, dataWorker: SignerWithAddress, liquidityProvider: SignerWithAddress; |
| 11 | + |
| 12 | +async function constructSimpleTree() { |
| 13 | + const wethSendToL2 = toBNWei(100); |
| 14 | + const wethAttributeToLps = toBNWei(10); |
| 15 | + const leafs = buildPoolRebalanceLeafs( |
| 16 | + [consts.repaymentChainId], // repayment chain. In this test we only want to send one token to one chain. |
| 17 | + [weth], // l1Token. We will only be sending WETH and DAI to the associated repayment chain. |
| 18 | + [[wethAttributeToLps]], // bundleLpFees. Set to 1 ETH and 10 DAI respectively to attribute to the LPs. |
| 19 | + [[wethSendToL2]], // netSendAmounts. Set to 100 ETH and 1000 DAI as the amount to send from L1->L2. |
| 20 | + [[wethSendToL2]] // runningBalances. Set to 100 ETH and 1000 DAI. |
| 21 | + ); |
| 22 | + const tree = await buildPoolRebalanceTree(leafs); |
| 23 | + |
| 24 | + return { wethSendToL2, wethAttributeToLps, leafs, tree }; |
| 25 | +} |
| 26 | + |
| 27 | +describe("HubPool LP fees", function () { |
| 28 | + beforeEach(async function () { |
| 29 | + [owner, dataWorker, liquidityProvider] = await ethers.getSigners(); |
| 30 | + ({ weth, hubPool, timer } = await hubPoolFixture()); |
| 31 | + await seedWallet(dataWorker, [], weth, consts.bondAmount.add(consts.finalFee).mul(2)); |
| 32 | + await seedWallet(liquidityProvider, [], weth, consts.amountToLp.mul(10)); |
| 33 | + |
| 34 | + await enableTokensForLP(owner, hubPool, weth, [weth]); |
| 35 | + await weth.connect(liquidityProvider).approve(hubPool.address, consts.amountToLp); |
| 36 | + await hubPool.connect(liquidityProvider).addLiquidity(weth.address, consts.amountToLp); |
| 37 | + await weth.connect(dataWorker).approve(hubPool.address, consts.bondAmount.mul(10)); |
| 38 | + }); |
| 39 | + |
| 40 | + it("Fee tracking variables are correctly updated at the execution of a refund", async function () { |
| 41 | + // Before any execution happens liquidity trackers are set as expected. |
| 42 | + const pooledTokenInfoPreExecution = await hubPool.pooledTokens(weth.address); |
| 43 | + expect(pooledTokenInfoPreExecution.liquidReserves).to.eq(consts.amountToLp); |
| 44 | + expect(pooledTokenInfoPreExecution.utilizedReserves).to.eq(0); |
| 45 | + expect(pooledTokenInfoPreExecution.undistributedLpFees).to.eq(0); |
| 46 | + expect(pooledTokenInfoPreExecution.lastLpFeeUpdate).to.eq(await timer.getCurrentTime()); |
| 47 | + expect(pooledTokenInfoPreExecution.isWeth).to.eq(true); |
| 48 | + |
| 49 | + const { wethSendToL2, wethAttributeToLps, leafs, tree } = await constructSimpleTree(); |
| 50 | + |
| 51 | + await hubPool.connect(dataWorker).initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot); |
| 52 | + await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); |
| 53 | + await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); |
| 54 | + |
| 55 | + // Validate the post execution values have updated as expected. Liquid reserves should be the original LPed amount |
| 56 | + // minus the amount sent to L2. Utilized reserves should be the amount sent to L2 plus the attribute to LPs. |
| 57 | + // Undistributed LP fees should be attribute to LPs. |
| 58 | + const pooledTokenInfoPostExecution = await hubPool.pooledTokens(weth.address); |
| 59 | + expect(pooledTokenInfoPostExecution.liquidReserves).to.eq(consts.amountToLp.sub(wethSendToL2)); |
| 60 | + expect(pooledTokenInfoPostExecution.utilizedReserves).to.eq(wethSendToL2.add(wethAttributeToLps)); |
| 61 | + expect(pooledTokenInfoPostExecution.undistributedLpFees).to.eq(wethAttributeToLps); |
| 62 | + }); |
| 63 | + |
| 64 | + it("Exchange rate current correctly attributes fees over the smear period", async function () { |
| 65 | + // Fees are designed to be attributed over a period of time so they dont all arrive on L1 as soon as the bundle is |
| 66 | + // executed. We can validate that fees are correctly smeared by attributing some and then moving time forward and |
| 67 | + // validating that key variable shift as a function of time. |
| 68 | + const { leafs, tree } = await constructSimpleTree(); |
| 69 | + |
| 70 | + // Exchange rate current before any fees are attributed execution should be 1. |
| 71 | + expect(await hubPool.callStatic.exchangeRateCurrent(weth.address)).to.eq(toWei(1)); |
| 72 | + await hubPool.exchangeRateCurrent(weth.address); |
| 73 | + |
| 74 | + await hubPool.connect(dataWorker).initiateRelayerRefund([3117], 1, tree.getHexRoot(), consts.mockTreeRoot); |
| 75 | + await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness); |
| 76 | + await hubPool.connect(dataWorker).executeRelayerRefund(leafs[0], tree.getHexProof(leafs[0])); |
| 77 | + |
| 78 | + // Exchange rate current right after the refund execution should be the amount deposited, grown by the 100 second |
| 79 | + // liveness period. Of the 10 ETH attributed to LPs, a total of 10*0.0000015*100=0.0015 was attributed to LPs. |
| 80 | + // The exchange rate is therefore (1000+0.0015)/1000=1.0000015. |
| 81 | + expect((await hubPool.callStatic.exchangeRateCurrent(weth.address)).toString()).to.eq(toWei(1.0000015)); |
| 82 | + |
| 83 | + // Validate the state variables are updated accordingly. In particular, undistributedLpFees should have decremented |
| 84 | + // by the amount allocated in the previous computation. This should be 10-0.0015=9.9985. |
| 85 | + await hubPool.exchangeRateCurrent(weth.address); // force state sync. |
| 86 | + expect((await hubPool.pooledTokens(weth.address)).undistributedLpFees).to.eq(toWei(9.9985)); |
| 87 | + |
| 88 | + // Next, advance time 2 days. Compute the ETH attributed to LPs by multiplying the original amount allocated(10), |
| 89 | + // minus the previous computation amount(0.0015) by the smear rate, by the duration to get the second periods |
| 90 | + // allocation of(10 - 0.0015) * 0.0000015 * (172800)=2.5916112.The exchange rate should be The sum of the |
| 91 | + // liquidity provided and the fees added in both periods as (1000+0.0015+2.5916112)/1000=1.0025931112. |
| 92 | + await timer.setCurrentTime(Number(await timer.getCurrentTime()) + 2 * 24 * 60 * 60); |
| 93 | + expect((await hubPool.callStatic.exchangeRateCurrent(weth.address)).toString()).to.eq(toWei(1.0025931112)); |
| 94 | + |
| 95 | + // Again, we can validate that the undistributedLpFees have been updated accordingly. This should be set to the |
| 96 | + // original amount (10) minus the two sets of attributed LP fees as 10-0.0015-2.5916112=7.4068888. |
| 97 | + await hubPool.exchangeRateCurrent(weth.address); // force state sync. |
| 98 | + expect((await hubPool.pooledTokens(weth.address)).undistributedLpFees).to.eq(toWei(7.4068888)); |
| 99 | + |
| 100 | + // Finally, advance time past the end of the smear period by moving forward 10 days. At this point all LP fees |
| 101 | + // should be attributed such that undistributedLpFees=0 and the exchange rate should simply be (1000+10)/1000=1.01. |
| 102 | + await timer.setCurrentTime(Number(await timer.getCurrentTime()) + 10 * 24 * 60 * 60); |
| 103 | + expect((await hubPool.callStatic.exchangeRateCurrent(weth.address)).toString()).to.eq(toWei(1.01)); |
| 104 | + await hubPool.exchangeRateCurrent(weth.address); // force state sync. |
| 105 | + expect((await hubPool.pooledTokens(weth.address)).undistributedLpFees).to.eq(toWei(0)); |
| 106 | + }); |
| 107 | +}); |
0 commit comments