diff --git a/contracts/core/index-fund/IIndexFund.sol b/contracts/core/index-fund/IIndexFund.sol index 2a56e7863..01db8df99 100644 --- a/contracts/core/index-fund/IIndexFund.sol +++ b/contracts/core/index-fund/IIndexFund.sol @@ -27,6 +27,15 @@ interface IIndexFund { uint256 nextRotationBlock; // block height to perform next rotation on } + struct FundResponse { + uint256 id; + string name; + string description; + uint32[] endowments; + uint256 splitToLiquid; + uint256 expiryTime; + } + /** * @notice function to update config of index fund * @dev can be called by owner to set new config @@ -77,9 +86,14 @@ interface IIndexFund { * @notice Function to update a Fund's endowment members * @dev Can be called by owner to add/remove endowments to a Fund * @param fundId The id of the Fund to be updated - * @param endowments An array of endowments to be set for a Fund + * @param endowmentsAdd An array of endowments to be added to a Fund + * @param endowmentsRemove An array of endowments to be removed from a Fund */ - function updateFundMembers(uint256 fundId, uint32[] memory endowments) external; + function updateFundMembers( + uint256 fundId, + uint32[] memory endowmentsAdd, + uint32[] memory endowmentsRemove + ) external; /** * @notice deposit function which can be called by user to add funds to index fund @@ -113,7 +127,7 @@ interface IIndexFund { * @param fundId Fund id * @return Fund details */ - function queryFundDetails(uint256 fundId) external view returns (IndexFundStorage.Fund memory); + function queryFundDetails(uint256 fundId) external view returns (IIndexFund.FundResponse memory); /** * @dev Query in which index funds is an endowment part of @@ -126,5 +140,5 @@ interface IIndexFund { * @dev Query active fund details * @return Fund details */ - function queryActiveFundDetails() external view returns (IndexFundStorage.Fund memory); + function queryActiveFundDetails() external view returns (IIndexFund.FundResponse memory); } diff --git a/contracts/core/index-fund/IndexFund.sol b/contracts/core/index-fund/IndexFund.sol index d3d755a49..7bf47b555 100644 --- a/contracts/core/index-fund/IndexFund.sol +++ b/contracts/core/index-fund/IndexFund.sol @@ -11,6 +11,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IIndexFund} from "./IIndexFund.sol"; import {Array, Array32} from "../../lib/array.sol"; +import {IterableMapping} from "../../lib/IterableMapping.sol"; import {Utils} from "../../lib/utils.sol"; import {IRegistrar} from "../registrar/interfaces/IRegistrar.sol"; import {RegistrarStorage} from "../registrar/storage.sol"; @@ -28,7 +29,7 @@ uint256 constant MIN_AMOUNT_PER_ENDOWMENT = 100; * It is responsible for creating new funds, adding endowments to funds, and * distributing funds to the endowment members */ -contract IndexFund is IIndexFund, Storage, OwnableUpgradeable, ReentrancyGuard { +contract IndexFund is IIndexFund, Storage, OwnableUpgradeable, ReentrancyGuard, IterableMapping { using SafeERC20 for IERC20; using SafeMath for uint256; @@ -128,13 +129,14 @@ contract IndexFund is IIndexFund, Storage, OwnableUpgradeable, ReentrancyGuard { id: state.nextFundId, name: name, description: description, - endowments: endowments, splitToLiquid: splitToLiquid, expiryTime: expiryTime }); + // set all Fund <> Endowment mappings for (uint8 i = 0; i < endowments.length; i++) { - state.FundsByEndowment[endowments[i]].push(state.nextFundId); + IterableMapping.set(state.FundsByEndowment[endowments[i]], state.nextFundId, true); + IterableMapping.set(state.EndowmentsByFund[state.nextFundId], endowments[i], true); } if (rotatingFund) { @@ -174,19 +176,14 @@ contract IndexFund is IIndexFund, Storage, OwnableUpgradeable, ReentrancyGuard { require(msg.sender == registrarConfig.accountsContract, "Unauthorized"); - bool found; - uint32 index; // remove endowment from all involved funds if in their endowments array - for (uint32 i = 0; i < state.FundsByEndowment[endowment].length; i++) { - uint256 fundId = state.FundsByEndowment[endowment][i]; - (index, found) = Array32.indexOf(state.Funds[fundId].endowments, endowment); - if (found) { - Array32.remove(state.Funds[fundId].endowments, index); - emit MemberRemoved(fundId, endowment); - // if endowment removal results in a fund having zero endowment members left, close out the fund - if (state.Funds[fundId].endowments.length == 0) { - removeFund(fundId); - } + for (uint i = 0; i < state.FundsByEndowment[endowment].keys.length; i++) { + uint256 fundId = IterableMapping.getKeyAtIndex(state.FundsByEndowment[endowment], i); + IterableMapping.remove(state.EndowmentsByFund[fundId], endowment); + emit MemberRemoved(fundId, endowment); + // if endowment removal results in a fund having zero endowment members left, close out the fund + if (state.EndowmentsByFund[fundId].keys.length == 0) { + removeFund(fundId); } } // wipe involved funds for the target endowment member ID @@ -197,50 +194,43 @@ contract IndexFund is IIndexFund, Storage, OwnableUpgradeable, ReentrancyGuard { * @notice Function to update a Fund's endowment members * @dev Can be called by owner to add/remove endowments to a Fund * @param fundId The id of the Fund to be updated - * @param endowments An array of endowments to be set for a Fund + * @param endowmentsAdd An array of endowments to be added to a Fund + * @param endowmentsRemove An array of endowments to be removed from a Fund */ - function updateFundMembers(uint256 fundId, uint32[] memory endowments) external onlyOwner { - require(endowments.length > 0, "Must pass at least one endowment member to add to the Fund"); + function updateFundMembers( + uint256 fundId, + uint32[] memory endowmentsAdd, + uint32[] memory endowmentsRemove + ) external onlyOwner { require( - endowments.length <= MAX_ENDOWMENT_MEMBERS, - "Fund endowment members exceeds upper limit" + endowmentsAdd.length > 0 || endowmentsRemove.length > 0, + "Must pass at least one endowment member to add to or remove from the Fund" ); require(!fundIsExpired(fundId, block.timestamp), "Fund Expired"); - uint32[] memory currEndowments = state.Funds[fundId].endowments; - bool found; - uint32 index; - uint256 fundIndex; - - // sort out which of the endowments passed need to be added to a Fund - for (uint32 i = 0; i < endowments.length; i++) { - (index, found) = Array32.indexOf(currEndowments, endowments[i]); - // if found in current Endowments, there's nothing we need to do - // if NOT in current Endowments, then we need to add it - if (!found) { - state.FundsByEndowment[endowments[i]].push(fundId); - } + // add Endowments passed to a Fund members and FundsByEndowment mappings + for (uint32 i = 0; i < endowmentsAdd.length; i++) { + IterableMapping.set(state.FundsByEndowment[endowmentsAdd[i]], fundId, true); + IterableMapping.set(state.EndowmentsByFund[fundId], endowmentsAdd[i], true); } - // sort out which of the current endowments need to be removed from a Fund - for (uint32 i = 0; i < currEndowments.length; i++) { - (index, found) = Array32.indexOf(endowments, currEndowments[i]); - // if found in new Endowments, there's nothing we need to do - // if NOT in new Endowments list, we need to remove it - if (!found) { - // remove fund from the endowment's involved funds list - uint256[] memory involvedFunds = state.FundsByEndowment[currEndowments[i]]; - (fundIndex, found) = Array.indexOf(involvedFunds, fundId); - Array.remove(state.FundsByEndowment[currEndowments[i]], fundIndex); - } - // if endowment removal results in a fund having zero endowment members left, close out the fund - if (state.Funds[fundId].endowments.length == 0) { - removeFund(fundId); - } + // Endowments to be removed from a Fund + for (uint32 i = 0; i < endowmentsRemove.length; i++) { + IterableMapping.remove(state.EndowmentsByFund[fundId], endowmentsRemove[i]); + IterableMapping.remove(state.FundsByEndowment[endowmentsRemove[i]], fundId); } - // set array of endowment members on the Fund - state.Funds[fundId].endowments = endowments; - emit MembersUpdated(fundId, endowments); + + // resulting fund has no members, remove it + if (state.EndowmentsByFund[fundId].keys.length == 0) { + removeFund(fundId); + } + + // final check that resulting fund members list is within limits + require( + state.EndowmentsByFund[fundId].keys.length <= MAX_ENDOWMENT_MEMBERS, + "Fund endowment members exceeds upper limit" + ); + emit MembersUpdated(fundId, keysAsUint32(state.EndowmentsByFund[fundId])); } /** @@ -410,9 +400,17 @@ contract IndexFund is IIndexFund, Storage, OwnableUpgradeable, ReentrancyGuard { * @param fundId Fund id * @return Fund details */ - function queryFundDetails(uint256 fundId) external view returns (IndexFundStorage.Fund memory) { - require(state.Funds[fundId].endowments.length > 0, "Invalid Fund ID"); - return state.Funds[fundId]; + function queryFundDetails(uint256 fundId) external view returns (IIndexFund.FundResponse memory) { + require(state.EndowmentsByFund[fundId].keys.length > 0, "Non-existent Fund ID"); + return + FundResponse({ + id: state.Funds[fundId].id, + name: state.Funds[fundId].name, + description: state.Funds[fundId].description, + endowments: keysAsUint32(state.EndowmentsByFund[fundId]), + splitToLiquid: state.Funds[fundId].splitToLiquid, + expiryTime: state.Funds[fundId].expiryTime + }); } /** @@ -421,16 +419,25 @@ contract IndexFund is IIndexFund, Storage, OwnableUpgradeable, ReentrancyGuard { * @return Fund details */ function queryInvolvedFunds(uint32 endowmentId) external view returns (uint256[] memory) { - return state.FundsByEndowment[endowmentId]; + return state.FundsByEndowment[endowmentId].keys; } /** * @dev Query active fund details * @return Fund details */ - function queryActiveFundDetails() external view returns (IndexFundStorage.Fund memory) { + function queryActiveFundDetails() external view returns (IIndexFund.FundResponse memory) { require(state.activeFund != 0, "Active fund not set"); - return state.Funds[state.activeFund]; + require(state.EndowmentsByFund[state.activeFund].keys.length > 0, "Non-existent Fund ID"); + return + FundResponse({ + id: state.Funds[state.activeFund].id, + name: state.Funds[state.activeFund].name, + description: state.Funds[state.activeFund].description, + endowments: keysAsUint32(state.EndowmentsByFund[state.activeFund]), + splitToLiquid: state.Funds[state.activeFund].splitToLiquid, + expiryTime: state.Funds[state.activeFund].expiryTime + }); } /* @@ -453,6 +460,7 @@ contract IndexFund is IIndexFund, Storage, OwnableUpgradeable, ReentrancyGuard { if (state.activeFund == fundId && state.rotatingFunds.length > 0) { state.activeFund = nextActiveFund(); } + delete state.EndowmentsByFund[fundId]; emit FundRemoved(fundId); } @@ -471,24 +479,24 @@ contract IndexFund is IIndexFund, Storage, OwnableUpgradeable, ReentrancyGuard { address token, uint256 amount ) internal { - require(state.Funds[fundId].endowments.length > 0, "Fund must have members"); + require(state.EndowmentsByFund[fundId].keys.length > 0, "Fund must have members"); // require enough funds to allow for downstream fees calulations, etc require( - amount >= MIN_AMOUNT_PER_ENDOWMENT.mul(state.Funds[fundId].endowments.length), + amount >= MIN_AMOUNT_PER_ENDOWMENT.mul(state.EndowmentsByFund[fundId].keys.length), "Amount must be enough to cover the minimum units per endowment for all members of a Fund" ); // execute donation message for each endowment in the fund - for (uint256 i = 0; i < state.Funds[fundId].endowments.length; i++) { + for (uint256 i = 0; i < state.EndowmentsByFund[fundId].keys.length; i++) { IAccounts(accountsContract).depositERC20( AccountMessages.DepositRequest({ - id: state.Funds[fundId].endowments[i], + id: uint32(IterableMapping.getKeyAtIndex(state.EndowmentsByFund[fundId], i)), lockedPercentage: 100 - liquidSplit, liquidPercentage: liquidSplit, donationMatch: msg.sender }), token, - amount.div(state.Funds[fundId].endowments.length) + amount.div(state.EndowmentsByFund[fundId].keys.length) ); } @@ -539,4 +547,17 @@ contract IndexFund is IIndexFund, Storage, OwnableUpgradeable, ReentrancyGuard { return state.rotatingFunds[index + 1]; } } + + /** + * @dev Converts a Map's keys from a Uint256 Array to Uint32 Array + * @param map Map + * @return keys32 Map's keys as a Uint32 Array + */ + function keysAsUint32(IterableMapping.Map storage map) internal view returns (uint32[] memory) { + uint32[] memory keys32 = new uint32[](map.keys.length); + for (uint256 i = 0; i < map.keys.length; i++) { + keys32[i] = uint32(map.keys[i]); + } + return keys32; + } } diff --git a/contracts/core/index-fund/scripts/deploy.ts b/contracts/core/index-fund/scripts/deploy.ts index 273c22856..4c764c0ad 100644 --- a/contracts/core/index-fund/scripts/deploy.ts +++ b/contracts/core/index-fund/scripts/deploy.ts @@ -32,7 +32,7 @@ export async function deployIndexFund( // deploy proxy logger.out("Deploying proxy..."); - const initData = indexFund.interface.encodeFunctionData("initialize(address,uint256,uint256)", [ + const initData = indexFund.interface.encodeFunctionData("initialize", [ registrar, config.INDEX_FUND_DATA.fundRotation, config.INDEX_FUND_DATA.fundingGoal, diff --git a/contracts/core/index-fund/storage.sol b/contracts/core/index-fund/storage.sol index 4656b8b50..bdb9c5327 100644 --- a/contracts/core/index-fund/storage.sol +++ b/contracts/core/index-fund/storage.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; +import {IterableMapping} from "../../lib/IterableMapping.sol"; import {IIndexFund} from "./IIndexFund.sol"; library IndexFundStorage { @@ -8,7 +9,6 @@ library IndexFundStorage { uint256 id; string name; string description; - uint32[] endowments; //Fund Specific: over-riding SC level setting to handle a fixed split value // Defines the % to split off into liquid account, and if defined overrides all other splits uint256 splitToLiquid; @@ -31,8 +31,10 @@ library IndexFundStorage { uint256[] rotatingFunds; // list of active, rotating funds (ex. 17 funds, 1 for each of the UNSDGs) // Fund ID >> Fund mapping(uint256 => Fund) Funds; - // Endow ID >> [Fund IDs] - mapping(uint32 => uint256[]) FundsByEndowment; + // Fund ID >> Mapping (Endow ID >> bool) + mapping(uint256 => IterableMapping.Map) EndowmentsByFund; + // Endow ID >> Mapping (Fund ID >> bool) + mapping(uint32 => IterableMapping.Map) FundsByEndowment; } } diff --git a/contracts/lib/IterableMapping.sol b/contracts/lib/IterableMapping.sol new file mode 100644 index 000000000..85d97c52e --- /dev/null +++ b/contracts/lib/IterableMapping.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; + +contract IterableMapping { + struct Map { + uint256[] keys; + mapping(uint256 => bool) values; + mapping(uint256 => uint) indexOf; + mapping(uint256 => bool) inserted; + } + + function get(Map storage map, uint256 key) internal view returns (bool) { + return map.values[key]; + } + + function getKeyAtIndex(Map storage map, uint index) internal view returns (uint256) { + return map.keys[index]; + } + + function size(Map storage map) internal view returns (uint) { + return map.keys.length; + } + + function set(Map storage map, uint256 key, bool val) internal { + if (map.inserted[key]) { + map.values[key] = val; + } else { + map.inserted[key] = true; + map.values[key] = val; + map.indexOf[key] = map.keys.length; + map.keys.push(key); + } + } + + function remove(Map storage map, uint256 key) internal { + if (!map.inserted[key]) { + return; + } + + delete map.inserted[key]; + delete map.values[key]; + + uint256 index = map.indexOf[key]; + uint256 lastKey = map.keys[map.keys.length - 1]; + + map.indexOf[lastKey] = index; + delete map.indexOf[key]; + + map.keys[index] = lastKey; + map.keys.pop(); + } +} diff --git a/tasks/deploy/deployIndexFund.ts b/tasks/deploy/deployIndexFund.ts index 2d35a70f1..c5ca95c47 100644 --- a/tasks/deploy/deployIndexFund.ts +++ b/tasks/deploy/deployIndexFund.ts @@ -30,8 +30,9 @@ task("deploy:IndexFund", "Will deploy IndexFund contract") const addresses = await getAddresses(hre); const registrar = taskArgs.registrar || addresses.registrar.proxy; + const owner = taskArgs.owner || addresses.multiSig.apTeam.proxy; - const deployment = await deployIndexFund(registrar, hre); + const deployment = await deployIndexFund(registrar, owner, hre); if (!deployment) { return; diff --git a/tasks/manage/changeOwner.ts b/tasks/manage/changeOwner.ts deleted file mode 100644 index a9d95f3c9..000000000 --- a/tasks/manage/changeOwner.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {task} from "hardhat/config"; -import type {TaskArguments} from "hardhat/types"; -import {IndexFund__factory} from "typechain-types"; -import {getAddresses, getSigners, logger} from "utils"; - -task("manage:changeOwner", "Will update the owner of the specified contract").setAction( - async (_taskArguments: TaskArguments, hre) => { - try { - const addresses = await getAddresses(hre); - const {proxyAdmin} = await getSigners(hre); - const indexfund = IndexFund__factory.connect(addresses.indexFund.proxy, proxyAdmin); - - logger.out("Current owner:"); - let currentConfig = await indexfund.queryConfig(); - logger.out(currentConfig.owner); - - logger.out("Changing owner to:"); - logger.out(addresses.multiSig.apTeam.proxy); - await indexfund.updateOwner(addresses.multiSig.apTeam.proxy); - } catch (error) { - logger.out(error, logger.Level.Error); - } - } -); diff --git a/tasks/manage/index.ts b/tasks/manage/index.ts index f79b6ef53..a421da06f 100644 --- a/tasks/manage/index.ts +++ b/tasks/manage/index.ts @@ -1,7 +1,6 @@ import "./accounts"; import "./addMultisigOwner"; import "./changeAdmin"; -import "./changeOwner"; import "./charityApplications"; import "./createEndowment"; import "./createIndexFund"; diff --git a/tasks/manage/indexFund/index.ts b/tasks/manage/indexFund/index.ts index 2b6ab803a..b59a7f442 100644 --- a/tasks/manage/indexFund/index.ts +++ b/tasks/manage/indexFund/index.ts @@ -1,2 +1 @@ -import "./updateOwner"; -import "./updateRegistrar"; +import "./updateConfig"; diff --git a/tasks/manage/indexFund/updateOwner.ts b/tasks/manage/indexFund/updateConfig.ts similarity index 50% rename from tasks/manage/indexFund/updateOwner.ts rename to tasks/manage/indexFund/updateConfig.ts index aad426c77..32c783ec1 100644 --- a/tasks/manage/indexFund/updateOwner.ts +++ b/tasks/manage/indexFund/updateConfig.ts @@ -2,51 +2,50 @@ import {task} from "hardhat/config"; import {APTeamMultiSig__factory, IndexFund__factory} from "typechain-types"; import {confirmAction, getAddresses, getSigners, logger} from "utils"; -type TaskArgs = {to: string; yes: boolean}; +type TaskArgs = { + newConfig: {registrarContract: string; fundingGoal: number; fundingRotation: number}; + yes: boolean; +}; -task("manage:IndexFund:updateOwner", "Will update the owner of the IndexFund") - .addOptionalParam( - "to", - "Address of the new owner. Ensure at least one of `apTeamMultisigOwners` is the controller of this address. Will default to `contract-address.json > multiSig.apTeam.proxy` if none is provided." +task("manage:IndexFund:updateConfig", "Will update the config of the IndexFund") + .addParam( + "newConfig", + "New Config. Registrar Contract address, funding rotation blocks & funding goal amount." ) .addFlag("yes", "Automatic yes to prompt.") .setAction(async (taskArgs: TaskArgs, hre) => { try { + let newConfig = taskArgs.newConfig; + logger.divider(); const addresses = await getAddresses(hre); const {apTeamMultisigOwners} = await getSigners(hre); - const newOwner = taskArgs.to || addresses.multiSig.apTeam.proxy; - - logger.out("Querying current IndexFund owner..."); + logger.out("Querying current IndexFund registrar..."); const indexFund = IndexFund__factory.connect( addresses.indexFund.proxy, apTeamMultisigOwners[0] ); - const curOwner = (await indexFund.queryConfig()).owner; - if (curOwner === newOwner) { - return logger.out(`"${newOwner}" is already the owner.`); - } - logger.out(`Current owner: ${curOwner}`); const isConfirmed = - taskArgs.yes || (await confirmAction(`Transfer ownership to: ${newOwner}`)); + taskArgs.yes || + (await confirmAction(`Update Registrar address to: ${newConfig.registrarContract}`)); if (!isConfirmed) { return logger.out("Confirmation denied.", logger.Level.Warn); } - logger.out(`Transferring ownership to: ${newOwner}...`); - const data = indexFund.interface.encodeFunctionData("updateOwner", [newOwner]); const apTeamMultiSig = APTeamMultiSig__factory.connect( - curOwner, // ensure connection to current owning APTeamMultiSig contract + addresses.multiSig.apTeam.proxy, // ensure connection to current owning APTeamMultiSig contract apTeamMultisigOwners[0] ); + const data = indexFund.interface.encodeFunctionData("updateConfig", [ + newConfig.registrarContract, + newConfig.fundingRotation, + newConfig.fundingGoal, + ]); const tx = await apTeamMultiSig.submitTransaction(indexFund.address, 0, data, "0x"); logger.out(`Tx hash: ${tx.hash}`); await tx.wait(); - - const updatedOwner = (await indexFund.queryConfig()).owner; - logger.out(`New owner: ${updatedOwner}`); } catch (error) { logger.out(error, logger.Level.Error); } diff --git a/tasks/manage/indexFund/updateRegistrar.ts b/tasks/manage/indexFund/updateRegistrar.ts deleted file mode 100644 index 9a804dae2..000000000 --- a/tasks/manage/indexFund/updateRegistrar.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {task} from "hardhat/config"; -import {APTeamMultiSig__factory, IndexFund__factory} from "typechain-types"; -import {confirmAction, getAddresses, getSigners, logger} from "utils"; - -type TaskArgs = {newRegistrar: string; yes: boolean}; - -task("manage:IndexFund:updateRegistrar", "Will update the registrar address of the IndexFund") - .addOptionalParam( - "newRegistrar", - "Address of the new registrar. Will default to `contract-address.json > registrar.proxy` if none is provided." - ) - .addFlag("yes", "Automatic yes to prompt.") - .setAction(async (taskArgs: TaskArgs, hre) => { - try { - logger.divider(); - const addresses = await getAddresses(hre); - const {apTeamMultisigOwners} = await getSigners(hre); - - const newRegistrar = taskArgs.newRegistrar || addresses.registrar.proxy; - - logger.out("Querying current IndexFund registrar..."); - const indexFund = IndexFund__factory.connect( - addresses.indexFund.proxy, - apTeamMultisigOwners[0] - ); - const curRegistrar = (await indexFund.queryConfig()).registrarContract; - if (curRegistrar === newRegistrar) { - return logger.out(`"${newRegistrar}" is already set as the registrar address.`); - } - logger.out(`Current registrar: ${curRegistrar}`); - - const isConfirmed = - taskArgs.yes || (await confirmAction(`Update Registrar address to: ${newRegistrar}`)); - if (!isConfirmed) { - return logger.out("Confirmation denied.", logger.Level.Warn); - } - - logger.out(`Updating Registrar address to: ${newRegistrar}...`); - const curOwner = (await indexFund.queryConfig()).owner; - const apTeamMultiSig = APTeamMultiSig__factory.connect( - curOwner, // ensure connection to current owning APTeamMultiSig contract - apTeamMultisigOwners[0] - ); - const data = indexFund.interface.encodeFunctionData("updateRegistrar", [newRegistrar]); - const tx = await apTeamMultiSig.submitTransaction(indexFund.address, 0, data, "0x"); - logger.out(`Tx hash: ${tx.hash}`); - await tx.wait(); - - const updatedOwner = (await indexFund.queryConfig()).registrarContract; - logger.out(`New registrar: ${updatedOwner}`); - } catch (error) { - logger.out(error, logger.Level.Error); - } - }); diff --git a/test/core/IndexFund.ts b/test/core/IndexFund.ts index b891a177f..d98b0a4a0 100644 --- a/test/core/IndexFund.ts +++ b/test/core/IndexFund.ts @@ -1,5 +1,5 @@ import {FakeContract, smock} from "@defi-wonderland/smock"; -import {expect, use} from "chai"; +import {expect} from "chai"; import hre from "hardhat"; import {BigNumber} from "ethers"; import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; @@ -13,7 +13,6 @@ import { DummyWMATIC__factory, IndexFund, IndexFund__factory, - IAccountsDepositWithdrawEndowments, ITransparentUpgradeableProxy__factory, Registrar, Registrar__factory, @@ -29,7 +28,7 @@ import { import {genWallet, getSigners} from "utils"; import {deployFacetAsProxy} from "test/core/accounts/utils/deployTestFacet"; import {AccountStorage} from "typechain-types/contracts/test/accounts/TestFacetProxyContract"; -import {LocalRegistrarLib} from "../../../typechain-types/contracts/core/registrar/LocalRegistrar"; +import {RegistrarStorage} from "typechain-types/contracts/core/registrar/Registrar"; describe("IndexFund", function () { const {ethers, upgrades} = hre; @@ -45,16 +44,10 @@ describe("IndexFund", function () { let facet: AccountsDepositWithdrawEndowments; let state: TestFacetProxyContract; - const defaultApParams = { - routerAddr: ethers.constants.AddressZero, - refundAddr: ethers.constants.AddressZero, - } as LocalRegistrarLib.AngelProtocolParamsStruct; - async function deployIndexFundAsProxy( fundRotation: number = 0, // no block-based rotation fundingGoal: number = 10000 ): Promise { - let apParams = defaultApParams; if (!registrar) { registrar = await smock.fake(new Registrar__factory()); } @@ -328,29 +321,33 @@ describe("IndexFund", function () { }); it("reverts when the message sender is not the owner", async function () { - expect(indexFund.connect(user).updateFundMembers(1, [1, 2])).to.be.revertedWith( + expect(indexFund.connect(user).updateFundMembers(1, [1, 2], [])).to.be.revertedWith( "Unauthorized" ); }); it("reverts when no members are passed", async function () { - expect(indexFund.updateFundMembers(1, [])).to.be.revertedWith( + expect(indexFund.updateFundMembers(1, [], [])).to.be.revertedWith( "Must pass at least one endowment member to add to the Fund" ); }); it("reverts when too many members are passed", async function () { - expect(indexFund.updateFundMembers(1, [1, 2, 3])).to.be.revertedWith( + expect(indexFund.updateFundMembers(1, [1, 2, 3], [])).to.be.revertedWith( "Fund endowment members exceeds upper limit" ); }); it("reverts when the fund is expired", async function () { - expect(indexFund.updateFundMembers(2, [1, 2])).to.be.revertedWith("Fund Expired"); + expect(indexFund.updateFundMembers(2, [1, 2], [])).to.be.revertedWith("Fund Expired"); }); it("passes when the fund is not expired and member inputs are valid", async function () { - expect(await indexFund.updateFundMembers(1, [1, 2])) + expect(await indexFund.updateFundMembers(1, [], [3])) + .to.emit(indexFund, "MembersUpdated") + .withArgs(1, [2]); + + expect(await indexFund.updateFundMembers(1, [1, 2], [3])) .to.emit(indexFund, "MembersUpdated") .withArgs(1, [1, 2]); }); @@ -502,7 +499,7 @@ describe("IndexFund", function () { currTime + 42069 ) ) - .to.emit("FundCreated") + .to.emit(indexFund, "FundCreated") .withArgs(3); time.increase(42069); // move time forward so Fund #3 is @ expiry @@ -511,7 +508,7 @@ describe("IndexFund", function () { expect(activeFund.id).to.equal(3); // should fail when prep clean up process removes the expired fund, leaving 0 funds available - expect(indexFund.depositERC20(0, token.address, 500, 0)).to.be.revertedWith( + expect(indexFund.depositERC20(0, token.address, 500)).to.be.revertedWith( "Must have rotating funds active to pass a Fund ID of 0" ); }); @@ -519,7 +516,7 @@ describe("IndexFund", function () { it("passes for a specific fund, amount > min & token is valid", async function () { // create 1 active, rotating fund expect(await indexFund.createIndexFund("Test Fund #4", "Test fund", [2, 3], true, 50, 0)) - .to.emit("FundCreated") + .to.emit(indexFund, "FundCreated") .withArgs(4); expect( @@ -535,7 +532,7 @@ describe("IndexFund", function () { it("passes for an active fund donation(amount-based rotation), amount > min & token is valid", async function () { // create 1 more active, rotating fund for full rotation testing expect(await indexFund.createIndexFund("Test Fund #5", "Test fund", [2], true, 100, 0)) - .to.emit("FundCreated") + .to.emit(indexFund, "FundCreated") .withArgs(5); let ifState = await indexFund.queryState(); diff --git a/test/core/accounts/AccountsUpdateStatusEndowments.ts b/test/core/accounts/AccountsUpdateStatusEndowments.ts index 8873a7783..3aa262448 100644 --- a/test/core/accounts/AccountsUpdateStatusEndowments.ts +++ b/test/core/accounts/AccountsUpdateStatusEndowments.ts @@ -172,7 +172,7 @@ describe("AccountsUpdateStatusEndowments", function () { expect(endowState[1].enumData).to.equal(1); expect(endowState[1].data.addr).to.equal(ethers.constants.AddressZero); expect(endowState[1].data.endowId).to.equal(0); - expect(endowState[1].data.fundId).to.equal(funds[0].id); + expect(endowState[1].data.fundId).to.equal(funds[0]); }); });