diff --git a/contracts/identity/IdentityV3.sol b/contracts/identity/IdentityV3.sol index 3c37186f..5814ef9b 100644 --- a/contracts/identity/IdentityV3.sol +++ b/contracts/identity/IdentityV3.sol @@ -66,12 +66,9 @@ contract IdentityV3 is __Pausable_init_unchained(); __EIP712_init_unchained("Identity", "1.0.0"); authenticationPeriod = 365 * 3; - _setupRole(DEFAULT_ADMIN_ROLE, avatar); _setupRole(DEFAULT_ADMIN_ROLE, _owner); - _setupRole(PAUSER_ROLE, avatar); _setupRole(PAUSER_ROLE, _owner); _setupRole(IDENTITY_ADMIN_ROLE, _owner); - _setupRole(IDENTITY_ADMIN_ROLE, avatar); oldIdentity = _oldIdentity; } diff --git a/contracts/identity/IdentityV4.sol b/contracts/identity/IdentityV4.sol new file mode 100644 index 00000000..36173b5a --- /dev/null +++ b/contracts/identity/IdentityV4.sol @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/SignatureCheckerUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; + +import "../utils/DAOUpgradeableContract.sol"; +import "../utils/NameService.sol"; +import "../Interfaces.sol"; + +// import "hardhat/console.sol"; + +/* @title Identity contract responsible for whitelisting + * and keeping track of amount of whitelisted users + */ +contract IdentityV4 is + DAOUpgradeableContract, + AccessControlUpgradeable, + PausableUpgradeable, + EIP712Upgradeable +{ + struct Identity { + uint256 dateAuthenticated; + uint256 dateAdded; + string did; + uint256 whitelistedOnChainId; + uint8 status; //0 nothing, 1 whitelisted, 2 daocontract, 255 blacklisted + uint32 authCount; + } + + bytes32 public constant IDENTITY_ADMIN_ROLE = keccak256("identity_admin"); + bytes32 public constant PAUSER_ROLE = keccak256("pause_admin"); + string public constant TYPED_STRUCTURE = + "ConnectIdentity(address whitelisted,address connected,uint256 deadline)"; + + /* @dev rough estimate of number of whitelisted addresses */ + uint256 public whitelistedCount; + uint256 public whitelistedContracts; + uint256 private unused_authenticationPeriod; + + mapping(address => Identity) public identities; + + mapping(bytes32 => address) public didHashToAddress; + + mapping(address => address) public connectedAccounts; + + IIdentity public oldIdentity; + + uint32[] public reverifyDaysOptions; + + event BlacklistAdded(address indexed account); + event BlacklistRemoved(address indexed account); + + event WhitelistedAdded(address indexed account); + event WhitelistedRemoved(address indexed account); + event WhitelistedAuthenticated(address indexed account, uint256 timestamp); + + event ContractAdded(address indexed account); + event ContractRemoved(address indexed account); + + event AccountConnected(address indexed connected, address indexed to); + event AccountDisconnected(address indexed disconnected, address indexed from); + + function initialize( + address _owner, + IIdentity _oldIdentity + ) public initializer { + __AccessControl_init_unchained(); + __Pausable_init_unchained(); + __EIP712_init_unchained("Identity", "1.0.0"); + _setupRole(DEFAULT_ADMIN_ROLE, _owner); + _setupRole(PAUSER_ROLE, _owner); + _setupRole(IDENTITY_ADMIN_ROLE, _owner); + + oldIdentity = _oldIdentity; + + // default reverify schedule (days) + // adjust as needed via setReverifyDaysOptions + reverifyDaysOptions.push(1); + reverifyDaysOptions.push(7); + reverifyDaysOptions.push(180); + } + + /** + * @dev used to initialize after deployment once nameservice is available + */ + function initDAO(address _ns) external onlyRole(DEFAULT_ADMIN_ROLE) { + require(address(nameService) == address(0), "already initialized"); + setDAO(INameService(_ns)); + _setupRole(DEFAULT_ADMIN_ROLE, avatar); + _setupRole(PAUSER_ROLE, avatar); + _setupRole(IDENTITY_ADMIN_ROLE, avatar); + } + + modifier onlyWhitelisted() { + require(isWhitelisted(msg.sender), "not whitelisted"); + _; + } + + /** + * @dev Set the reverification schedule (days). Only identity admins. + * Provide non-empty array. Values must fit in uint8 (<=255). + */ + function setReverifyDaysOptions( + uint8[] calldata options + ) external onlyRole(IDENTITY_ADMIN_ROLE) { + require(options.length > 0, "empty options"); + // replace storage array + delete reverifyDaysOptions; + for (uint256 i = 0; i < options.length; i++) { + reverifyDaysOptions.push(options[i]); + } + } + + /** + * @dev Sets the authentication date of `account` + * to the current time. + * Can only be called by Identity Administrators. + * @param account address to change its auth date + */ + function authenticate(address account) public { + return authenticateWithTimestamp(account, block.timestamp); + } + + /** + * @dev Sets the authentication date of `account` + * to the current time. + * Can only be called by Identity Administrators. + * @param account address to change its auth date + * @param timestamp the authentication timestamp + */ + function authenticateWithTimestamp( + address account, + uint256 timestamp + ) public onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + require(identities[account].status == 1, "not whitelisted"); + uint daysSinceAuthentication = (timestamp - + identities[account].dateAuthenticated) / 1 days; + + // should happen before updating dateAuthenticated + if (shouldReverify(account, daysSinceAuthentication)) { + identities[account].authCount += 1; + // if authCount has advanced past the last configured reverify step, + // reset back to start (cycle). This ensures we won't index out of bounds + // and allows repeated cycles through the reverify schedule. + if (identities[account].authCount >= reverifyDaysOptions.length) { + identities[account].authCount = 0; + } + } + identities[account].dateAuthenticated = timestamp; + + emit WhitelistedAuthenticated(account, timestamp); + } + + /** + * @dev Adds an address as whitelisted. + * Can only be called by Identity Administrators. + * @param account address to add as whitelisted + */ + function addWhitelisted( + address account + ) public onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + _addWhitelisted(account, _chainId()); + } + + /** + @dev Adds an address as whitelisted under a specific ID + @param account The address to add + @param did the ID to add account under + */ + function addWhitelistedWithDIDAndChain( + address account, + string memory did, + uint256 orgChain, + uint256 dateAuthenticated + ) external onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + _addWhitelistedWithDID(account, did, orgChain); + + //in case we are whitelisting on a new chain an already whitelisted account, we need to make sure it expires at the same time + if (dateAuthenticated > 0) { + identities[account].dateAuthenticated = dateAuthenticated; + } + } + + /** + * @dev Adds an address as whitelisted under a specific ID + * @param account The address to add + * @param did the ID to add account under + */ + function addWhitelistedWithDID( + address account, + string memory did + ) public onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + _addWhitelistedWithDID(account, did, _chainId()); + } + + /** + * @dev Removes an address as whitelisted. + * Can only be called by Identity Administrators. + * @param account address to remove as whitelisted + */ + function removeWhitelisted( + address account + ) public onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + _removeWhitelisted(account); + } + + /** + * @dev Renounces message sender from whitelisted + */ + function renounceWhitelisted() external whenNotPaused onlyWhitelisted { + _removeWhitelisted(msg.sender); + } + + function shouldReverify( + address account, + uint daysSinceAuth + ) public view returns (bool) { + if (identities[account].authCount >= reverifyDaysOptions.length) + return false; + uint reverifyAfterDays = reverifyDaysOptions[identities[account].authCount]; + if (daysSinceAuth >= reverifyAfterDays) return true; + + return false; + } + + /** + * @dev Returns true if given address has been added to whitelist + * @param account the address to check + * @return a bool indicating weather the address is present in whitelist + */ + function isWhitelisted(address account) public view returns (bool) { + uint256 daysSinceAuthentication = (block.timestamp - + identities[account].dateAuthenticated) / 1 days; + + if (identities[account].status == 1) { + return shouldReverify(account, daysSinceAuthentication) == false; + } + + if (address(oldIdentity) != address(0)) { + try oldIdentity.isWhitelisted(account) returns (bool res) { + return res; + } catch { + return false; + } + } + return false; + } + + /** + * @dev Function that gives the date the given user was added + * @param account The address to check + * @return The date the address was added + */ + function lastAuthenticated(address account) external view returns (uint256) { + if (identities[account].dateAuthenticated > 0) + return identities[account].dateAuthenticated; + if (address(oldIdentity) != address(0)) { + try oldIdentity.lastAuthenticated(account) returns (uint256 _lastAuth) { + return _lastAuth; + } catch { + return 0; + } + } + return 0; + } + + /** + * @dev Adds an address to blacklist. + * Can only be called by Identity Administrators. + * @param account address to add as blacklisted + */ + function addBlacklisted( + address account + ) public onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + _removeWhitelisted(account); + identities[account].status = 255; + emit BlacklistAdded(account); + } + + /** + * @dev Removes an address from blacklist + * Can only be called by Identity Administrators. + * @param account address to remove as blacklisted + */ + function removeBlacklisted( + address account + ) external onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + if ( + address(oldIdentity) != address(0) && oldIdentity.isBlacklisted(account) + ) oldIdentity.removeBlacklisted(account); + + identities[account].status = 0; + emit BlacklistRemoved(account); + } + + /** + * @dev Function to add a Contract to list of contracts + * @param account The address to add + */ + function addContract( + address account + ) public onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + require(isContract(account), "Given address is not a contract"); + _addWhitelisted(account, _chainId()); + identities[account].status = 2; //this must come after _addWhitelisted + + emit ContractAdded(account); + } + + /** + * @dev Function to remove a Contract from list of contracts + * @param account The address to add + */ + function removeContract( + address account + ) public onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + if ( + address(oldIdentity) != address(0) && oldIdentity.isDAOContract(account) + ) { + oldIdentity.removeContract(account); + } + _removeWhitelisted(account); + + emit ContractRemoved(account); + } + + /** + * @dev Function to check if given contract is on list of contracts. + * @param account to check + * @return a bool indicating if address is on list of contracts + */ + function isDAOContract(address account) external view returns (bool) { + if (identities[account].status == 2) return true; + if (address(oldIdentity) != address(0)) { + try oldIdentity.isDAOContract(account) returns (bool res) { + return res; + } catch { + return false; + } + } + return false; + } + + /** + * @dev Internal function to add to whitelisted + * @param account the address to add + */ + function _addWhitelisted(address account, uint256 orgChain) internal { + require(identities[account].status == 0, "already has status"); + whitelistedCount += 1; + identities[account].status = 1; + identities[account].dateAdded = block.timestamp; + identities[account].dateAuthenticated = block.timestamp; + identities[account].whitelistedOnChainId = orgChain; + identities[account].authCount = 0; + connectedAccounts[account] = address(0); + + if (isContract(account)) { + whitelistedContracts += 1; + } + + emit WhitelistedAdded(account); + } + + /** + * @dev Internal whitelisting with did function. + * @param account the address to add + * @param did the id to register account under + */ + function _addWhitelistedWithDID( + address account, + string memory did, + uint256 orgChain + ) internal { + bytes32 pHash = keccak256(bytes(did)); + require(didHashToAddress[pHash] == address(0), "DID already registered"); + + identities[account].did = did; + didHashToAddress[pHash] = account; + + _addWhitelisted(account, orgChain); + } + + /** + * @dev Internal function to remove from whitelisted + * @param account the address to add + */ + function _removeWhitelisted(address account) internal { + if (identities[account].status == 1 || identities[account].status == 2) { + whitelistedCount -= 1; + + if (isContract(account) && whitelistedContracts > 0) { + whitelistedContracts -= 1; + } + + string memory did = identities[account].did; + bytes32 pHash = keccak256(bytes(did)); + + delete identities[account]; + delete didHashToAddress[pHash]; + + emit WhitelistedRemoved(account); + } + + if ( + address(oldIdentity) != address(0) && oldIdentity.isWhitelisted(account) + ) { + oldIdentity.removeWhitelisted(account); + } + } + + /// @notice helper function to get current chain id + /// @return chainId id + function _chainId() internal view returns (uint256 chainId) { + assembly { + chainId := chainid() + } + } + + /** + * @dev Returns true if given address has been added to the blacklist + * @param account the address to check + * @return a bool indicating weather the address is present in the blacklist + */ + function isBlacklisted(address account) public view returns (bool) { + if (identities[account].status == 255) return true; + if (address(oldIdentity) != address(0)) { + try oldIdentity.isBlacklisted(account) returns (bool res) { + return res; + } catch { + return false; + } + } + return false; + } + + /** + * @dev Function to see if given address is a contract + * @return true if address is a contract + */ + function isContract(address _addr) internal view returns (bool) { + uint256 length; + assembly { + length := extcodesize(_addr) + } + return length > 0; + } + + /** + @dev allows user to connect more accounts to his identity. msg.sender needs to be whitelisted + @param account the account to connect to msg.sender + */ + function connectAccount(address account) external onlyWhitelisted { + require( + !isWhitelisted(account) && !isBlacklisted(account), + "invalid account" + ); + require(connectedAccounts[account] == address(0x0), "already connected"); + + connectedAccounts[account] = msg.sender; + emit AccountConnected(account, msg.sender); + } + + /** + @dev disconnect a connected account from identity. can be performed either by identity or the connected account + @param connected the account to disconnect + */ + function disconnectAccount(address connected) external { + require( + connectedAccounts[connected] == msg.sender || msg.sender == connected, + "unauthorized" + ); + delete connectedAccounts[connected]; + emit AccountDisconnected(connected, msg.sender); + } + + /** + @dev returns the identity in case account is connected or is the identity itself otherwise returns the empty address + @param account address to get its identity + @return whitelisted the identity or address 0 if _account not connected or not identity + **/ + function getWhitelistedRoot( + address account + ) external view returns (address whitelisted) { + if (isWhitelisted(account)) return account; + if (isWhitelisted(connectedAccounts[account])) + return connectedAccounts[account]; + + return address(0x0); + } + + function pause(bool toPause) external onlyRole(PAUSER_ROLE) { + if (toPause) _pause(); + else _unpause(); + } + + /** + @dev modify account did can be called by account owner or identity admin + @param account the account to modify + @param did the did to set + */ + function setDID(address account, string calldata did) external { + require( + msg.sender == account || hasRole(IDENTITY_ADMIN_ROLE, msg.sender), + "not authorized" + ); + _setDID(account, did); + } + + function _setDID(address account, string memory did) internal { + require(isWhitelisted(account), "not whitelisted"); + require(bytes(did).length > 0, "did empty"); + bytes32 pHash = keccak256(bytes(did)); + require(didHashToAddress[pHash] == address(0), "DID already registered"); + + if (address(oldIdentity) != address(0)) { + address oldDIDOwner; + try oldIdentity.didHashToAddress(pHash) returns (address _didOwner) { + oldDIDOwner = _didOwner; + } catch {} + //if owner not the same and doesnt have a new did set then revert + require( + oldDIDOwner == address(0) || + oldDIDOwner == account || + bytes(identities[oldDIDOwner].did).length > 0, + "DID already registered oldIdentity" + ); + } + + bytes32 oldHash = keccak256(bytes(identities[account].did)); + delete didHashToAddress[oldHash]; + identities[account].did = did; + didHashToAddress[pHash] = account; + } + + /** + @dev for backward compatability with V1 + @param account to get DID for + @return did of the account + */ + function addrToDID( + address account + ) external view returns (string memory did) { + did = identities[account].did; + bytes32 pHash = keccak256(bytes(did)); + + //if did was set in this contract return it, otherwise check oldidentity + if (didHashToAddress[pHash] == account) return did; + + if (address(oldIdentity) != address(0)) { + try oldIdentity.addrToDID(account) returns (string memory _did) { + return _did; + } catch { + return ""; + } + } + + return ""; + } + + function getWhitelistedOnChainId( + address account + ) external view returns (uint256 chainId) { + chainId = identities[account].whitelistedOnChainId; + return chainId > 0 ? chainId : _chainId(); + } + + /** + * backward compatability with IdentityV1 that GoodDollar token checks if the identity contract is registered + */ + function isRegistered() external pure returns (bool) { + return true; + } +} diff --git a/test/governance/ClaimersDistribution.test.ts b/test/governance/ClaimersDistribution.test.ts index e87c69a3..6b708f3e 100644 --- a/test/governance/ClaimersDistribution.test.ts +++ b/test/governance/ClaimersDistribution.test.ts @@ -257,6 +257,6 @@ describe("ClaimersDistribution", () => { // console.log({ totalGas }, tx.gasUsed.toNumber(), tx2.gasUsed.toNumber()); } console.log(Object.keys(gasCosts)); - expect(totalGas / 30).lt(316000); + expect(totalGas / 30).lt(319000); }); }); diff --git a/test/helpers.ts b/test/helpers.ts index 04a7dd80..c7e73f31 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -12,7 +12,7 @@ import IUniswapV2Pair from "@uniswap/v2-core/build/IUniswapV2Pair.json"; import UniswapV2Factory from "@uniswap/v2-core/build/UniswapV2Factory.json"; import WETH9 from "@uniswap/v2-periphery/build/WETH9.json"; import UniswapV2Router02 from "@uniswap/v2-periphery/build/UniswapV2Router02.json"; -import { GoodMarketMaker, CompoundVotingMachine } from "../types"; +import { GoodMarketMaker, CompoundVotingMachine, IdentityV4 } from "../types"; import { Contract } from "ethers"; import testDeployer from "@superfluid-finance/ethereum-contracts/dev-scripts/deploy-test-framework"; @@ -24,9 +24,7 @@ export const getStakingFactory = async ( | "GoodAaveStakingV2" | "GoodCompoundStakingV2" ) => { - let swapHelper = await ethers - .getContractFactory("UniswapV2SwapHelper") - .then(_ => _.deploy()); + let swapHelper = await ethers.getContractFactory("UniswapV2SwapHelper").then(_ => _.deploy()); const simpleStakingFactory = await ethers.getContractFactory(factory, { libraries: { @@ -46,17 +44,13 @@ export const deploySuperFluid = async () => { }; export const deploySuperGoodDollar = async (sfContracts, tokenArgs) => { - const SuperGoodDollarFactory = await ethers.getContractFactory( - "SuperGoodDollar" - ); + const SuperGoodDollarFactory = await ethers.getContractFactory("SuperGoodDollar"); console.log("deploying supergooddollar logic"); const SuperGoodDollar = await SuperGoodDollarFactory.deploy(sfContracts.host); console.log("deploying supergooddollar proxy"); - const GoodDollarProxyFactory = await ethers.getContractFactory( - "contracts/token/superfluid/UUPSProxy.sol:UUPSProxy" - ); + const GoodDollarProxyFactory = await ethers.getContractFactory("contracts/token/superfluid/UUPSProxy.sol:UUPSProxy"); const GoodDollarProxy = await GoodDollarProxyFactory.deploy(); console.log("deployed supergooddollar proxy, initializing proxy..."); await GoodDollarProxy.initializeProxy(SuperGoodDollar.address); @@ -67,15 +61,12 @@ export const deploySuperGoodDollar = async (sfContracts, tokenArgs) => { const outNftProxy = await GoodDollarProxyFactory.deploy(); const inNftProxy = await GoodDollarProxyFactory.deploy(); - const constantInflowNFT = await ethers.deployContract("ConstantInflowNFT", [ + const constantInflowNFT = await ethers.deployContract("ConstantInflowNFT", [sfContracts.host, outNftProxy.address]); + + const constantOutflowNFT = await ethers.deployContract("ConstantOutflowNFT", [ sfContracts.host, - outNftProxy.address + inNftProxy.address ]); - - const constantOutflowNFT = await ethers.deployContract( - "ConstantOutflowNFT", - [sfContracts.host, inNftProxy.address] - ); await outNftProxy.initializeProxy(constantOutflowNFT.address); await inNftProxy.initializeProxy(constantInflowNFT.address); @@ -83,42 +74,27 @@ export const deploySuperGoodDollar = async (sfContracts, tokenArgs) => { await SuperGoodDollar.attach(GoodDollarProxy.address)[ "initialize(string,string,uint256,address,address,address,address)" ](...tokenArgs); - const GoodDollar = await ethers.getContractAt( - "SuperGoodDollar", - GoodDollarProxy.address - ); + const GoodDollar = await ethers.getContractAt("SuperGoodDollar", GoodDollarProxy.address); console.log("supergooddollar created successfully"); await constantOutflowNFT .attach(outNftProxy.address) - .initialize( - (await GoodDollar.symbol()) + " Outflow NFT", - (await GoodDollar.symbol()) + " COF" - ); + .initialize((await GoodDollar.symbol()) + " Outflow NFT", (await GoodDollar.symbol()) + " COF"); await constantInflowNFT .attach(inNftProxy.address) - .initialize( - (await GoodDollar.symbol()) + " Inflow NFT", - (await GoodDollar.symbol()) + " CIF" - ); + .initialize((await GoodDollar.symbol()) + " Inflow NFT", (await GoodDollar.symbol()) + " CIF"); return GoodDollar; } else { console.log("initializing supergooddollar...."); await SuperGoodDollar.attach(GoodDollarProxy.address)[ "initialize(string,string,uint256,address,address,address,address)" ](...tokenArgs); - const GoodDollar = await ethers.getContractAt( - "SuperGoodDollar", - GoodDollarProxy.address - ); + const GoodDollar = await ethers.getContractAt("SuperGoodDollar", GoodDollarProxy.address); console.log("supergooddollar created successfully"); return GoodDollar; } }; -export const createDAO = async ( - tokenType: "super" | "regular" = "super", - identity: "v2" | "v3" = "v3" -) => { +export const createDAO = async (tokenType: "super" | "regular" = "super", identity: "v2" | "v3" | "v4" = "v4") => { let [root, ...signers] = await ethers.getSigners(); const sfContracts = await deploySuperFluid(); @@ -130,38 +106,23 @@ export const createDAO = async ( let cDAI = await cdaiFactory.deploy(dai.address); - const DAOCreatorFactory = new ethers.ContractFactory( - DAOCreatorABI.abi, - DAOCreatorABI.bytecode, - root - ); + const DAOCreatorFactory = new ethers.ContractFactory(DAOCreatorABI.abi, DAOCreatorABI.bytecode, root); - const IdentityFactory = await ethers.getContractFactory( - identity === "v2" ? "IdentityV2" : "IdentityV3" - ); + const IdentityFactory = await ethers.getContractFactory("Identity" + identity.toUpperCase()); - const FeeFormulaFactory = new ethers.ContractFactory( - FeeFormulaABI.abi, - FeeFormulaABI.bytecode, - root - ); + const FeeFormulaFactory = new ethers.ContractFactory(FeeFormulaABI.abi, FeeFormulaABI.bytecode, root); - const BancorFormula = await ( - await ethers.getContractFactory("BancorFormula") - ).deploy(); + const BancorFormula = await (await ethers.getContractFactory("BancorFormula")).deploy(); await BancorFormula.init(); console.log("deploy upgradeable identity..."); - const Identity = await upgrades.deployProxy( - IdentityFactory, - [root.address, ethers.constants.AddressZero], - { - kind: "uups" - } - ); - + const Identity = (await upgrades.deployProxy(IdentityFactory, [root.address, ethers.constants.AddressZero], { + kind: "uups" + })) as IdentityV4; + // put reverify far in the future to avoid interference with tests + if (identity === "v4") await Identity.setReverifyDaysOptions([180]); const daoCreator = await DAOCreatorFactory.deploy(); const FeeFormula = await FeeFormulaFactory.deploy(0); const GReputation = await ethers.getContractFactory("GReputation"); @@ -183,19 +144,10 @@ export const createDAO = async ( GoodDollar = await upgrades.deployProxy( GoodDollarFactory, - [ - "GoodDollar", - "G$", - 0, - FeeFormula.address, - Identity.address, - ethers.constants.AddressZero, - daoCreator.address - ], + ["GoodDollar", "G$", 0, FeeFormula.address, Identity.address, ethers.constants.AddressZero, daoCreator.address], { kind: "uups", - initializer: - "initialize(string, string, uint256, address, address, address,address)" + initializer: "initialize(string, string, uint256, address, address, address,address)" } ); } else { @@ -218,20 +170,11 @@ export const createDAO = async ( daoCreator: daoCreator.address }); // await Identity.setAuthenticationPeriod(365); - await daoCreator.forgeOrg( - GoodDollar.address, - reputation.address, - [], - 1000, - [] - ); + await daoCreator.forgeOrg(GoodDollar.address, reputation.address, [], 1000, []); const Avatar = new ethers.Contract( await daoCreator.avatar(), - [ - "function owner() view returns (address)", - "function nativeToken() view returns (address)" - ], + ["function owner() view returns (address)", "function nativeToken() view returns (address)"], root ); @@ -245,11 +188,7 @@ export const createDAO = async ( await GoodDollar.isPauser(Avatar.address) ); - const ccFactory = new ethers.ContractFactory( - ContributionCalculation.abi, - ContributionCalculation.bytecode, - root - ); + const ccFactory = new ethers.ContractFactory(ContributionCalculation.abi, ContributionCalculation.bytecode, root); const contribution = await ccFactory.deploy(Avatar.address, 0, 1e15); @@ -332,13 +271,9 @@ export const createDAO = async ( const MM = await ethers.getContractFactory("GoodMarketMaker"); - let marketMaker = (await upgrades.deployProxy( - MM, - [nameService.address, 999388834642296, 1e15], - { - kind: "uups" - } - )) as unknown as GoodMarketMaker; + let marketMaker = (await upgrades.deployProxy(MM, [nameService.address, 999388834642296, 1e15], { + kind: "uups" + })) as unknown as GoodMarketMaker; await (await reputation.updateDAO(nameService.address)).wait(); @@ -346,28 +281,16 @@ export const createDAO = async ( //generic call permissions let schemeMock = signers[signers.length - 1]; - const ictrl = await ethers.getContractAt( - "Controller", - controller, - schemeMock - ); + const ictrl = await ethers.getContractAt("Controller", controller, schemeMock); const setSchemes = async (addrs, params = []) => { for (let i in addrs) { - await ictrl.registerScheme( - addrs[i], - params[i] || ethers.constants.HashZero, - "0x0000001F", - Avatar.address - ); + await ictrl.registerScheme(addrs[i], params[i] || ethers.constants.HashZero, "0x0000001F", Avatar.address); } }; const setDAOAddress = async (name, addr) => { - const encoded = nameService.interface.encodeFunctionData("setAddress", [ - name, - addr - ]); + const encoded = nameService.interface.encodeFunctionData("setAddress", [name, addr]); await ictrl.genericCall(nameService.address, encoded, Avatar.address, 0); }; @@ -377,21 +300,20 @@ export const createDAO = async ( expect(funcNameEnd).to.be.gt(-1); const functionName = functionAbi.substring(0, funcNameEnd); - await expect(contract[functionAbi](...parameters)).to.revertedWith( - /avatar/ - ); - const encoded = contract.interface.encodeFunctionData(functionName, [ - ...parameters - ]); + await expect(contract[functionAbi](...parameters)).to.revertedWith(/avatar/); + const encoded = contract.interface.encodeFunctionData(functionName, [...parameters]); await ictrl.genericCall(contract.address, encoded, Avatar.address, 0); }; const setReserveToken = async (token, gdReserve, tokenReserve, RR) => { - const encoded = marketMaker.interface.encodeFunctionData( - "initializeToken", - [token, gdReserve, tokenReserve, RR, 0] - ); + const encoded = marketMaker.interface.encodeFunctionData("initializeToken", [ + token, + gdReserve, + tokenReserve, + RR, + 0 + ]); await ictrl.genericCall(marketMaker.address, encoded, Avatar.address, 0); }; @@ -417,9 +339,9 @@ export const createDAO = async ( const gd = await Avatar.nativeToken(); //make GoodCap minter console.log("Setting reserve as minter..."); - const encoded = ( - await ethers.getContractAt("IGoodDollar", gd) - ).interface.encodeFunctionData("addMinter", [goodReserve.address]); + const encoded = (await ethers.getContractAt("IGoodDollar", gd)).interface.encodeFunctionData("addMinter", [ + goodReserve.address + ]); await ictrl.genericCall(gd, encoded, Avatar.address, 0); @@ -427,25 +349,17 @@ export const createDAO = async ( await ictrl.genericCall( goodReserve.address, - goodReserve.interface.encodeFunctionData("setDistributionHelper", [ - distHelper.address - ]), + goodReserve.interface.encodeFunctionData("setDistributionHelper", [distHelper.address]), Avatar.address, 0 ); - const gasFeeMockFactory = await ethers.getContractFactory( - "GasPriceMockOracle" - ); + const gasFeeMockFactory = await ethers.getContractFactory("GasPriceMockOracle"); const gasFeeOracle = await gasFeeMockFactory.deploy(); - const daiEthPriceMockFactory = await ethers.getContractFactory( - "DaiEthPriceMockOracle" - ); + const daiEthPriceMockFactory = await ethers.getContractFactory("DaiEthPriceMockOracle"); const daiEthOracle = await daiEthPriceMockFactory.deploy(); - const ethUsdOracleFactory = await ethers.getContractFactory( - "EthUSDMockOracle" - ); + const ethUsdOracleFactory = await ethers.getContractFactory("EthUSDMockOracle"); const ethUsdOracle = await ethUsdOracleFactory.deploy(); console.log("setting nameservice addrresses..."); @@ -531,15 +445,17 @@ export const deployUBI = async (deployedDAO, withFirstClaim = true) => { const gd = await nameService.getAddress("GOODDOLLAR"); - let encoded = ( - await ethers.getContractAt("IGoodDollar", gd) - ).interface.encodeFunctionData("mint", [firstClaim.address, 1000000]); + let encoded = (await ethers.getContractAt("IGoodDollar", gd)).interface.encodeFunctionData("mint", [ + firstClaim.address, + 1000000 + ]); await genericCall(gd, encoded); - encoded = ( - await ethers.getContractAt("IGoodDollar", gd) - ).interface.encodeFunctionData("mint", [ubiScheme.address, 1000000]); + encoded = (await ethers.getContractAt("IGoodDollar", gd)).interface.encodeFunctionData("mint", [ + ubiScheme.address, + 1000000 + ]); await genericCall(gd, encoded); @@ -547,9 +463,7 @@ export const deployUBI = async (deployedDAO, withFirstClaim = true) => { await setSchemes([firstClaim.address, ubiScheme.address]); if (withFirstClaim) { - encoded = firstClaim.interface.encodeFunctionData("setUBIScheme", [ - ubiScheme.address - ]); + encoded = firstClaim.interface.encodeFunctionData("setUBIScheme", [ubiScheme.address]); await genericCall(firstClaim.address, encoded); await firstClaim.start(); @@ -598,15 +512,17 @@ export const deployOldUBI = async deployedDAO => { const gd = await nameService.getAddress("GOODDOLLAR"); - let encoded = ( - await ethers.getContractAt("IGoodDollar", gd) - ).interface.encodeFunctionData("mint", [firstClaim.address, 1000000]); + let encoded = (await ethers.getContractAt("IGoodDollar", gd)).interface.encodeFunctionData("mint", [ + firstClaim.address, + 1000000 + ]); await genericCall(gd, encoded); - encoded = ( - await ethers.getContractAt("IGoodDollar", gd) - ).interface.encodeFunctionData("mint", [ubiScheme.address, 1000000]); + encoded = (await ethers.getContractAt("IGoodDollar", gd)).interface.encodeFunctionData("mint", [ + ubiScheme.address, + 1000000 + ]); await genericCall(gd, encoded); @@ -651,26 +567,16 @@ export const deployOldVoting = async dao => { SchemeRegistrarF.deploy() ]); console.log("setting parameters"); - const voteParametersHash = await absoluteVote.getParametersHash( - 50, - ethers.constants.AddressZero - ); + const voteParametersHash = await absoluteVote.getParametersHash(50, ethers.constants.AddressZero); console.log("setting params for voting machine and schemes"); await Promise.all([ - schemeRegistrar.setParameters( - voteParametersHash, - voteParametersHash, - absoluteVote.address - ), + schemeRegistrar.setParameters(voteParametersHash, voteParametersHash, absoluteVote.address), absoluteVote.setParameters(50, ethers.constants.AddressZero), upgradeScheme.setParameters(voteParametersHash, absoluteVote.address) ]); - const upgradeParametersHash = await upgradeScheme.getParametersHash( - voteParametersHash, - absoluteVote.address - ); + const upgradeParametersHash = await upgradeScheme.getParametersHash(voteParametersHash, absoluteVote.address); // Deploy SchemeRegistrar const schemeRegisterParams = await schemeRegistrar.getParametersHash( @@ -710,18 +616,10 @@ export const deployUniswap = async (comp, dai) => { UniswapV2Factory.bytecode, (await ethers.getSigners())[0] ); - const wethFactory = new ethers.ContractFactory( - WETH9.abi, - WETH9.bytecode, - (await ethers.getSigners())[0] - ); + const wethFactory = new ethers.ContractFactory(WETH9.abi, WETH9.bytecode, (await ethers.getSigners())[0]); const weth = await wethFactory.deploy(); - const factory = await uniswapFactory.deploy( - ( - await ethers.getSigners() - )[0].address - ); + const factory = await uniswapFactory.deploy((await ethers.getSigners())[0].address); const router = await routerFactory.deploy(factory.address, weth.address); await factory.createPair(comp.address, weth.address); // Create comp and weth pair const compPairAddress = factory.getPair(comp.address, weth.address); @@ -729,28 +627,11 @@ export const deployUniswap = async (comp, dai) => { await factory.createPair(dai.address, weth.address); // Create comp and dai pair const daiPairAddress = factory.getPair(dai.address, weth.address); - const compPair = new Contract( - compPairAddress, - JSON.stringify(IUniswapV2Pair.abi), - staker - ).connect(founder); - const daiPair = new Contract( - daiPairAddress, - JSON.stringify(IUniswapV2Pair.abi), - staker - ).connect(founder); - await dai["mint(address,uint256)"]( - founder.address, - ethers.utils.parseEther("2000000") - ); - await dai["mint(address,uint256)"]( - daiPair.address, - ethers.utils.parseEther("2000000") - ); - await comp["mint(address,uint256)"]( - compPair.address, - ethers.utils.parseEther("200000") - ); + const compPair = new Contract(compPairAddress, JSON.stringify(IUniswapV2Pair.abi), staker).connect(founder); + const daiPair = new Contract(daiPairAddress, JSON.stringify(IUniswapV2Pair.abi), staker).connect(founder); + await dai["mint(address,uint256)"](founder.address, ethers.utils.parseEther("2000000")); + await dai["mint(address,uint256)"](daiPair.address, ethers.utils.parseEther("2000000")); + await comp["mint(address,uint256)"](compPair.address, ethers.utils.parseEther("200000")); console.log("depositing eth to liquidity pools"); await weth.deposit({ value: ethers.utils.parseEther("4000") }); console.log(await weth.balanceOf(founder.address).then(_ => _.toString())); diff --git a/test/identity/IdentityV3.test.ts b/test/identity/IdentityV3.test.ts index b198d8d2..dd38aa11 100644 --- a/test/identity/IdentityV3.test.ts +++ b/test/identity/IdentityV3.test.ts @@ -16,16 +16,12 @@ describe("IdentityV3", () => { let avatar, gd: IGoodDollar, Controller, id: IIdentity; + const createDAOv3 = async () => { + return createDAO("super", "v3"); + }; before(async () => { [founder, ...signers] = await ethers.getSigners(); - let { - controller, - avatar: av, - gd: gooddollar, - identity: idv2, - genericCall: gc - } = await loadFixture(createDAO); - + let { controller, avatar: av, gd: gooddollar, identity: idv2, genericCall: gc } = await loadFixture(createDAOv3); genericCall = gc; identity = (await ethers.getContractAt("IdentityV3", idv2)) as IdentityV3; Controller = controller; @@ -38,34 +34,24 @@ describe("IdentityV3", () => { // "" // ); - gd = (await ethers.getContractAt( - "IGoodDollar", - gooddollar, - founder - )) as IGoodDollar; + gd = (await ethers.getContractAt("IGoodDollar", gooddollar, founder)) as IGoodDollar; }); it("should set DAO by creator", async () => { let f = await ethers.getContractFactory("IdentityV3"); - let newid = (await upgrades.deployProxy( - f, - [signers[0].address, ethers.constants.AddressZero], - { kind: "uups" } - )) as IdentityV2; + let newid = (await upgrades.deployProxy(f, [signers[0].address, ethers.constants.AddressZero], { + kind: "uups" + })) as IdentityV3; expect(await newid.dao()).eq(ethers.constants.AddressZero); - await expect( - newid.connect(signers[0]).initDAO(await identity.nameService()) - ).not.reverted; + await expect(newid.connect(signers[0]).initDAO(await identity.nameService())).not.reverted; expect(await newid.dao()).not.eq(ethers.constants.AddressZero); }); it("should not be able to set DAO by non-creator", async () => { let f = await ethers.getContractFactory("IdentityV3"); - let newid = (await upgrades.deployProxy( - f, - [signers[0].address, ethers.constants.AddressZero], - { kind: "uups" } - )) as IdentityV2; + let newid = (await upgrades.deployProxy(f, [signers[0].address, ethers.constants.AddressZero], { + kind: "uups" + })) as IdentityV3; expect(await newid.dao()).eq(ethers.constants.AddressZero); await expect(newid.initDAO(await identity.nameService())).reverted; }); @@ -96,43 +82,35 @@ describe("IdentityV3", () => { await identity.addWhitelisted(whitelisted.address); - const diffWhitelistedCount = ( - (await identity.whitelistedCount()) as any - ).sub(oldWhitelistedCount); + const diffWhitelistedCount = ((await identity.whitelistedCount()) as any).sub(oldWhitelistedCount); expect(diffWhitelistedCount.toString()).to.be.equal("1"); await identity.removeWhitelisted(whitelisted.address); const whitelistedCount = (await identity.whitelistedCount()) as any; - expect(whitelistedCount.toString()).to.be.equal( - oldWhitelistedCount.toString() - ); + expect(whitelistedCount.toString()).to.be.equal(oldWhitelistedCount.toString()); }); it("should revert when non admin tries to add whitelisted", async () => { let whitelisted = signers[1]; - await expect( - identity.connect(signers[2]).addWhitelisted(whitelisted.address) - ).revertedWith(/AccessControl: account/); + await expect(identity.connect(signers[2]).addWhitelisted(whitelisted.address)).revertedWith( + /AccessControl: account/ + ); }); it("should revert when non admin tries to add blacklist", async () => { let blacklisted = signers[1]; - await expect( - identity.connect(signers[2]).addBlacklisted(blacklisted.address) - ).revertedWith(/AccessControl: account/); + await expect(identity.connect(signers[2]).addBlacklisted(blacklisted.address)).revertedWith( + /AccessControl: account/ + ); }); it("should revert when non admin tries to set the authentication period", async () => { - await expect(identity.connect(signers[2]).setAuthenticationPeriod(10)) - .reverted; + await expect(identity.connect(signers[2]).setAuthenticationPeriod(10)).reverted; }); it("should let owner set auth period", async () => { - const encoded = identity.interface.encodeFunctionData( - "setAuthenticationPeriod", - [10] - ); + const encoded = identity.interface.encodeFunctionData("setAuthenticationPeriod", [10]); await genericCall(identity.address, encoded); expect(await identity.authenticationPeriod()).eq(10); }); @@ -148,9 +126,7 @@ describe("IdentityV3", () => { it("should revert when non admin tries to authentice a user", async () => { let authuser = signers[0].address; - await expect( - identity.connect(signers[2]).authenticate(authuser) - ).revertedWith(/AccessControl: account/); + await expect(identity.connect(signers[2]).authenticate(authuser)).revertedWith(/AccessControl: account/); }); it("should authenticate the user with the correct timestamp", async () => { @@ -167,18 +143,14 @@ describe("IdentityV3", () => { it("should add identity admin", async () => { let outsider = signers[5].address; await identity.grantRole(await identity.IDENTITY_ADMIN_ROLE(), outsider); - expect( - await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider) - ).true; + expect(await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider)).true; }); it("should remove identity admin", async () => { let outsider = signers[5].address; await identity.revokeRole(await identity.IDENTITY_ADMIN_ROLE(), outsider); - expect( - await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider) - ).false; + expect(await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider)).false; }); it("should revert when adding to whitelisted twice", async () => { @@ -223,16 +195,14 @@ describe("IdentityV3", () => { it("should not allow adding with used did", async () => { let whitelisted2 = signers[2]; - await expect( - identity.addWhitelistedWithDID(whitelisted2.address, "testString") - ).revertedWith(/DID already registered/); + await expect(identity.addWhitelistedWithDID(whitelisted2.address, "testString")).revertedWith( + /DID already registered/ + ); }); it("should not allow adding non contract to contracts", async () => { let outsider = signers[0]; - await expect(identity.addContract(outsider.address)).revertedWith( - /Given address is not a contract/ - ); + await expect(identity.addContract(outsider.address)).revertedWith(/Given address is not a contract/); }); it("should add contract to contracts", async () => { @@ -255,19 +225,13 @@ describe("IdentityV3", () => { const toconnect = signers[10]; let whitelisted = signers[1]; - expect(await identity.getWhitelistedRoot(toconnect.address)).eq( - ethers.constants.AddressZero - ); + expect(await identity.getWhitelistedRoot(toconnect.address)).eq(ethers.constants.AddressZero); await loadFixture(connectedFixture); - expect(await identity.getWhitelistedRoot(whitelisted.address)).eq( - whitelisted.address - ); + expect(await identity.getWhitelistedRoot(whitelisted.address)).eq(whitelisted.address); - expect(await identity.getWhitelistedRoot(toconnect.address)).eq( - whitelisted.address - ); + expect(await identity.getWhitelistedRoot(toconnect.address)).eq(whitelisted.address); }); it("should not allow to connect account already whitelisted", async () => { @@ -276,9 +240,7 @@ describe("IdentityV3", () => { await identity.addWhitelisted(signers[2].address); let whitelisted = signers[1]; - await expect( - identity.connect(whitelisted).connectAccount(signers[2].address) - ).revertedWith(/invalid account/); + await expect(identity.connect(whitelisted).connectAccount(signers[2].address)).revertedWith(/invalid account/); }); it("should allow to disconnect account by owner or connected", async () => { @@ -287,14 +249,10 @@ describe("IdentityV3", () => { const connected = signers[10]; const whitelisted = signers[1]; await identity.connect(connected).disconnectAccount(connected.address); - expect(await identity.getWhitelistedRoot(connected.address)).eq( - ethers.constants.AddressZero - ); + expect(await identity.getWhitelistedRoot(connected.address)).eq(ethers.constants.AddressZero); await loadFixture(connectedFixture); await identity.connect(whitelisted).disconnectAccount(connected.address); - expect(await identity.getWhitelistedRoot(connected.address)).eq( - ethers.constants.AddressZero - ); + expect(await identity.getWhitelistedRoot(connected.address)).eq(ethers.constants.AddressZero); }); it("should not allow to disconnect account not by owner or by connected", async () => { @@ -302,9 +260,7 @@ describe("IdentityV3", () => { const connected = signers[10]; const whitelisted = signers[1]; - await expect(identity.disconnectAccount(connected.address)).revertedWith( - /unauthorized/ - ); + await expect(identity.disconnectAccount(connected.address)).revertedWith(/unauthorized/); }); it("should not allow to connect to an already connected account", async () => { @@ -314,9 +270,7 @@ describe("IdentityV3", () => { expect(await identity.isWhitelisted(signers[2].address)).true; const connected = signers[10]; - await expect( - identity.connect(signers[2]).connectAccount(connected.address) - ).revertedWith(/already connected/); + await expect(identity.connect(signers[2]).connectAccount(connected.address)).revertedWith(/already connected/); }); it("should return same root for multiple connected accounts", async () => { @@ -335,27 +289,19 @@ describe("IdentityV3", () => { const toWhitelist = signers[2]; const ts = (Date.now() / 1000 - 100000).toFixed(0); - await identity.addWhitelistedWithDIDAndChain( - toWhitelist.address, - "xxx", - 1234, - ts - ); + await identity.addWhitelistedWithDIDAndChain(toWhitelist.address, "xxx", 1234, ts); const record = await identity.identities(toWhitelist.address); expect(record.whitelistedOnChainId).eq(1234); expect(record.dateAuthenticated).eq(ts); }); const oldidFixture = async () => { - const newid = (await upgrades.deployProxy( - await ethers.getContractFactory("IdentityV3"), - [founder.address, identity.address] - )) as IdentityV3; - - await identity.grantRole( - await identity.IDENTITY_ADMIN_ROLE(), - newid.address - ); + const newid = (await upgrades.deployProxy(await ethers.getContractFactory("IdentityV3"), [ + founder.address, + identity.address + ])) as IdentityV3; + + await identity.grantRole(await identity.IDENTITY_ADMIN_ROLE(), newid.address); await identity.addBlacklisted(signers[4].address); await identity.addContract(identity.address); await identity.removeWhitelisted(signers[3].address); @@ -365,9 +311,7 @@ describe("IdentityV3", () => { it("should default to old identity isWhitelisted, isBlacklisted, isContract", async () => { const { newid } = await loadFixture(oldidFixture); - expect(await (await identity.identities(signers[3].address)).did).eq( - "testolddid" - ); + expect(await (await identity.identities(signers[3].address)).did).eq("testolddid"); expect(await (await newid.identities(signers[3].address)).did).eq(""); expect(await identity.addrToDID(signers[3].address)).eq("testolddid"); @@ -396,51 +340,31 @@ describe("IdentityV3", () => { it("should not set did if set in oldidentity", async () => { const { newid } = await loadFixture(oldidFixture); - await expect( - newid - .connect(signers[1]) - ["setDID(address,string)"](signers[1].address, "testolddid") - ).revertedWith(/DID already registered oldIdentity/); + await expect(newid.connect(signers[1])["setDID(address,string)"](signers[1].address, "testolddid")).revertedWith( + /DID already registered oldIdentity/ + ); }); it("should set did if set in oldidentity by same owner", async () => { const { newid } = await loadFixture(oldidFixture); - await expect( - newid - .connect(signers[3]) - ["setDID(address,string)"](signers[3].address, "testolddid") - ).not.reverted; + await expect(newid.connect(signers[3])["setDID(address,string)"](signers[3].address, "testolddid")).not.reverted; expect(await newid.addrToDID(signers[3].address)).eq("testolddid"); }); it("should set did if set in oldidentity by different owner but updated in new identity", async () => { const { newid } = await loadFixture(oldidFixture); - await expect( - newid - .connect(signers[3]) - ["setDID(address,string)"](signers[3].address, "newdid") - ).not.reverted; + await expect(newid.connect(signers[3])["setDID(address,string)"](signers[3].address, "newdid")).not.reverted; expect(await newid.addrToDID(signers[3].address)).eq("newdid"); - await expect( - newid - .connect(signers[1]) - ["setDID(address,string)"](signers[1].address, "testolddid") - ).not.reverted; + await expect(newid.connect(signers[1])["setDID(address,string)"](signers[1].address, "testolddid")).not.reverted; expect(await newid.addrToDID(signers[1].address)).eq("testolddid"); }); it("should let admin setDID", async () => { - await expect( - identity["setDID(address,string)"](signers[1].address, "admindid") - ).not.reverted; + await expect(identity["setDID(address,string)"](signers[1].address, "admindid")).not.reverted; expect(await identity.addrToDID(signers[1].address)).eq("admindid"); - await expect( - identity - .connect(signers[2]) - ["setDID(address,string)"](signers[1].address, "admindid") - ).reverted; + await expect(identity.connect(signers[2])["setDID(address,string)"](signers[1].address, "admindid")).reverted; }); it("should be registered for v1 compatability", async () => { diff --git a/test/identity/IdentityV4.test.ts b/test/identity/IdentityV4.test.ts new file mode 100644 index 00000000..ec4f26f0 --- /dev/null +++ b/test/identity/IdentityV4.test.ts @@ -0,0 +1,432 @@ +import hre, { ethers, upgrades } from "hardhat"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { IGoodDollar, IIdentity, IdentityV4 } from "../../types"; +import { createDAO, increaseTime, advanceBlocks } from "../helpers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +const BN = ethers.BigNumber; + +describe("IdentityV4", () => { + let identity: IdentityV4, founder: SignerWithAddress; + let user1 = ethers.Wallet.createRandom().connect(ethers.provider); + let user2 = ethers.Wallet.createRandom().connect(ethers.provider); + let signers: Array; + let genericCall; + + let avatar, gd: IGoodDollar, Controller, id: IIdentity; + + before(async () => { + [founder, ...signers] = await ethers.getSigners(); + let { controller, avatar: av, gd: gooddollar, identity: idv2, genericCall: gc } = await loadFixture(createDAO); + + genericCall = gc; + identity = (await ethers.getContractAt("IdentityV4", idv2)) as IdentityV4; + Controller = controller; + avatar = av; + // await daoCreator.setSchemes( + // avatar, + // [identity], + // [ethers.constants.HashZero], + // ["0x0000001F"], + // "" + // ); + + gd = (await ethers.getContractAt("IGoodDollar", gooddollar, founder)) as IGoodDollar; + }); + + it("should set DAO by creator", async () => { + let f = await ethers.getContractFactory("IdentityV4"); + let newid = (await upgrades.deployProxy(f, [signers[0].address, ethers.constants.AddressZero], { + kind: "uups" + })) as IdentityV4; + expect(await newid.dao()).eq(ethers.constants.AddressZero); + await expect(newid.connect(signers[0]).initDAO(await identity.nameService())).not.reverted; + expect(await newid.dao()).not.eq(ethers.constants.AddressZero); + }); + + it("should not be able to set DAO by non-creator", async () => { + let f = await ethers.getContractFactory("IdentityV4"); + let newid = (await upgrades.deployProxy(f, [signers[0].address, ethers.constants.AddressZero], { + kind: "uups" + })) as IdentityV4; + expect(await newid.dao()).eq(ethers.constants.AddressZero); + await expect(newid.initDAO(await identity.nameService())).reverted; + }); + + it("should blacklist address", async () => { + let blacklisted = signers[1]; + await identity.addBlacklisted(blacklisted.address); + expect(await identity.isBlacklisted(blacklisted.address)).true; + + await identity.removeBlacklisted(blacklisted.address); + expect(await identity.isBlacklisted(blacklisted.address)).false; + }); + + it("should add, check and remove whitelisted", async () => { + let whitelisted = signers[1]; + await identity.addWhitelisted(whitelisted.address); + expect(await identity.isWhitelisted(whitelisted.address)).true; + const id = await identity.identities(whitelisted.address); + expect(id.whitelistedOnChainId).gt(0); + + await identity.removeWhitelisted(whitelisted.address); + expect(await identity.isWhitelisted(whitelisted.address)).false; + }); + + it("should increment and decrement whitelisteds when adding whitelisted", async () => { + let whitelisted = signers[1]; + const oldWhitelistedCount = (await identity.whitelistedCount()) as any; + + await identity.addWhitelisted(whitelisted.address); + + const diffWhitelistedCount = ((await identity.whitelistedCount()) as any).sub(oldWhitelistedCount); + expect(diffWhitelistedCount.toString()).to.be.equal("1"); + + await identity.removeWhitelisted(whitelisted.address); + + const whitelistedCount = (await identity.whitelistedCount()) as any; + expect(whitelistedCount.toString()).to.be.equal(oldWhitelistedCount.toString()); + }); + + it("should revert when non admin tries to add whitelisted", async () => { + let whitelisted = signers[1]; + await expect(identity.connect(signers[2]).addWhitelisted(whitelisted.address)).revertedWith( + /AccessControl: account/ + ); + }); + + it("should revert when non admin tries to add blacklist", async () => { + let blacklisted = signers[1]; + await expect(identity.connect(signers[2]).addBlacklisted(blacklisted.address)).revertedWith( + /AccessControl: account/ + ); + }); + + // it("should revert when non admin tries to set the authentication period", async () => { + // await expect(identity.connect(signers[2]).setAuthenticationPeriod(10)).reverted; + // }); + + // it("should let owner set auth period", async () => { + // const encoded = identity.interface.encodeFunctionData("setAuthenticationPeriod", [10]); + // await genericCall(identity.address, encoded); + // expect(await identity.authenticationPeriod()).eq(10); + // }); + + it("should revert when non admin tries to pause", async () => { + await expect(identity.connect(signers[2]).pause(true)).reverted; + }); + + it("should let admin pause", async () => { + await expect(identity.pause(true)).not.reverted; + await expect(identity.pause(false)).not.reverted; + }); + + it("should revert when non admin tries to authentice a user", async () => { + let authuser = signers[0].address; + await expect(identity.connect(signers[2]).authenticate(authuser)).revertedWith(/AccessControl: account/); + }); + + it("should authenticate the user with the correct timestamp", async () => { + let authuser = signers[0].address; + await identity.addWhitelisted(authuser); + await identity.authenticate(authuser); + let dateAuthenticated1 = await identity.lastAuthenticated(authuser); + await increaseTime(10); + await identity.authenticate(authuser); + let dateAuthenticated2 = await identity.lastAuthenticated(authuser); + expect(dateAuthenticated2.toNumber() - dateAuthenticated1.toNumber()).gt(0); + }); + + it("should add identity admin", async () => { + let outsider = signers[5].address; + await identity.grantRole(await identity.IDENTITY_ADMIN_ROLE(), outsider); + expect(await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider)).true; + }); + + it("should remove identity admin", async () => { + let outsider = signers[5].address; + await identity.revokeRole(await identity.IDENTITY_ADMIN_ROLE(), outsider); + + expect(await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider)).false; + }); + + it("should revert when adding to whitelisted twice", async () => { + let whitelisted = signers[1]; + await identity.addWhitelisted(whitelisted.address); + await expect(identity.addWhitelisted(whitelisted.address)).reverted; + + await identity.removeWhitelisted(whitelisted.address); + }); + + it("should not increment whitelisted counter when adding whitelisted", async () => { + let whitelisted = signers[1]; + await identity.addWhitelisted(whitelisted.address); + let whitelistedCount = await identity.whitelistedCount(); + + await expect(identity.addWhitelisted(whitelisted.address)).reverted; + + let whitelistedCountNew = await identity.whitelistedCount(); + expect(whitelistedCountNew).to.be.equal(whitelistedCount).gt(0); + + await identity.removeWhitelisted(whitelisted.address); + }); + + it("should renounce whitelisted", async () => { + let whitelisted = signers[1]; + await identity.addWhitelisted(whitelisted.address); + expect(await identity.isWhitelisted(whitelisted.address)).true; + await identity.connect(whitelisted).renounceWhitelisted(); + expect(await identity.isWhitelisted(whitelisted.address)).false; + }); + + it("should add with did", async () => { + let whitelisted = signers[1]; + + await identity.addWhitelistedWithDID(whitelisted.address, "testString"); + + const id = await identity.identities(whitelisted.address); + + expect(id.did).to.be.equal("testString"); + }); + + it("should not allow adding with used did", async () => { + let whitelisted2 = signers[2]; + + await expect(identity.addWhitelistedWithDID(whitelisted2.address, "testString")).revertedWith( + /DID already registered/ + ); + }); + + it("should not allow adding non contract to contracts", async () => { + let outsider = signers[0]; + await expect(identity.addContract(outsider.address)).revertedWith(/Given address is not a contract/); + }); + + it("should add contract to contracts", async () => { + await identity.addContract(gd.address); + const wasAdded = await identity.isDAOContract(gd.address); + expect(wasAdded).to.be.true; + }); + + const connectedFixture = async () => { + const toconnect = signers[10]; + const toconnect2 = signers[11]; + let whitelisted = signers[1]; + + await identity.connect(whitelisted).connectAccount(toconnect.address); + await identity.connect(whitelisted).connectAccount(toconnect2.address); + return {}; + }; + + it("should allow to connect account", async () => { + const toconnect = signers[10]; + let whitelisted = signers[1]; + + expect(await identity.getWhitelistedRoot(toconnect.address)).eq(ethers.constants.AddressZero); + + await loadFixture(connectedFixture); + + expect(await identity.getWhitelistedRoot(whitelisted.address)).eq(whitelisted.address); + + expect(await identity.getWhitelistedRoot(toconnect.address)).eq(whitelisted.address); + }); + + it("should not allow to connect account already whitelisted", async () => { + await loadFixture(connectedFixture); + + await identity.addWhitelisted(signers[2].address); + let whitelisted = signers[1]; + + await expect(identity.connect(whitelisted).connectAccount(signers[2].address)).revertedWith(/invalid account/); + }); + + it("should allow to disconnect account by owner or connected", async () => { + await loadFixture(connectedFixture); + + const connected = signers[10]; + const whitelisted = signers[1]; + await identity.connect(connected).disconnectAccount(connected.address); + expect(await identity.getWhitelistedRoot(connected.address)).eq(ethers.constants.AddressZero); + await loadFixture(connectedFixture); + await identity.connect(whitelisted).disconnectAccount(connected.address); + expect(await identity.getWhitelistedRoot(connected.address)).eq(ethers.constants.AddressZero); + }); + + it("should not allow to disconnect account not by owner or by connected", async () => { + await loadFixture(connectedFixture); + + const connected = signers[10]; + const whitelisted = signers[1]; + await expect(identity.disconnectAccount(connected.address)).revertedWith(/unauthorized/); + }); + + it("should not allow to connect to an already connected account", async () => { + await loadFixture(connectedFixture); + + await identity.addWhitelisted(signers[2].address); + expect(await identity.isWhitelisted(signers[2].address)).true; + const connected = signers[10]; + + await expect(identity.connect(signers[2]).connectAccount(connected.address)).revertedWith(/already connected/); + }); + + it("should return same root for multiple connected accounts", async () => { + await loadFixture(connectedFixture); + + const connected = signers[10]; + const connected2 = signers[11]; + const whitelisted = signers[1]; + expect(await identity.getWhitelistedRoot(connected.address)) + .eq(await identity.getWhitelistedRoot(connected2.address)) + .eq(whitelisted.address); + }); + + it("should add whitelisted with orgchain and dateauthenticated", async () => { + await loadFixture(connectedFixture); + const toWhitelist = signers[2]; + + const ts = (Date.now() / 1000 - 100000).toFixed(0); + await identity.addWhitelistedWithDIDAndChain(toWhitelist.address, "xxx", 1234, ts); + const record = await identity.identities(toWhitelist.address); + expect(record.whitelistedOnChainId).eq(1234); + expect(record.dateAuthenticated).eq(ts); + }); + + const oldidFixture = async () => { + const newid = (await upgrades.deployProxy(await ethers.getContractFactory("IdentityV4"), [ + founder.address, + identity.address + ])) as IdentityV4; + + await identity.grantRole(await identity.IDENTITY_ADMIN_ROLE(), newid.address); + await identity.addBlacklisted(signers[4].address); + await identity.addContract(identity.address); + await identity.removeWhitelisted(signers[3].address); + await identity.addWhitelistedWithDID(signers[3].address, "testolddid"); + return { newid }; + }; + + it("should default to old identity isWhitelisted, isBlacklisted, isContract", async () => { + const { newid } = await loadFixture(oldidFixture); + expect(await (await identity.identities(signers[3].address)).did).eq("testolddid"); + expect(await (await newid.identities(signers[3].address)).did).eq(""); + + expect(await identity.addrToDID(signers[3].address)).eq("testolddid"); + expect(await newid.addrToDID(signers[3].address)).eq("testolddid"); + expect(await newid.isBlacklisted(signers[4].address)).true; + expect(await newid.isWhitelisted(signers[3].address)).true; + expect(await newid.isDAOContract(identity.address)).true; + }); + + it("should remove whitelisted,blacklisted,contract from old identity", async () => { + const { newid } = await loadFixture(oldidFixture); + await newid.removeBlacklisted(signers[4].address); + await newid.removeWhitelisted(signers[3].address); + await newid.removeContract(identity.address); + + expect(await newid.addrToDID(signers[3].address)).eq(""); + expect(await newid.isBlacklisted(signers[4].address)).false; + expect(await newid.isWhitelisted(signers[3].address)).false; + expect(await newid.isDAOContract(identity.address)).false; + + expect(await identity.isBlacklisted(signers[4].address)).false; + expect(await identity.isWhitelisted(signers[3].address)).false; + expect(await identity.isDAOContract(identity.address)).false; + }); + + it("should not set did if set in oldidentity", async () => { + const { newid } = await loadFixture(oldidFixture); + + await expect(newid.connect(signers[1])["setDID(address,string)"](signers[1].address, "testolddid")).revertedWith( + /DID already registered oldIdentity/ + ); + }); + + it("should set did if set in oldidentity by same owner", async () => { + const { newid } = await loadFixture(oldidFixture); + + await expect(newid.connect(signers[3])["setDID(address,string)"](signers[3].address, "testolddid")).not.reverted; + expect(await newid.addrToDID(signers[3].address)).eq("testolddid"); + }); + it("should set did if set in oldidentity by different owner but updated in new identity", async () => { + const { newid } = await loadFixture(oldidFixture); + + await expect(newid.connect(signers[3])["setDID(address,string)"](signers[3].address, "newdid")).not.reverted; + expect(await newid.addrToDID(signers[3].address)).eq("newdid"); + + await expect(newid.connect(signers[1])["setDID(address,string)"](signers[1].address, "testolddid")).not.reverted; + expect(await newid.addrToDID(signers[1].address)).eq("testolddid"); + }); + + it("should let admin setDID", async () => { + await expect(identity["setDID(address,string)"](signers[1].address, "admindid")).not.reverted; + expect(await identity.addrToDID(signers[1].address)).eq("admindid"); + await expect(identity.connect(signers[2])["setDID(address,string)"](signers[1].address, "admindid")).reverted; + }); + + it("should be registered for v1 compatability", async () => { + expect(await identity.isRegistered()).true; + }); + + // New tests added below --------------------------------------------------- + + it("should allow identity admin to set reverifyDaysOptions and reject empty", async () => { + // admin (default signer) sets new schedule + await expect(identity.setReverifyDaysOptions([2, 5, 10])).not.reverted; + expect(await identity.reverifyDaysOptions(0)).to.equal(2); + expect(await identity.reverifyDaysOptions(1)).to.equal(5); + expect(await identity.reverifyDaysOptions(2)).to.equal(10); + + // empty options should revert + await expect(identity.setReverifyDaysOptions([])).revertedWith("empty options"); + }); + + it("non-admin should not set reverifyDaysOptions", async () => { + await expect(identity.connect(signers[2]).setReverifyDaysOptions([1, 7, 180])).revertedWith( + /AccessControl: account/ + ); + }); + + it("should follow reverify schedule and cycle authCount", async () => { + await expect(identity.setReverifyDaysOptions([1, 7, 180])).not.reverted; + + const u = signers[12]; + // ensure a fresh account is whitelisted + await identity.addWhitelisted(u.address); + let record = await identity.identities(u.address); + expect(record.authCount).to.equal(0); + + // default reverifyDaysOptions set in initialize: [1,7,180] + // move forward 2 days (past first reverify day) + await increaseTime(2 * 24 * 3600); + expect(await identity.isWhitelisted(u.address)).to.be.false; + + // admin authenticates -> should increment authCount to 1 + await identity.authenticate(u.address); + record = await identity.identities(u.address); + expect(record.authCount).to.equal(1); + expect(await identity.isWhitelisted(u.address)).to.be.true; + + // move forward 8 days (past second reverify day = 7) + await increaseTime(8 * 24 * 3600); + expect(await identity.isWhitelisted(u.address)).to.be.false; + + // authenticate again -> authCount becomes 2 + await identity.authenticate(u.address); + record = await identity.identities(u.address); + expect(record.authCount).to.equal(2); + expect(await identity.isWhitelisted(u.address)).to.be.true; + // move forward 181 days (past third reverify day = 180) + await increaseTime(181 * 24 * 3600); + expect(await identity.isWhitelisted(u.address)).to.be.false; + + // authenticate again -> authCount should wrap back to 0 (cycle) + await identity.authenticate(u.address); + record = await identity.identities(u.address); + expect(record.authCount).to.equal(0); + expect(await identity.isWhitelisted(u.address)).to.be.true; + // cleanup (remove whitelisted) to avoid affecting other tests + await identity.removeWhitelisted(u.address); + }); +});