diff --git a/contracts/reserve/DistributionHelper.sol b/contracts/reserve/DistributionHelper.sol index 5f121ccf..db2fdf73 100644 --- a/contracts/reserve/DistributionHelper.sol +++ b/contracts/reserve/DistributionHelper.sol @@ -44,7 +44,7 @@ contract DistributionHelper is 0x467719aD09025FcC6cF6F8311755809d45a5E5f3; enum TransferType { - FuseBridge, + DEPRECATED_FuseBridge, LayerZeroBridge, AxelarBridge, Contract @@ -235,12 +235,8 @@ contract DistributionHelper is DistributionRecipient storage _recipient, uint256 _amount ) internal { - if (_recipient.transferType == TransferType.FuseBridge) { - nativeToken().transferAndCall( - fuseBridge, - _amount, - abi.encodePacked(_recipient.addr) - ); + if (_recipient.transferType == TransferType.DEPRECATED_FuseBridge) { + revert("DEPRECATED"); } else if (_recipient.transferType == TransferType.LayerZeroBridge) { nativeToken().approve(address(mpbBridge), _amount); (uint256 lzFee, ) = ILayerZeroFeeEstimator(address(mpbBridge)) diff --git a/contracts/utils/ReserveRestore.sol b/contracts/utils/ReserveRestore.sol index 18503a39..469a7cba 100644 --- a/contracts/utils/ReserveRestore.sol +++ b/contracts/utils/ReserveRestore.sol @@ -10,20 +10,84 @@ contract ReserveRestore { NameService ns; uint256 public constant LOCKED_HACKED_FUNDS = 971921364208; bool public executed; + address owner; constructor(NameService _ns) { ns = _ns; + owner = msg.sender; } - function upgrade(address daiFrom) external { + function end() external { + require(msg.sender == owner, "not owner"); + Controller ctrl = Controller(ns.getAddress("CONTROLLER")); + address avatar = ns.dao().avatar(); + + // prevent executing again} + require(ctrl.unregisterSelf(avatar), "unregistering failed"); + } + + function donate(address daiFrom, uint256 amount) external { + require(msg.sender == owner, "not owner"); + address avatar = ns.dao().avatar(); + + GoodReserveCDai reserve = GoodReserveCDai(ns.getAddress("RESERVE")); + ERC20(ns.getAddress("DAI")).transferFrom(daiFrom, address(this), amount); + uint256 daiBalance = ERC20(ns.getAddress("DAI")).balanceOf(address(this)); + cERC20 cdai = cERC20(ns.getAddress("CDAI")); + ERC20 dai = ERC20(ns.getAddress("DAI")); + + dai.approve(address(cdai), daiBalance); + //Mint cDAIs + uint256 cDaiResult = cdai.mint(daiBalance); + require(cDaiResult == 0, "Minting cDai failed"); + uint256 cdaiBalance = cdai.balanceOf(address(this)); + cdai.transfer(address(reserve), cdaiBalance); + cdaiBalance = cdai.balanceOf(address(reserve)); + + uint256 gdSupply = ERC20(ns.getAddress("GOODDOLLAR")).totalSupply() - + LOCKED_HACKED_FUNDS; + console.log("supply: %s", gdSupply); + // get 0.0001 dai price in cdai + uint256 currentPrice = reserve.currentPrice(); + console.log("currentPrice: %s", currentPrice); + + console.log("cdaiBalance: %s", cdaiBalance); + + // given price calculate the reserve ratio + uint32 reserveRatio = uint32( + (cdaiBalance * 1e2 * 1e6) / (currentPrice * gdSupply) + ); // mul by 1e2 to cover gd precision, cdaibalance precision=initialprice, mul by 1e6 to receive result in the precision of reserveRatio(1e6) + console.log("reserveRatio: %s", reserveRatio); + Controller ctrl = Controller(ns.getAddress("CONTROLLER")); + // function initializeToken( + // ERC20 _token, + // uint256 _gdSupply, + // uint256 _tokenSupply, + // uint32 _reserveRatio, + // uint256 _lastExpansion + // ) + (bool ok, ) = ctrl.genericCall( + address(reserve.getMarketMaker()), + abi.encodeCall( + GoodMarketMaker.initializeToken, + (cdai, gdSupply, cdaiBalance, reserveRatio, block.timestamp) + ), + address(avatar), + 0 + ); + require(ok, "initializeToken failed"); + } + + function upgrade(address daiFrom, uint256 amount) external { + require(msg.sender == owner, "not owner"); require(executed == false, "already upgraded"); executed = true; address avatar = ns.dao().avatar(); GoodReserveCDai reserve = GoodReserveCDai(ns.getAddress("RESERVE")); - ERC20(ns.getAddress("DAI")).transferFrom(daiFrom, address(this), 200000e18); + ERC20(ns.getAddress("DAI")).transferFrom(daiFrom, address(this), amount); uint256 daiBalance = ERC20(ns.getAddress("DAI")).balanceOf(address(this)); - require(daiBalance >= 200000e18, "not enough reserve"); + require(daiBalance >= 50000e18, "not enough reserve"); cERC20 cdai = cERC20(ns.getAddress("CDAI")); ERC20 dai = ERC20(ns.getAddress("DAI")); @@ -89,8 +153,5 @@ contract ReserveRestore { ); require(ok, "setContributionRatio failed"); - - // prevent executing again - require(ctrl.unregisterSelf(avatar), "unregistering failed"); } } diff --git a/scripts/proposals/reserveRestore.ts b/scripts/proposals/reserveRestore.ts index 9d75e4d5..63a26250 100644 --- a/scripts/proposals/reserveRestore.ts +++ b/scripts/proposals/reserveRestore.ts @@ -28,6 +28,7 @@ * - upgrade governance * - unpause reserve * - unpause goodfundmanager + * - switch fuse distribution to use lz bridge insted of deprecated fuse bridge * * * Fuse: @@ -48,7 +49,7 @@ import { executeViaGuardian, executeViaSafe, verifyProductionSigner } from "../m import ProtocolSettings from "../../releases/deploy-settings.json"; import dao from "../../releases/deployment.json"; -import { ExchangeHelper, FuseOldBridgeKill, GoodFundManager, GoodMarketMaker, GoodReserveCDai, IGoodDollar } from "../../types"; +import { ExchangeHelper, FuseOldBridgeKill, GoodFundManager, GoodMarketMaker, GoodReserveCDai, IGoodDollar, ReserveRestore } from "../../types"; let { name: networkName } = network; @@ -56,6 +57,9 @@ const isSimulation = network.name === "hardhat" || network.name === "fork" || ne // hacker and hacked multichain bridge accounts const LOCKED_ACCOUNTS = ["0xeC577447D314cf1e443e9f4488216651450DBE7c", "0xD17652350Cfd2A37bA2f947C910987a3B1A1c60d", "0x6738fA889fF31F82d9Fe8862ec025dbE318f3Fde"] +const INITIAL_DAI = ethers.utils.parseEther("100000") // 100k +// reserve funder (goodlabs safe) +const funder = "0xF0652a820dd39EC956659E0018Da022132f2f40a" export const upgradeMainnet = async network => { const isProduction = networkName.includes("production"); @@ -102,12 +106,11 @@ export const upgradeMainnet = async network => { const govImpl = await ethers.deployContract("CompoundVotingMachine"); const distHelperImplt = await ethers.deployContract("DistributionHelper"); const marketMakerImpl = await ethers.deployContract("GoodMarketMaker"); - const upgradeImpl = await ethers.deployContract("ReserveRestore", [release.NameService]); + const upgradeImpl = await ethers.deployContract("ReserveRestore", [release.NameService]) as ReserveRestore; const gd = (await ethers.getContractAt("IGoodDollar", release.GoodDollar)) as IGoodDollar; - // reserve funder (goodlabs safe) - const funder = "0xF0652a820dd39EC956659E0018Da022132f2f40a" + // test blacklisting to prevent burn by hacker if (isSimulation) { @@ -155,9 +158,10 @@ export const upgradeMainnet = async network => { release.StakersDistribution, //upgrade stakers dist release.GoodMarketMaker, //upgrade mm release.CompoundVotingMachine, // upgrade gov + release.DistributionHelper, // switch to lz bridge for fuse release.ExchangeHelper, // activate upgrade changes release.Controller, - upgradeImpl.address, + // upgradeImpl.address, release.GuardiansSafe + "_" + release.GoodReserveCDai ]; @@ -180,9 +184,10 @@ export const upgradeMainnet = async network => { "upgradeTo(address)", "upgradeTo(address)", "upgradeTo(address)", + "addOrUpdateRecipient((uint32,uint32,address,uint8))", "setAddresses()", "registerScheme(address,bytes32,bytes4,address)", // give upgrade contract permissions - "upgrade(address)", + // "upgrade(address, uint256)", "unpause()" ]; @@ -203,6 +208,10 @@ export const upgradeMainnet = async network => { ethers.utils.defaultAbiCoder.encode(["address"], [stakersDistImpl.address]), ethers.utils.defaultAbiCoder.encode(["address"], [marketMakerImpl.address]), ethers.utils.defaultAbiCoder.encode(["address"], [govImpl.address]), + ethers.utils.defaultAbiCoder.encode( + ["uint32", "uint32", "address", "uint8"], + [1000, 122, dao["production"].UBIScheme, 1] //10% chainId 122 ubischeme 1-lz bridge + ), "0x", //setAddresses ethers.utils.defaultAbiCoder.encode( ["address", "bytes32", "bytes4", "address"], @@ -213,7 +222,6 @@ export const upgradeMainnet = async network => { release.Avatar ] ), - ethers.utils.defaultAbiCoder.encode(["address"], [funder]), "0x" ]; @@ -239,17 +247,21 @@ export const upgradeMainnet = async network => { } if (isSimulation) { - await mainnetPostChecks() + await mainnetPostChecks(upgradeImpl) } }; -const mainnetPostChecks = async () => { +const mainnetPostChecks = async (upgradeImpl: ReserveRestore) => { networkName = "production-mainnet"; let release: { [key: string]: any } = dao[networkName]; let [root, ...signers] = await ethers.getSigners(); const gd = await ethers.getContractAt("IGoodDollar", release.GoodDollar); + //execute the reserve initialization + (await upgradeImpl.upgrade(funder, INITIAL_DAI)).wait() + + const locked = await ethers.getImpersonatedSigner(LOCKED_ACCOUNTS[0]); const tx = await gd .connect(locked) @@ -299,12 +311,25 @@ const mainnetPostChecks = async () => { console.log("ubiEvents after collect interest:", ubiEvents) // check expansion after some time await time.increase(365 * 60 * 60 * 24) + const gdSupplyBeforeExpansion = await gd.totalSupply(); + const reserveStateBeforeYearExpansion = await mm.reserveTokens(release.cDAI) + const expansionTX = await (await gfm.collectInterest([], false)).wait() const ubiExpansionEvents = last(await reserve.queryFilter(reserve.filters.UBIMinted(), -1)) console.log("gfm events after 1 year expansion:", expansionTX.events?.filter(_ => _.event === 'FundsTransferred')) console.log("ubiEvents after 1 year expansion:", ubiExpansionEvents) const reserveStateAfterYearExpansion = await mm.reserveTokens(release.cDAI) - console.log({ reserveStateAfterYearExpansion }) + const gdSupplyAfterExpansion = await gd.totalSupply(); + console.log({ reserveStateAfterYearExpansion, gdSupplyAfterExpansion, gdSupplyBeforeExpansion, reserveStateBeforeYearExpansion }) + + //execute the reserve initialization + await (await upgradeImpl.donate(funder, INITIAL_DAI)).wait() + const [cdaiPriceAfterDonation, daiPriceAfterDonation] = await (await Promise.all([reserve.currentPrice(), reserve.currentPriceDAI()])).map(_ => _.toNumber()) + console.log("price after dai donation:", { cdaiPriceAfterDonation, daiPriceAfterDonation }) + const reserveStateAfterDonation = await mm.reserveTokens(release.cDAI) + console.log({ reserveStateAfterDonation }) + + await (await upgradeImpl.end()).wait() } export const upgradeFuse = async network => { let [root] = await ethers.getSigners(); diff --git a/test/reserve/DistributionHelper.test.ts b/test/reserve/DistributionHelper.test.ts index a1a1f988..dea96f0b 100644 --- a/test/reserve/DistributionHelper.test.ts +++ b/test/reserve/DistributionHelper.test.ts @@ -195,7 +195,7 @@ describe("DistributionHelper", () => { expect(dr.transferType).to.equal(2); }); - it("should distribute via fuse bridge", async () => { + it("should not distribute via fuse bridge", async () => { const { distHelper, bridge } = await loadFixture(fixture); const recipient = signers[0]; @@ -215,15 +215,15 @@ describe("DistributionHelper", () => { await genericCall(distHelper.address, encodedCall, avatar.address, 0); await goodDollar.mint(distHelper.address, "100000000000"); - await distHelper.onDistribution("100000000000"); - expect(await goodDollar.balanceOf(bridge.address)).to.equal( - (100000000000 * 2000) / 10000 - ); - - const events = await bridge.queryFilter(bridge.filters.OnToken()); - expect(events[0].args.sender).to.equal(distHelper.address); - expect(events[0].args.amount).to.equal((100000000000 * 2000) / 10000); - expect(events[0].args.data).to.equal(recipient.address.toLowerCase()); + await expect(distHelper.onDistribution("100000000000")).revertedWith("DEPRECATED"); + // expect(await goodDollar.balanceOf(bridge.address)).to.equal( + // (100000000000 * 2000) / 10000 + // ); + + // const events = await bridge.queryFilter(bridge.filters.OnToken()); + // expect(events[0].args.sender).to.equal(distHelper.address); + // expect(events[0].args.amount).to.equal((100000000000 * 2000) / 10000); + // expect(events[0].args.data).to.equal(recipient.address.toLowerCase()); }); it("should distribute via layerzero bridge", async () => {