|
| 1 | +import "module-alias/register"; |
| 2 | + |
| 3 | +import { BigNumber } from "@ethersproject/bignumber"; |
| 4 | +import { ethers } from "hardhat"; |
| 5 | + |
| 6 | +import { Address, Bytes } from "@utils/types"; |
| 7 | +import { Account } from "@utils/test/types"; |
| 8 | +import { IERC20, UniswapV2Router02 } from "@typechain/index"; |
| 9 | +import { |
| 10 | + SetToken, |
| 11 | + TradeModule, |
| 12 | + ManagerIssuanceHookMock, |
| 13 | + UniswapV2ExchangeAdapterV2, |
| 14 | +} from "@utils/contracts"; |
| 15 | +import { ZERO } from "@utils/constants"; |
| 16 | +import DeployHelper from "@utils/deploys"; |
| 17 | +import { |
| 18 | + ether, |
| 19 | + bitcoin, |
| 20 | +} from "@utils/index"; |
| 21 | +import { |
| 22 | + cacheBeforeEach, |
| 23 | + getAccounts, |
| 24 | + getSystemFixture, |
| 25 | + getWaffleExpect, |
| 26 | + getUniswapFixture, |
| 27 | + getForkedTokens, |
| 28 | + initializeForkedTokens, |
| 29 | + ForkedTokens |
| 30 | +} from "@utils/test/index"; |
| 31 | + |
| 32 | +import { SystemFixture, UniswapFixture } from "@utils/fixtures"; |
| 33 | + |
| 34 | +const expect = getWaffleExpect(); |
| 35 | + |
| 36 | +describe("SushiSwap TradeModule Integration [ @forked-mainnet ]", () => { |
| 37 | + let owner: Account; |
| 38 | + let manager: Account; |
| 39 | + |
| 40 | + let deployer: DeployHelper; |
| 41 | + |
| 42 | + let uniswapExchangeAdapterV2: UniswapV2ExchangeAdapterV2; |
| 43 | + let uniswapAdapterV2Name: string; |
| 44 | + |
| 45 | + let setup: SystemFixture; |
| 46 | + let sushiswapSetup: UniswapFixture; |
| 47 | + let sushiswapRouter: UniswapV2Router02; |
| 48 | + let tradeModule: TradeModule; |
| 49 | + let tokens: ForkedTokens; |
| 50 | + let wbtcRate: BigNumber; |
| 51 | + |
| 52 | + before(async () => { |
| 53 | + [ |
| 54 | + owner, |
| 55 | + manager, |
| 56 | + ] = await getAccounts(); |
| 57 | + |
| 58 | + |
| 59 | + deployer = new DeployHelper(owner.wallet); |
| 60 | + setup = getSystemFixture(owner.address); |
| 61 | + await setup.initialize(); |
| 62 | + |
| 63 | + wbtcRate = ether(29); |
| 64 | + |
| 65 | + sushiswapSetup = getUniswapFixture(owner.address); |
| 66 | + sushiswapRouter = sushiswapSetup.getForkedSushiswapRouter(); |
| 67 | + |
| 68 | + uniswapExchangeAdapterV2 = await deployer.adapters.deployUniswapV2ExchangeAdapterV2(sushiswapRouter.address); |
| 69 | + uniswapAdapterV2Name = "UNISWAPV2"; |
| 70 | + |
| 71 | + tradeModule = await deployer.modules.deployTradeModule(setup.controller.address); |
| 72 | + await setup.controller.addModule(tradeModule.address); |
| 73 | + |
| 74 | + await setup.integrationRegistry.addIntegration( |
| 75 | + tradeModule.address, |
| 76 | + uniswapAdapterV2Name, |
| 77 | + uniswapExchangeAdapterV2.address |
| 78 | + ); |
| 79 | + |
| 80 | + await initializeForkedTokens(deployer); |
| 81 | + }); |
| 82 | + |
| 83 | + describe("#trade", function() { |
| 84 | + let sourceToken: IERC20; |
| 85 | + let wbtcUnits: BigNumber; |
| 86 | + let destinationToken: IERC20; |
| 87 | + let setToken: SetToken; |
| 88 | + let issueQuantity: BigNumber; |
| 89 | + |
| 90 | + context("when trading a Default component on Sushiswap (version 2 adapter)", async () => { |
| 91 | + let mockPreIssuanceHook: ManagerIssuanceHookMock; |
| 92 | + let sourceTokenQuantity: BigNumber; |
| 93 | + let destinationTokenQuantity: BigNumber; |
| 94 | + |
| 95 | + let subjectDestinationToken: Address; |
| 96 | + let subjectSourceToken: Address; |
| 97 | + let subjectSourceQuantity: BigNumber; |
| 98 | + let subjectAdapterName: string; |
| 99 | + let subjectSetToken: Address; |
| 100 | + let subjectMinDestinationQuantity: BigNumber; |
| 101 | + let subjectData: Bytes; |
| 102 | + let subjectCaller: Account; |
| 103 | + |
| 104 | + cacheBeforeEach(async () => { |
| 105 | + tokens = getForkedTokens(); |
| 106 | + |
| 107 | + sourceToken = tokens.wbtc; |
| 108 | + destinationToken = tokens.weth; |
| 109 | + wbtcUnits = BigNumber.from(100000000); // 1 WBTC in base units 1 * 10 ** 8 |
| 110 | + |
| 111 | + // Create Set token |
| 112 | + setToken = await setup.createSetToken( |
| 113 | + [sourceToken.address], |
| 114 | + [wbtcUnits], |
| 115 | + [setup.issuanceModule.address, tradeModule.address], |
| 116 | + manager.address |
| 117 | + ); |
| 118 | + |
| 119 | + await sourceToken.approve(sushiswapRouter.address, bitcoin(100)); |
| 120 | + await destinationToken.approve(sushiswapRouter.address, ether(3400)); |
| 121 | + |
| 122 | + tradeModule = tradeModule.connect(manager.wallet); |
| 123 | + await tradeModule.initialize(setToken.address); |
| 124 | + |
| 125 | + sourceTokenQuantity = wbtcUnits; |
| 126 | + const sourceTokenDecimals = 8; |
| 127 | + destinationTokenQuantity = wbtcRate.mul(sourceTokenQuantity).div(10 ** sourceTokenDecimals); |
| 128 | + |
| 129 | + // Transfer from wbtc whale to manager |
| 130 | + await sourceToken.transfer(manager.address, wbtcUnits.mul(1)); |
| 131 | + |
| 132 | + // Approve tokens to Controller and call issue |
| 133 | + sourceToken = sourceToken.connect(manager.wallet); |
| 134 | + await sourceToken.approve(setup.issuanceModule.address, ethers.constants.MaxUint256); |
| 135 | + |
| 136 | + // Deploy mock issuance hook and initialize issuance module |
| 137 | + setup.issuanceModule = setup.issuanceModule.connect(manager.wallet); |
| 138 | + mockPreIssuanceHook = await deployer.mocks.deployManagerIssuanceHookMock(); |
| 139 | + await setup.issuanceModule.initialize(setToken.address, mockPreIssuanceHook.address); |
| 140 | + |
| 141 | + issueQuantity = ether(1); |
| 142 | + await setup.issuanceModule.issue(setToken.address, issueQuantity, owner.address); |
| 143 | + }); |
| 144 | + |
| 145 | + beforeEach(async () => { |
| 146 | + subjectSourceToken = sourceToken.address; |
| 147 | + subjectDestinationToken = destinationToken.address; |
| 148 | + subjectSourceQuantity = sourceTokenQuantity; |
| 149 | + subjectSetToken = setToken.address; |
| 150 | + subjectMinDestinationQuantity = destinationTokenQuantity.sub(ether(1)); // Receive a min of 28 WETH for 1 WBTC |
| 151 | + subjectAdapterName = uniswapAdapterV2Name; |
| 152 | + |
| 153 | + const tradePath = [subjectSourceToken, subjectDestinationToken]; |
| 154 | + const shouldSwapForExactToken = false; |
| 155 | + |
| 156 | + subjectData = await uniswapExchangeAdapterV2.getUniswapExchangeData( |
| 157 | + tradePath, |
| 158 | + shouldSwapForExactToken |
| 159 | + ); |
| 160 | + subjectCaller = manager; |
| 161 | + }); |
| 162 | + |
| 163 | + async function subject(): Promise<any> { |
| 164 | + tradeModule = tradeModule.connect(subjectCaller.wallet); |
| 165 | + return tradeModule.trade( |
| 166 | + subjectSetToken, |
| 167 | + subjectAdapterName, |
| 168 | + subjectSourceToken, |
| 169 | + subjectSourceQuantity, |
| 170 | + subjectDestinationToken, |
| 171 | + subjectMinDestinationQuantity, |
| 172 | + subjectData |
| 173 | + ); |
| 174 | + } |
| 175 | + |
| 176 | + it("should transfer the correct components to the SetToken", async () => { |
| 177 | + const oldDestinationTokenBalance = await destinationToken.balanceOf(setToken.address); |
| 178 | + const [, expectedReceiveQuantity] = await sushiswapRouter.getAmountsOut( |
| 179 | + subjectSourceQuantity, |
| 180 | + [subjectSourceToken, subjectDestinationToken] |
| 181 | + ); |
| 182 | + |
| 183 | + await subject(); |
| 184 | + |
| 185 | + const expectedDestinationTokenBalance = oldDestinationTokenBalance.add(expectedReceiveQuantity); |
| 186 | + const newDestinationTokenBalance = await destinationToken.balanceOf(setToken.address); |
| 187 | + expect(expectedReceiveQuantity).to.be.gt(ZERO); |
| 188 | + expect(newDestinationTokenBalance).to.eq(expectedDestinationTokenBalance); |
| 189 | + }); |
| 190 | + |
| 191 | + it("should transfer the correct components from the SetToken", async () => { |
| 192 | + const oldSourceTokenBalance = await sourceToken.balanceOf(setToken.address); |
| 193 | + |
| 194 | + await subject(); |
| 195 | + |
| 196 | + const totalSourceQuantity = issueQuantity.mul(sourceTokenQuantity).div(ether(1)); |
| 197 | + const expectedSourceTokenBalance = oldSourceTokenBalance.sub(totalSourceQuantity); |
| 198 | + const newSourceTokenBalance = await sourceToken.balanceOf(setToken.address); |
| 199 | + expect(newSourceTokenBalance).to.eq(expectedSourceTokenBalance); |
| 200 | + }); |
| 201 | + }); |
| 202 | + }); |
| 203 | +}); |
0 commit comments