From 04a6e62e48e5567223e0723317f8576c4fc9d0f0 Mon Sep 17 00:00:00 2001 From: Parv Date: Wed, 19 Jan 2022 01:31:38 +0530 Subject: [PATCH 01/11] feat(SameAssetMigration): add copy of UniswapSingleTransferMigration with tests --- .../migrations/SameAssetTransferMigration.sol | 201 ++++++ .../migrations/SameAssetTransferMigration.ts | 633 ++++++++++++++++++ 2 files changed, 834 insertions(+) create mode 100644 contracts/migrations/SameAssetTransferMigration.sol create mode 100644 test/contracts/migrations/SameAssetTransferMigration.ts diff --git a/contracts/migrations/SameAssetTransferMigration.sol b/contracts/migrations/SameAssetTransferMigration.sol new file mode 100644 index 00000000..8a171d9f --- /dev/null +++ b/contracts/migrations/SameAssetTransferMigration.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "../libs/Details.sol"; +import "../vaults/Vault.sol"; +import "../interfaces/IMigration.sol"; +import "../interfaces/ISingleAssetVault.sol"; + +/// @title Vault migrator from erc20 to erc20 (non-lp) +/// @author Carl Farterson (@carlfarterson) +/// @notice create a vault that instantly swaps token A for token B +/// when recollateralizing to a vault with a different base token +/// @dev This contract moves the pooled/locked balances from +/// one erc20 to another +contract UniswapSingleTransferMigration is + Initializable, + Ownable, + ReentrancyGuard, + Vault, + IMigration +{ + struct UniswapSingleTransfer { + // The earliest time that the swap can occur + uint256 soonest; + // Fee configured to pay on swap + uint24 fee; + // if migration is active and startMigration() has not been triggered + bool started; + // meToken has executed the swap and can finish migrating + bool swapped; + } + + mapping(address => UniswapSingleTransfer) private _uniswapSingleTransfers; + + // NOTE: this can be found at + // github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/ISwapRouter.sol + ISwapRouter private immutable _router = + ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); + + // args for uniswap router + // TODO: configurable fee + uint24 public constant MINFEE = 500; // 0.05% + uint24 public constant MIDFEE = 3000; // 0.3% (Default fee) + uint24 public constant MAXFEE = 10000; // 1% + + constructor( + address _dao, + address _foundry, + IHub _hub, + IMeTokenRegistry _meTokenRegistry, + IMigrationRegistry _migrationRegistry + ) Vault(_dao, _foundry, _hub, _meTokenRegistry, _migrationRegistry) {} + + function initMigration(address _meToken, bytes memory _encodedArgs) + external + override + { + require(msg.sender == address(meTokenRegistry), "!meTokenRegistry"); + + (uint256 soonest, uint24 fee) = abi.decode( + _encodedArgs, + (uint256, uint24) + ); + UniswapSingleTransfer storage usts_ = _uniswapSingleTransfers[_meToken]; + usts_.fee = fee; + usts_.soonest = soonest; + } + + function poke(address _meToken) external override nonReentrant { + // Make sure meToken is in a state of resubscription + UniswapSingleTransfer storage usts_ = _uniswapSingleTransfers[_meToken]; + Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); + Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); + if ( + usts_.soonest != 0 && + block.timestamp > usts_.soonest && + !usts_.started + ) { + ISingleAssetVault(hub_.vault).startMigration(_meToken); + usts_.started = true; + _swap(_meToken); + } + } + + function finishMigration(address _meToken) + external + override + nonReentrant + returns (uint256 amountOut) + { + require(msg.sender == address(meTokenRegistry), "!meTokenRegistry"); + UniswapSingleTransfer storage usts_ = _uniswapSingleTransfers[_meToken]; + require(usts_.soonest < block.timestamp, "timestamp < soonest"); + + Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); + Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); + Details.Hub memory targetHub_ = hub.getDetails(meToken_.targetHubId); + + // TODO: require migration hasn't finished, block.timestamp > meToken_.startTime + if (!usts_.started) { + ISingleAssetVault(hub_.vault).startMigration(_meToken); + usts_.started = true; + amountOut = _swap(_meToken); + } else { + // No swap, amountOut = amountIn + amountOut = meToken_.balancePooled + meToken_.balanceLocked; + } + + // Send asset to new vault only if there's a migration vault + IERC20(targetHub_.asset).transfer(targetHub_.vault, amountOut); + + // reset mappings + delete _uniswapSingleTransfers[_meToken]; + } + + function getDetails(address _meToken) + external + view + returns (UniswapSingleTransfer memory usts_) + { + usts_ = _uniswapSingleTransfers[_meToken]; + } + + // Kicks off meToken warmup period + function isValid(address _meToken, bytes memory _encodedArgs) + public + view + override + returns (bool) + { + // _encodedArgs empty + if (_encodedArgs.length == 0) return false; + (uint256 soon, uint24 fee) = abi.decode( + _encodedArgs, + (uint256, uint24) + ); + // Too soon + if (soon < block.timestamp) return false; + Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); + // MeToken not subscribed to a hub + if (meToken_.hubId == 0) return false; + // Invalid fee + if (fee == MINFEE || fee == MIDFEE || fee == MAXFEE) { + return true; + } else { + return false; + } + } + + function _swap(address _meToken) private returns (uint256 amountOut) { + UniswapSingleTransfer storage usts_ = _uniswapSingleTransfers[_meToken]; + Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); + Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); + Details.Hub memory targetHub_ = hub.getDetails(meToken_.targetHubId); + uint256 amountIn = meToken_.balancePooled + meToken_.balanceLocked; + + // Only swap if + // - There are tokens to swap + // - The resubscription has started + // - The asset hasn't been swapped + // - Current time is past the soonest it can swap, and time to swap has been set + if ( + amountIn == 0 || + !usts_.started || + usts_.swapped || + usts_.soonest == 0 || + usts_.soonest > block.timestamp + ) { + return 0; + } + + // Approve router to spend + IERC20(hub_.asset).approve(address(_router), amountIn); + + // https://docs.uniswap.org/protocol/guides/swaps/single-swaps + ISwapRouter.ExactInputSingleParams memory params = ISwapRouter + .ExactInputSingleParams({ + tokenIn: hub_.asset, + tokenOut: targetHub_.asset, + fee: usts_.fee, + recipient: address(this), + deadline: block.timestamp, + amountIn: amountIn, + amountOutMinimum: 0, + sqrtPriceLimitX96: 0 + }); + + usts_.swapped = true; + + // The call to `exactInputSingle` executes the swap + amountOut = _router.exactInputSingle(params); + + // Based on amountIn and amountOut, update balancePooled and balanceLocked + meTokenRegistry.updateBalances(_meToken, amountOut); + } +} diff --git a/test/contracts/migrations/SameAssetTransferMigration.ts b/test/contracts/migrations/SameAssetTransferMigration.ts new file mode 100644 index 00000000..a7adb9d4 --- /dev/null +++ b/test/contracts/migrations/SameAssetTransferMigration.ts @@ -0,0 +1,633 @@ +import { ethers, getNamedAccounts } from "hardhat"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { deploy, getContractAt } from "../../utils/helpers"; +import { Signer, BigNumber } from "ethers"; +import { ERC20 } from "../../../artifacts/types/ERC20"; +import { Foundry } from "../../../artifacts/types/Foundry"; +import { Hub } from "../../../artifacts/types/Hub"; +import { BancorABDK } from "../../../artifacts/types/BancorABDK"; +import { MeTokenFactory } from "../../../artifacts/types/MeTokenFactory"; +import { MeTokenRegistry } from "../../../artifacts/types/MeTokenRegistry"; +import { CurveRegistry } from "../../../artifacts/types/CurveRegistry"; +import { MigrationRegistry } from "../../../artifacts/types/MigrationRegistry"; +import { SingleAssetVault } from "../../../artifacts/types/SingleAssetVault"; +import { MeToken } from "../../../artifacts/types/MeToken"; +import { impersonate, mineBlock, passHours } from "../../utils/hardhatNode"; +import { UniswapSingleTransferMigration } from "../../../artifacts/types/UniswapSingleTransferMigration"; +import { hubSetup } from "../../utils/hubSetup"; +import { expect } from "chai"; +import { Fees } from "../../../artifacts/types/Fees"; +import { VaultRegistry } from "../../../artifacts/types/VaultRegistry"; +import { WeightedAverage } from "../../../artifacts/types/WeightedAverage"; + +const setup = async () => { + describe("UniswapSingleTransferMigration.sol", () => { + let earliestSwapTime: number; + let DAI: string; + let WETH: string; + let DAIWhale: string; + let WETHWhale: string; + let daiHolder: Signer; + let wethHolder: Signer; + let dai: ERC20; + let weth: ERC20; + let account0: SignerWithAddress; + let account1: SignerWithAddress; + let account2: SignerWithAddress; + let migrationRegistry: MigrationRegistry; + let migration: UniswapSingleTransferMigration; + let curve: BancorABDK; + let meTokenRegistry: MeTokenRegistry; + let initialVault: SingleAssetVault; + let targetVault: SingleAssetVault; + let foundry: Foundry; + let meToken: MeToken; + let hub: Hub; + let fee: Fees; + let vaultRegistry: VaultRegistry; + + const hubId1 = 1; + const hubId2 = 2; + const name = "Carl meToken"; + const symbol = "CARL"; + const amount = ethers.utils.parseEther("100"); + const fees = 3000; + const refundRatio = 500000; + const MAX_WEIGHT = 1000000; + const reserveWeight = MAX_WEIGHT / 2; + const PRECISION = BigNumber.from(10).pow(6); + const baseY = PRECISION.div(1000).toString(); + const hubWarmup = 7 * 60 * 24 * 24; // 1 week + const warmup = 2 * 60 * 24 * 24; // 2 days + const duration = 4 * 60 * 24 * 24; // 4 days + const coolDown = 5 * 60 * 24 * 24; // 5 days + + let encodedCurveDetails: string; + let encodedMigrationArgs: string; + let badEncodedMigrationArgs: string; + let encodedVaultDAIArgs: string; + let encodedVaultWETHArgs: string; + let block; + let migrationDetails: [BigNumber, number, boolean, boolean] & { + soonest: BigNumber; + fee: number; + started: boolean; + swapped: boolean; + }; + + before(async () => { + ({ DAI, DAIWhale, WETH, WETHWhale } = await getNamedAccounts()); + encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint32"], + [baseY, reserveWeight] + ); + encodedVaultDAIArgs = ethers.utils.defaultAbiCoder.encode( + ["address"], + [DAI] + ); + encodedVaultWETHArgs = ethers.utils.defaultAbiCoder.encode( + ["address"], + [WETH] + ); + + const block = await ethers.provider.getBlock("latest"); + earliestSwapTime = block.timestamp + 600 * 60; // 10h in future + + encodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint24"], + [earliestSwapTime, fees] + ); + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + curve = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); + + ({ + migrationRegistry, + singleAssetVault: initialVault, + account0, + account1, + account2, + meTokenRegistry, + vaultRegistry, + fee, + } = await hubSetup( + encodedCurveDetails, + encodedVaultDAIArgs, + refundRatio, + hub, + foundry, + curve + )); + + targetVault = await deploy( + "SingleAssetVault", + undefined, //no libs + account0.address, // DAO + foundry.address, // foundry + hub.address, // hub + meTokenRegistry.address, //IMeTokenRegistry + migrationRegistry.address //IMigrationRegistry + ); + await vaultRegistry.approve(targetVault.address); + + // Register 2nd hub to which we'll migrate to + await hub.register( + account0.address, + WETH, + targetVault.address, + curve.address, + refundRatio, + encodedCurveDetails, + encodedVaultWETHArgs + ); + // Deploy uniswap migration and approve it to the registry + migration = await deploy( + "UniswapSingleTransferMigration", + undefined, + account0.address, + foundry.address, + hub.address, + meTokenRegistry.address, + migrationRegistry.address + ); + await migrationRegistry.approve( + initialVault.address, + targetVault.address, + migration.address + ); + // Pre fund owner & buyer w/ DAI & WETH + dai = await getContractAt("ERC20", DAI); + weth = await getContractAt("ERC20", WETH); + daiHolder = await impersonate(DAIWhale); + wethHolder = await impersonate(WETHWhale); + dai + .connect(daiHolder) + .transfer(account0.address, ethers.utils.parseEther("100")); + dai + .connect(daiHolder) + .transfer(account2.address, ethers.utils.parseEther("1000")); + weth + .connect(wethHolder) + .transfer(account0.address, ethers.utils.parseEther("10")); + weth + .connect(wethHolder) + .transfer(account2.address, ethers.utils.parseEther("1000")); + let max = ethers.constants.MaxUint256; + await dai.connect(account1).approve(meTokenRegistry.address, max); + await dai.connect(account2).approve(initialVault.address, max); + await weth.connect(account2).approve(migration.address, max); + await weth.connect(account2).approve(targetVault.address, max); + + // Create meToken + await meTokenRegistry + .connect(account1) + .subscribe(name, symbol, hubId1, amount); + const meTokenAddr = await meTokenRegistry.getOwnerMeToken( + account1.address + ); + meToken = await getContractAt("MeToken", meTokenAddr); + await hub.setWarmup(hubWarmup); + }); + + describe("isValid()", () => { + it("Returns false for invalid encoding", async () => { + const isValid = await migration.isValid(meToken.address, "0x"); + expect(isValid).to.be.false; + }); + it("Returns true for valid encoding", async () => { + const isValid = await migration.isValid( + meToken.address, + encodedMigrationArgs + ); + expect(isValid).to.be.true; + }); + it("Returns false for start time before current time", async () => { + badEncodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint24"], + [earliestSwapTime - 720 * 60, fees] // 2 hours beforehand + ); + const isValid = await migration.isValid( + meToken.address, + badEncodedMigrationArgs + ); + expect(isValid).to.be.false; + }); + it("Returns false for nonexistent meToken", async () => { + const isValid = await migration.isValid( + account0.address, + encodedMigrationArgs + ); + expect(isValid).to.be.false; + }); + it("Returns false for invalid fee", async () => { + badEncodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint24"], + [earliestSwapTime, 2999] + ); + const isValid = await migration.isValid( + meToken.address, + badEncodedMigrationArgs + ); + expect(isValid).to.be.false; + }); + }); + + describe("initMigration()", () => { + it("Reverts when sender is not meTokenRegistry", async () => { + await expect( + migration.initMigration(meToken.address, encodedMigrationArgs) + ).to.be.revertedWith("!meTokenRegistry"); + }); + it("Fails from bad encodings", async () => { + await expect( + meTokenRegistry + .connect(account1) + .initResubscribe( + meToken.address, + hubId2, + migration.address, + badEncodedMigrationArgs + ) + ).to.be.revertedWith("Invalid _encodedMigrationArgs"); + }); + it("should revert when try to approve already approved vaults", async () => { + await expect( + migrationRegistry.approve( + initialVault.address, + targetVault.address, + migration.address + ) + ).to.be.revertedWith("migration already approved"); + }); + it("should be able to unapprove migration vaults", async () => { + let tx = await migrationRegistry.unapprove( + initialVault.address, + targetVault.address, + migration.address + ); + await tx.wait(); + + // should revert to init resubscribe when unapproved + await expect( + meTokenRegistry + .connect(account1) + .initResubscribe( + meToken.address, + hubId2, + migration.address, + encodedMigrationArgs + ) + ).to.be.revertedWith("!approved"); + }); + it("should revert when try to unapprove already unapproved vaults", async () => { + await expect( + migrationRegistry.unapprove( + initialVault.address, + targetVault.address, + migration.address + ) + ).to.be.revertedWith("migration not approved"); + + // approve vaults again + const tx = await migrationRegistry.approve( + initialVault.address, + targetVault.address, + migration.address + ); + }); + it("Set correct _ust values", async () => { + await meTokenRegistry + .connect(account1) + .initResubscribe( + meToken.address, + hubId2, + migration.address, + encodedMigrationArgs + ); + const migrationDetails = await migration.getDetails(meToken.address); + expect(migrationDetails.fee).to.equal(fees); + expect(migrationDetails.soonest).to.equal(earliestSwapTime); + }); + }); + + describe("poke()", () => { + it("should be able to call for invalid metoken, but wont run startMigration()", async () => { + const tx = await migration.poke(account0.address); + await tx.wait(); + + await expect(tx).to.not.emit(initialVault, "StartMigration"); + }); + it("should be able to call before soonest, but wont run startMigration()", async () => { + migrationDetails = await migration.getDetails(meToken.address); + block = await ethers.provider.getBlock("latest"); + expect(migrationDetails.soonest).to.be.gt(block.timestamp); + + const tx = await migration.poke(meToken.address); + await tx.wait(); + + await expect(tx).to.not.emit(initialVault, "StartMigration"); + migrationDetails = await migration.getDetails(meToken.address); + expect(migrationDetails.started).to.be.equal(false); + }); + it("Triggers startMigration()", async () => { + await mineBlock(migrationDetails.soonest.toNumber() + 1); + block = await ethers.provider.getBlock("latest"); + expect(migrationDetails.soonest).to.be.lt(block.timestamp); + + const tx = await migration.poke(meToken.address); + await tx.wait(); + + await expect(tx) + .to.emit(initialVault, "StartMigration") + .withArgs(meToken.address) + // TODO check updated balance here + .to.emit(meTokenRegistry, "UpdateBalances"); + migrationDetails = await migration.getDetails(meToken.address); + expect(migrationDetails.started).to.be.equal(true); + expect(migrationDetails.swapped).to.be.equal(true); + }); + it("should be able to call when migration already started, but wont run startMigration()", async () => { + const tx = await migration.poke(meToken.address); + await tx.wait(); + + await expect(tx).to.not.emit(initialVault, "StartMigration"); + }); + }); + describe("finishMigration()", () => { + it("Reverts when sender is not meTokenRegistry", async () => { + await expect( + migration.finishMigration(meToken.address) + ).to.be.revertedWith("!meTokenRegistry"); + }); + it("Should not trigger startsMigration() if already started", async () => { + const meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + + block = await ethers.provider.getBlock("latest"); + expect(meTokenRegistryDetails.endTime).to.be.lt(block.timestamp); + + const tx = await meTokenRegistry.finishResubscribe(meToken.address); + await tx.wait(); + + await expect(tx) + .to.emit(meTokenRegistry, "FinishResubscribe") + .to.emit(weth, "Transfer") + .withArgs( + migration.address, + targetVault.address, + meTokenRegistryDetails.balancePooled.add( + meTokenRegistryDetails.balanceLocked + ) + ) + .to.not.emit(initialVault, "StartMigration"); + + migrationDetails = await migration.getDetails(meToken.address); + expect(migrationDetails.fee).to.equal(0); + expect(migrationDetails.soonest).to.equal(0); + expect(migrationDetails.started).to.equal(false); + expect(migrationDetails.swapped).to.equal(false); + }); + it("should revert before soonest", async () => { + await migrationRegistry.approve( + targetVault.address, + initialVault.address, + migration.address + ); + + block = await ethers.provider.getBlock("latest"); + earliestSwapTime = block.timestamp + 600 * 60; // 10h in future + + encodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint24"], + [earliestSwapTime, fees] + ); + + await meTokenRegistry + .connect(account1) + .initResubscribe( + meToken.address, + hubId1, + migration.address, + encodedMigrationArgs + ); + migrationDetails = await migration.getDetails(meToken.address); + expect(migrationDetails.fee).to.equal(fees); + expect(migrationDetails.soonest).to.equal(earliestSwapTime); + + const meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + await mineBlock(meTokenRegistryDetails.endTime.toNumber() + 2); + block = await ethers.provider.getBlock("latest"); + expect(meTokenRegistryDetails.endTime).to.be.lt(block.timestamp); + + await expect( + meTokenRegistry.finishResubscribe(meToken.address) + ).to.be.revertedWith("timestamp < soonest"); + }); + it("Triggers startsMigration() if it hasn't already started", async () => { + let meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + migrationDetails = await migration.getDetails(meToken.address); + + await mineBlock(migrationDetails.soonest.toNumber() + 2); + block = await ethers.provider.getBlock("latest"); + expect(migrationDetails.soonest).to.be.lt(block.timestamp); + + const tx = await meTokenRegistry.finishResubscribe(meToken.address); + await tx.wait(); + + meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + await expect(tx) + .to.emit(meTokenRegistry, "FinishResubscribe") + .to.emit(targetVault, "StartMigration") + .withArgs(meToken.address) + // TODO check updated balance here + .to.emit(dai, "Transfer") + .withArgs( + migration.address, + initialVault.address, + meTokenRegistryDetails.balancePooled.add( + meTokenRegistryDetails.balanceLocked + ) + ) + .to.emit(meTokenRegistry, "UpdateBalances"); + migrationDetails = await migration.getDetails(meToken.address); + expect(migrationDetails.fee).to.equal(0); + expect(migrationDetails.soonest).to.equal(0); + expect(migrationDetails.started).to.equal(false); + expect(migrationDetails.swapped).to.equal(false); + }); + + describe("During resubscribe", () => { + before(async () => { + await meTokenRegistry.setWarmup(warmup); + await meTokenRegistry.setDuration(duration); + await meTokenRegistry.setCooldown(coolDown); + + await meTokenRegistry + .connect(account2) + .subscribe(name, symbol, hubId1, 0); + const meTokenAddr = await meTokenRegistry.getOwnerMeToken( + account2.address + ); + meToken = await getContractAt("MeToken", meTokenAddr); + + block = await ethers.provider.getBlock("latest"); + earliestSwapTime = block.timestamp + 600 * 60; // 10h in future + + encodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint24"], + [earliestSwapTime, fees] + ); + + await meTokenRegistry + .connect(account2) + .initResubscribe( + meToken.address, + hubId2, + migration.address, + encodedMigrationArgs + ); + migrationDetails = await migration.getDetails(meToken.address); + expect(migrationDetails.fee).to.equal(fees); + expect(migrationDetails.soonest).to.equal(earliestSwapTime); + }); + + it("From warmup => startTime: assets transferred to/from initial vault", async () => { + const initialVaultBalanceBefore = await dai.balanceOf( + initialVault.address + ); + + const tx = await foundry + .connect(account2) + .mint(meToken.address, amount, account2.address); + await tx.wait(); + + await expect(tx).to.be.emit(dai, "Transfer"); + + const initialVaultBalanceAfter = await dai.balanceOf( + initialVault.address + ); + + expect( + initialVaultBalanceAfter.sub(initialVaultBalanceBefore) + ).to.equal(amount); + }); + it("From soonest => endTime: assets transferred to/from migration vault", async () => { + const meTokenDetails = await meTokenRegistry.getDetails( + meToken.address + ); + + await mineBlock(meTokenDetails.startTime.toNumber() + 2); + block = await ethers.provider.getBlock("latest"); + expect(meTokenDetails.startTime).to.be.lt(block.timestamp); + + const initialVaultDAIBefore = await dai.balanceOf( + initialVault.address + ); + const initialVaultWETHBefore = await weth.balanceOf( + initialVault.address + ); + const migrationDAIBefore = await dai.balanceOf(migration.address); + const migrationWETHBefore = await weth.balanceOf(migration.address); + + const tx = await foundry + .connect(account2) + .mint(meToken.address, amount, account2.address); + await tx.wait(); + + await expect(tx).to.be.emit(dai, "Transfer"); + + const initialVaultDAIAfter = await dai.balanceOf( + initialVault.address + ); + const initialVaultWETHAfter = await weth.balanceOf( + initialVault.address + ); + const migrationDAIAfter = await dai.balanceOf(migration.address); + const migrationWETHAfter = await weth.balanceOf(migration.address); + + expect(initialVaultWETHBefore.sub(initialVaultWETHAfter)).to.be.equal( + 0 + ); // initial vault weth balance has no change + expect(initialVaultDAIBefore.sub(initialVaultDAIAfter)).to.equal( + amount + ); // amount deposited before start time + expect(migrationDAIAfter.sub(migrationDAIBefore)).to.be.equal(0); // no change + // TODO fix with swap balance + expect(migrationWETHAfter.sub(migrationWETHBefore)).to.be.gt(amount); // gt due to swap amount + }); + it("After endTime: assets transferred to/from target vault", async () => { + const meTokenDetails = await meTokenRegistry.getDetails( + meToken.address + ); + + await mineBlock(meTokenDetails.endTime.toNumber() + 2); + block = await ethers.provider.getBlock("latest"); + expect(meTokenDetails.endTime).to.be.lt(block.timestamp); + + const initialVaultDAIBefore = await dai.balanceOf( + initialVault.address + ); + const initialVaultWETHBefore = await weth.balanceOf( + initialVault.address + ); + const migrationDAIBefore = await dai.balanceOf(migration.address); + const migrationWETHBefore = await weth.balanceOf(migration.address); + const targetVaultDAIBefore = await dai.balanceOf(targetVault.address); + const targetVaultWETHBefore = await weth.balanceOf( + targetVault.address + ); + + const tx = await foundry + .connect(account2) + .mint(meToken.address, amount, account2.address); + await tx.wait(); + + await expect(tx).to.be.emit(weth, "Transfer"); + + const initialVaultDAIAfter = await dai.balanceOf( + initialVault.address + ); + const initialVaultWETHAfter = await weth.balanceOf( + initialVault.address + ); + const migrationDAIAfter = await dai.balanceOf(migration.address); + const migrationWETHAfter = await weth.balanceOf(migration.address); + const targetVaultDAIAfter = await dai.balanceOf(targetVault.address); + const targetVaultWETHAfter = await weth.balanceOf( + targetVault.address + ); + + expect(initialVaultWETHBefore.sub(initialVaultWETHAfter)).to.be.equal( + 0 + ); // initial vault weth balance has no change + expect(initialVaultDAIBefore.sub(initialVaultDAIAfter)).to.equal(0); // initial vault dai balance has no change + expect(migrationDAIAfter.sub(migrationDAIBefore)).to.be.equal(0); // no change + expect(migrationWETHAfter).to.be.equal(0); // migration balance goes to target vault + expect(targetVaultDAIAfter.sub(targetVaultDAIBefore)).to.be.equal(0); // no change + expect(targetVaultWETHAfter.sub(targetVaultWETHBefore)).to.be.equal( + amount.add(migrationWETHBefore) + ); // newly minted amount + migration weth balance + }); + }); + }); + }); +}; + +setup().then(() => { + run(); +}); From b3d6e35f08fc79201836005b40190154e46be4cf Mon Sep 17 00:00:00 2001 From: Parv Date: Wed, 19 Jan 2022 01:36:04 +0530 Subject: [PATCH 02/11] fix(SingleAssetVault): comment unused variable name - wont show the warning now --- contracts/vaults/SingleAssetVault.sol | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/contracts/vaults/SingleAssetVault.sol b/contracts/vaults/SingleAssetVault.sol index 304b2d51..c2b8a7d3 100644 --- a/contracts/vaults/SingleAssetVault.sol +++ b/contracts/vaults/SingleAssetVault.sol @@ -36,13 +36,10 @@ contract SingleAssetVault is Vault, ISingleAssetVault { emit StartMigration(_meToken); } - // solhint-disable-next-line - function isValid(address _asset, bytes memory _encodedArgs) - public - pure - override - returns (bool) - { + function isValid( + address _asset, + bytes memory /* _encodedArgs */ + ) public pure override returns (bool) { if (_asset == address(0)) { return false; } From 53321c578dc3831d6407d0d5423a86aef9e04d7f Mon Sep 17 00:00:00 2001 From: Parv Date: Wed, 19 Jan 2022 20:50:12 +0530 Subject: [PATCH 03/11] feat: same asset migration --- .../migrations/SameAssetTransferMigration.sol | 147 ++----- contracts/registries/MeTokenRegistry.sol | 15 +- .../migrations/SameAssetTransferMigration.ts | 378 ++++++++---------- test/contracts/registries/MeTokenRegistry.ts | 15 +- 4 files changed, 214 insertions(+), 341 deletions(-) diff --git a/contracts/migrations/SameAssetTransferMigration.sol b/contracts/migrations/SameAssetTransferMigration.sol index 8a171d9f..b1ffec45 100644 --- a/contracts/migrations/SameAssetTransferMigration.sol +++ b/contracts/migrations/SameAssetTransferMigration.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; -import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; @@ -17,36 +16,21 @@ import "../interfaces/ISingleAssetVault.sol"; /// when recollateralizing to a vault with a different base token /// @dev This contract moves the pooled/locked balances from /// one erc20 to another -contract UniswapSingleTransferMigration is +contract SameAssetTransferMigration is Initializable, Ownable, ReentrancyGuard, Vault, IMigration { - struct UniswapSingleTransfer { - // The earliest time that the swap can occur - uint256 soonest; - // Fee configured to pay on swap - uint24 fee; + struct SameAssetMigration { + // if migration is active + bool isMigrating; // if migration is active and startMigration() has not been triggered bool started; - // meToken has executed the swap and can finish migrating - bool swapped; } - mapping(address => UniswapSingleTransfer) private _uniswapSingleTransfers; - - // NOTE: this can be found at - // github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/ISwapRouter.sol - ISwapRouter private immutable _router = - ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); - - // args for uniswap router - // TODO: configurable fee - uint24 public constant MINFEE = 500; // 0.05% - uint24 public constant MIDFEE = 3000; // 0.3% (Default fee) - uint24 public constant MAXFEE = 10000; // 1% + mapping(address => SameAssetMigration) private _sameAssetMigration; constructor( address _dao, @@ -56,34 +40,31 @@ contract UniswapSingleTransferMigration is IMigrationRegistry _migrationRegistry ) Vault(_dao, _foundry, _hub, _meTokenRegistry, _migrationRegistry) {} - function initMigration(address _meToken, bytes memory _encodedArgs) - external - override - { + function initMigration( + address _meToken, + bytes memory /* _encodedArgs */ + ) external override { require(msg.sender == address(meTokenRegistry), "!meTokenRegistry"); - (uint256 soonest, uint24 fee) = abi.decode( - _encodedArgs, - (uint256, uint24) - ); - UniswapSingleTransfer storage usts_ = _uniswapSingleTransfers[_meToken]; - usts_.fee = fee; - usts_.soonest = soonest; + Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); + Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); + Details.Hub memory targetHub_ = hub.getDetails(meToken_.targetHubId); + + require(hub_.asset == targetHub_.asset, "asset different"); + + SameAssetMigration storage usts_ = _sameAssetMigration[_meToken]; + usts_.isMigrating = true; } function poke(address _meToken) external override nonReentrant { // Make sure meToken is in a state of resubscription - UniswapSingleTransfer storage usts_ = _uniswapSingleTransfers[_meToken]; + // TODO can add a require that checks if _meToken is resubscribing + SameAssetMigration storage usts_ = _sameAssetMigration[_meToken]; Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); - if ( - usts_.soonest != 0 && - block.timestamp > usts_.soonest && - !usts_.started - ) { + if (usts_.isMigrating && !usts_.started) { ISingleAssetVault(hub_.vault).startMigration(_meToken); usts_.started = true; - _swap(_meToken); } } @@ -94,108 +75,42 @@ contract UniswapSingleTransferMigration is returns (uint256 amountOut) { require(msg.sender == address(meTokenRegistry), "!meTokenRegistry"); - UniswapSingleTransfer storage usts_ = _uniswapSingleTransfers[_meToken]; - require(usts_.soonest < block.timestamp, "timestamp < soonest"); + SameAssetMigration storage usts_ = _sameAssetMigration[_meToken]; Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); Details.Hub memory targetHub_ = hub.getDetails(meToken_.targetHubId); - // TODO: require migration hasn't finished, block.timestamp > meToken_.startTime if (!usts_.started) { ISingleAssetVault(hub_.vault).startMigration(_meToken); usts_.started = true; - amountOut = _swap(_meToken); - } else { - // No swap, amountOut = amountIn - amountOut = meToken_.balancePooled + meToken_.balanceLocked; + // amountOut = _swap(_meToken); } + amountOut = meToken_.balancePooled + meToken_.balanceLocked; // Send asset to new vault only if there's a migration vault IERC20(targetHub_.asset).transfer(targetHub_.vault, amountOut); // reset mappings - delete _uniswapSingleTransfers[_meToken]; + delete _sameAssetMigration[_meToken]; } function getDetails(address _meToken) external view - returns (UniswapSingleTransfer memory usts_) + returns (SameAssetMigration memory usts_) { - usts_ = _uniswapSingleTransfers[_meToken]; + usts_ = _sameAssetMigration[_meToken]; } // Kicks off meToken warmup period - function isValid(address _meToken, bytes memory _encodedArgs) - public - view - override - returns (bool) - { - // _encodedArgs empty - if (_encodedArgs.length == 0) return false; - (uint256 soon, uint24 fee) = abi.decode( - _encodedArgs, - (uint256, uint24) - ); - // Too soon - if (soon < block.timestamp) return false; + function isValid( + address _meToken, + bytes memory /* _encodedArgs */ + ) public view override returns (bool) { Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); // MeToken not subscribed to a hub if (meToken_.hubId == 0) return false; - // Invalid fee - if (fee == MINFEE || fee == MIDFEE || fee == MAXFEE) { - return true; - } else { - return false; - } - } - - function _swap(address _meToken) private returns (uint256 amountOut) { - UniswapSingleTransfer storage usts_ = _uniswapSingleTransfers[_meToken]; - Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); - Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); - Details.Hub memory targetHub_ = hub.getDetails(meToken_.targetHubId); - uint256 amountIn = meToken_.balancePooled + meToken_.balanceLocked; - - // Only swap if - // - There are tokens to swap - // - The resubscription has started - // - The asset hasn't been swapped - // - Current time is past the soonest it can swap, and time to swap has been set - if ( - amountIn == 0 || - !usts_.started || - usts_.swapped || - usts_.soonest == 0 || - usts_.soonest > block.timestamp - ) { - return 0; - } - - // Approve router to spend - IERC20(hub_.asset).approve(address(_router), amountIn); - - // https://docs.uniswap.org/protocol/guides/swaps/single-swaps - ISwapRouter.ExactInputSingleParams memory params = ISwapRouter - .ExactInputSingleParams({ - tokenIn: hub_.asset, - tokenOut: targetHub_.asset, - fee: usts_.fee, - recipient: address(this), - deadline: block.timestamp, - amountIn: amountIn, - amountOutMinimum: 0, - sqrtPriceLimitX96: 0 - }); - - usts_.swapped = true; - - // The call to `exactInputSingle` executes the swap - amountOut = _router.exactInputSingle(params); - - // Based on amountIn and amountOut, update balancePooled and balanceLocked - meTokenRegistry.updateBalances(_meToken, amountOut); + return true; } } diff --git a/contracts/registries/MeTokenRegistry.sol b/contracts/registries/MeTokenRegistry.sol index 8ac119dc..1736c9db 100644 --- a/contracts/registries/MeTokenRegistry.sol +++ b/contracts/registries/MeTokenRegistry.sol @@ -132,9 +132,6 @@ contract MeTokenRegistry is Ownable, IMeTokenRegistry { require(!hub_.updating, "hub updating"); require(!targetHub_.updating, "targetHub updating"); - // TODO: what if asset is same? Is a migration vault needed since it'll start/end - // at the same and not change to a different asset? - require(hub_.asset != targetHub_.asset, "asset same"); require(_migration != address(0), "migration address(0)"); // Ensure the migration we're using is approved @@ -147,12 +144,6 @@ contract MeTokenRegistry is Ownable, IMeTokenRegistry { "!approved" ); - require( - IVault(_migration).isValid(_meToken, _encodedMigrationArgs), - "Invalid _encodedMigrationArgs" - ); - IMigration(_migration).initMigration(_meToken, _encodedMigrationArgs); - meToken_.startTime = block.timestamp + _warmup; meToken_.endTime = block.timestamp + _warmup + _duration; meToken_.endCooldown = @@ -163,6 +154,12 @@ contract MeTokenRegistry is Ownable, IMeTokenRegistry { meToken_.targetHubId = _targetHubId; meToken_.migration = _migration; + require( + IVault(_migration).isValid(_meToken, _encodedMigrationArgs), + "Invalid _encodedMigrationArgs" + ); + IMigration(_migration).initMigration(_meToken, _encodedMigrationArgs); + emit InitResubscribe( _meToken, _targetHubId, diff --git a/test/contracts/migrations/SameAssetTransferMigration.ts b/test/contracts/migrations/SameAssetTransferMigration.ts index a7adb9d4..39eaf99b 100644 --- a/test/contracts/migrations/SameAssetTransferMigration.ts +++ b/test/contracts/migrations/SameAssetTransferMigration.ts @@ -6,14 +6,12 @@ import { ERC20 } from "../../../artifacts/types/ERC20"; import { Foundry } from "../../../artifacts/types/Foundry"; import { Hub } from "../../../artifacts/types/Hub"; import { BancorABDK } from "../../../artifacts/types/BancorABDK"; -import { MeTokenFactory } from "../../../artifacts/types/MeTokenFactory"; import { MeTokenRegistry } from "../../../artifacts/types/MeTokenRegistry"; -import { CurveRegistry } from "../../../artifacts/types/CurveRegistry"; import { MigrationRegistry } from "../../../artifacts/types/MigrationRegistry"; import { SingleAssetVault } from "../../../artifacts/types/SingleAssetVault"; import { MeToken } from "../../../artifacts/types/MeToken"; import { impersonate, mineBlock, passHours } from "../../utils/hardhatNode"; -import { UniswapSingleTransferMigration } from "../../../artifacts/types/UniswapSingleTransferMigration"; +import { SameAssetTransferMigration } from "../../../artifacts/types/SameAssetTransferMigration"; import { hubSetup } from "../../utils/hubSetup"; import { expect } from "chai"; import { Fees } from "../../../artifacts/types/Fees"; @@ -21,30 +19,24 @@ import { VaultRegistry } from "../../../artifacts/types/VaultRegistry"; import { WeightedAverage } from "../../../artifacts/types/WeightedAverage"; const setup = async () => { - describe("UniswapSingleTransferMigration.sol", () => { + describe("SameAssetTransferMigration.sol", () => { let earliestSwapTime: number; let DAI: string; - let WETH: string; let DAIWhale: string; - let WETHWhale: string; let daiHolder: Signer; - let wethHolder: Signer; let dai: ERC20; - let weth: ERC20; let account0: SignerWithAddress; let account1: SignerWithAddress; let account2: SignerWithAddress; let migrationRegistry: MigrationRegistry; - let migration: UniswapSingleTransferMigration; + let migration: SameAssetTransferMigration; let curve: BancorABDK; let meTokenRegistry: MeTokenRegistry; let initialVault: SingleAssetVault; - let targetVault: SingleAssetVault; + // let targetVault: SingleAssetVault; let foundry: Foundry; let meToken: MeToken; let hub: Hub; - let fee: Fees; - let vaultRegistry: VaultRegistry; const hubId1 = 1; const hubId2 = 2; @@ -66,17 +58,14 @@ const setup = async () => { let encodedMigrationArgs: string; let badEncodedMigrationArgs: string; let encodedVaultDAIArgs: string; - let encodedVaultWETHArgs: string; let block; - let migrationDetails: [BigNumber, number, boolean, boolean] & { - soonest: BigNumber; - fee: number; + let migrationDetails: [boolean, boolean] & { + isMigrating: boolean; started: boolean; - swapped: boolean; }; before(async () => { - ({ DAI, DAIWhale, WETH, WETHWhale } = await getNamedAccounts()); + ({ DAI, DAIWhale } = await getNamedAccounts()); encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( ["uint256", "uint32"], [baseY, reserveWeight] @@ -85,18 +74,11 @@ const setup = async () => { ["address"], [DAI] ); - encodedVaultWETHArgs = ethers.utils.defaultAbiCoder.encode( - ["address"], - [WETH] - ); const block = await ethers.provider.getBlock("latest"); earliestSwapTime = block.timestamp + 600 * 60; // 10h in future - encodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint24"], - [earliestSwapTime, fees] - ); + encodedMigrationArgs = "0x"; const weightedAverage = await deploy("WeightedAverage"); foundry = await deploy("Foundry", { WeightedAverage: weightedAverage.address, @@ -116,8 +98,6 @@ const setup = async () => { account1, account2, meTokenRegistry, - vaultRegistry, - fee, } = await hubSetup( encodedCurveDetails, encodedVaultDAIArgs, @@ -127,30 +107,30 @@ const setup = async () => { curve )); - targetVault = await deploy( - "SingleAssetVault", - undefined, //no libs - account0.address, // DAO - foundry.address, // foundry - hub.address, // hub - meTokenRegistry.address, //IMeTokenRegistry - migrationRegistry.address //IMigrationRegistry - ); - await vaultRegistry.approve(targetVault.address); + // targetVault = await deploy( + // "SingleAssetVault", + // undefined, //no libs + // account0.address, // DAO + // foundry.address, // foundry + // hub.address, // hub + // meTokenRegistry.address, //IMeTokenRegistry + // migrationRegistry.address //IMigrationRegistry + // ); + // await vaultRegistry.approve(targetVault.address); // Register 2nd hub to which we'll migrate to await hub.register( account0.address, - WETH, - targetVault.address, + DAI, + initialVault.address, curve.address, refundRatio, encodedCurveDetails, - encodedVaultWETHArgs + encodedVaultDAIArgs ); // Deploy uniswap migration and approve it to the registry - migration = await deploy( - "UniswapSingleTransferMigration", + migration = await deploy( + "SameAssetTransferMigration", undefined, account0.address, foundry.address, @@ -160,31 +140,23 @@ const setup = async () => { ); await migrationRegistry.approve( initialVault.address, - targetVault.address, + initialVault.address, migration.address ); - // Pre fund owner & buyer w/ DAI & WETH + // Pre fund owner & buyer w/ DAI dai = await getContractAt("ERC20", DAI); - weth = await getContractAt("ERC20", WETH); daiHolder = await impersonate(DAIWhale); - wethHolder = await impersonate(WETHWhale); dai .connect(daiHolder) .transfer(account0.address, ethers.utils.parseEther("100")); dai .connect(daiHolder) .transfer(account2.address, ethers.utils.parseEther("1000")); - weth - .connect(wethHolder) - .transfer(account0.address, ethers.utils.parseEther("10")); - weth - .connect(wethHolder) - .transfer(account2.address, ethers.utils.parseEther("1000")); + let max = ethers.constants.MaxUint256; await dai.connect(account1).approve(meTokenRegistry.address, max); await dai.connect(account2).approve(initialVault.address, max); - await weth.connect(account2).approve(migration.address, max); - await weth.connect(account2).approve(targetVault.address, max); + await dai.connect(account2).approve(migration.address, max); // Create meToken await meTokenRegistry @@ -198,10 +170,11 @@ const setup = async () => { }); describe("isValid()", () => { - it("Returns false for invalid encoding", async () => { - const isValid = await migration.isValid(meToken.address, "0x"); - expect(isValid).to.be.false; - }); + // TODO checks when metoken do not have a hub + // it("Returns false for invalid encoding", async () => { + // const isValid = await migration.isValid(meToken.address, "0x"); + // expect(isValid).to.be.false; + // }); it("Returns true for valid encoding", async () => { const isValid = await migration.isValid( meToken.address, @@ -209,35 +182,35 @@ const setup = async () => { ); expect(isValid).to.be.true; }); - it("Returns false for start time before current time", async () => { - badEncodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint24"], - [earliestSwapTime - 720 * 60, fees] // 2 hours beforehand - ); - const isValid = await migration.isValid( - meToken.address, - badEncodedMigrationArgs - ); - expect(isValid).to.be.false; - }); - it("Returns false for nonexistent meToken", async () => { - const isValid = await migration.isValid( - account0.address, - encodedMigrationArgs - ); - expect(isValid).to.be.false; - }); - it("Returns false for invalid fee", async () => { - badEncodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint24"], - [earliestSwapTime, 2999] - ); - const isValid = await migration.isValid( - meToken.address, - badEncodedMigrationArgs - ); - expect(isValid).to.be.false; - }); + // it("Returns false for start time before current time", async () => { + // badEncodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( + // ["uint256", "uint24"], + // [earliestSwapTime - 720 * 60, fees] // 2 hours beforehand + // ); + // const isValid = await migration.isValid( + // meToken.address, + // badEncodedMigrationArgs + // ); + // expect(isValid).to.be.false; + // }); + // it("Returns false for nonexistent meToken", async () => { + // const isValid = await migration.isValid( + // account0.address, + // encodedMigrationArgs + // ); + // expect(isValid).to.be.false; + // }); + // it("Returns false for invalid fee", async () => { + // badEncodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( + // ["uint256", "uint24"], + // [earliestSwapTime, 2999] + // ); + // const isValid = await migration.isValid( + // meToken.address, + // badEncodedMigrationArgs + // ); + // expect(isValid).to.be.false; + // }); }); describe("initMigration()", () => { @@ -246,23 +219,23 @@ const setup = async () => { migration.initMigration(meToken.address, encodedMigrationArgs) ).to.be.revertedWith("!meTokenRegistry"); }); - it("Fails from bad encodings", async () => { - await expect( - meTokenRegistry - .connect(account1) - .initResubscribe( - meToken.address, - hubId2, - migration.address, - badEncodedMigrationArgs - ) - ).to.be.revertedWith("Invalid _encodedMigrationArgs"); - }); + // it("Fails from bad encodings", async () => { + // await expect( + // meTokenRegistry + // .connect(account1) + // .initResubscribe( + // meToken.address, + // hubId2, + // migration.address, + // badEncodedMigrationArgs + // ) + // ).to.be.revertedWith("Invalid _encodedMigrationArgs"); + // }); it("should revert when try to approve already approved vaults", async () => { await expect( migrationRegistry.approve( initialVault.address, - targetVault.address, + initialVault.address, migration.address ) ).to.be.revertedWith("migration already approved"); @@ -270,7 +243,7 @@ const setup = async () => { it("should be able to unapprove migration vaults", async () => { let tx = await migrationRegistry.unapprove( initialVault.address, - targetVault.address, + initialVault.address, migration.address ); await tx.wait(); @@ -291,19 +264,23 @@ const setup = async () => { await expect( migrationRegistry.unapprove( initialVault.address, - targetVault.address, + initialVault.address, migration.address ) ).to.be.revertedWith("migration not approved"); // approve vaults again - const tx = await migrationRegistry.approve( + await migrationRegistry.approve( + initialVault.address, initialVault.address, - targetVault.address, migration.address ); }); it("Set correct _ust values", async () => { + migrationDetails = await migration.getDetails(meToken.address); + expect(migrationDetails.isMigrating).to.equal(false); + expect(migrationDetails.started).to.equal(false); + await meTokenRegistry .connect(account1) .initResubscribe( @@ -312,9 +289,9 @@ const setup = async () => { migration.address, encodedMigrationArgs ); - const migrationDetails = await migration.getDetails(meToken.address); - expect(migrationDetails.fee).to.equal(fees); - expect(migrationDetails.soonest).to.equal(earliestSwapTime); + migrationDetails = await migration.getDetails(meToken.address); + expect(migrationDetails.isMigrating).to.equal(true); + expect(migrationDetails.started).to.equal(false); }); }); @@ -325,34 +302,35 @@ const setup = async () => { await expect(tx).to.not.emit(initialVault, "StartMigration"); }); - it("should be able to call before soonest, but wont run startMigration()", async () => { - migrationDetails = await migration.getDetails(meToken.address); - block = await ethers.provider.getBlock("latest"); - expect(migrationDetails.soonest).to.be.gt(block.timestamp); - - const tx = await migration.poke(meToken.address); - await tx.wait(); - - await expect(tx).to.not.emit(initialVault, "StartMigration"); - migrationDetails = await migration.getDetails(meToken.address); - expect(migrationDetails.started).to.be.equal(false); - }); + // it("should be able to call before soonest, but wont run startMigration()", async () => { + // migrationDetails = await migration.getDetails(meToken.address); + // block = await ethers.provider.getBlock("latest"); + // expect(migrationDetails.soonest).to.be.gt(block.timestamp); + + // const tx = await migration.poke(meToken.address); + // await tx.wait(); + + // await expect(tx).to.not.emit(initialVault, "StartMigration"); + // migrationDetails = await migration.getDetails(meToken.address); + // expect(migrationDetails.started).to.be.equal(false); + // }); it("Triggers startMigration()", async () => { - await mineBlock(migrationDetails.soonest.toNumber() + 1); + const meTokenDetails = await meTokenRegistry.getDetails( + meToken.address + ); + await mineBlock(meTokenDetails.startTime.toNumber() + 2); block = await ethers.provider.getBlock("latest"); - expect(migrationDetails.soonest).to.be.lt(block.timestamp); + expect(meTokenDetails.startTime).to.be.lt(block.timestamp); const tx = await migration.poke(meToken.address); await tx.wait(); await expect(tx) .to.emit(initialVault, "StartMigration") - .withArgs(meToken.address) - // TODO check updated balance here - .to.emit(meTokenRegistry, "UpdateBalances"); + .withArgs(meToken.address); + // .to.emit(meTokenRegistry, "UpdateBalances"); migrationDetails = await migration.getDetails(meToken.address); expect(migrationDetails.started).to.be.equal(true); - expect(migrationDetails.swapped).to.be.equal(true); }); it("should be able to call when migration already started, but wont run startMigration()", async () => { const tx = await migration.poke(meToken.address); @@ -380,10 +358,10 @@ const setup = async () => { await expect(tx) .to.emit(meTokenRegistry, "FinishResubscribe") - .to.emit(weth, "Transfer") + .to.emit(dai, "Transfer") .withArgs( migration.address, - targetVault.address, + initialVault.address, meTokenRegistryDetails.balancePooled.add( meTokenRegistryDetails.balanceLocked ) @@ -391,18 +369,51 @@ const setup = async () => { .to.not.emit(initialVault, "StartMigration"); migrationDetails = await migration.getDetails(meToken.address); - expect(migrationDetails.fee).to.equal(0); - expect(migrationDetails.soonest).to.equal(0); + // expect(migrationDetails.fee).to.equal(0); + // expect(migrationDetails.soonest).to.equal(0); + expect(migrationDetails.isMigrating).to.equal(false); expect(migrationDetails.started).to.equal(false); - expect(migrationDetails.swapped).to.equal(false); + // expect(migrationDetails.swapped).to.equal(false); }); - it("should revert before soonest", async () => { - await migrationRegistry.approve( - targetVault.address, - initialVault.address, - migration.address - ); - + // it("should revert before soonest", async () => { + // // await migrationRegistry.approve( + // // initialVault.address, + // // initialVault.address, + // // migration.address + // // ); + + // block = await ethers.provider.getBlock("latest"); + // earliestSwapTime = block.timestamp + 600 * 60; // 10h in future + + // encodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( + // ["uint256", "uint24"], + // [earliestSwapTime, fees] + // ); + + // await meTokenRegistry + // .connect(account1) + // .initResubscribe( + // meToken.address, + // hubId1, + // migration.address, + // encodedMigrationArgs + // ); + // migrationDetails = await migration.getDetails(meToken.address); + // // expect(migrationDetails.fee).to.equal(fees); + // // expect(migrationDetails.soonest).to.equal(earliestSwapTime); + + // const meTokenRegistryDetails = await meTokenRegistry.getDetails( + // meToken.address + // ); + // await mineBlock(meTokenRegistryDetails.endTime.toNumber() + 2); + // block = await ethers.provider.getBlock("latest"); + // expect(meTokenRegistryDetails.endTime).to.be.lt(block.timestamp); + + // await expect( + // meTokenRegistry.finishResubscribe(meToken.address) + // ).to.be.revertedWith("timestamp < soonest"); + // }); + it("Triggers startsMigration() if it hasn't already started", async () => { block = await ethers.provider.getBlock("latest"); earliestSwapTime = block.timestamp + 600 * 60; // 10h in future @@ -419,30 +430,9 @@ const setup = async () => { migration.address, encodedMigrationArgs ); - migrationDetails = await migration.getDetails(meToken.address); - expect(migrationDetails.fee).to.equal(fees); - expect(migrationDetails.soonest).to.equal(earliestSwapTime); - - const meTokenRegistryDetails = await meTokenRegistry.getDetails( - meToken.address - ); - await mineBlock(meTokenRegistryDetails.endTime.toNumber() + 2); - block = await ethers.provider.getBlock("latest"); - expect(meTokenRegistryDetails.endTime).to.be.lt(block.timestamp); - - await expect( - meTokenRegistry.finishResubscribe(meToken.address) - ).to.be.revertedWith("timestamp < soonest"); - }); - it("Triggers startsMigration() if it hasn't already started", async () => { let meTokenRegistryDetails = await meTokenRegistry.getDetails( meToken.address ); - migrationDetails = await migration.getDetails(meToken.address); - - await mineBlock(migrationDetails.soonest.toNumber() + 2); - block = await ethers.provider.getBlock("latest"); - expect(migrationDetails.soonest).to.be.lt(block.timestamp); const tx = await meTokenRegistry.finishResubscribe(meToken.address); await tx.wait(); @@ -452,7 +442,7 @@ const setup = async () => { ); await expect(tx) .to.emit(meTokenRegistry, "FinishResubscribe") - .to.emit(targetVault, "StartMigration") + .to.emit(initialVault, "StartMigration") .withArgs(meToken.address) // TODO check updated balance here .to.emit(dai, "Transfer") @@ -462,13 +452,14 @@ const setup = async () => { meTokenRegistryDetails.balancePooled.add( meTokenRegistryDetails.balanceLocked ) - ) - .to.emit(meTokenRegistry, "UpdateBalances"); + ); + // .to.emit(meTokenRegistry, "UpdateBalances"); migrationDetails = await migration.getDetails(meToken.address); - expect(migrationDetails.fee).to.equal(0); - expect(migrationDetails.soonest).to.equal(0); + // expect(migrationDetails.fee).to.equal(0); + // expect(migrationDetails.soonest).to.equal(0); + expect(migrationDetails.isMigrating).to.equal(false); expect(migrationDetails.started).to.equal(false); - expect(migrationDetails.swapped).to.equal(false); + // expect(migrationDetails.swapped).to.equal(false); }); describe("During resubscribe", () => { @@ -502,8 +493,8 @@ const setup = async () => { encodedMigrationArgs ); migrationDetails = await migration.getDetails(meToken.address); - expect(migrationDetails.fee).to.equal(fees); - expect(migrationDetails.soonest).to.equal(earliestSwapTime); + // expect(migrationDetails.fee).to.equal(fees); + // expect(migrationDetails.soonest).to.equal(earliestSwapTime); }); it("From warmup => startTime: assets transferred to/from initial vault", async () => { @@ -526,7 +517,7 @@ const setup = async () => { initialVaultBalanceAfter.sub(initialVaultBalanceBefore) ).to.equal(amount); }); - it("From soonest => endTime: assets transferred to/from migration vault", async () => { + it("From startTime => endTime: assets transferred to/from migration vault", async () => { const meTokenDetails = await meTokenRegistry.getDetails( meToken.address ); @@ -538,11 +529,7 @@ const setup = async () => { const initialVaultDAIBefore = await dai.balanceOf( initialVault.address ); - const initialVaultWETHBefore = await weth.balanceOf( - initialVault.address - ); const migrationDAIBefore = await dai.balanceOf(migration.address); - const migrationWETHBefore = await weth.balanceOf(migration.address); const tx = await foundry .connect(account2) @@ -554,21 +541,16 @@ const setup = async () => { const initialVaultDAIAfter = await dai.balanceOf( initialVault.address ); - const initialVaultWETHAfter = await weth.balanceOf( - initialVault.address - ); const migrationDAIAfter = await dai.balanceOf(migration.address); - const migrationWETHAfter = await weth.balanceOf(migration.address); - expect(initialVaultWETHBefore.sub(initialVaultWETHAfter)).to.be.equal( - 0 - ); // initial vault weth balance has no change expect(initialVaultDAIBefore.sub(initialVaultDAIAfter)).to.equal( amount ); // amount deposited before start time - expect(migrationDAIAfter.sub(migrationDAIBefore)).to.be.equal(0); // no change + expect(migrationDAIAfter.sub(migrationDAIBefore)).to.be.equal( + amount.mul(2) + ); // TODO fix with swap balance - expect(migrationWETHAfter.sub(migrationWETHBefore)).to.be.gt(amount); // gt due to swap amount + // expect(migrationWETHAfter.sub(migrationWETHBefore)).to.be.gt(amount); // gt due to swap amount }); it("After endTime: assets transferred to/from target vault", async () => { const meTokenDetails = await meTokenRegistry.getDetails( @@ -582,46 +564,26 @@ const setup = async () => { const initialVaultDAIBefore = await dai.balanceOf( initialVault.address ); - const initialVaultWETHBefore = await weth.balanceOf( - initialVault.address - ); const migrationDAIBefore = await dai.balanceOf(migration.address); - const migrationWETHBefore = await weth.balanceOf(migration.address); - const targetVaultDAIBefore = await dai.balanceOf(targetVault.address); - const targetVaultWETHBefore = await weth.balanceOf( - targetVault.address - ); const tx = await foundry .connect(account2) .mint(meToken.address, amount, account2.address); await tx.wait(); - await expect(tx).to.be.emit(weth, "Transfer"); + await expect(tx).to.be.emit(dai, "Transfer"); const initialVaultDAIAfter = await dai.balanceOf( initialVault.address ); - const initialVaultWETHAfter = await weth.balanceOf( - initialVault.address - ); const migrationDAIAfter = await dai.balanceOf(migration.address); - const migrationWETHAfter = await weth.balanceOf(migration.address); - const targetVaultDAIAfter = await dai.balanceOf(targetVault.address); - const targetVaultWETHAfter = await weth.balanceOf( - targetVault.address - ); - expect(initialVaultWETHBefore.sub(initialVaultWETHAfter)).to.be.equal( - 0 - ); // initial vault weth balance has no change - expect(initialVaultDAIBefore.sub(initialVaultDAIAfter)).to.equal(0); // initial vault dai balance has no change - expect(migrationDAIAfter.sub(migrationDAIBefore)).to.be.equal(0); // no change - expect(migrationWETHAfter).to.be.equal(0); // migration balance goes to target vault - expect(targetVaultDAIAfter.sub(targetVaultDAIBefore)).to.be.equal(0); // no change - expect(targetVaultWETHAfter.sub(targetVaultWETHBefore)).to.be.equal( - amount.add(migrationWETHBefore) - ); // newly minted amount + migration weth balance + expect(initialVaultDAIAfter.sub(initialVaultDAIBefore)).to.equal( + amount.mul(3) + ); + expect(migrationDAIBefore.sub(migrationDAIAfter)).to.be.equal( + amount.mul(2) + ); }); }); }); diff --git a/test/contracts/registries/MeTokenRegistry.ts b/test/contracts/registries/MeTokenRegistry.ts index 893229d0..2b0fa51f 100644 --- a/test/contracts/registries/MeTokenRegistry.ts +++ b/test/contracts/registries/MeTokenRegistry.ts @@ -546,14 +546,13 @@ const setup = async () => { ).to.be.revertedWith("Invalid _encodedMigrationArgs"); }); it("Fails when current and target hub has same asset", async () => { - await expect( - meTokenRegistry.initResubscribe( - meToken, - hubId3, - migration.address, - encodedMigrationArgs - ) - ).to.be.revertedWith("asset same"); + const tx = await meTokenRegistry.callStatic.initResubscribe( + meToken, + hubId3, + migration.address, + encodedMigrationArgs + ); + await expect(tx).to.not.be.revertedWith("asset same"); }); it("Fails when migration address is 0", async () => { await expect( From 9a5d0a89142c98d9755dca2b9b3a9c702c31f2bd Mon Sep 17 00:00:00 2001 From: Parv Date: Wed, 19 Jan 2022 20:52:29 +0530 Subject: [PATCH 04/11] fix: callStatic error --- test/contracts/registries/MeTokenRegistry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/contracts/registries/MeTokenRegistry.ts b/test/contracts/registries/MeTokenRegistry.ts index 2b0fa51f..9caf0c86 100644 --- a/test/contracts/registries/MeTokenRegistry.ts +++ b/test/contracts/registries/MeTokenRegistry.ts @@ -546,7 +546,7 @@ const setup = async () => { ).to.be.revertedWith("Invalid _encodedMigrationArgs"); }); it("Fails when current and target hub has same asset", async () => { - const tx = await meTokenRegistry.callStatic.initResubscribe( + const tx = meTokenRegistry.callStatic.initResubscribe( meToken, hubId3, migration.address, From fb0903ebbf388dc183f98dc23f117254e081ec9a Mon Sep 17 00:00:00 2001 From: Parv Date: Thu, 20 Jan 2022 17:47:52 +0530 Subject: [PATCH 05/11] fix: removed unused inheritance --- contracts/migrations/SameAssetTransferMigration.sol | 8 +------- contracts/migrations/UniswapSingleTransferMigration.sol | 8 +------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/contracts/migrations/SameAssetTransferMigration.sol b/contracts/migrations/SameAssetTransferMigration.sol index b1ffec45..dd275baa 100644 --- a/contracts/migrations/SameAssetTransferMigration.sol +++ b/contracts/migrations/SameAssetTransferMigration.sol @@ -16,13 +16,7 @@ import "../interfaces/ISingleAssetVault.sol"; /// when recollateralizing to a vault with a different base token /// @dev This contract moves the pooled/locked balances from /// one erc20 to another -contract SameAssetTransferMigration is - Initializable, - Ownable, - ReentrancyGuard, - Vault, - IMigration -{ +contract SameAssetTransferMigration is ReentrancyGuard, Vault, IMigration { struct SameAssetMigration { // if migration is active bool isMigrating; diff --git a/contracts/migrations/UniswapSingleTransferMigration.sol b/contracts/migrations/UniswapSingleTransferMigration.sol index 8a171d9f..8eb750e4 100644 --- a/contracts/migrations/UniswapSingleTransferMigration.sol +++ b/contracts/migrations/UniswapSingleTransferMigration.sol @@ -17,13 +17,7 @@ import "../interfaces/ISingleAssetVault.sol"; /// when recollateralizing to a vault with a different base token /// @dev This contract moves the pooled/locked balances from /// one erc20 to another -contract UniswapSingleTransferMigration is - Initializable, - Ownable, - ReentrancyGuard, - Vault, - IMigration -{ +contract UniswapSingleTransferMigration is ReentrancyGuard, Vault, IMigration { struct UniswapSingleTransfer { // The earliest time that the swap can occur uint256 soonest; From 3dace3e3eb9c2d9c89bee38e29f72d3270ab2660 Mon Sep 17 00:00:00 2001 From: Parv Date: Thu, 20 Jan 2022 17:52:11 +0530 Subject: [PATCH 06/11] fix: removed unused imports --- contracts/migrations/SameAssetTransferMigration.sol | 4 +--- contracts/migrations/UniswapSingleTransferMigration.sol | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/contracts/migrations/SameAssetTransferMigration.sol b/contracts/migrations/SameAssetTransferMigration.sol index dd275baa..741bfd12 100644 --- a/contracts/migrations/SameAssetTransferMigration.sol +++ b/contracts/migrations/SameAssetTransferMigration.sol @@ -2,8 +2,6 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "../libs/Details.sol"; import "../vaults/Vault.sol"; @@ -20,7 +18,7 @@ contract SameAssetTransferMigration is ReentrancyGuard, Vault, IMigration { struct SameAssetMigration { // if migration is active bool isMigrating; - // if migration is active and startMigration() has not been triggered + // if startMigration() has been triggered bool started; } diff --git a/contracts/migrations/UniswapSingleTransferMigration.sol b/contracts/migrations/UniswapSingleTransferMigration.sol index 8eb750e4..5be20b2a 100644 --- a/contracts/migrations/UniswapSingleTransferMigration.sol +++ b/contracts/migrations/UniswapSingleTransferMigration.sol @@ -3,8 +3,6 @@ pragma solidity ^0.8.0; import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "../libs/Details.sol"; import "../vaults/Vault.sol"; From 4716845b1f0a05fe7660c6b580fe5423194f3ff1 Mon Sep 17 00:00:00 2001 From: Parv Date: Thu, 20 Jan 2022 18:00:22 +0530 Subject: [PATCH 07/11] fix: checks and optimisations --- contracts/migrations/SameAssetTransferMigration.sol | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/contracts/migrations/SameAssetTransferMigration.sol b/contracts/migrations/SameAssetTransferMigration.sol index 741bfd12..c1ace499 100644 --- a/contracts/migrations/SameAssetTransferMigration.sol +++ b/contracts/migrations/SameAssetTransferMigration.sol @@ -44,17 +44,15 @@ contract SameAssetTransferMigration is ReentrancyGuard, Vault, IMigration { require(hub_.asset == targetHub_.asset, "asset different"); - SameAssetMigration storage usts_ = _sameAssetMigration[_meToken]; - usts_.isMigrating = true; + _sameAssetMigration[_meToken].isMigrating = true; } function poke(address _meToken) external override nonReentrant { - // Make sure meToken is in a state of resubscription - // TODO can add a require that checks if _meToken is resubscribing SameAssetMigration storage usts_ = _sameAssetMigration[_meToken]; Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); - if (usts_.isMigrating && !usts_.started) { + require(usts_.isMigrating, "!migrating"); + if (!usts_.started) { ISingleAssetVault(hub_.vault).startMigration(_meToken); usts_.started = true; } @@ -68,6 +66,7 @@ contract SameAssetTransferMigration is ReentrancyGuard, Vault, IMigration { { require(msg.sender == address(meTokenRegistry), "!meTokenRegistry"); SameAssetMigration storage usts_ = _sameAssetMigration[_meToken]; + require(usts_.isMigrating, "!migrating"); Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); @@ -76,7 +75,6 @@ contract SameAssetTransferMigration is ReentrancyGuard, Vault, IMigration { if (!usts_.started) { ISingleAssetVault(hub_.vault).startMigration(_meToken); usts_.started = true; - // amountOut = _swap(_meToken); } amountOut = meToken_.balancePooled + meToken_.balanceLocked; From 1e870c4b5ffb58182368fbe00270c12aa6b1e50f Mon Sep 17 00:00:00 2001 From: Parv Date: Thu, 20 Jan 2022 18:02:49 +0530 Subject: [PATCH 08/11] fix: removed unnecessary check --- contracts/migrations/SameAssetTransferMigration.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/migrations/SameAssetTransferMigration.sol b/contracts/migrations/SameAssetTransferMigration.sol index c1ace499..88f2ab0a 100644 --- a/contracts/migrations/SameAssetTransferMigration.sol +++ b/contracts/migrations/SameAssetTransferMigration.sol @@ -51,7 +51,6 @@ contract SameAssetTransferMigration is ReentrancyGuard, Vault, IMigration { SameAssetMigration storage usts_ = _sameAssetMigration[_meToken]; Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); - require(usts_.isMigrating, "!migrating"); if (!usts_.started) { ISingleAssetVault(hub_.vault).startMigration(_meToken); usts_.started = true; From 60edfcee85a145d228cee8b0de004fcca0a9d919 Mon Sep 17 00:00:00 2001 From: Parv Date: Thu, 20 Jan 2022 18:08:32 +0530 Subject: [PATCH 09/11] fix: bug --- contracts/migrations/SameAssetTransferMigration.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/migrations/SameAssetTransferMigration.sol b/contracts/migrations/SameAssetTransferMigration.sol index 88f2ab0a..ff54966f 100644 --- a/contracts/migrations/SameAssetTransferMigration.sol +++ b/contracts/migrations/SameAssetTransferMigration.sol @@ -51,7 +51,7 @@ contract SameAssetTransferMigration is ReentrancyGuard, Vault, IMigration { SameAssetMigration storage usts_ = _sameAssetMigration[_meToken]; Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); - if (!usts_.started) { + if (usts_.isMigrating && !usts_.started) { ISingleAssetVault(hub_.vault).startMigration(_meToken); usts_.started = true; } From 1375731cc2028b25b0a7d7e8f37283989c39cbad Mon Sep 17 00:00:00 2001 From: Parv Date: Thu, 20 Jan 2022 19:05:07 +0530 Subject: [PATCH 10/11] test: remove comments and minor changes --- .../migrations/SameAssetTransferMigration.ts | 130 +----------------- 1 file changed, 7 insertions(+), 123 deletions(-) diff --git a/test/contracts/migrations/SameAssetTransferMigration.ts b/test/contracts/migrations/SameAssetTransferMigration.ts index 39eaf99b..604a26f3 100644 --- a/test/contracts/migrations/SameAssetTransferMigration.ts +++ b/test/contracts/migrations/SameAssetTransferMigration.ts @@ -14,8 +14,6 @@ import { impersonate, mineBlock, passHours } from "../../utils/hardhatNode"; import { SameAssetTransferMigration } from "../../../artifacts/types/SameAssetTransferMigration"; import { hubSetup } from "../../utils/hubSetup"; import { expect } from "chai"; -import { Fees } from "../../../artifacts/types/Fees"; -import { VaultRegistry } from "../../../artifacts/types/VaultRegistry"; import { WeightedAverage } from "../../../artifacts/types/WeightedAverage"; const setup = async () => { @@ -56,7 +54,6 @@ const setup = async () => { let encodedCurveDetails: string; let encodedMigrationArgs: string; - let badEncodedMigrationArgs: string; let encodedVaultDAIArgs: string; let block; let migrationDetails: [boolean, boolean] & { @@ -107,17 +104,6 @@ const setup = async () => { curve )); - // targetVault = await deploy( - // "SingleAssetVault", - // undefined, //no libs - // account0.address, // DAO - // foundry.address, // foundry - // hub.address, // hub - // meTokenRegistry.address, //IMeTokenRegistry - // migrationRegistry.address //IMigrationRegistry - // ); - // await vaultRegistry.approve(targetVault.address); - // Register 2nd hub to which we'll migrate to await hub.register( account0.address, @@ -170,11 +156,6 @@ const setup = async () => { }); describe("isValid()", () => { - // TODO checks when metoken do not have a hub - // it("Returns false for invalid encoding", async () => { - // const isValid = await migration.isValid(meToken.address, "0x"); - // expect(isValid).to.be.false; - // }); it("Returns true for valid encoding", async () => { const isValid = await migration.isValid( meToken.address, @@ -182,35 +163,13 @@ const setup = async () => { ); expect(isValid).to.be.true; }); - // it("Returns false for start time before current time", async () => { - // badEncodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( - // ["uint256", "uint24"], - // [earliestSwapTime - 720 * 60, fees] // 2 hours beforehand - // ); - // const isValid = await migration.isValid( - // meToken.address, - // badEncodedMigrationArgs - // ); - // expect(isValid).to.be.false; - // }); - // it("Returns false for nonexistent meToken", async () => { - // const isValid = await migration.isValid( - // account0.address, - // encodedMigrationArgs - // ); - // expect(isValid).to.be.false; - // }); - // it("Returns false for invalid fee", async () => { - // badEncodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( - // ["uint256", "uint24"], - // [earliestSwapTime, 2999] - // ); - // const isValid = await migration.isValid( - // meToken.address, - // badEncodedMigrationArgs - // ); - // expect(isValid).to.be.false; - // }); + it("Returns false for nonexistent meToken", async () => { + const isValid = await migration.isValid( + account0.address, + encodedMigrationArgs + ); + expect(isValid).to.be.false; + }); }); describe("initMigration()", () => { @@ -219,18 +178,6 @@ const setup = async () => { migration.initMigration(meToken.address, encodedMigrationArgs) ).to.be.revertedWith("!meTokenRegistry"); }); - // it("Fails from bad encodings", async () => { - // await expect( - // meTokenRegistry - // .connect(account1) - // .initResubscribe( - // meToken.address, - // hubId2, - // migration.address, - // badEncodedMigrationArgs - // ) - // ).to.be.revertedWith("Invalid _encodedMigrationArgs"); - // }); it("should revert when try to approve already approved vaults", async () => { await expect( migrationRegistry.approve( @@ -302,18 +249,6 @@ const setup = async () => { await expect(tx).to.not.emit(initialVault, "StartMigration"); }); - // it("should be able to call before soonest, but wont run startMigration()", async () => { - // migrationDetails = await migration.getDetails(meToken.address); - // block = await ethers.provider.getBlock("latest"); - // expect(migrationDetails.soonest).to.be.gt(block.timestamp); - - // const tx = await migration.poke(meToken.address); - // await tx.wait(); - - // await expect(tx).to.not.emit(initialVault, "StartMigration"); - // migrationDetails = await migration.getDetails(meToken.address); - // expect(migrationDetails.started).to.be.equal(false); - // }); it("Triggers startMigration()", async () => { const meTokenDetails = await meTokenRegistry.getDetails( meToken.address @@ -328,7 +263,6 @@ const setup = async () => { await expect(tx) .to.emit(initialVault, "StartMigration") .withArgs(meToken.address); - // .to.emit(meTokenRegistry, "UpdateBalances"); migrationDetails = await migration.getDetails(meToken.address); expect(migrationDetails.started).to.be.equal(true); }); @@ -369,50 +303,9 @@ const setup = async () => { .to.not.emit(initialVault, "StartMigration"); migrationDetails = await migration.getDetails(meToken.address); - // expect(migrationDetails.fee).to.equal(0); - // expect(migrationDetails.soonest).to.equal(0); expect(migrationDetails.isMigrating).to.equal(false); expect(migrationDetails.started).to.equal(false); - // expect(migrationDetails.swapped).to.equal(false); }); - // it("should revert before soonest", async () => { - // // await migrationRegistry.approve( - // // initialVault.address, - // // initialVault.address, - // // migration.address - // // ); - - // block = await ethers.provider.getBlock("latest"); - // earliestSwapTime = block.timestamp + 600 * 60; // 10h in future - - // encodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( - // ["uint256", "uint24"], - // [earliestSwapTime, fees] - // ); - - // await meTokenRegistry - // .connect(account1) - // .initResubscribe( - // meToken.address, - // hubId1, - // migration.address, - // encodedMigrationArgs - // ); - // migrationDetails = await migration.getDetails(meToken.address); - // // expect(migrationDetails.fee).to.equal(fees); - // // expect(migrationDetails.soonest).to.equal(earliestSwapTime); - - // const meTokenRegistryDetails = await meTokenRegistry.getDetails( - // meToken.address - // ); - // await mineBlock(meTokenRegistryDetails.endTime.toNumber() + 2); - // block = await ethers.provider.getBlock("latest"); - // expect(meTokenRegistryDetails.endTime).to.be.lt(block.timestamp); - - // await expect( - // meTokenRegistry.finishResubscribe(meToken.address) - // ).to.be.revertedWith("timestamp < soonest"); - // }); it("Triggers startsMigration() if it hasn't already started", async () => { block = await ethers.provider.getBlock("latest"); earliestSwapTime = block.timestamp + 600 * 60; // 10h in future @@ -444,7 +337,6 @@ const setup = async () => { .to.emit(meTokenRegistry, "FinishResubscribe") .to.emit(initialVault, "StartMigration") .withArgs(meToken.address) - // TODO check updated balance here .to.emit(dai, "Transfer") .withArgs( migration.address, @@ -453,13 +345,9 @@ const setup = async () => { meTokenRegistryDetails.balanceLocked ) ); - // .to.emit(meTokenRegistry, "UpdateBalances"); migrationDetails = await migration.getDetails(meToken.address); - // expect(migrationDetails.fee).to.equal(0); - // expect(migrationDetails.soonest).to.equal(0); expect(migrationDetails.isMigrating).to.equal(false); expect(migrationDetails.started).to.equal(false); - // expect(migrationDetails.swapped).to.equal(false); }); describe("During resubscribe", () => { @@ -493,8 +381,6 @@ const setup = async () => { encodedMigrationArgs ); migrationDetails = await migration.getDetails(meToken.address); - // expect(migrationDetails.fee).to.equal(fees); - // expect(migrationDetails.soonest).to.equal(earliestSwapTime); }); it("From warmup => startTime: assets transferred to/from initial vault", async () => { @@ -549,8 +435,6 @@ const setup = async () => { expect(migrationDAIAfter.sub(migrationDAIBefore)).to.be.equal( amount.mul(2) ); - // TODO fix with swap balance - // expect(migrationWETHAfter.sub(migrationWETHBefore)).to.be.gt(amount); // gt due to swap amount }); it("After endTime: assets transferred to/from target vault", async () => { const meTokenDetails = await meTokenRegistry.getDetails( From 1e15513b3a5114279456125c86f2f04a4a91f3fb Mon Sep 17 00:00:00 2001 From: Carl Farterson Date: Thu, 20 Jan 2022 15:53:05 -0800 Subject: [PATCH 11/11] chore: pr feedback --- contracts/migrations/SameAssetTransferMigration.sol | 8 +++----- contracts/registries/MeTokenRegistry.sol | 9 +++++---- test/contracts/registries/MeTokenRegistry.ts | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/contracts/migrations/SameAssetTransferMigration.sol b/contracts/migrations/SameAssetTransferMigration.sol index ff54966f..86385be0 100644 --- a/contracts/migrations/SameAssetTransferMigration.sol +++ b/contracts/migrations/SameAssetTransferMigration.sol @@ -8,12 +8,10 @@ import "../vaults/Vault.sol"; import "../interfaces/IMigration.sol"; import "../interfaces/ISingleAssetVault.sol"; -/// @title Vault migrator from erc20 to erc20 (non-lp) +/// @title Same asset vault migrator /// @author Carl Farterson (@carlfarterson) -/// @notice create a vault that instantly swaps token A for token B -/// when recollateralizing to a vault with a different base token -/// @dev This contract moves the pooled/locked balances from -/// one erc20 to another +/// @notice create a vault to hold an asset if a meToken is resubscribing +/// to a different hub with the same asset contract SameAssetTransferMigration is ReentrancyGuard, Vault, IMigration { struct SameAssetMigration { // if migration is active diff --git a/contracts/registries/MeTokenRegistry.sol b/contracts/registries/MeTokenRegistry.sol index 1736c9db..51dc2054 100644 --- a/contracts/registries/MeTokenRegistry.sol +++ b/contracts/registries/MeTokenRegistry.sol @@ -134,6 +134,11 @@ contract MeTokenRegistry is Ownable, IMeTokenRegistry { require(_migration != address(0), "migration address(0)"); + require( + IVault(_migration).isValid(_meToken, _encodedMigrationArgs), + "Invalid _encodedMigrationArgs" + ); + // Ensure the migration we're using is approved require( migrationRegistry.isApproved( @@ -154,10 +159,6 @@ contract MeTokenRegistry is Ownable, IMeTokenRegistry { meToken_.targetHubId = _targetHubId; meToken_.migration = _migration; - require( - IVault(_migration).isValid(_meToken, _encodedMigrationArgs), - "Invalid _encodedMigrationArgs" - ); IMigration(_migration).initMigration(_meToken, _encodedMigrationArgs); emit InitResubscribe( diff --git a/test/contracts/registries/MeTokenRegistry.ts b/test/contracts/registries/MeTokenRegistry.ts index 9caf0c86..33b004df 100644 --- a/test/contracts/registries/MeTokenRegistry.ts +++ b/test/contracts/registries/MeTokenRegistry.ts @@ -545,7 +545,7 @@ const setup = async () => { ) ).to.be.revertedWith("Invalid _encodedMigrationArgs"); }); - it("Fails when current and target hub has same asset", async () => { + it("Passes when current and target hub have same asset", async () => { const tx = meTokenRegistry.callStatic.initResubscribe( meToken, hubId3,