diff --git a/contracts/facets/MeTokenRegistryFacet.sol b/contracts/facets/MeTokenRegistryFacet.sol index ef02fc81..78f55978 100644 --- a/contracts/facets/MeTokenRegistryFacet.sol +++ b/contracts/facets/MeTokenRegistryFacet.sol @@ -155,7 +155,7 @@ contract MeTokenRegistryFacet is require(sender == meTokenInfo.owner, "!owner"); require(meTokenInfo.targetHubId != 0, "!resubscribing"); require( - block.timestamp < meTokenInfo.startTime, + !IMigration(meTokenInfo.migration).migrationStarted(meToken), "Resubscription has started" ); diff --git a/contracts/interfaces/IMigration.sol b/contracts/interfaces/IMigration.sol index 08258381..e23352af 100644 --- a/contracts/interfaces/IMigration.sol +++ b/contracts/interfaces/IMigration.sol @@ -21,4 +21,12 @@ interface IMigration { function finishMigration(address meToken) external returns (uint256 amountOut); + + /// @notice Method returns bool if migration started + /// @param meToken Address of meToken + /// @return started True if migration started else false + function migrationStarted(address meToken) + external + view + returns (bool started); } diff --git a/contracts/migrations/SameAssetTransferMigration.sol b/contracts/migrations/SameAssetTransferMigration.sol index 71c21dd8..857dbf97 100644 --- a/contracts/migrations/SameAssetTransferMigration.sol +++ b/contracts/migrations/SameAssetTransferMigration.sol @@ -122,4 +122,14 @@ contract SameAssetTransferMigration is ReentrancyGuard, Vault, IMigration { if (meTokenInfo.hubId == 0) return false; return true; } + + /// @inheritdoc IMigration + function migrationStarted(address meToken) + external + view + override + returns (bool started) + { + return _sameAssetMigration[meToken].started; + } } diff --git a/contracts/migrations/UniswapSingleTransferMigration.sol b/contracts/migrations/UniswapSingleTransferMigration.sol index 9ca8e3ca..a9d2ed10 100644 --- a/contracts/migrations/UniswapSingleTransferMigration.sol +++ b/contracts/migrations/UniswapSingleTransferMigration.sol @@ -84,7 +84,9 @@ contract UniswapSingleTransferMigration is ReentrancyGuard, Vault, IMigration { meTokenInfo.hubId ); if ( - usts.soonest != 0 && block.timestamp > usts.soonest && !usts.started + usts.soonest != 0 && // this is to ensure the meToken is resubscribing + block.timestamp > usts.soonest && + !usts.started ) { ISingleAssetVault(hubInfo.vault).startMigration(meToken); usts.started = true; @@ -163,6 +165,16 @@ contract UniswapSingleTransferMigration is ReentrancyGuard, Vault, IMigration { } } + /// @inheritdoc IMigration + function migrationStarted(address meToken) + external + view + override + returns (bool started) + { + return _uniswapSingleTransfers[meToken].started; + } + /// @dev parent call must have reentrancy check function _swap(address meToken) private returns (uint256 amountOut) { UniswapSingleTransfer storage usts = _uniswapSingleTransfers[meToken]; diff --git a/test/contracts/facets/MeTokenRegistryFacet.ts b/test/contracts/facets/MeTokenRegistryFacet.ts index d4cf8be5..bb6793ec 100644 --- a/test/contracts/facets/MeTokenRegistryFacet.ts +++ b/test/contracts/facets/MeTokenRegistryFacet.ts @@ -80,6 +80,7 @@ const setup = async () => { let hub: HubFacet; let token: ERC20; let fee: FeesFacet; + let dai: ERC20; let weth: ERC20; let account0: SignerWithAddress; let account1: SignerWithAddress; @@ -175,6 +176,7 @@ const setup = async () => { foundry.address // diamond ); await migration.deployed(); + dai = await getContractAt("ERC20", DAI); weth = await getContractAt("ERC20", WETH); }); @@ -612,7 +614,7 @@ const setup = async () => { meTokenRegistry.connect(account1).cancelResubscribe(meTokenAddr1) ).to.be.revertedWith("!resubscribing"); }); - it("Successfully resets meToken info", async () => { + it("Successfully cancels resubscribe", async () => { block = await ethers.provider.getBlock("latest"); expect( (await meTokenRegistry.getMeTokenInfo(meTokenAddr0)).startTime @@ -666,15 +668,60 @@ const setup = async () => { ); await tx.wait(); }); - it("Fails if resubscription already started", async () => { - const meTokenRegistryDetails = await meTokenRegistry.getMeTokenInfo( - meTokenAddr0 + it("should revert if migration started (usts.started = true)", async () => { + // forward time to endCoolDown + await mineBlock( + ( + await meTokenRegistry.getMeTokenInfo(meTokenAddr0) + ).endCooldown.toNumber() + 2 ); - // forward time after start time - await mineBlock(meTokenRegistryDetails.startTime.toNumber() + 2); block = await ethers.provider.getBlock("latest"); - expect(meTokenRegistryDetails.startTime).to.be.lt(block.timestamp); + const earliestSwapTime = block.timestamp + 600 * 60; // 10h in future + encodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint24"], + [earliestSwapTime, fees] + ); + + await meTokenRegistry.initResubscribe( + meToken, + targetHubId, + migration.address, + encodedMigrationArgs + ); + + await dai + .connect(tokenHolder) + .transfer(account0.address, tokenDeposited); + await dai.approve( + singleAssetVault.address, + ethers.constants.MaxUint256 + ); + await foundry.mint(meTokenAddr0, tokenDeposited, account0.address); + + await mineBlock( + (await migration.getDetails(meTokenAddr0)).soonest.toNumber() + 2 + ); // starTime > soonest + + tx = await migration.poke(meTokenAddr0); // would call startMigration and swap + + // called from startMigration + await expect(tx) + .to.emit(dai, "Transfer") + .withArgs( + singleAssetVault.address, + migration.address, + tokenDeposited + ); + await expect(tx) + .to.emit(singleAssetVault, "StartMigration") + .withArgs(meTokenAddr0); + // migration -> uniswap + await expect(tx).to.emit(dai, "Transfer"); + // uniswap -> migration + await expect(tx).to.emit(weth, "Transfer"); + + // should revert to cancelResubscribe now await expect( meTokenRegistry.cancelResubscribe(meTokenAddr0) ).to.be.revertedWith("Resubscription has started"); @@ -687,7 +734,7 @@ const setup = async () => { meTokenRegistry.connect(account1).finishResubscribe(meTokenAddr1) ).to.be.revertedWith("No targetHubId"); }); - it("Fails if updating but cooldown not reached", async () => { + it("Fails if updating but endTime not reached", async () => { const meTokenRegistryDetails = await meTokenRegistry.getMeTokenInfo( meTokenAddr0 );