From a177c1df9237deaa12d8449078fded5cbeef21ee Mon Sep 17 00:00:00 2001 From: Parv Date: Thu, 27 Jan 2022 20:14:46 +0530 Subject: [PATCH 1/7] feat: accept donations - need confirmation: donation fee as zero --- contracts/Foundry.sol | 18 ++++++++++++++++++ contracts/interfaces/IFoundry.sol | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/contracts/Foundry.sol b/contracts/Foundry.sol index 4e593b48..19febf70 100644 --- a/contracts/Foundry.sol +++ b/contracts/Foundry.sol @@ -243,6 +243,24 @@ contract Foundry is IFoundry, Ownable, Initializable { ); } + function donate(address _meToken, uint256 _assetsDeposited) + external + override + { + Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); + Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); + require(meToken_.migration == address(0), "metoken resubscribing"); + + IVault vault = IVault(hub_.vault); + address asset = hub_.asset; + + vault.handleDeposit(msg.sender, asset, _assetsDeposited, 0); + + meTokenRegistry.updateBalanceLocked(true, _meToken, _assetsDeposited); + + emit Donate(_meToken, asset, msg.sender, _assetsDeposited); + } + // NOTE: for now this does not include fees function _calculateMeTokensMinted( address _meToken, diff --git a/contracts/interfaces/IFoundry.sol b/contracts/interfaces/IFoundry.sol index 6455ccc7..65f9a317 100644 --- a/contracts/interfaces/IFoundry.sol +++ b/contracts/interfaces/IFoundry.sol @@ -36,6 +36,18 @@ interface IFoundry { uint256 _assetsReturned ); + /// @notice Event of donating to meToken owner + /// @param _meToken address of meToken burned + /// @param _asset address of asset returned + /// @param _donor address donating the asset + /// @param _assetsDeposited amount of assets to c + event Donate( + address _meToken, + address _asset, + address _donor, + uint256 _assetsDeposited + ); + /// @notice Mint a meToken by depositing the underlying asset /// @param _meToken address of meToken to mint /// @param _assetsDeposited amount of assets to deposit @@ -55,4 +67,9 @@ interface IFoundry { uint256 _meTokensBurned, address _recipient ) external; + + /// @notice Donate a meToken's underlying asset to its owner + /// @param _meToken address of meToken to burn + /// @param _assetsDeposited amount of asset to donate + function donate(address _meToken, uint256 _assetsDeposited) external; } From e9b55a3cbd2bad48eea5a81fdaacfa429dead056 Mon Sep 17 00:00:00 2001 From: Parv Date: Thu, 27 Jan 2022 20:14:54 +0530 Subject: [PATCH 2/7] test: accept donations --- test/contracts/Foundry.ts | 117 +++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 3 deletions(-) diff --git a/test/contracts/Foundry.ts b/test/contracts/Foundry.ts index 8715f557..9cbbf71f 100644 --- a/test/contracts/Foundry.ts +++ b/test/contracts/Foundry.ts @@ -25,7 +25,7 @@ import { MeToken } from "../../artifacts/types/MeToken"; import { expect } from "chai"; import { UniswapSingleTransferMigration } from "../../artifacts/types/UniswapSingleTransferMigration"; import { hubSetup } from "../utils/hubSetup"; -import { ICurve } from "../../artifacts/types"; +import { ICurve, SameAssetTransferMigration } from "../../artifacts/types"; const setup = async () => { describe("Foundry.sol", () => { @@ -46,6 +46,8 @@ const setup = async () => { let singleAssetVault: SingleAssetVault; let migrationRegistry: MigrationRegistry; let curveRegistry: CurveRegistry; + let encodedCurveDetails: string; + let encodedVaultArgs: string; const hubId = 1; const name = "Carl meToken"; @@ -73,12 +75,12 @@ const setup = async () => { // const reserveWeight = BigNumber.from(MAX_WEIGHT).div(2).toString(); before(async () => { ({ DAI, DAIWhale } = await getNamedAccounts()); - const encodedVaultArgs = ethers.utils.defaultAbiCoder.encode( + encodedVaultArgs = ethers.utils.defaultAbiCoder.encode( ["address"], [DAI] ); // TODO: pass in name of curve to deploy, encodedCurveDetails to general func - const encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( + encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( ["uint256", "uint32"], [baseY, reserveWeight] ); @@ -1072,6 +1074,115 @@ const setup = async () => { await meToken.balanceOf(account2.address) ); }); + after(async () => { + const oldDetails = await hub.getDetails(hubId); + await mineBlock(oldDetails.endTime.toNumber() + 2); + const block = await ethers.provider.getBlock("latest"); + expect(oldDetails.endTime).to.be.lt(block.timestamp); + + await hub.finishUpdate(hubId); + const newDetails = await hub.getDetails(hubId); + expect(newDetails.updating).to.be.equal(false); + }); + }); + describe("donate", () => { + let migration: SameAssetTransferMigration; + before(async () => { + await hub.register( + account0.address, + DAI, + singleAssetVault.address, + _curve.address, + refundRatio, + encodedCurveDetails, + encodedVaultArgs + ); + migration = await deploy( + "SameAssetTransferMigration", + undefined, + account0.address, + foundry.address, + hub.address, + meTokenRegistry.address, + migrationRegistry.address + ); + + await migrationRegistry.approve( + singleAssetVault.address, + singleAssetVault.address, + migration.address + ); + + const encodedMigrationArgs = "0x"; + + await meTokenRegistry + .connect(account2) + .initResubscribe( + meToken.address, + 2, + migration.address, + encodedMigrationArgs + ); + expect( + (await meTokenRegistry.getDetails(meToken.address)).migration + ).to.equal(migration.address); + }); + it("should revert when meToken is resubscribing", async () => { + await expect(foundry.donate(meToken.address, 10)).to.be.revertedWith( + "metoken resubscribing" + ); + }); + it("should be able to donate", async () => { + const meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + await mineBlock(meTokenRegistryDetails.endTime.toNumber() + 2); + const block = await ethers.provider.getBlock("latest"); + expect(meTokenRegistryDetails.endTime).to.be.lt(block.timestamp); + await meTokenRegistry.finishResubscribe(meToken.address); + + const oldVaultBalance = await dai.balanceOf(singleAssetVault.address); + const oldAccountBalance = await dai.balanceOf(account0.address); + const oldMeTokenDetails = await meTokenRegistry.getDetails( + meToken.address + ); + const oldAccruedFee = await singleAssetVault.accruedFees(dai.address); + + const assetsDeposited = 10; + const tx = await foundry.donate(meToken.address, assetsDeposited); + + await expect(tx) + .to.emit(foundry, "Donate") + .withArgs( + meToken.address, + dai.address, + account0.address, + assetsDeposited + ) + .to.emit(singleAssetVault, "HandleDeposit") + .withArgs(account0.address, dai.address, assetsDeposited, 0) + .to.emit(meTokenRegistry, "UpdateBalanceLocked") + .withArgs(true, meToken.address, assetsDeposited); + + const newMeTokenDetails = await meTokenRegistry.getDetails( + meToken.address + ); + const newVaultBalance = await dai.balanceOf(singleAssetVault.address); + const newAccountBalance = await dai.balanceOf(account0.address); + const newAccruedFee = await singleAssetVault.accruedFees(dai.address); + + expect(oldMeTokenDetails.balanceLocked.add(assetsDeposited)).to.equal( + newMeTokenDetails.balanceLocked + ); + expect(oldMeTokenDetails.balancePooled).to.equal( + newMeTokenDetails.balancePooled + ); + expect(oldVaultBalance.add(assetsDeposited)).to.equal(newVaultBalance); + expect(oldAccountBalance.sub(assetsDeposited)).to.equal( + newAccountBalance + ); + expect(oldAccruedFee).to.equal(newAccruedFee); + }); }); }); }; From 26b92b9ff441f982db5a11e91fe7bdde6a38c183 Mon Sep 17 00:00:00 2001 From: Parv Date: Fri, 28 Jan 2022 22:24:08 +0530 Subject: [PATCH 3/7] fix(UniswapSingleTransferMigration): add require to check if migration assets are different --- contracts/migrations/UniswapSingleTransferMigration.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contracts/migrations/UniswapSingleTransferMigration.sol b/contracts/migrations/UniswapSingleTransferMigration.sol index d3275835..412fd171 100644 --- a/contracts/migrations/UniswapSingleTransferMigration.sol +++ b/contracts/migrations/UniswapSingleTransferMigration.sol @@ -54,6 +54,12 @@ contract UniswapSingleTransferMigration is ReentrancyGuard, Vault, IMigration { { require(msg.sender == address(meTokenRegistry), "!meTokenRegistry"); + 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, "same asset"); + (uint256 soonest, uint24 fee) = abi.decode( _encodedArgs, (uint256, uint24) From 9660125e090958b7e69abc5dcbcf2d631c85787f Mon Sep 17 00:00:00 2001 From: Parv Date: Fri, 28 Jan 2022 22:27:03 +0530 Subject: [PATCH 4/7] test: add donation with uniswap migration --- test/contracts/Foundry.ts | 131 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 4 deletions(-) diff --git a/test/contracts/Foundry.ts b/test/contracts/Foundry.ts index 9cbbf71f..d0a55a17 100644 --- a/test/contracts/Foundry.ts +++ b/test/contracts/Foundry.ts @@ -30,9 +30,9 @@ import { ICurve, SameAssetTransferMigration } from "../../artifacts/types"; const setup = async () => { describe("Foundry.sol", () => { let DAI: string; - let DAIWhale: string; - let daiHolder: Signer; + let WETH: string; let dai: ERC20; + let weth: ERC20; let account0: SignerWithAddress; let account1: SignerWithAddress; let account2: SignerWithAddress; @@ -62,6 +62,7 @@ const setup = async () => { const tokenDeposited = ethers.utils.parseEther( tokenDepositedInETH.toString() ); + const fee = 3000; // TODO: pass in curve arguments to function // TODO: then loop over array of set of curve arguments @@ -74,7 +75,7 @@ const setup = async () => { // weight at 50% linear curve // const reserveWeight = BigNumber.from(MAX_WEIGHT).div(2).toString(); before(async () => { - ({ DAI, DAIWhale } = await getNamedAccounts()); + ({ DAI, WETH } = await getNamedAccounts()); encodedVaultArgs = ethers.utils.defaultAbiCoder.encode( ["address"], [DAI] @@ -111,9 +112,13 @@ const setup = async () => { // Prefund owner/buyer w/ DAI dai = token; + weth = await getContractAt("ERC20", WETH); await dai .connect(tokenHolder) .transfer(account0.address, amount1.mul(10)); + await weth + .connect(tokenHolder) + .transfer(account0.address, amount1.mul(10)); await dai .connect(tokenHolder) .transfer(account1.address, amount1.mul(10)); @@ -122,6 +127,7 @@ const setup = async () => { .transfer(account2.address, amount1.mul(10)); const max = ethers.constants.MaxUint256; await dai.connect(account0).approve(singleAssetVault.address, max); + await weth.connect(account0).approve(singleAssetVault.address, max); await dai.connect(account1).approve(singleAssetVault.address, max); await dai.connect(account2).approve(singleAssetVault.address, max); await dai.connect(account1).approve(meTokenRegistry.address, max); @@ -1085,7 +1091,7 @@ const setup = async () => { expect(newDetails.updating).to.be.equal(false); }); }); - describe("donate", () => { + describe("donate with same asset migration", () => { let migration: SameAssetTransferMigration; before(async () => { await hub.register( @@ -1184,6 +1190,123 @@ const setup = async () => { expect(oldAccruedFee).to.equal(newAccruedFee); }); }); + + describe("donate with same UniswapSingleTransfer migration", () => { + let migration: UniswapSingleTransferMigration; + before(async () => { + await hub.register( + account0.address, + WETH, + singleAssetVault.address, + _curve.address, + refundRatio, + encodedCurveDetails, + encodedVaultArgs + ); + migration = await deploy( + "UniswapSingleTransferMigration", + undefined, + account0.address, + foundry.address, + hub.address, + meTokenRegistry.address, + migrationRegistry.address + ); + + await migrationRegistry.approve( + singleAssetVault.address, + singleAssetVault.address, + migration.address + ); + + let block = await ethers.provider.getBlock("latest"); + const earliestSwapTime = block.timestamp + 600 * 60; // 10h in future + const encodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint24"], + [earliestSwapTime, fee] + ); + + await meTokenRegistry + .connect(account2) + .initResubscribe( + meToken.address, + 3, + migration.address, + encodedMigrationArgs + ); + expect( + (await meTokenRegistry.getDetails(meToken.address)).migration + ).to.equal(migration.address); + const 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); + }); + it("should revert when meToken is resubscribing", async () => { + await expect(foundry.donate(meToken.address, 10)).to.be.revertedWith( + "metoken resubscribing" + ); + }); + it("should be able to donate", async () => { + await meTokenRegistry.finishResubscribe(meToken.address); + + const oldDAIVaultBalance = await dai.balanceOf( + singleAssetVault.address + ); + const oldWETHVaultBalance = await weth.balanceOf( + singleAssetVault.address + ); + const oldAccountBalance = await weth.balanceOf(account0.address); + const oldMeTokenDetails = await meTokenRegistry.getDetails( + meToken.address + ); + const oldAccruedFee = await singleAssetVault.accruedFees(weth.address); + + const assetsDeposited = 10; + const tx = await foundry.donate(meToken.address, assetsDeposited); + + await expect(tx) + .to.emit(foundry, "Donate") + .withArgs( + meToken.address, + weth.address, + account0.address, + assetsDeposited + ) + .to.emit(singleAssetVault, "HandleDeposit") + .withArgs(account0.address, weth.address, assetsDeposited, 0) + .to.emit(meTokenRegistry, "UpdateBalanceLocked") + .withArgs(true, meToken.address, assetsDeposited); + + const newMeTokenDetails = await meTokenRegistry.getDetails( + meToken.address + ); + const newDAIVaultBalance = await dai.balanceOf( + singleAssetVault.address + ); + const newWETHVaultBalance = await weth.balanceOf( + singleAssetVault.address + ); + const newAccountBalance = await weth.balanceOf(account0.address); + const newAccruedFee = await singleAssetVault.accruedFees(weth.address); + + expect(oldMeTokenDetails.balanceLocked.add(assetsDeposited)).to.equal( + newMeTokenDetails.balanceLocked + ); + expect(oldMeTokenDetails.balancePooled).to.equal( + newMeTokenDetails.balancePooled + ); + expect(oldDAIVaultBalance).to.equal(newDAIVaultBalance); + expect(oldWETHVaultBalance.add(assetsDeposited)).to.equal( + newWETHVaultBalance + ); + expect(oldAccountBalance.sub(assetsDeposited)).to.equal( + newAccountBalance + ); + expect(oldAccruedFee).to.equal(newAccruedFee); + }); + }); }); }; From 15918910519c6307d6cd85ab98d29769beafea35 Mon Sep 17 00:00:00 2001 From: Parv Date: Sat, 29 Jan 2022 10:48:34 +0530 Subject: [PATCH 5/7] fix: reduce fork asset transfer amount - this was required as otherwise transfer transaction would fail. - possibily tokenHolder runs out of tokens --- test/integration/MeTokenRegistry/ResubscribeRefundRatio.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/MeTokenRegistry/ResubscribeRefundRatio.ts b/test/integration/MeTokenRegistry/ResubscribeRefundRatio.ts index 870ca4fc..049d127f 100644 --- a/test/integration/MeTokenRegistry/ResubscribeRefundRatio.ts +++ b/test/integration/MeTokenRegistry/ResubscribeRefundRatio.ts @@ -114,11 +114,11 @@ const setup = async () => { // Pre-load owner and buyer w/ DAI await dai .connect(tokenHolder) - .transfer(account2.address, ethers.utils.parseEther("500")); + .transfer(account2.address, ethers.utils.parseEther("250")); await weth .connect(tokenHolder) - .transfer(account2.address, ethers.utils.parseEther("500")); + .transfer(account2.address, ethers.utils.parseEther("250")); // Create meToken and subscribe to Hub1 await meTokenRegistry @@ -155,7 +155,7 @@ const setup = async () => { migration.address, encodedMigrationArgs ); - tokenDepositedInETH = 100; + tokenDepositedInETH = 50; tokenDeposited = ethers.utils.parseEther(tokenDepositedInETH.toString()); await dai .connect(account2) From 9fb29d45793f274e9c0d49fc35c674faeddbb96a Mon Sep 17 00:00:00 2001 From: Carl Farterson Date: Sun, 30 Jan 2022 01:27:23 -0800 Subject: [PATCH 6/7] fix: meToken casing --- contracts/Foundry.sol | 2 +- test/contracts/Foundry.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/Foundry.sol b/contracts/Foundry.sol index 19febf70..94a1acb1 100644 --- a/contracts/Foundry.sol +++ b/contracts/Foundry.sol @@ -249,7 +249,7 @@ contract Foundry is IFoundry, Ownable, Initializable { { Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); - require(meToken_.migration == address(0), "metoken resubscribing"); + require(meToken_.migration == address(0), "meToken resubscribing"); IVault vault = IVault(hub_.vault); address asset = hub_.asset; diff --git a/test/contracts/Foundry.ts b/test/contracts/Foundry.ts index d0a55a17..11d52f0d 100644 --- a/test/contracts/Foundry.ts +++ b/test/contracts/Foundry.ts @@ -1135,7 +1135,7 @@ const setup = async () => { }); it("should revert when meToken is resubscribing", async () => { await expect(foundry.donate(meToken.address, 10)).to.be.revertedWith( - "metoken resubscribing" + "meToken resubscribing" ); }); it("should be able to donate", async () => { From d42e6884b8d971e3acdebe5ae26fd9d42d5c1f95 Mon Sep 17 00:00:00 2001 From: Carl Farterson Date: Sun, 30 Jan 2022 01:49:54 -0800 Subject: [PATCH 7/7] fix: missed camelcase --- test/contracts/Foundry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/contracts/Foundry.ts b/test/contracts/Foundry.ts index 11d52f0d..26ac4150 100644 --- a/test/contracts/Foundry.ts +++ b/test/contracts/Foundry.ts @@ -1245,7 +1245,7 @@ const setup = async () => { }); it("should revert when meToken is resubscribing", async () => { await expect(foundry.donate(meToken.address, 10)).to.be.revertedWith( - "metoken resubscribing" + "meToken resubscribing" ); }); it("should be able to donate", async () => {