diff --git a/contracts/identity/IdentityV3.sol b/contracts/identity/IdentityV3.sol new file mode 100644 index 00000000..c572de3c --- /dev/null +++ b/contracts/identity/IdentityV3.sol @@ -0,0 +1,534 @@ +// 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 IdentityV3 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 + } + + 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)"; + + uint256 public whitelistedCount; + uint256 public whitelistedContracts; + uint256 public authenticationPeriod; + + mapping(address => Identity) public identities; + + mapping(bytes32 => address) public didHashToAddress; + + mapping(address => address) public connectedAccounts; + + IIdentity public oldIdentity; + + 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); + + function initialize( + address _owner, + IIdentity _oldIdentity + ) public initializer { + __AccessControl_init_unchained(); + __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; + } + + /** + * @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 Sets a new value for authenticationPeriod. + * Can only be called by Identity Administrators. + * @param period new value for authenticationPeriod + */ + function setAuthenticationPeriod(uint256 period) external whenNotPaused { + _onlyAvatar(); + authenticationPeriod = period; + } + + /** + * @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"); + 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); + } + + /** + * @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 ( + (daysSinceAuthentication <= authenticationPeriod) && + identities[account].status == 1 + ) return true; + + 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 { + 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; + 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; + } + + /** + @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]; + } + + /** + @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/contracts/token/ERC20PresetMinterPauserUpgradeable.sol b/contracts/token/ERC20PresetMinterPauserUpgradeable.sol index 4ebbf11d..9673a9a4 100644 --- a/contracts/token/ERC20PresetMinterPauserUpgradeable.sol +++ b/contracts/token/ERC20PresetMinterPauserUpgradeable.sol @@ -33,11 +33,10 @@ contract ERC20PresetMinterPauserUpgradeable is ERC20BurnableUpgradeable, ERC20PausableUpgradeable { - function initialize(string memory name, string memory symbol) - public - virtual - initializer - { + function initialize( + string memory name, + string memory symbol + ) public virtual initializer { __ERC20PresetMinterPauser_init(name, symbol); } @@ -53,7 +52,7 @@ contract ERC20PresetMinterPauserUpgradeable is function __ERC20PresetMinterPauser_init( string memory name, string memory symbol - ) internal initializer { + ) internal onlyInitializing { __Context_init_unchained(); __ERC165_init_unchained(); __AccessControl_init_unchained(); @@ -67,9 +66,9 @@ contract ERC20PresetMinterPauserUpgradeable is } function __ERC20PresetMinterPauser_init_unchained( - string memory name, - string memory symbol - ) internal initializer { + string memory, + string memory + ) internal onlyInitializing { _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); _setupRole(MINTER_ROLE, _msgSender()); diff --git a/contracts/token/GoodDollar.sol b/contracts/token/GoodDollar.sol index 2969e442..c9932c35 100644 --- a/contracts/token/GoodDollar.sol +++ b/contracts/token/GoodDollar.sol @@ -65,7 +65,7 @@ contract GoodDollar is ) internal override onlyOwner {} function decimals() public view virtual override returns (uint8) { - return 2; + return 18; } function setFormula(IFeesFormula _formula) external onlyOwner { diff --git a/contracts/token/GoodDollar2.sol b/contracts/token/GoodDollar2.sol new file mode 100644 index 00000000..fa86d520 --- /dev/null +++ b/contracts/token/GoodDollar2.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +import "./GoodDollar.sol"; + +/** + * @title The GoodDollar V2 ERC677 token contract + */ + +contract GoodDollar2 is GoodDollar { + function decimals() public view virtual override returns (uint8) { + return 2; + } +} diff --git a/contracts/token/superfluid/ISuperToken.sol b/contracts/token/superfluid/ISuperToken.sol index f507217d..7c5bec31 100644 --- a/contracts/token/superfluid/ISuperToken.sol +++ b/contracts/token/superfluid/ISuperToken.sol @@ -429,28 +429,9 @@ interface ISuperToken is ISuperfluidToken, TokenInfo, IERC20, IERC777 { * ERC20x-specific Functions *************************************************************************/ - function CONSTANT_OUTFLOW_NFT() external view returns (IConstantOutflowNFT); - - function CONSTANT_INFLOW_NFT() external view returns (IConstantInflowNFT); - function poolAdminNFT() external view returns (IPoolAdminNFT); function poolMemberNFT() external view returns (IPoolMemberNFT); - - /** - * @dev Constant Outflow NFT proxy created event - * @param constantOutflowNFT constant outflow nft address - */ - event ConstantOutflowNFTCreated( - IConstantOutflowNFT indexed constantOutflowNFT - ); - - /** - * @dev Constant Inflow NFT proxy created event - * @param constantInflowNFT constant inflow nft address - */ - event ConstantInflowNFTCreated(IConstantInflowNFT indexed constantInflowNFT); - /************************************************************************** * Function modifiers for access control and parameter validations * diff --git a/contracts/token/superfluid/SuperGoodDollar.sol b/contracts/token/superfluid/SuperGoodDollar.sol index d153fa57..80f2ca86 100644 --- a/contracts/token/superfluid/SuperGoodDollar.sol +++ b/contracts/token/superfluid/SuperGoodDollar.sol @@ -66,9 +66,7 @@ contract SuperGoodDollar is IFeesFormula _formula, IIdentity _identity, address _feeRecipient, - address _owner, - IConstantOutflowNFT _outflowNFT, - IConstantInflowNFT _inflowNFT + address _owner ) public initializer { initialize(IERC20(address(0)), 18, n, s); __AccessControl_init_unchained(); @@ -82,8 +80,6 @@ contract SuperGoodDollar is formula = _formula; cap = _cap; _setNFTProxyContracts( - _outflowNFT, - _inflowNFT, IPoolAdminNFT(address(0)), IPoolMemberNFT(address(0)) ); @@ -374,34 +370,19 @@ contract SuperGoodDollar is *************************************************************************/ function setNFTProxyContracts( - IConstantOutflowNFT _constantOutflowNFT, - IConstantInflowNFT _constantInflowNFT, IPoolAdminNFT _poolAdminNFT, IPoolMemberNFT _poolMemberNFT ) public { _onlyOwner(); - _setNFTProxyContracts( - _constantOutflowNFT, - _constantInflowNFT, - _poolAdminNFT, - _poolMemberNFT - ); + _setNFTProxyContracts(_poolAdminNFT, _poolMemberNFT); } function _setNFTProxyContracts( - IConstantOutflowNFT _constantOutflowNFT, - IConstantInflowNFT _constantInflowNFT, IPoolAdminNFT _poolAdminNFT, IPoolMemberNFT _poolMemberNFT ) internal { - CONSTANT_OUTFLOW_NFT = _constantOutflowNFT; - CONSTANT_INFLOW_NFT = _constantInflowNFT; poolAdminNFT = _poolAdminNFT; poolMemberNFT = _poolMemberNFT; - - // emit NFT proxy creation events - emit ConstantOutflowNFTCreated(CONSTANT_OUTFLOW_NFT); - emit ConstantInflowNFTCreated(CONSTANT_INFLOW_NFT); } function recover(IERC20 token) public { diff --git a/contracts/token/superfluid/SuperToken.sol b/contracts/token/superfluid/SuperToken.sol index 34603a28..d8492310 100644 --- a/contracts/token/superfluid/SuperToken.sol +++ b/contracts/token/superfluid/SuperToken.sol @@ -65,10 +65,10 @@ contract SuperToken is UUPSProxiable, SuperfluidToken, ISuperToken { ERC777Helper.Operators internal _operators; /// @notice Constant Outflow NFT proxy address - IConstantOutflowNFT public CONSTANT_OUTFLOW_NFT; + IConstantOutflowNFT private _unused_CONSTANT_OUTFLOW_NFT; /// @notice Constant Inflow NFT proxy address - IConstantInflowNFT public CONSTANT_INFLOW_NFT; + IConstantInflowNFT private _unused_CONSTANT_INFLOW_NFT; /// @notice Pool Admin NFT proxy address IPoolAdminNFT public poolAdminNFT; diff --git a/contracts/token/superfluid/SuperfluidToken.sol b/contracts/token/superfluid/SuperfluidToken.sol index cdceff27..93544021 100644 --- a/contracts/token/superfluid/SuperfluidToken.sol +++ b/contracts/token/superfluid/SuperfluidToken.sol @@ -69,15 +69,14 @@ abstract contract SuperfluidToken is ISuperfluidToken { *************************************************************************/ /// @dev ISuperfluidToken.realtimeBalanceOf implementation - function realtimeBalanceOf(address account, uint256 timestamp) + function realtimeBalanceOf( + address account, + uint256 timestamp + ) public view override - returns ( - int256 availableBalance, - uint256 deposit, - uint256 owedDeposit - ) + returns (int256 availableBalance, uint256 deposit, uint256 owedDeposit) { availableBalance = _sharedSettledBalances[account]; ISuperAgreement[] memory activeAgreements = getAccountActiveAgreements( @@ -105,7 +104,9 @@ abstract contract SuperfluidToken is ISuperfluidToken { } /// @dev ISuperfluidToken.realtimeBalanceOfNow implementation - function realtimeBalanceOfNow(address account) + function realtimeBalanceOfNow( + address account + ) public view override @@ -116,38 +117,35 @@ abstract contract SuperfluidToken is ISuperfluidToken { uint256 timestamp ) { - timestamp = _host.getNow(); + timestamp = address(_host) != address(0) ? _host.getNow() : block.timestamp; (availableBalance, deposit, owedDeposit) = realtimeBalanceOf( account, - timestamp + address(_host) != address(0) ? _host.getNow() : block.timestamp ); } - function isAccountCritical(address account, uint256 timestamp) - public - view - override - returns (bool isCritical) - { + function isAccountCritical( + address account, + uint256 timestamp + ) public view override returns (bool isCritical) { (int256 availableBalance, , ) = realtimeBalanceOf(account, timestamp); return availableBalance < 0; } - function isAccountCriticalNow(address account) - external - view - override - returns (bool isCritical) - { - return isAccountCritical(account, _host.getNow()); + function isAccountCriticalNow( + address account + ) external view override returns (bool isCritical) { + return + isAccountCritical( + account, + address(_host) != address(0) ? _host.getNow() : block.timestamp + ); } - function isAccountSolvent(address account, uint256 timestamp) - public - view - override - returns (bool isSolvent) - { + function isAccountSolvent( + address account, + uint256 timestamp + ) public view override returns (bool isSolvent) { ( int256 availableBalance, uint256 deposit, @@ -159,23 +157,24 @@ abstract contract SuperfluidToken is ISuperfluidToken { return realtimeBalance >= 0; } - function isAccountSolventNow(address account) - external - view - override - returns (bool isSolvent) - { - return isAccountSolvent(account, _host.getNow()); + function isAccountSolventNow( + address account + ) external view override returns (bool isSolvent) { + return + isAccountSolvent( + account, + address(_host) != address(0) ? _host.getNow() : block.timestamp + ); } /// @dev ISuperfluidToken.getAccountActiveAgreements implementation - function getAccountActiveAgreements(address account) - public - view - override - returns (ISuperAgreement[] memory) - { - return _host.mapAgreementClasses(~_inactiveAgreementBitmap[account]); + function getAccountActiveAgreements( + address account + ) public view override returns (ISuperAgreement[] memory) { + return + address(_host) != address(0) + ? _host.mapAgreementClasses(~_inactiveAgreementBitmap[account]) + : new ISuperAgreement[](0); } /************************************************************************** @@ -190,7 +189,10 @@ abstract contract SuperfluidToken is ISuperfluidToken { } function _burn(address account, uint256 amount) internal { - (int256 availableBalance, , ) = realtimeBalanceOf(account, _host.getNow()); + (int256 availableBalance, , ) = realtimeBalanceOf( + account, + address(_host) != address(0) ? _host.getNow() : block.timestamp + ); if (availableBalance < amount.toInt256()) { revert SF_TOKEN_BURN_INSUFFICIENT_BALANCE(); } @@ -200,12 +202,11 @@ abstract contract SuperfluidToken is ISuperfluidToken { _totalSupply = _totalSupply - amount; } - function _move( - address from, - address to, - int256 amount - ) internal { - (int256 availableBalance, , ) = realtimeBalanceOf(from, _host.getNow()); + function _move(address from, address to, int256 amount) internal { + (int256 availableBalance, , ) = realtimeBalanceOf( + from, + address(_host) != address(0) ? _host.getNow() : block.timestamp + ); if (availableBalance < amount) { revert SF_TOKEN_MOVE_INSUFFICIENT_BALANCE(); } @@ -227,11 +228,10 @@ abstract contract SuperfluidToken is ISuperfluidToken { *************************************************************************/ /// @dev ISuperfluidToken.createAgreement implementation - function createAgreement(bytes32 id, bytes32[] calldata data) - public - virtual - override - { + function createAgreement( + bytes32 id, + bytes32[] calldata data + ) public virtual override { address agreementClass = msg.sender; bytes32 slot = keccak256(abi.encode("AgreementData", agreementClass, id)); if (FixedSizeData.hasData(slot, data.length)) { @@ -252,10 +252,10 @@ abstract contract SuperfluidToken is ISuperfluidToken { } /// @dev ISuperfluidToken.updateAgreementData implementation - function updateAgreementData(bytes32 id, bytes32[] calldata data) - external - override - { + function updateAgreementData( + bytes32 id, + bytes32[] calldata data + ) external override { address agreementClass = msg.sender; bytes32 slot = keccak256(abi.encode("AgreementData", agreementClass, id)); FixedSizeData.storeData(slot, data); @@ -263,10 +263,10 @@ abstract contract SuperfluidToken is ISuperfluidToken { } /// @dev ISuperfluidToken.terminateAgreement implementation - function terminateAgreement(bytes32 id, uint256 dataLength) - external - override - { + function terminateAgreement( + bytes32 id, + uint256 dataLength + ) external override { address agreementClass = msg.sender; bytes32 slot = keccak256(abi.encode("AgreementData", agreementClass, id)); if (!FixedSizeData.hasData(slot, dataLength)) { @@ -303,11 +303,10 @@ abstract contract SuperfluidToken is ISuperfluidToken { } /// @dev ISuperfluidToken.settleBalance implementation - function settleBalance(address account, int256 delta) - external - override - onlyAgreement - { + function settleBalance( + address account, + int256 delta + ) external override onlyAgreement { _sharedSettledBalances[account] = _sharedSettledBalances[account] + delta; } diff --git a/contracts/ubi/UBISchemeV2.sol b/contracts/ubi/UBISchemeV2.sol index 9508517e..72cea70e 100644 --- a/contracts/ubi/UBISchemeV2.sol +++ b/contracts/ubi/UBISchemeV2.sol @@ -129,17 +129,12 @@ contract UBISchemeV2 is DAOUpgradeableContract { /** * @dev Constructor * @param _ns the DAO - * @param _maxInactiveDays Days of grace without claiming request */ - function initialize( - INameService _ns, - uint256 _maxInactiveDays - ) public initializer { - require(_maxInactiveDays > 0, "Max inactive days cannot be zero"); + function initialize(INameService _ns) public initializer { setDAO(_ns); shouldWithdrawFromDAO = false; cycleLength = 30; //30 days - periodStart = (block.timestamp / (1 days)) * 1 days + 12 hours; //set start time to GMT noon + periodStart = (block.timestamp / (1 days)) * 1 days - 12 hours; //set start time to GMT noon startOfCycle = periodStart; minActiveUsers = 1000; reserveFactor = 10500; @@ -240,13 +235,12 @@ contract UBISchemeV2 is DAOUpgradeableContract { uint256 currentBalance = token.balanceOf(address(this)); //start early cycle if daily pool size is +%5 previous pool or not enough until end of cycle uint256 nextDailyPool = currentBalance / cycleLength; - bool shouldStartEarlyCycle = nextDailyPool > - (dailyCyclePool * 105) / 100 || - currentBalance < (dailyCyclePool * (cycleLength - currentDayInCycle())); + bool shouldStartCycle = currentDayInCycle() >= currentCycleLength || + (nextDailyPool > (dailyCyclePool * 105) / 100 || + currentBalance < + (dailyCyclePool * (cycleLength - currentDayInCycle()))); - if ( - currentDayInCycle() >= currentCycleLength || shouldStartEarlyCycle - ) //start of cycle or first time + if (shouldStartCycle) //start of cycle or first time { if (shouldWithdrawFromDAO) { _withdrawFromDao(); @@ -347,16 +341,16 @@ contract UBISchemeV2 is DAOUpgradeableContract { uint256 currentBalance = nativeToken().balanceOf(address(this)); //start early cycle if we can increase the daily UBI pool uint256 nextDailyPool = currentBalance / cycleLength; - bool shouldStartEarlyCycle = nextDailyPool > (dailyCyclePool * 105) / 100 || + bool shouldStartEarlyCycle = currentDayInCycle() + 1 >= + currentCycleLength || + nextDailyPool > (dailyCyclePool * 105) / 100 || currentBalance < (dailyCyclePool * (cycleLength - currentDayInCycle())); uint256 _dailyCyclePool = dailyCyclePool; uint256 _dailyUbi; - if ( - (currentDayInCycle() + 1) >= currentCycleLength || shouldStartEarlyCycle - ) //start of cycle or first time + if (shouldStartEarlyCycle) //start of cycle or first time { - _dailyCyclePool = currentBalance / cycleLength; + _dailyCyclePool = nextDailyPool; } _dailyUbi = diff --git a/hardhat.config.ts b/hardhat.config.ts index 250eea05..3d443433 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -10,6 +10,7 @@ import "solidity-coverage"; import "hardhat-gas-reporter"; import "hardhat-contract-sizer"; import "hardhat-storage-layout"; +import "hardhat-deploy"; import { task, types } from "hardhat/config"; import { sha3 } from "web3-utils"; import { config } from "dotenv"; @@ -35,12 +36,12 @@ const ethplorer_key = process.env.ETHPLORER_KEY; const MAINNET_URL = "https://mainnet.infura.io/v3/" + infura_api; -const goerli = { +const xdc = { accounts: { mnemonic }, - url: "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", + url: "https://rpc.xdc.network", gas: 3000000, - gasPrice: 2e9, - chainId: 5 + gasPrice: 12.5e9, + chainId: 50 }; // console.log({ mnemonic: sha3(mnemonic) }); @@ -63,6 +64,8 @@ const hhconfig: HardhatUserConfig = { etherscan: { apiKey: { mainnet: etherscan_key, + txdc: etherscan_key, + xdc: etherscan_key, celo: celoscan_key, alfajores: celoscan_key, base: basescan_key @@ -83,6 +86,22 @@ const hhconfig: HardhatUserConfig = { apiURL: "https://alfajores.celoscan.io/api", browserURL: "https://alfajores.celoscan.io/" } + }, + { + network: "txdc", + chainId: 51, + urls: { + apiURL: "https://api.etherscan.io/v2/api?chainid=51", + browserURL: "https://testnet.xdcscan.com/" + } + }, + { + network: "xdc", + chainId: 50, + urls: { + apiURL: "https://api.etherscan.io/v2/api?chainid=50", + browserURL: "https://xdcscan.com/" + } } ] }, @@ -130,27 +149,6 @@ const hhconfig: HardhatUserConfig = { gasPrice: 1000000000, //1 gwei url: "http://127.0.0.1:8545/" }, - ropsten: { - accounts: { mnemonic }, - url: "https://ropsten.infura.io/v3/" + infura_api, - gas: 8000000, - gasPrice: 25000000000, - chainId: 3 - }, - kovan: { - accounts: { mnemonic }, - url: "https://kovan.infura.io/v3/" + infura_api, - gas: 3000000, - gasPrice: 1000000000, - chainId: 42 - }, - "kovan-mainnet": { - accounts: { mnemonic }, - url: "https://kovan.infura.io/v3/" + infura_api, - gas: 3000000, - gasPrice: 1000000000, - chainId: 42 - }, fuse: { accounts: { mnemonic }, url: "https://rpc.fuse.io/", @@ -211,14 +209,14 @@ const hhconfig: HardhatUserConfig = { accounts: [deployerPrivateKey], url: "https://forno.celo.org", gas: 8000000, - gasPrice: 25e9, + gasPrice: 26e9, chainId: 42220 }, celo: { accounts: { mnemonic }, url: "https://forno.celo.org", gas: 3000000, - gasPrice: 25e9, + gasPrice: 25.01e9, chainId: 42220 }, alfajores: { @@ -266,9 +264,18 @@ const hhconfig: HardhatUserConfig = { gasPrice: 500000000, chainId: 100 }, - goerli, - "development-goerli": goerli, - "staging-goerli": goerli + txdc: { + accounts: [deployerPrivateKey], + url: "https://rpc.apothem.network", + chainId: 51 + }, + "production-xdc": { + ...xdc, + accounts: [deployerPrivateKey] + }, + "development-xdc": { + ...xdc + } }, mocha: { timeout: 6000000 diff --git a/package.json b/package.json index ab14e72b..fc9d3d64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gooddollar/goodprotocol", - "version": "2.0.33-beta.0", + "version": "2.0.34-beta.0", "description": "GoodDollar Protocol", "engines": { "node": ">=16.x" @@ -76,7 +76,7 @@ "@mean-finance/uniswap-v3-oracle": "^1.0.3", "@nomicfoundation/hardhat-chai-matchers": "1", "@nomicfoundation/hardhat-network-helpers": "^1.0.8", - "@nomicfoundation/hardhat-verify": "2", + "@nomicfoundation/hardhat-verify": "^2.1.0", "@nomiclabs/hardhat-ethers": "^2.2.1", "@nomiclabs/hardhat-waffle": "^2.0.6", "@openzeppelin/contracts": "^4.8.0", @@ -111,6 +111,7 @@ "graphql-request": "^3.4.0", "hardhat": "^2.*", "hardhat-contract-sizer": "^2.6.1", + "hardhat-deploy": "^1.0.4", "hardhat-gas-reporter": "^1.0.8", "hardhat-storage-layout": "^0.1.7", "lodash": "^4.17.21", diff --git a/releases/deploy-settings.json b/releases/deploy-settings.json index 038f8027..d1855e7c 100644 --- a/releases/deploy-settings.json +++ b/releases/deploy-settings.json @@ -298,5 +298,9 @@ "superfluidHost": "0xA4Ff07cF81C02CFD356184879D953970cA957585", "superfluidInflowNFTLogic": "", "superfluidOutflowNNFTLogic": "" + }, + "development-xdc": { + "gasPrice": 12.5e9, + "superfluidHost": "0x0000000000000000000000000000000000000000" } -} \ No newline at end of file +} diff --git a/releases/deployment.json b/releases/deployment.json index b6dd2f79..4b5d398f 100644 --- a/releases/deployment.json +++ b/releases/deployment.json @@ -632,5 +632,31 @@ "ProxyFactory": "0xD86cd6Bb3c2a576b2C2CEF4e047b1A7adC5Be62D", "SuperfluidFaucet": "0x51A073a0F910B3902a98a584397101211F01d81F", "AdminWallet": "0x7119CD89D4792aF90277d84cDffa3F2Ab22a0022" + }, + "txdc": { + "ProxyFactory": "0x737ac1C082D5548Ec44f8CA053d263672327047d", + "DAOCreator": "0x848cf57be7054B4Ac68037B4E5f92f3C6f30A1b0", + "GoodDollar": "0x23279D336E55F54D0F92aE2e56e2126De13E38BA", + "Avatar": "0x49A1e001922bC154B3B92B6c79D5B5CEdba54F91", + "Controller": "0xe02fab3D127F4AF0932f3c0025EA563B7F8f6AB6", + "Identity": "0xD6520ba9E2aF65e140E09e7444C1dD437b24768C", + "NameService": "0xd63d169cb2d18E8221E2401D0f4f19DF9FFAD83B", + "FeeFormula": "0x4aC8AAcef1736c1e9980f57A3c063707e21651AD", + "SuperfluidHost": "0x0000000000000000000000000000000000000000" + }, + "development-xdc": { + "networkId": 50, + "ProxyFactory": "0x2a866c021AAeD17DE0e679dB52b13079847Ec66B", + "GoodDollar": "0xA13625A72Aef90645CfCe34e25c114629d7855e7", + "Avatar": "0xC47747659b74Cc23210DD851b254260b0B039eED", + "Controller": "0x36B0273c0537D73265Ae2607a3E9Be949Bff97c8", + "Identity": "0xa6632e9551A340E8582cc797017fbA645695E29f", + "NameService": "0x00a619Ee0Fe0c3646Acf2C6D2FC89FA1aDE98f1D", + "FeeFormula": "0x94459db2E5F27CDec32a8aa94c0a54F4ff33C93a", + "DAOCreator": "0x6c8a0B3A2bdebc48827bd1FE383AA5e75b7143df", + "AdminWallet": "0x63B69B1b986427a63cB8d6Ba25CdF08605471BE9", + "Faucet": "0x5CE5e22329914F3317B770184a405E91ef72e489", + "Invites": "0xE376DAd6FAe634332B514cE8fF2aD23FBB5d82EF", + "UBIScheme": "0xA2619D468EfE2f6D8b3D915B999327C8fE13aE2c" } } diff --git a/scripts/multichain-deploy/0_proxyFactory-deploy.ts b/scripts/multichain-deploy/0_proxyFactory-deploy.ts index 4f06d52f..85685709 100644 --- a/scripts/multichain-deploy/0_proxyFactory-deploy.ts +++ b/scripts/multichain-deploy/0_proxyFactory-deploy.ts @@ -1,9 +1,10 @@ -import { network, ethers, upgrades, run } from "hardhat"; -import { Contract } from "ethers"; +import hre from "hardhat"; +import { network, ethers } from "hardhat"; -import { verifyProductionSigner } from "./helpers"; +import { verifyContract, verifyOnEtherscan, verifyProductionSigner } from "./helpers"; import releaser from "../../scripts/releaser"; import dao from "../../releases/deployment.json"; +import { keccak256, parseUnits, toUtf8Bytes } from "ethers/lib/utils"; const { name } = network; const celoProxyByteCode = @@ -15,7 +16,7 @@ export const deployUniversalProxyFactory = async () => { const deployTx = { nonce: 0, gasPrice: 50e9, - gasLimit: 891002, + gasLimit: 2891002, data: celoProxyByteCode }; @@ -98,8 +99,30 @@ export const deployProxy = async (defaultAdmin = null) => { await releaser(release, network.name, "deployment", false); }; +const deployProxyMethod2 = async (defaultAdmin = null) => { + let [signer] = await ethers.getSigners(); + console.log("deploying proxyfactory with signer", { + address: signer.address, + balance: await ethers.provider.getBalance(signer.address).then(_ => _.toString()) + }); + const artifact = await hre.artifacts.readArtifact("ProxyFactory1967"); + const result = await hre.deployments.deterministic("ProxyFactory", { + skipIfAlreadyDeployed: true, + salt: keccak256(toUtf8Bytes("ProxyFactory")), + contract: artifact, + from: signer.address + }); + const deployed = await result.deploy(); + console.log(result, deployed); + const release = { + ProxyFactory: deployed.address + }; + await releaser(release, network.name, "deployment", false); + await verifyContract(deployed.address, artifact.sourceName + ":" + artifact.contractName); +}; export const main = async (networkName = name) => { - await deployProxy(); + // await deployProxy(); + await deployProxyMethod2(); }; if (process.argv[1].includes("proxyFactory-deploy")) { diff --git a/scripts/multichain-deploy/1_basicdao-deploy.ts b/scripts/multichain-deploy/1_basicdao-deploy.ts index 7862ce4f..3f355074 100644 --- a/scripts/multichain-deploy/1_basicdao-deploy.ts +++ b/scripts/multichain-deploy/1_basicdao-deploy.ts @@ -12,12 +12,18 @@ import DAOCreatorABI from "@gooddollar/goodcontracts/build/contracts/DaoCreatorG // import IdentityABI from "@gooddollar/goodcontracts/build/contracts/Identity.json"; import FeeFormulaABI from "@gooddollar/goodcontracts/build/contracts/FeeFormula.json"; -import { deployDeterministic, deploySuperGoodDollar, verifyProductionSigner } from "./helpers"; +import { + deployDeterministic, + deploySuperGoodDollar, + verifyProductionSigner, + verifyContract, + verifyOnEtherscan +} from "./helpers"; import releaser from "../releaser"; import ProtocolSettings from "../../releases/deploy-settings.json"; import dao from "../../releases/deployment.json"; import { TransactionResponse } from "@ethersproject/providers"; - +import { getImplementationAddress } from "@openzeppelin/upgrades-core"; const printDeploy = async (c: Contract | TransactionResponse): Promise => { if (c instanceof Contract) { await c.deployed(); @@ -33,10 +39,11 @@ const printDeploy = async (c: Contract | TransactionResponse): Promise { let protocolSettings = defaultsDeep({}, ProtocolSettings[network.name], ProtocolSettings["default"]); - let release: { [key: string]: any } = dao[network.name]; + let release: { [key: string]: any } = dao[network.name] || {}; const isProduction = network.name.includes("production"); let [root] = await ethers.getSigners(); + const daoOwner = root.address; if (isProduction) verifyProductionSigner(root); @@ -52,16 +59,13 @@ export const createDAO = async () => { const FeeFormulaFactory = new ethers.ContractFactory(FeeFormulaABI.abi, FeeFormulaABI.bytecode, root); - const proxyFactory = await ethers.getContractAt("ProxyFactory1967", release.ProxyFactory); - const salt = ethers.BigNumber.from(ethers.utils.keccak256(ethers.utils.toUtf8Bytes("NameService"))); - const nameserviceFutureAddress = await proxyFactory["getDeploymentAddress(uint256,address)"](salt, root.address); - console.log("deploying identity", { nameserviceFutureAddress }); + console.log("deploying identity"); let Identity; - if (release.Identity) Identity = await ethers.getContractAt("IdentityV2", release.Identity); + if (release.Identity) Identity = await ethers.getContractAt("IdentityV3", release.Identity); else Identity = (await deployDeterministic( { - name: "IdentityV2", + name: "IdentityV3", salt: "Identity", isUpgradeable: true }, @@ -70,7 +74,17 @@ export const createDAO = async () => { let daoCreator; if (release.DAOCreator) daoCreator = await DAOCreatorFactory.attach(release.DAOCreator); - else daoCreator = (await DAOCreatorFactory.deploy().then(printDeploy)) as Contract; + else { + daoCreator = await deployDeterministic( + { + name: "DAOCreator", + factory: DAOCreatorFactory, + isUpgradeable: false + }, + [] + ); + // daoCreator = (await DAOCreatorFactory.deploy().then(printDeploy)) as Contract; + } let FeeFormula; if (release.FeeFormula) FeeFormula = await FeeFormulaFactory.attach(release.FeeFormula); @@ -80,8 +94,7 @@ export const createDAO = async () => { )) as Contract; let GoodDollar; - let { superfluidHost, superfluidInflowNFTLogic, superfluidOutflowNFTLogic } = - protocolSettings; + let { superfluidHost, superfluidInflowNFTLogic, superfluidOutflowNFTLogic } = protocolSettings; if (protocolSettings.superfluidHost) { GoodDollar = await deploySuperGoodDollar( { @@ -118,66 +131,58 @@ export const createDAO = async () => { ).then(printDeploy)) as Contract; } - const GReputation = (await deployDeterministic( - { - name: "GReputation", - isUpgradeable: true, - initializer: "initialize(address, string, bytes32, uint256)" - }, - [ - ethers.constants.AddressZero, - "fuse", - protocolSettings.governance.gdaoAirdrop, //should fail on real deploy if not set - protocolSettings.governance.gdaoTotalSupply //should fail on real deploy if not set - ] - ).then(printDeploy)) as Contract; - // console.log("setting identity auth period"); // await Identity.setAuthenticationPeriod(365).then(printDeploy); - console.log("creating dao"); - await daoCreator.forgeOrg(GoodDollar.address, GReputation.address, [], 0, []).then(printDeploy); - console.log("forgeOrg done "); - const Avatar = new ethers.Contract( - await daoCreator.avatar(), + const avatar = await daoCreator.avatar(); + let Avatar = new ethers.Contract( + avatar, ["function owner() view returns (address)", "function nativeToken() view returns (address)"], root ); - - console.log("Done deploying DAO, setting schemes permissions"); - - let schemes = [daoOwner, Identity.address]; + if (avatar === ethers.constants.AddressZero) { + console.log("creating dao"); + await daoCreator.forgeOrg(GoodDollar.address, ethers.constants.AddressZero, [], 0, []).then(printDeploy); + console.log("forgeOrg done "); + console.log("Done deploying DAO, setting schemes permissions"); + + Avatar = new ethers.Contract( + await daoCreator.avatar(), + ["function owner() view returns (address)", "function nativeToken() view returns (address)"], + root + ); + let schemes = [daoOwner, Identity.address]; + + console.log("setting schemes", schemes); + + await daoCreator + .setSchemes( + Avatar.address, + schemes, + schemes.map(_ => ethers.constants.HashZero), + ["0x0000001f", "0x00000001"], + "" + ) + .then(printDeploy); + } else { + console.log("dao already exists, avatar is", avatar); + } const gd = await Avatar.nativeToken(); const controller = await Avatar.owner(); - console.log("setting schemes", schemes); - - await daoCreator - .setSchemes( - Avatar.address, - schemes, - schemes.map(_ => ethers.constants.HashZero), - ["0x0000001f", "0x00000001"], - "" - ) - .then(printDeploy); - const NameService = await deployDeterministic({ name: "NameService", isUpgradeable: true }, [ controller, - ["CONTROLLER", "AVATAR", "IDENTITY", "GOODDOLLAR", "REPUTATION"].map(_ => - ethers.utils.keccak256(ethers.utils.toUtf8Bytes(_)) - ), - [controller, Avatar.address, Identity.address, gd, GReputation.address] + ["CONTROLLER", "AVATAR", "IDENTITY", "GOODDOLLAR"].map(_ => ethers.utils.keccak256(ethers.utils.toUtf8Bytes(_))), + [controller, Avatar.address, Identity.address, gd] ]); - console.log("set GRep nameservice.."); - await (await GReputation.updateDAO(NameService.address)).wait(); console.log("set Identity nameservice.."); - await Identity.initDAO(NameService.address).then(printDeploy); - + if ((await Identity.nameService()) == ethers.constants.AddressZero) { + await Identity.initDAO(NameService.address).then(printDeploy); + } //verifications const Controller = await ethers.getContractAt("Controller", controller); @@ -194,37 +199,24 @@ export const createDAO = async () => { const deployerIsNotGDPauser = (await GoodDollar.isPauser(root.address)) === false; - const deployerIsNotRepMinter = (await GReputation.hasRole(GReputation.MINTER_ROLE(), root.address)) === false; - const avatarIsRepMinter = await GReputation.hasRole(GReputation.MINTER_ROLE(), Avatar.address); - const deployerIsIdentityOwner = await Identity.hasRole(ethers.constants.HashZero, root.address); - const avatarIsIdentityOwner = await Identity.hasRole(ethers.constants.HashZero, Avatar.address); - - //try to modify DAO -> should not succeed - await (await GReputation.updateDAO(ethers.constants.AddressZero)).wait(); - - const grepHasDAOSet = (await GReputation.nameService()) === NameService.address; - - const factoryIsNotGoodOwner = (await GReputation.hasRole(ethers.constants.HashZero, proxyFactory.address)) === false; + const avatarIsIdentityOwner = (await Identity.hasRole(ethers.constants.HashZero, Avatar.address)) === true; - const factoryIsNotGDMinter = (await GoodDollar.isMinter(proxyFactory.address)) === false; + const factoryIsNotGDMinter = (await GoodDollar.isMinter(release.ProxyFactory)) === false; const factoryIsNotGDPauser = (await GoodDollar.isPauser(root.address)) === false; - + const identityNameService = (await Identity.nameService()) === NameService.address; console.log({ daoOwnerDaoPermissions, deployerIsNotGDMinter, - deployerIsNotRepMinter, deployerIsNotGDPauser, - factoryIsNotGoodOwner, factoryIsNotGDMinter, factoryIsNotGDPauser, - avatarIsRepMinter, deployerIsIdentityOwner, avatarIsIdentityOwner, avatarIsGDMinter, - grepHasDAOSet + identityNameService }); release = { @@ -234,11 +226,57 @@ export const createDAO = async () => { Controller: controller, Identity: Identity.address, NameService: NameService.address, - GReputation: GReputation.address, FeeFormula: FeeFormula.address, DAOCreator: daoCreator.address }; await releaser(release, network.name, "deployment", false); + + await verifyOnEtherscan( + network.config.chainId, + "scripts/multichain-deploy/flattened/Avatar.sol", + "Avatar", + Avatar.address, + "v0.5.16+commit.9c3226ce", + { + types: ["string", "address", "address"], + values: ["GoodDollar", GoodDollar.address, ethers.constants.AddressZero] + } + ); + await verifyOnEtherscan( + network.config.chainId, + "scripts/multichain-deploy/flattened/Controller.sol", + "Controller", + Controller.address, + "v0.5.16+commit.9c3226ce", + { + types: ["address"], + values: [Avatar.address] + } + ); + await verifyOnEtherscan( + network.config.chainId, + "scripts/multichain-deploy/flattened/FeeFormula.sol", + "FeeFormula", + FeeFormula.address, + "v0.5.16+commit.9c3226ce", + { + types: ["uint256"], + values: [0] + } + ); + + let impl = await getImplementationAddress(ethers.provider, Identity.address); + await verifyContract(impl, "contracts/identity/IdentityV3.sol:IdentityV3", network.name); + impl = await getImplementationAddress(ethers.provider, NameService.address); + await verifyContract(impl, "contracts/utils/NameService.sol:NameService", network.name); + if (protocolSettings.superfluidHost) { + impl = await getImplementationAddress(ethers.provider, GoodDollar.address); + await verifyContract(GoodDollar.address, "contracts/token/superfluid/UUPSProxy.sol:UUPSProxy", network.name); + await verifyContract(impl, "contracts/token/superfluid/SuperGoodDollar.sol:SuperGoodDollar", network.name); + } else { + impl = await getImplementationAddress(ethers.provider, GoodDollar.address); + await verifyContract(impl, "contracts/token/GoodDollar.sol:GoodDollar", network.name); + } }; export const main = async () => { diff --git a/scripts/multichain-deploy/2_helpers-deploy.ts b/scripts/multichain-deploy/2_helpers-deploy.ts index 5e2f8efb..013c6bd3 100644 --- a/scripts/multichain-deploy/2_helpers-deploy.ts +++ b/scripts/multichain-deploy/2_helpers-deploy.ts @@ -11,6 +11,7 @@ import releaser from "../releaser"; import ProtocolSettings from "../../releases/deploy-settings.json"; import dao from "../../releases/deployment.json"; import { TransactionResponse } from "@ethersproject/providers"; +import { AdminWallet, Faucet } from "../../types"; const { name } = network; @@ -64,24 +65,28 @@ export const deployHelpers = async () => { isUpgradeable: true }, [walletAdmins, release.NameService, root.address, protocolSettings.gasPrice] - ).then(printDeploy)) as Contract; + ).then(printDeploy)) as AdminWallet; + + await AdminWallet.setDefaults(1e6, 9e6, 3, protocolSettings.gasPrice); // const AdminWallet = await ethers.getContractAt("AdminWallet", release.AdminWallet); const gd = await ethers.getContractAt("IGoodDollar", release.GoodDollar); const decimals = await gd.decimals(); - const Faucet = await deployDeterministic({ name: "Faucet", salt: "Faucet", isUpgradeable: true }, [ + const Faucet = (await deployDeterministic({ name: "Faucet", salt: "Faucet", isUpgradeable: true }, [ release.NameService, protocolSettings.gasPrice, AdminWallet.address, root.address - ]); + ])) as Faucet; + + await Faucet.setGasTopping(1.5e6); // const Faucet = await ethers.getContractAt("Faucet", release.Faucet); const Invites = await deployDeterministic({ name: "InvitesV2", salt: "InvitesV2", isUpgradeable: true }, [ release.NameService, - ethers.BigNumber.from(100).mul(ethers.BigNumber.from("10").pow(decimals)), + ethers.utils.parseUnits("100", decimals), root.address ]); @@ -124,7 +129,7 @@ export const deployHelpers = async () => { if (!network.name.includes("production")) { console.log("minting G$s to invites on dev envs"); - await gd.mint(Invites.address, ethers.BigNumber.from(1e6).mul(ethers.BigNumber.from("10").pow(decimals))); //1million GD + await gd.mint(Invites.address, ethers.utils.parseUnits("1000000", decimals)); //1million GD } console.log({ @@ -134,11 +139,11 @@ export const deployHelpers = async () => { }); let impl = await getImplementationAddress(ethers.provider, AdminWallet.address); - await verifyContract(impl, "AdminWallet", network.name); + await verifyContract(impl, "contracts/utils/AdminWallet.sol:AdminWallet", network.name); impl = await getImplementationAddress(ethers.provider, Faucet.address); - await verifyContract(impl, "Faucet", network.name); + await verifyContract(impl, "contracts/fuseFaucet/Faucet.sol:Faucet", network.name); impl = await getImplementationAddress(ethers.provider, Invites.address); - await verifyContract(impl, "InvitesV2", network.name); + await verifyContract(impl, "contracts/invite/InvitesV2.sol:InvitesV2", network.name); }; export const main = async () => { diff --git a/scripts/multichain-deploy/4_ubi-deploy.ts b/scripts/multichain-deploy/4_ubi-deploy.ts index 97e01c0e..ccff85a2 100644 --- a/scripts/multichain-deploy/4_ubi-deploy.ts +++ b/scripts/multichain-deploy/4_ubi-deploy.ts @@ -56,36 +56,37 @@ export const deployHelpers = async () => { console.log("deploying ubi pool"); const UBIScheme = (await deployDeterministic( { - name: "UBIScheme", + name: "UBISchemeV2", + factory: await ethers.getContractFactory("UBISchemeV2"), isUpgradeable: true }, - [release.NameService, ethers.constants.AddressZero, protocolSettings.ubi.maxInactiveDays] + [release.NameService] ).then(printDeploy)) as Contract; - console.log("deploying claimers distribution"); - - const ClaimersDistribution = (await deployDeterministic({ name: "ClaimersDistribution", isUpgradeable: true }, [ - release.NameService - ]).then(printDeploy)) as Contract; + const torelease = { + UBIScheme: UBIScheme.address + }; + release = { + ...release, + ...torelease + }; + await releaser(torelease, network.name, "deployment", false); console.log("setting nameservice addresses via guardian"); const proposalContracts = [ - release.NameService //nameservice add MinterWrapper + release.NameService //nameservice ]; const proposalEthValues = proposalContracts.map(_ => 0); const proposalFunctionSignatures = [ - "setAddresses(bytes32[],address[])" //add ubischeme and claimersdistribution in nameservice + "setAddresses(bytes32[],address[])" //add ubischeme ]; const proposalFunctionInputs = [ ethers.utils.defaultAbiCoder.encode( ["bytes32[]", "address[]"], - [ - [keccak256(toUtf8Bytes("UBISCHEME")), keccak256(toUtf8Bytes("GDAO_CLAIMERS"))], - [UBIScheme.address, ClaimersDistribution.address] - ] + [[keccak256(toUtf8Bytes("UBISCHEME"))], [UBIScheme.address]] ) ]; @@ -96,12 +97,6 @@ export const deployHelpers = async () => { await gd.mint(UBIScheme.address, ethers.BigNumber.from(1e6).mul(ethers.BigNumber.from("10").pow(decimals))); //1million GD } - let impl = await getImplementationAddress(ethers.provider, UBIScheme.address); - await verifyContract(impl, "UBIScheme", network.name); - - impl = await getImplementationAddress(ethers.provider, ClaimersDistribution.address); - await verifyContract(impl, "ClaimersDistribution", network.name); - try { if (viaGuardians) { await executeViaSafe( @@ -123,6 +118,9 @@ export const deployHelpers = async () => { } catch (e) { console.error("proposal execution failed...", e.message); } + + let impl = await getImplementationAddress(ethers.provider, UBIScheme.address); + await verifyContract(impl, "contracts/ubi/UBISchemeV2.sol:UBISchemeV2", network.name); }; export const main = async (networkName = name) => { diff --git a/scripts/multichain-deploy/5_gov-deploy.ts b/scripts/multichain-deploy/5_gov-deploy.ts index 52bf2f0d..1652707b 100644 --- a/scripts/multichain-deploy/5_gov-deploy.ts +++ b/scripts/multichain-deploy/5_gov-deploy.ts @@ -5,11 +5,13 @@ import { network, ethers, upgrades, run } from "hardhat"; import { Contract } from "ethers"; import { defaultsDeep } from "lodash"; +import { getImplementationAddress } from "@openzeppelin/upgrades-core"; import { deployDeterministic, executeViaGuardian, executeViaSafe, - verifyProductionSigner + verifyProductionSigner, + verifyContract } from "./helpers"; import releaser from "../releaser"; import ProtocolSettings from "../../releases/deploy-settings.json"; @@ -19,9 +21,7 @@ import { keccak256, toUtf8Bytes } from "ethers/lib/utils"; const { name } = network; -const printDeploy = async ( - c: Contract | TransactionResponse -): Promise => { +const printDeploy = async (c: Contract | TransactionResponse): Promise => { if (c instanceof Contract) { await c.deployed(); console.log("deployed to: ", c.address); @@ -34,11 +34,7 @@ const printDeploy = async ( }; export const deployGov = async () => { - let protocolSettings = defaultsDeep( - {}, - ProtocolSettings[network.name], - ProtocolSettings["default"] - ); + let protocolSettings = defaultsDeep({}, ProtocolSettings[network.name], ProtocolSettings["default"]); let release: { [key: string]: any } = dao[network.name]; let [root] = await ethers.getSigners(); @@ -52,11 +48,23 @@ export const deployGov = async () => { console.log("got signers:", { network, root: root.address, - balance: await ethers.provider - .getBalance(root.address) - .then(_ => _.toString()) + balance: await ethers.provider.getBalance(root.address).then(_ => _.toString()) }); + const GReputation = (await deployDeterministic( + { + name: "GReputation", + isUpgradeable: true, + initializer: "initialize(address, string, bytes32, uint256)" + }, + [ + release.NameService, + "fuse", + protocolSettings.governance.gdaoAirdrop, //should fail on real deploy if not set + protocolSettings.governance.gdaoTotalSupply //should fail on real deploy if not set + ] + ).then(printDeploy)) as Contract; + console.log("deploying voting machine"); const VotingMachine = (await deployDeterministic( @@ -72,8 +80,16 @@ export const deployGov = async () => { ] ).then(printDeploy)) as Contract; + console.log("deploying claimers distribution"); + + const ClaimersDistribution = (await deployDeterministic({ name: "ClaimersDistribution", isUpgradeable: true }, [ + release.NameService + ]).then(printDeploy)) as Contract; + const torelease = { - CompoundVotingMachine: VotingMachine.address + CompoundVotingMachine: VotingMachine.address, + GReputation: GReputation.address, + ClaimersDistribution: ClaimersDistribution.address }; release = { ...release, @@ -81,29 +97,51 @@ export const deployGov = async () => { }; await releaser(torelease, network.name, "deployment", false); + const deployerIsNotRepMinter = (await GReputation.hasRole(GReputation.MINTER_ROLE(), root.address)) === false; + const avatarIsRepMinter = await GReputation.hasRole(GReputation.MINTER_ROLE(), release.Avatar); + const grepHasDAOSet = (await GReputation.nameService()) === release.NameService; + const factoryIsNotGoodOwner = (await GReputation.hasRole(ethers.constants.HashZero, release.ProxyFactory)) === false; + console.log("verify GReputation permissions", { + deployerIsNotRepMinter, + avatarIsRepMinter, + grepHasDAOSet, + factoryIsNotGoodOwner + }); + console.log("adding genericcall permissions to voting contract."); const proposalContracts = [ + release.NameService, release.Controller //nameservice add MinterWrapper ]; const proposalEthValues = proposalContracts.map(_ => 0); const proposalFunctionSignatures = [ + "setAddresses(bytes32[],address[])", //add reputation "registerScheme(address,bytes32,bytes4,address)" //make sure compoundvotingmachine has generic call so it can control the DAO ]; const proposalFunctionInputs = [ ethers.utils.defaultAbiCoder.encode( - ["address", "bytes32", "bytes4", "address"], + ["bytes32[]", "address[]"], [ - release.CompoundVotingMachine, - ethers.constants.HashZero, - "0x000000f1", - release.Avatar + [keccak256(toUtf8Bytes("REPUTATION")), keccak256(toUtf8Bytes("GDAO_CLAIMERS"))], + [GReputation.address, ClaimersDistribution.address] ] + ), + ethers.utils.defaultAbiCoder.encode( + ["address", "bytes32", "bytes4", "address"], + [release.CompoundVotingMachine, ethers.constants.HashZero, "0x000000f1", release.Avatar] ) ]; + let impl = await getImplementationAddress(ethers.provider, ClaimersDistribution.address); + await verifyContract(impl, "ClaimersDistribution", network.name); + impl = await getImplementationAddress(ethers.provider, VotingMachine.address); + await verifyContract(impl, "CompoundVotingMachine", network.name); + impl = await getImplementationAddress(ethers.provider, GReputation.address); + await verifyContract(impl, "GReputation", network.name); + try { if (viaGuardians) { await executeViaSafe( @@ -126,15 +164,9 @@ export const deployGov = async () => { console.error("proposal execution failed...", e.message); } - const Controller = await ethers.getContractAt( - "Controller", - release.Controller - ); + const Controller = await ethers.getContractAt("Controller", release.Controller); - const votingMachineDaoPermissions = await Controller.getSchemePermissions( - daoOwner, - release.Avatar - ); + const votingMachineDaoPermissions = await Controller.getSchemePermissions(daoOwner, release.Avatar); console.log({ votingMachineDaoPermissions diff --git a/scripts/multichain-deploy/flattened/Avatar.sol b/scripts/multichain-deploy/flattened/Avatar.sol new file mode 100644 index 00000000..b5018746 --- /dev/null +++ b/scripts/multichain-deploy/flattened/Avatar.sol @@ -0,0 +1,913 @@ +/** + *Submitted for verification at celoscan.io on 2022-09-18 + */ + +// File: openzeppelin-solidity/contracts/ownership/Ownable.sol + +/** + * @title Ownable + * @dev The Ownable contract has an owner address, and provides basic authorization control + * functions, this simplifies the implementation of "user permissions". + */ +contract Ownable { + address private _owner; + + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + /** + * @dev The Ownable constructor sets the original `owner` of the contract to the sender + * account. + */ + constructor() internal { + _owner = msg.sender; + emit OwnershipTransferred(address(0), _owner); + } + + /** + * @return the address of the owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(isOwner()); + _; + } + + /** + * @return true if `msg.sender` is the owner of the contract. + */ + function isOwner() public view returns (bool) { + return msg.sender == _owner; + } + + /** + * @dev Allows the current owner to relinquish control of the contract. + * @notice Renouncing to ownership will leave the contract without an owner. + * It will not be possible to call the functions with the `onlyOwner` + * modifier anymore. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) public onlyOwner { + _transferOwnership(newOwner); + } + + /** + * @dev Transfers control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0)); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} + +// File: @daostack/infra/contracts/Reputation.sol + +pragma solidity ^0.5.4; + +/** + * @title Reputation system + * @dev A DAO has Reputation System which allows peers to rate other peers in order to build trust . + * A reputation is use to assign influence measure to a DAO'S peers. + * Reputation is similar to regular tokens but with one crucial difference: It is non-transferable. + * The Reputation contract maintain a map of address to reputation value. + * It provides an onlyOwner functions to mint and burn reputation _to (or _from) a specific address. + */ + +contract Reputation is Ownable { + uint8 public decimals = 18; //Number of decimals of the smallest unit + // Event indicating minting of reputation to an address. + event Mint(address indexed _to, uint256 _amount); + // Event indicating burning of reputation for an address. + event Burn(address indexed _from, uint256 _amount); + + /// @dev `Checkpoint` is the structure that attaches a block number to a + /// given value, the block number attached is the one that last changed the + /// value + struct Checkpoint { + // `fromBlock` is the block number that the value was generated from + uint128 fromBlock; + // `value` is the amount of reputation at a specific block number + uint128 value; + } + + // `balances` is the map that tracks the balance of each address, in this + // contract when the balance changes the block number that the change + // occurred is also included in the map + mapping(address => Checkpoint[]) balances; + + // Tracks the history of the `totalSupply` of the reputation + Checkpoint[] totalSupplyHistory; + + /// @notice Constructor to create a Reputation + constructor() public {} + + /// @dev This function makes it easy to get the total number of reputation + /// @return The total number of reputation + function totalSupply() public view returns (uint256) { + return totalSupplyAt(block.number); + } + + //////////////// + // Query balance and totalSupply in History + //////////////// + /** + * @dev return the reputation amount of a given owner + * @param _owner an address of the owner which we want to get his reputation + */ + function balanceOf(address _owner) public view returns (uint256 balance) { + return balanceOfAt(_owner, block.number); + } + + /// @dev Queries the balance of `_owner` at a specific `_blockNumber` + /// @param _owner The address from which the balance will be retrieved + /// @param _blockNumber The block number when the balance is queried + /// @return The balance at `_blockNumber` + function balanceOfAt( + address _owner, + uint256 _blockNumber + ) public view returns (uint256) { + if ( + (balances[_owner].length == 0) || + (balances[_owner][0].fromBlock > _blockNumber) + ) { + return 0; + // This will return the expected balance during normal situations + } else { + return getValueAt(balances[_owner], _blockNumber); + } + } + + /// @notice Total amount of reputation at a specific `_blockNumber`. + /// @param _blockNumber The block number when the totalSupply is queried + /// @return The total amount of reputation at `_blockNumber` + function totalSupplyAt(uint256 _blockNumber) public view returns (uint256) { + if ( + (totalSupplyHistory.length == 0) || + (totalSupplyHistory[0].fromBlock > _blockNumber) + ) { + return 0; + // This will return the expected totalSupply during normal situations + } else { + return getValueAt(totalSupplyHistory, _blockNumber); + } + } + + /// @notice Generates `_amount` reputation that are assigned to `_owner` + /// @param _user The address that will be assigned the new reputation + /// @param _amount The quantity of reputation generated + /// @return True if the reputation are generated correctly + function mint( + address _user, + uint256 _amount + ) public onlyOwner returns (bool) { + uint256 curTotalSupply = totalSupply(); + require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow + uint256 previousBalanceTo = balanceOf(_user); + require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow + updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount); + updateValueAtNow(balances[_user], previousBalanceTo + _amount); + emit Mint(_user, _amount); + return true; + } + + /// @notice Burns `_amount` reputation from `_owner` + /// @param _user The address that will lose the reputation + /// @param _amount The quantity of reputation to burn + /// @return True if the reputation are burned correctly + function burn( + address _user, + uint256 _amount + ) public onlyOwner returns (bool) { + uint256 curTotalSupply = totalSupply(); + uint256 amountBurned = _amount; + uint256 previousBalanceFrom = balanceOf(_user); + if (previousBalanceFrom < amountBurned) { + amountBurned = previousBalanceFrom; + } + updateValueAtNow(totalSupplyHistory, curTotalSupply - amountBurned); + updateValueAtNow(balances[_user], previousBalanceFrom - amountBurned); + emit Burn(_user, amountBurned); + return true; + } + + //////////////// + // Internal helper functions to query and set a value in a snapshot array + //////////////// + + /// @dev `getValueAt` retrieves the number of reputation at a given block number + /// @param checkpoints The history of values being queried + /// @param _block The block number to retrieve the value at + /// @return The number of reputation being queried + function getValueAt( + Checkpoint[] storage checkpoints, + uint256 _block + ) internal view returns (uint256) { + if (checkpoints.length == 0) { + return 0; + } + + // Shortcut for the actual value + if (_block >= checkpoints[checkpoints.length - 1].fromBlock) { + return checkpoints[checkpoints.length - 1].value; + } + if (_block < checkpoints[0].fromBlock) { + return 0; + } + + // Binary search of the value in the array + uint256 min = 0; + uint256 max = checkpoints.length - 1; + while (max > min) { + uint256 mid = (max + min + 1) / 2; + if (checkpoints[mid].fromBlock <= _block) { + min = mid; + } else { + max = mid - 1; + } + } + return checkpoints[min].value; + } + + /// @dev `updateValueAtNow` used to update the `balances` map and the + /// `totalSupplyHistory` + /// @param checkpoints The history of data being updated + /// @param _value The new number of reputation + function updateValueAtNow( + Checkpoint[] storage checkpoints, + uint256 _value + ) internal { + require(uint128(_value) == _value); //check value is in the 128 bits bounderies + if ( + (checkpoints.length == 0) || + (checkpoints[checkpoints.length - 1].fromBlock < block.number) + ) { + Checkpoint storage newCheckPoint = checkpoints[checkpoints.length++]; + newCheckPoint.fromBlock = uint128(block.number); + newCheckPoint.value = uint128(_value); + } else { + Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length - 1]; + oldCheckPoint.value = uint128(_value); + } + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +interface IERC20 { + function transfer(address to, uint256 value) external returns (bool); + + function approve(address spender, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + function totalSupply() external view returns (uint256); + + function balanceOf(address who) external view returns (uint256); + + function allowance( + address owner, + address spender + ) external view returns (uint256); + + event Transfer(address indexed from, address indexed to, uint256 value); + + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// File: openzeppelin-solidity/contracts/math/SafeMath.sol + +/** + * @title SafeMath + * @dev Unsigned math operations with safety checks that revert on error + */ +library SafeMath { + /** + * @dev Multiplies two unsigned integers, reverts on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b); + + return c; + } + + /** + * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a); + uint256 c = a - b; + + return c; + } + + /** + * @dev Adds two unsigned integers, reverts on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a); + + return c; + } + + /** + * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), + * reverts when dividing by zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b != 0); + return a % b; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol + +/** + * @title Standard ERC20 token + * + * @dev Implementation of the basic standard token. + * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md + * Originally based on code by FirstBlood: + * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol + * + * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for + * all accounts just by listening to said events. Note that this isn't required by the specification, and other + * compliant implementations may not do it. + */ +contract ERC20 is IERC20 { + using SafeMath for uint256; + + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowed; + + uint256 private _totalSupply; + + /** + * @dev Total number of tokens in existence + */ + function totalSupply() public view returns (uint256) { + return _totalSupply; + } + + /** + * @dev Gets the balance of the specified address. + * @param owner The address to query the balance of. + * @return An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address owner) public view returns (uint256) { + return _balances[owner]; + } + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + * @param owner address The address which owns the funds. + * @param spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance( + address owner, + address spender + ) public view returns (uint256) { + return _allowed[owner][spender]; + } + + /** + * @dev Transfer token for a specified address + * @param to The address to transfer to. + * @param value The amount to be transferred. + */ + function transfer(address to, uint256 value) public returns (bool) { + _transfer(msg.sender, to, value); + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * Beware that changing an allowance with this method brings the risk that someone may use both the old + * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * @param spender The address which will spend the funds. + * @param value The amount of tokens to be spent. + */ + function approve(address spender, uint256 value) public returns (bool) { + require(spender != address(0)); + + _allowed[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); + return true; + } + + /** + * @dev Transfer tokens from one address to another. + * Note that while this function emits an Approval event, this is not required as per the specification, + * and other compliant implementations may not emit the event. + * @param from address The address which you want to send tokens from + * @param to address The address which you want to transfer to + * @param value uint256 the amount of tokens to be transferred + */ + function transferFrom( + address from, + address to, + uint256 value + ) public returns (bool) { + _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); + _transfer(from, to, value); + emit Approval(from, msg.sender, _allowed[from][msg.sender]); + return true; + } + + /** + * @dev Increase the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed_[_spender] == 0. To increment + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * Emits an Approval event. + * @param spender The address which will spend the funds. + * @param addedValue The amount of tokens to increase the allowance by. + */ + function increaseAllowance( + address spender, + uint256 addedValue + ) public returns (bool) { + require(spender != address(0)); + + _allowed[msg.sender][spender] = _allowed[msg.sender][spender].add( + addedValue + ); + emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); + return true; + } + + /** + * @dev Decrease the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed_[_spender] == 0. To decrement + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * Emits an Approval event. + * @param spender The address which will spend the funds. + * @param subtractedValue The amount of tokens to decrease the allowance by. + */ + function decreaseAllowance( + address spender, + uint256 subtractedValue + ) public returns (bool) { + require(spender != address(0)); + + _allowed[msg.sender][spender] = _allowed[msg.sender][spender].sub( + subtractedValue + ); + emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); + return true; + } + + /** + * @dev Transfer token for a specified addresses + * @param from The address to transfer from. + * @param to The address to transfer to. + * @param value The amount to be transferred. + */ + function _transfer(address from, address to, uint256 value) internal { + require(to != address(0)); + + _balances[from] = _balances[from].sub(value); + _balances[to] = _balances[to].add(value); + emit Transfer(from, to, value); + } + + /** + * @dev Internal function that mints an amount of the token and assigns it to + * an account. This encapsulates the modification of balances such that the + * proper events are emitted. + * @param account The account that will receive the created tokens. + * @param value The amount that will be created. + */ + function _mint(address account, uint256 value) internal { + require(account != address(0)); + + _totalSupply = _totalSupply.add(value); + _balances[account] = _balances[account].add(value); + emit Transfer(address(0), account, value); + } + + /** + * @dev Internal function that burns an amount of the token of a given + * account. + * @param account The account whose tokens will be burnt. + * @param value The amount that will be burnt. + */ + function _burn(address account, uint256 value) internal { + require(account != address(0)); + + _totalSupply = _totalSupply.sub(value); + _balances[account] = _balances[account].sub(value); + emit Transfer(account, address(0), value); + } + + /** + * @dev Internal function that burns an amount of the token of a given + * account, deducting from the sender's allowance for said account. Uses the + * internal burn function. + * Emits an Approval event (reflecting the reduced allowance). + * @param account The account whose tokens will be burnt. + * @param value The amount that will be burnt. + */ + function _burnFrom(address account, uint256 value) internal { + _allowed[account][msg.sender] = _allowed[account][msg.sender].sub(value); + _burn(account, value); + emit Approval(account, msg.sender, _allowed[account][msg.sender]); + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20Burnable.sol + +/** + * @title Burnable Token + * @dev Token that can be irreversibly burned (destroyed). + */ +contract ERC20Burnable is ERC20 { + /** + * @dev Burns a specific amount of tokens. + * @param value The amount of token to be burned. + */ + function burn(uint256 value) public { + _burn(msg.sender, value); + } + + /** + * @dev Burns a specific amount of tokens from the target address and decrements allowance + * @param from address The address which you want to send tokens from + * @param value uint256 The amount of token to be burned + */ + function burnFrom(address from, uint256 value) public { + _burnFrom(from, value); + } +} + +// File: @daostack/arc/contracts/controller/DAOToken.sol + +/** + * @title DAOToken, base on zeppelin contract. + * @dev ERC20 compatible token. It is a mintable, burnable token. + */ + +contract DAOToken is ERC20, ERC20Burnable, Ownable { + string public name; + string public symbol; + // solhint-disable-next-line const-name-snakecase + uint8 public constant decimals = 18; + uint256 public cap; + + /** + * @dev Constructor + * @param _name - token name + * @param _symbol - token symbol + * @param _cap - token cap - 0 value means no cap + */ + constructor(string memory _name, string memory _symbol, uint256 _cap) public { + name = _name; + symbol = _symbol; + cap = _cap; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. + */ + function mint(address _to, uint256 _amount) public onlyOwner returns (bool) { + if (cap > 0) require(totalSupply().add(_amount) <= cap); + _mint(_to, _amount); + return true; + } +} + +// File: openzeppelin-solidity/contracts/utils/Address.sol + +/** + * Utility library of inline functions on addresses + */ +library Address { + /** + * Returns whether the target address is a contract + * @dev This function will return false if invoked during the constructor of a contract, + * as the code is not actually created until after the constructor finishes. + * @param account address of the account to check + * @return whether the target address is a contract + */ + function isContract(address account) internal view returns (bool) { + uint256 size; + // XXX Currently there is no better way to check if there is a contract in an address + // than to check the size of the code at that address. + // See https://ethereum.stackexchange.com/a/14016/36603 + // for more details about how this works. + // TODO Check this again before the Serenity release, because all addresses will be + // contracts then. + // solhint-disable-next-line no-inline-assembly + assembly { + size := extcodesize(account) + } + return size > 0; + } +} + +// File: @daostack/arc/contracts/libs/SafeERC20.sol + +/* + +SafeERC20 by daostack. +The code is based on a fix by SECBIT Team. + +USE WITH CAUTION & NO WARRANTY + +REFERENCE & RELATED READING +- https://github.com/ethereum/solidity/issues/4116 +- https://medium.com/@chris_77367/explaining-unexpected-reverts-starting-with-solidity-0-4-22-3ada6e82308c +- https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca +- https://gist.github.com/BrendanChou/88a2eeb80947ff00bcf58ffdafeaeb61 + +*/ + +library SafeERC20 { + using Address for address; + + bytes4 private constant TRANSFER_SELECTOR = + bytes4(keccak256(bytes("transfer(address,uint256)"))); + bytes4 private constant TRANSFERFROM_SELECTOR = + bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); + bytes4 private constant APPROVE_SELECTOR = + bytes4(keccak256(bytes("approve(address,uint256)"))); + + function safeTransfer( + address _erc20Addr, + address _to, + uint256 _value + ) internal { + // Must be a contract addr first! + require(_erc20Addr.isContract()); + + (bool success, bytes memory returnValue) = // solhint-disable-next-line avoid-low-level-calls + _erc20Addr.call(abi.encodeWithSelector(TRANSFER_SELECTOR, _to, _value)); + // call return false when something wrong + require(success); + //check return value + require( + returnValue.length == 0 || + (returnValue.length == 32 && (returnValue[31] != 0)) + ); + } + + function safeTransferFrom( + address _erc20Addr, + address _from, + address _to, + uint256 _value + ) internal { + // Must be a contract addr first! + require(_erc20Addr.isContract()); + + (bool success, bytes memory returnValue) = // solhint-disable-next-line avoid-low-level-calls + _erc20Addr.call( + abi.encodeWithSelector(TRANSFERFROM_SELECTOR, _from, _to, _value) + ); + // call return false when something wrong + require(success); + //check return value + require( + returnValue.length == 0 || + (returnValue.length == 32 && (returnValue[31] != 0)) + ); + } + + function safeApprove( + address _erc20Addr, + address _spender, + uint256 _value + ) internal { + // Must be a contract addr first! + require(_erc20Addr.isContract()); + + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. + require( + (_value == 0) || + (IERC20(_erc20Addr).allowance(address(this), _spender) == 0) + ); + + (bool success, bytes memory returnValue) = // solhint-disable-next-line avoid-low-level-calls + _erc20Addr.call(abi.encodeWithSelector(APPROVE_SELECTOR, _spender, _value)); + // call return false when something wrong + require(success); + //check return value + require( + returnValue.length == 0 || + (returnValue.length == 32 && (returnValue[31] != 0)) + ); + } +} + +// File: @daostack/arc/contracts/controller/Avatar.sol + +/** + * @title An Avatar holds tokens, reputation and ether for a controller + */ +contract Avatar is Ownable { + using SafeERC20 for address; + + string public orgName; + DAOToken public nativeToken; + Reputation public nativeReputation; + + event GenericCall( + address indexed _contract, + bytes _data, + uint _value, + bool _success + ); + event SendEther(uint256 _amountInWei, address indexed _to); + event ExternalTokenTransfer( + address indexed _externalToken, + address indexed _to, + uint256 _value + ); + event ExternalTokenTransferFrom( + address indexed _externalToken, + address _from, + address _to, + uint256 _value + ); + event ExternalTokenApproval( + address indexed _externalToken, + address _spender, + uint256 _value + ); + event ReceiveEther(address indexed _sender, uint256 _value); + event MetaData(string _metaData); + + /** + * @dev the constructor takes organization name, native token and reputation system + and creates an avatar for a controller + */ + constructor( + string memory _orgName, + DAOToken _nativeToken, + Reputation _nativeReputation + ) public { + orgName = _orgName; + nativeToken = _nativeToken; + nativeReputation = _nativeReputation; + } + + /** + * @dev enables an avatar to receive ethers + */ + function() external payable { + emit ReceiveEther(msg.sender, msg.value); + } + + /** + * @dev perform a generic call to an arbitrary contract + * @param _contract the contract's address to call + * @param _data ABI-encoded contract call to call `_contract` address. + * @param _value value (ETH) to transfer with the transaction + * @return bool success or fail + * bytes - the return bytes of the called contract's function. + */ + function genericCall( + address _contract, + bytes memory _data, + uint256 _value + ) public onlyOwner returns (bool success, bytes memory returnValue) { + // solhint-disable-next-line avoid-call-value + (success, returnValue) = _contract.call.value(_value)(_data); + emit GenericCall(_contract, _data, _value, success); + } + + /** + * @dev send ethers from the avatar's wallet + * @param _amountInWei amount to send in Wei units + * @param _to send the ethers to this address + * @return bool which represents success + */ + function sendEther( + uint256 _amountInWei, + address payable _to + ) public onlyOwner returns (bool) { + _to.transfer(_amountInWei); + emit SendEther(_amountInWei, _to); + return true; + } + + /** + * @dev external token transfer + * @param _externalToken the token contract + * @param _to the destination address + * @param _value the amount of tokens to transfer + * @return bool which represents success + */ + function externalTokenTransfer( + IERC20 _externalToken, + address _to, + uint256 _value + ) public onlyOwner returns (bool) { + address(_externalToken).safeTransfer(_to, _value); + emit ExternalTokenTransfer(address(_externalToken), _to, _value); + return true; + } + + /** + * @dev external token transfer from a specific account + * @param _externalToken the token contract + * @param _from the account to spend token from + * @param _to the destination address + * @param _value the amount of tokens to transfer + * @return bool which represents success + */ + function externalTokenTransferFrom( + IERC20 _externalToken, + address _from, + address _to, + uint256 _value + ) public onlyOwner returns (bool) { + address(_externalToken).safeTransferFrom(_from, _to, _value); + emit ExternalTokenTransferFrom(address(_externalToken), _from, _to, _value); + return true; + } + + /** + * @dev externalTokenApproval approve the spender address to spend a specified amount of tokens + * on behalf of msg.sender. + * @param _externalToken the address of the Token Contract + * @param _spender address + * @param _value the amount of ether (in Wei) which the approval is referring to. + * @return bool which represents a success + */ + function externalTokenApproval( + IERC20 _externalToken, + address _spender, + uint256 _value + ) public onlyOwner returns (bool) { + address(_externalToken).safeApprove(_spender, _value); + emit ExternalTokenApproval(address(_externalToken), _spender, _value); + return true; + } + + /** + * @dev metaData emits an event with a string, should contain the hash of some meta data. + * @param _metaData a string representing a hash of the meta data + * @return bool which represents a success + */ + function metaData(string memory _metaData) public onlyOwner returns (bool) { + emit MetaData(_metaData); + return true; + } +} diff --git a/scripts/multichain-deploy/flattened/Controller.sol b/scripts/multichain-deploy/flattened/Controller.sol new file mode 100644 index 00000000..954dc4c4 --- /dev/null +++ b/scripts/multichain-deploy/flattened/Controller.sol @@ -0,0 +1,1891 @@ +// File: openzeppelin-solidity/contracts/ownership/Ownable.sol + +pragma solidity ^0.5.0; + +/** + * @title Ownable + * @dev The Ownable contract has an owner address, and provides basic authorization control + * functions, this simplifies the implementation of "user permissions". + */ +contract Ownable { + address private _owner; + + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + /** + * @dev The Ownable constructor sets the original `owner` of the contract to the sender + * account. + */ + constructor() internal { + _owner = msg.sender; + emit OwnershipTransferred(address(0), _owner); + } + + /** + * @return the address of the owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(isOwner()); + _; + } + + /** + * @return true if `msg.sender` is the owner of the contract. + */ + function isOwner() public view returns (bool) { + return msg.sender == _owner; + } + + /** + * @dev Allows the current owner to relinquish control of the contract. + * @notice Renouncing to ownership will leave the contract without an owner. + * It will not be possible to call the functions with the `onlyOwner` + * modifier anymore. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) public onlyOwner { + _transferOwnership(newOwner); + } + + /** + * @dev Transfers control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0)); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} + +// File: @daostack/infra/contracts/Reputation.sol + +pragma solidity ^0.5.4; + +/** + * @title Reputation system + * @dev A DAO has Reputation System which allows peers to rate other peers in order to build trust . + * A reputation is use to assign influence measure to a DAO'S peers. + * Reputation is similar to regular tokens but with one crucial difference: It is non-transferable. + * The Reputation contract maintain a map of address to reputation value. + * It provides an onlyOwner functions to mint and burn reputation _to (or _from) a specific address. + */ + +contract Reputation is Ownable { + uint8 public decimals = 18; //Number of decimals of the smallest unit + // Event indicating minting of reputation to an address. + event Mint(address indexed _to, uint256 _amount); + // Event indicating burning of reputation for an address. + event Burn(address indexed _from, uint256 _amount); + + /// @dev `Checkpoint` is the structure that attaches a block number to a + /// given value, the block number attached is the one that last changed the + /// value + struct Checkpoint { + // `fromBlock` is the block number that the value was generated from + uint128 fromBlock; + // `value` is the amount of reputation at a specific block number + uint128 value; + } + + // `balances` is the map that tracks the balance of each address, in this + // contract when the balance changes the block number that the change + // occurred is also included in the map + mapping(address => Checkpoint[]) balances; + + // Tracks the history of the `totalSupply` of the reputation + Checkpoint[] totalSupplyHistory; + + /// @notice Constructor to create a Reputation + constructor() public {} + + /// @dev This function makes it easy to get the total number of reputation + /// @return The total number of reputation + function totalSupply() public view returns (uint256) { + return totalSupplyAt(block.number); + } + + //////////////// + // Query balance and totalSupply in History + //////////////// + /** + * @dev return the reputation amount of a given owner + * @param _owner an address of the owner which we want to get his reputation + */ + function balanceOf(address _owner) public view returns (uint256 balance) { + return balanceOfAt(_owner, block.number); + } + + /// @dev Queries the balance of `_owner` at a specific `_blockNumber` + /// @param _owner The address from which the balance will be retrieved + /// @param _blockNumber The block number when the balance is queried + /// @return The balance at `_blockNumber` + function balanceOfAt( + address _owner, + uint256 _blockNumber + ) public view returns (uint256) { + if ( + (balances[_owner].length == 0) || + (balances[_owner][0].fromBlock > _blockNumber) + ) { + return 0; + // This will return the expected balance during normal situations + } else { + return getValueAt(balances[_owner], _blockNumber); + } + } + + /// @notice Total amount of reputation at a specific `_blockNumber`. + /// @param _blockNumber The block number when the totalSupply is queried + /// @return The total amount of reputation at `_blockNumber` + function totalSupplyAt(uint256 _blockNumber) public view returns (uint256) { + if ( + (totalSupplyHistory.length == 0) || + (totalSupplyHistory[0].fromBlock > _blockNumber) + ) { + return 0; + // This will return the expected totalSupply during normal situations + } else { + return getValueAt(totalSupplyHistory, _blockNumber); + } + } + + /// @notice Generates `_amount` reputation that are assigned to `_owner` + /// @param _user The address that will be assigned the new reputation + /// @param _amount The quantity of reputation generated + /// @return True if the reputation are generated correctly + function mint( + address _user, + uint256 _amount + ) public onlyOwner returns (bool) { + uint256 curTotalSupply = totalSupply(); + require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow + uint256 previousBalanceTo = balanceOf(_user); + require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow + updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount); + updateValueAtNow(balances[_user], previousBalanceTo + _amount); + emit Mint(_user, _amount); + return true; + } + + /// @notice Burns `_amount` reputation from `_owner` + /// @param _user The address that will lose the reputation + /// @param _amount The quantity of reputation to burn + /// @return True if the reputation are burned correctly + function burn( + address _user, + uint256 _amount + ) public onlyOwner returns (bool) { + uint256 curTotalSupply = totalSupply(); + uint256 amountBurned = _amount; + uint256 previousBalanceFrom = balanceOf(_user); + if (previousBalanceFrom < amountBurned) { + amountBurned = previousBalanceFrom; + } + updateValueAtNow(totalSupplyHistory, curTotalSupply - amountBurned); + updateValueAtNow(balances[_user], previousBalanceFrom - amountBurned); + emit Burn(_user, amountBurned); + return true; + } + + //////////////// + // Internal helper functions to query and set a value in a snapshot array + //////////////// + + /// @dev `getValueAt` retrieves the number of reputation at a given block number + /// @param checkpoints The history of values being queried + /// @param _block The block number to retrieve the value at + /// @return The number of reputation being queried + function getValueAt( + Checkpoint[] storage checkpoints, + uint256 _block + ) internal view returns (uint256) { + if (checkpoints.length == 0) { + return 0; + } + + // Shortcut for the actual value + if (_block >= checkpoints[checkpoints.length - 1].fromBlock) { + return checkpoints[checkpoints.length - 1].value; + } + if (_block < checkpoints[0].fromBlock) { + return 0; + } + + // Binary search of the value in the array + uint256 min = 0; + uint256 max = checkpoints.length - 1; + while (max > min) { + uint256 mid = (max + min + 1) / 2; + if (checkpoints[mid].fromBlock <= _block) { + min = mid; + } else { + max = mid - 1; + } + } + return checkpoints[min].value; + } + + /// @dev `updateValueAtNow` used to update the `balances` map and the + /// `totalSupplyHistory` + /// @param checkpoints The history of data being updated + /// @param _value The new number of reputation + function updateValueAtNow( + Checkpoint[] storage checkpoints, + uint256 _value + ) internal { + require(uint128(_value) == _value); //check value is in the 128 bits bounderies + if ( + (checkpoints.length == 0) || + (checkpoints[checkpoints.length - 1].fromBlock < block.number) + ) { + Checkpoint storage newCheckPoint = checkpoints[checkpoints.length++]; + newCheckPoint.fromBlock = uint128(block.number); + newCheckPoint.value = uint128(_value); + } else { + Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length - 1]; + oldCheckPoint.value = uint128(_value); + } + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol + +pragma solidity ^0.5.0; + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +interface IERC20 { + function transfer(address to, uint256 value) external returns (bool); + + function approve(address spender, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + function totalSupply() external view returns (uint256); + + function balanceOf(address who) external view returns (uint256); + + function allowance( + address owner, + address spender + ) external view returns (uint256); + + event Transfer(address indexed from, address indexed to, uint256 value); + + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// File: openzeppelin-solidity/contracts/math/SafeMath.sol + +pragma solidity ^0.5.0; + +/** + * @title SafeMath + * @dev Unsigned math operations with safety checks that revert on error + */ +library SafeMath { + /** + * @dev Multiplies two unsigned integers, reverts on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b); + + return c; + } + + /** + * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a); + uint256 c = a - b; + + return c; + } + + /** + * @dev Adds two unsigned integers, reverts on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a); + + return c; + } + + /** + * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), + * reverts when dividing by zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b != 0); + return a % b; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol + +pragma solidity ^0.5.0; + +/** + * @title Standard ERC20 token + * + * @dev Implementation of the basic standard token. + * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md + * Originally based on code by FirstBlood: + * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol + * + * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for + * all accounts just by listening to said events. Note that this isn't required by the specification, and other + * compliant implementations may not do it. + */ +contract ERC20 is IERC20 { + using SafeMath for uint256; + + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowed; + + uint256 private _totalSupply; + + /** + * @dev Total number of tokens in existence + */ + function totalSupply() public view returns (uint256) { + return _totalSupply; + } + + /** + * @dev Gets the balance of the specified address. + * @param owner The address to query the balance of. + * @return An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address owner) public view returns (uint256) { + return _balances[owner]; + } + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + * @param owner address The address which owns the funds. + * @param spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance( + address owner, + address spender + ) public view returns (uint256) { + return _allowed[owner][spender]; + } + + /** + * @dev Transfer token for a specified address + * @param to The address to transfer to. + * @param value The amount to be transferred. + */ + function transfer(address to, uint256 value) public returns (bool) { + _transfer(msg.sender, to, value); + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * Beware that changing an allowance with this method brings the risk that someone may use both the old + * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * @param spender The address which will spend the funds. + * @param value The amount of tokens to be spent. + */ + function approve(address spender, uint256 value) public returns (bool) { + require(spender != address(0)); + + _allowed[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); + return true; + } + + /** + * @dev Transfer tokens from one address to another. + * Note that while this function emits an Approval event, this is not required as per the specification, + * and other compliant implementations may not emit the event. + * @param from address The address which you want to send tokens from + * @param to address The address which you want to transfer to + * @param value uint256 the amount of tokens to be transferred + */ + function transferFrom( + address from, + address to, + uint256 value + ) public returns (bool) { + _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); + _transfer(from, to, value); + emit Approval(from, msg.sender, _allowed[from][msg.sender]); + return true; + } + + /** + * @dev Increase the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed_[_spender] == 0. To increment + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * Emits an Approval event. + * @param spender The address which will spend the funds. + * @param addedValue The amount of tokens to increase the allowance by. + */ + function increaseAllowance( + address spender, + uint256 addedValue + ) public returns (bool) { + require(spender != address(0)); + + _allowed[msg.sender][spender] = _allowed[msg.sender][spender].add( + addedValue + ); + emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); + return true; + } + + /** + * @dev Decrease the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed_[_spender] == 0. To decrement + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * Emits an Approval event. + * @param spender The address which will spend the funds. + * @param subtractedValue The amount of tokens to decrease the allowance by. + */ + function decreaseAllowance( + address spender, + uint256 subtractedValue + ) public returns (bool) { + require(spender != address(0)); + + _allowed[msg.sender][spender] = _allowed[msg.sender][spender].sub( + subtractedValue + ); + emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); + return true; + } + + /** + * @dev Transfer token for a specified addresses + * @param from The address to transfer from. + * @param to The address to transfer to. + * @param value The amount to be transferred. + */ + function _transfer(address from, address to, uint256 value) internal { + require(to != address(0)); + + _balances[from] = _balances[from].sub(value); + _balances[to] = _balances[to].add(value); + emit Transfer(from, to, value); + } + + /** + * @dev Internal function that mints an amount of the token and assigns it to + * an account. This encapsulates the modification of balances such that the + * proper events are emitted. + * @param account The account that will receive the created tokens. + * @param value The amount that will be created. + */ + function _mint(address account, uint256 value) internal { + require(account != address(0)); + + _totalSupply = _totalSupply.add(value); + _balances[account] = _balances[account].add(value); + emit Transfer(address(0), account, value); + } + + /** + * @dev Internal function that burns an amount of the token of a given + * account. + * @param account The account whose tokens will be burnt. + * @param value The amount that will be burnt. + */ + function _burn(address account, uint256 value) internal { + require(account != address(0)); + + _totalSupply = _totalSupply.sub(value); + _balances[account] = _balances[account].sub(value); + emit Transfer(account, address(0), value); + } + + /** + * @dev Internal function that burns an amount of the token of a given + * account, deducting from the sender's allowance for said account. Uses the + * internal burn function. + * Emits an Approval event (reflecting the reduced allowance). + * @param account The account whose tokens will be burnt. + * @param value The amount that will be burnt. + */ + function _burnFrom(address account, uint256 value) internal { + _allowed[account][msg.sender] = _allowed[account][msg.sender].sub(value); + _burn(account, value); + emit Approval(account, msg.sender, _allowed[account][msg.sender]); + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20Burnable.sol + +pragma solidity ^0.5.0; + +/** + * @title Burnable Token + * @dev Token that can be irreversibly burned (destroyed). + */ +contract ERC20Burnable is ERC20 { + /** + * @dev Burns a specific amount of tokens. + * @param value The amount of token to be burned. + */ + function burn(uint256 value) public { + _burn(msg.sender, value); + } + + /** + * @dev Burns a specific amount of tokens from the target address and decrements allowance + * @param from address The address which you want to send tokens from + * @param value uint256 The amount of token to be burned + */ + function burnFrom(address from, uint256 value) public { + _burnFrom(from, value); + } +} + +// File: @daostack/arc/contracts/controller/DAOToken.sol + +pragma solidity ^0.5.4; + +/** + * @title DAOToken, base on zeppelin contract. + * @dev ERC20 compatible token. It is a mintable, burnable token. + */ + +contract DAOToken is ERC20, ERC20Burnable, Ownable { + string public name; + string public symbol; + // solhint-disable-next-line const-name-snakecase + uint8 public constant decimals = 18; + uint256 public cap; + + /** + * @dev Constructor + * @param _name - token name + * @param _symbol - token symbol + * @param _cap - token cap - 0 value means no cap + */ + constructor(string memory _name, string memory _symbol, uint256 _cap) public { + name = _name; + symbol = _symbol; + cap = _cap; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. + */ + function mint(address _to, uint256 _amount) public onlyOwner returns (bool) { + if (cap > 0) require(totalSupply().add(_amount) <= cap); + _mint(_to, _amount); + return true; + } +} + +// File: openzeppelin-solidity/contracts/utils/Address.sol + +pragma solidity ^0.5.0; + +/** + * Utility library of inline functions on addresses + */ +library Address { + /** + * Returns whether the target address is a contract + * @dev This function will return false if invoked during the constructor of a contract, + * as the code is not actually created until after the constructor finishes. + * @param account address of the account to check + * @return whether the target address is a contract + */ + function isContract(address account) internal view returns (bool) { + uint256 size; + // XXX Currently there is no better way to check if there is a contract in an address + // than to check the size of the code at that address. + // See https://ethereum.stackexchange.com/a/14016/36603 + // for more details about how this works. + // TODO Check this again before the Serenity release, because all addresses will be + // contracts then. + // solhint-disable-next-line no-inline-assembly + assembly { + size := extcodesize(account) + } + return size > 0; + } +} + +// File: @daostack/arc/contracts/libs/SafeERC20.sol + +/* + +SafeERC20 by daostack. +The code is based on a fix by SECBIT Team. + +USE WITH CAUTION & NO WARRANTY + +REFERENCE & RELATED READING +- https://github.com/ethereum/solidity/issues/4116 +- https://medium.com/@chris_77367/explaining-unexpected-reverts-starting-with-solidity-0-4-22-3ada6e82308c +- https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca +- https://gist.github.com/BrendanChou/88a2eeb80947ff00bcf58ffdafeaeb61 + +*/ +pragma solidity ^0.5.4; + +library SafeERC20 { + using Address for address; + + bytes4 private constant TRANSFER_SELECTOR = + bytes4(keccak256(bytes("transfer(address,uint256)"))); + bytes4 private constant TRANSFERFROM_SELECTOR = + bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); + bytes4 private constant APPROVE_SELECTOR = + bytes4(keccak256(bytes("approve(address,uint256)"))); + + function safeTransfer( + address _erc20Addr, + address _to, + uint256 _value + ) internal { + // Must be a contract addr first! + require(_erc20Addr.isContract()); + + (bool success, bytes memory returnValue) = // solhint-disable-next-line avoid-low-level-calls + _erc20Addr.call(abi.encodeWithSelector(TRANSFER_SELECTOR, _to, _value)); + // call return false when something wrong + require(success); + //check return value + require( + returnValue.length == 0 || + (returnValue.length == 32 && (returnValue[31] != 0)) + ); + } + + function safeTransferFrom( + address _erc20Addr, + address _from, + address _to, + uint256 _value + ) internal { + // Must be a contract addr first! + require(_erc20Addr.isContract()); + + (bool success, bytes memory returnValue) = // solhint-disable-next-line avoid-low-level-calls + _erc20Addr.call( + abi.encodeWithSelector(TRANSFERFROM_SELECTOR, _from, _to, _value) + ); + // call return false when something wrong + require(success); + //check return value + require( + returnValue.length == 0 || + (returnValue.length == 32 && (returnValue[31] != 0)) + ); + } + + function safeApprove( + address _erc20Addr, + address _spender, + uint256 _value + ) internal { + // Must be a contract addr first! + require(_erc20Addr.isContract()); + + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. + require( + (_value == 0) || + (IERC20(_erc20Addr).allowance(address(this), _spender) == 0) + ); + + (bool success, bytes memory returnValue) = // solhint-disable-next-line avoid-low-level-calls + _erc20Addr.call(abi.encodeWithSelector(APPROVE_SELECTOR, _spender, _value)); + // call return false when something wrong + require(success); + //check return value + require( + returnValue.length == 0 || + (returnValue.length == 32 && (returnValue[31] != 0)) + ); + } +} + +// File: @daostack/arc/contracts/controller/Avatar.sol + +pragma solidity ^0.5.4; + +/** + * @title An Avatar holds tokens, reputation and ether for a controller + */ +contract Avatar is Ownable { + using SafeERC20 for address; + + string public orgName; + DAOToken public nativeToken; + Reputation public nativeReputation; + + event GenericCall( + address indexed _contract, + bytes _data, + uint _value, + bool _success + ); + event SendEther(uint256 _amountInWei, address indexed _to); + event ExternalTokenTransfer( + address indexed _externalToken, + address indexed _to, + uint256 _value + ); + event ExternalTokenTransferFrom( + address indexed _externalToken, + address _from, + address _to, + uint256 _value + ); + event ExternalTokenApproval( + address indexed _externalToken, + address _spender, + uint256 _value + ); + event ReceiveEther(address indexed _sender, uint256 _value); + event MetaData(string _metaData); + + /** + * @dev the constructor takes organization name, native token and reputation system + and creates an avatar for a controller + */ + constructor( + string memory _orgName, + DAOToken _nativeToken, + Reputation _nativeReputation + ) public { + orgName = _orgName; + nativeToken = _nativeToken; + nativeReputation = _nativeReputation; + } + + /** + * @dev enables an avatar to receive ethers + */ + function() external payable { + emit ReceiveEther(msg.sender, msg.value); + } + + /** + * @dev perform a generic call to an arbitrary contract + * @param _contract the contract's address to call + * @param _data ABI-encoded contract call to call `_contract` address. + * @param _value value (ETH) to transfer with the transaction + * @return bool success or fail + * bytes - the return bytes of the called contract's function. + */ + function genericCall( + address _contract, + bytes memory _data, + uint256 _value + ) public onlyOwner returns (bool success, bytes memory returnValue) { + // solhint-disable-next-line avoid-call-value + (success, returnValue) = _contract.call.value(_value)(_data); + emit GenericCall(_contract, _data, _value, success); + } + + /** + * @dev send ethers from the avatar's wallet + * @param _amountInWei amount to send in Wei units + * @param _to send the ethers to this address + * @return bool which represents success + */ + function sendEther( + uint256 _amountInWei, + address payable _to + ) public onlyOwner returns (bool) { + _to.transfer(_amountInWei); + emit SendEther(_amountInWei, _to); + return true; + } + + /** + * @dev external token transfer + * @param _externalToken the token contract + * @param _to the destination address + * @param _value the amount of tokens to transfer + * @return bool which represents success + */ + function externalTokenTransfer( + IERC20 _externalToken, + address _to, + uint256 _value + ) public onlyOwner returns (bool) { + address(_externalToken).safeTransfer(_to, _value); + emit ExternalTokenTransfer(address(_externalToken), _to, _value); + return true; + } + + /** + * @dev external token transfer from a specific account + * @param _externalToken the token contract + * @param _from the account to spend token from + * @param _to the destination address + * @param _value the amount of tokens to transfer + * @return bool which represents success + */ + function externalTokenTransferFrom( + IERC20 _externalToken, + address _from, + address _to, + uint256 _value + ) public onlyOwner returns (bool) { + address(_externalToken).safeTransferFrom(_from, _to, _value); + emit ExternalTokenTransferFrom(address(_externalToken), _from, _to, _value); + return true; + } + + /** + * @dev externalTokenApproval approve the spender address to spend a specified amount of tokens + * on behalf of msg.sender. + * @param _externalToken the address of the Token Contract + * @param _spender address + * @param _value the amount of ether (in Wei) which the approval is referring to. + * @return bool which represents a success + */ + function externalTokenApproval( + IERC20 _externalToken, + address _spender, + uint256 _value + ) public onlyOwner returns (bool) { + address(_externalToken).safeApprove(_spender, _value); + emit ExternalTokenApproval(address(_externalToken), _spender, _value); + return true; + } + + /** + * @dev metaData emits an event with a string, should contain the hash of some meta data. + * @param _metaData a string representing a hash of the meta data + * @return bool which represents a success + */ + function metaData(string memory _metaData) public onlyOwner returns (bool) { + emit MetaData(_metaData); + return true; + } +} + +// File: @daostack/arc/contracts/globalConstraints/GlobalConstraintInterface.sol + +pragma solidity ^0.5.4; + +contract GlobalConstraintInterface { + enum CallPhase { + Pre, + Post, + PreAndPost + } + + function pre( + address _scheme, + bytes32 _params, + bytes32 _method + ) public returns (bool); + + function post( + address _scheme, + bytes32 _params, + bytes32 _method + ) public returns (bool); + + /** + * @dev when return if this globalConstraints is pre, post or both. + * @return CallPhase enum indication Pre, Post or PreAndPost. + */ + function when() public returns (CallPhase); +} + +// File: @daostack/arc/contracts/controller/ControllerInterface.sol + +pragma solidity ^0.5.4; + +/** + * @title Controller contract + * @dev A controller controls the organizations tokens ,reputation and avatar. + * It is subject to a set of schemes and constraints that determine its behavior. + * Each scheme has it own parameters and operation permissions. + */ +interface ControllerInterface { + /** + * @dev Mint `_amount` of reputation that are assigned to `_to` . + * @param _amount amount of reputation to mint + * @param _to beneficiary address + * @return bool which represents a success + */ + function mintReputation( + uint256 _amount, + address _to, + address _avatar + ) external returns (bool); + + /** + * @dev Burns `_amount` of reputation from `_from` + * @param _amount amount of reputation to burn + * @param _from The address that will lose the reputation + * @return bool which represents a success + */ + function burnReputation( + uint256 _amount, + address _from, + address _avatar + ) external returns (bool); + + /** + * @dev mint tokens . + * @param _amount amount of token to mint + * @param _beneficiary beneficiary address + * @param _avatar address + * @return bool which represents a success + */ + function mintTokens( + uint256 _amount, + address _beneficiary, + address _avatar + ) external returns (bool); + + /** + * @dev register or update a scheme + * @param _scheme the address of the scheme + * @param _paramsHash a hashed configuration of the usage of the scheme + * @param _permissions the permissions the new scheme will have + * @param _avatar address + * @return bool which represents a success + */ + function registerScheme( + address _scheme, + bytes32 _paramsHash, + bytes4 _permissions, + address _avatar + ) external returns (bool); + + /** + * @dev unregister a scheme + * @param _avatar address + * @param _scheme the address of the scheme + * @return bool which represents a success + */ + function unregisterScheme( + address _scheme, + address _avatar + ) external returns (bool); + + /** + * @dev unregister the caller's scheme + * @param _avatar address + * @return bool which represents a success + */ + function unregisterSelf(address _avatar) external returns (bool); + + /** + * @dev add or update Global Constraint + * @param _globalConstraint the address of the global constraint to be added. + * @param _params the constraint parameters hash. + * @param _avatar the avatar of the organization + * @return bool which represents a success + */ + function addGlobalConstraint( + address _globalConstraint, + bytes32 _params, + address _avatar + ) external returns (bool); + + /** + * @dev remove Global Constraint + * @param _globalConstraint the address of the global constraint to be remove. + * @param _avatar the organization avatar. + * @return bool which represents a success + */ + function removeGlobalConstraint( + address _globalConstraint, + address _avatar + ) external returns (bool); + + /** + * @dev upgrade the Controller + * The function will trigger an event 'UpgradeController'. + * @param _newController the address of the new controller. + * @param _avatar address + * @return bool which represents a success + */ + function upgradeController( + address _newController, + Avatar _avatar + ) external returns (bool); + + /** + * @dev perform a generic call to an arbitrary contract + * @param _contract the contract's address to call + * @param _data ABI-encoded contract call to call `_contract` address. + * @param _avatar the controller's avatar address + * @param _value value (ETH) to transfer with the transaction + * @return bool -success + * bytes - the return value of the called _contract's function. + */ + function genericCall( + address _contract, + bytes calldata _data, + Avatar _avatar, + uint256 _value + ) external returns (bool, bytes memory); + + /** + * @dev send some ether + * @param _amountInWei the amount of ether (in Wei) to send + * @param _to address of the beneficiary + * @param _avatar address + * @return bool which represents a success + */ + function sendEther( + uint256 _amountInWei, + address payable _to, + Avatar _avatar + ) external returns (bool); + + /** + * @dev send some amount of arbitrary ERC20 Tokens + * @param _externalToken the address of the Token Contract + * @param _to address of the beneficiary + * @param _value the amount of ether (in Wei) to send + * @param _avatar address + * @return bool which represents a success + */ + function externalTokenTransfer( + IERC20 _externalToken, + address _to, + uint256 _value, + Avatar _avatar + ) external returns (bool); + + /** + * @dev transfer token "from" address "to" address + * One must to approve the amount of tokens which can be spend from the + * "from" account.This can be done using externalTokenApprove. + * @param _externalToken the address of the Token Contract + * @param _from address of the account to send from + * @param _to address of the beneficiary + * @param _value the amount of ether (in Wei) to send + * @param _avatar address + * @return bool which represents a success + */ + function externalTokenTransferFrom( + IERC20 _externalToken, + address _from, + address _to, + uint256 _value, + Avatar _avatar + ) external returns (bool); + + /** + * @dev externalTokenApproval approve the spender address to spend a specified amount of tokens + * on behalf of msg.sender. + * @param _externalToken the address of the Token Contract + * @param _spender address + * @param _value the amount of ether (in Wei) which the approval is referring to. + * @return bool which represents a success + */ + function externalTokenApproval( + IERC20 _externalToken, + address _spender, + uint256 _value, + Avatar _avatar + ) external returns (bool); + + /** + * @dev metaData emits an event with a string, should contain the hash of some meta data. + * @param _metaData a string representing a hash of the meta data + * @param _avatar Avatar + * @return bool which represents a success + */ + function metaData( + string calldata _metaData, + Avatar _avatar + ) external returns (bool); + + /** + * @dev getNativeReputation + * @param _avatar the organization avatar. + * @return organization native reputation + */ + function getNativeReputation(address _avatar) external view returns (address); + + function isSchemeRegistered( + address _scheme, + address _avatar + ) external view returns (bool); + + function getSchemeParameters( + address _scheme, + address _avatar + ) external view returns (bytes32); + + function getGlobalConstraintParameters( + address _globalConstraint, + address _avatar + ) external view returns (bytes32); + + function getSchemePermissions( + address _scheme, + address _avatar + ) external view returns (bytes4); + + /** + * @dev globalConstraintsCount return the global constraint pre and post count + * @return uint256 globalConstraintsPre count. + * @return uint256 globalConstraintsPost count. + */ + function globalConstraintsCount( + address _avatar + ) external view returns (uint, uint); + + function isGlobalConstraintRegistered( + address _globalConstraint, + address _avatar + ) external view returns (bool); +} + +// File: @daostack/arc/contracts/controller/Controller.sol + +pragma solidity ^0.5.4; + +/** + * @title Controller contract + * @dev A controller controls the organizations tokens, reputation and avatar. + * It is subject to a set of schemes and constraints that determine its behavior. + * Each scheme has it own parameters and operation permissions. + */ +contract Controller is ControllerInterface { + struct Scheme { + bytes32 paramsHash; // a hash "configuration" of the scheme + bytes4 permissions; // A bitwise flags of permissions, + // All 0: Not registered, + // 1st bit: Flag if the scheme is registered, + // 2nd bit: Scheme can register other schemes + // 3rd bit: Scheme can add/remove global constraints + // 4th bit: Scheme can upgrade the controller + // 5th bit: Scheme can call genericCall on behalf of + // the organization avatar + } + + struct GlobalConstraint { + address gcAddress; + bytes32 params; + } + + struct GlobalConstraintRegister { + bool isRegistered; //is registered + uint256 index; //index at globalConstraints + } + + mapping(address => Scheme) public schemes; + + Avatar public avatar; + DAOToken public nativeToken; + Reputation public nativeReputation; + // newController will point to the new controller after the present controller is upgraded + address public newController; + // globalConstraintsPre that determine pre conditions for all actions on the controller + + GlobalConstraint[] public globalConstraintsPre; + // globalConstraintsPost that determine post conditions for all actions on the controller + GlobalConstraint[] public globalConstraintsPost; + // globalConstraintsRegisterPre indicate if a globalConstraints is registered as a pre global constraint + mapping(address => GlobalConstraintRegister) + public globalConstraintsRegisterPre; + // globalConstraintsRegisterPost indicate if a globalConstraints is registered as a post global constraint + mapping(address => GlobalConstraintRegister) + public globalConstraintsRegisterPost; + + event MintReputation( + address indexed _sender, + address indexed _to, + uint256 _amount + ); + event BurnReputation( + address indexed _sender, + address indexed _from, + uint256 _amount + ); + event MintTokens( + address indexed _sender, + address indexed _beneficiary, + uint256 _amount + ); + event RegisterScheme(address indexed _sender, address indexed _scheme); + event UnregisterScheme(address indexed _sender, address indexed _scheme); + event UpgradeController( + address indexed _oldController, + address _newController + ); + + event AddGlobalConstraint( + address indexed _globalConstraint, + bytes32 _params, + GlobalConstraintInterface.CallPhase _when + ); + + event RemoveGlobalConstraint( + address indexed _globalConstraint, + uint256 _index, + bool _isPre + ); + + constructor(Avatar _avatar) public { + avatar = _avatar; + nativeToken = avatar.nativeToken(); + nativeReputation = avatar.nativeReputation(); + schemes[msg.sender] = Scheme({ + paramsHash: bytes32(0), + permissions: bytes4(0x0000001F) + }); + } + + // Do not allow mistaken calls: + // solhint-disable-next-line payable-fallback + function() external { + revert(); + } + + // Modifiers: + modifier onlyRegisteredScheme() { + require( + schemes[msg.sender].permissions & bytes4(0x00000001) == bytes4(0x00000001) + ); + _; + } + + modifier onlyRegisteringSchemes() { + require( + schemes[msg.sender].permissions & bytes4(0x00000002) == bytes4(0x00000002) + ); + _; + } + + modifier onlyGlobalConstraintsScheme() { + require( + schemes[msg.sender].permissions & bytes4(0x00000004) == bytes4(0x00000004) + ); + _; + } + + modifier onlyUpgradingScheme() { + require( + schemes[msg.sender].permissions & bytes4(0x00000008) == bytes4(0x00000008) + ); + _; + } + + modifier onlyGenericCallScheme() { + require( + schemes[msg.sender].permissions & bytes4(0x00000010) == bytes4(0x00000010) + ); + _; + } + + modifier onlyMetaDataScheme() { + require( + schemes[msg.sender].permissions & bytes4(0x00000010) == bytes4(0x00000010) + ); + _; + } + + modifier onlySubjectToConstraint(bytes32 func) { + uint256 idx; + for (idx = 0; idx < globalConstraintsPre.length; idx++) { + require( + (GlobalConstraintInterface(globalConstraintsPre[idx].gcAddress)).pre( + msg.sender, + globalConstraintsPre[idx].params, + func + ) + ); + } + _; + for (idx = 0; idx < globalConstraintsPost.length; idx++) { + require( + (GlobalConstraintInterface(globalConstraintsPost[idx].gcAddress)).post( + msg.sender, + globalConstraintsPost[idx].params, + func + ) + ); + } + } + + modifier isAvatarValid(address _avatar) { + require(_avatar == address(avatar)); + _; + } + + /** + * @dev Mint `_amount` of reputation that are assigned to `_to` . + * @param _amount amount of reputation to mint + * @param _to beneficiary address + * @return bool which represents a success + */ + function mintReputation( + uint256 _amount, + address _to, + address _avatar + ) + external + onlyRegisteredScheme + onlySubjectToConstraint("mintReputation") + isAvatarValid(_avatar) + returns (bool) + { + emit MintReputation(msg.sender, _to, _amount); + return nativeReputation.mint(_to, _amount); + } + + /** + * @dev Burns `_amount` of reputation from `_from` + * @param _amount amount of reputation to burn + * @param _from The address that will lose the reputation + * @return bool which represents a success + */ + function burnReputation( + uint256 _amount, + address _from, + address _avatar + ) + external + onlyRegisteredScheme + onlySubjectToConstraint("burnReputation") + isAvatarValid(_avatar) + returns (bool) + { + emit BurnReputation(msg.sender, _from, _amount); + return nativeReputation.burn(_from, _amount); + } + + /** + * @dev mint tokens . + * @param _amount amount of token to mint + * @param _beneficiary beneficiary address + * @return bool which represents a success + */ + function mintTokens( + uint256 _amount, + address _beneficiary, + address _avatar + ) + external + onlyRegisteredScheme + onlySubjectToConstraint("mintTokens") + isAvatarValid(_avatar) + returns (bool) + { + emit MintTokens(msg.sender, _beneficiary, _amount); + return nativeToken.mint(_beneficiary, _amount); + } + + /** + * @dev register a scheme + * @param _scheme the address of the scheme + * @param _paramsHash a hashed configuration of the usage of the scheme + * @param _permissions the permissions the new scheme will have + * @return bool which represents a success + */ + function registerScheme( + address _scheme, + bytes32 _paramsHash, + bytes4 _permissions, + address _avatar + ) + external + onlyRegisteringSchemes + onlySubjectToConstraint("registerScheme") + isAvatarValid(_avatar) + returns (bool) + { + Scheme memory scheme = schemes[_scheme]; + + // Check scheme has at least the permissions it is changing, and at least the current permissions: + // Implementation is a bit messy. One must recall logic-circuits ^^ + + // produces non-zero if sender does not have all of the perms that are changing between old and new + require( + bytes4(0x0000001f) & + (_permissions ^ scheme.permissions) & + (~schemes[msg.sender].permissions) == + bytes4(0) + ); + + // produces non-zero if sender does not have all of the perms in the old scheme + require( + bytes4(0x0000001f) & + (scheme.permissions & (~schemes[msg.sender].permissions)) == + bytes4(0) + ); + + // Add or change the scheme: + schemes[_scheme].paramsHash = _paramsHash; + schemes[_scheme].permissions = _permissions | bytes4(0x00000001); + emit RegisterScheme(msg.sender, _scheme); + return true; + } + + /** + * @dev unregister a scheme + * @param _scheme the address of the scheme + * @return bool which represents a success + */ + function unregisterScheme( + address _scheme, + address _avatar + ) + external + onlyRegisteringSchemes + onlySubjectToConstraint("unregisterScheme") + isAvatarValid(_avatar) + returns (bool) + { + //check if the scheme is registered + if (_isSchemeRegistered(_scheme) == false) { + return false; + } + // Check the unregistering scheme has enough permissions: + require( + bytes4(0x0000001f) & + (schemes[_scheme].permissions & (~schemes[msg.sender].permissions)) == + bytes4(0) + ); + + // Unregister: + emit UnregisterScheme(msg.sender, _scheme); + delete schemes[_scheme]; + return true; + } + + /** + * @dev unregister the caller's scheme + * @return bool which represents a success + */ + function unregisterSelf( + address _avatar + ) external isAvatarValid(_avatar) returns (bool) { + if (_isSchemeRegistered(msg.sender) == false) { + return false; + } + delete schemes[msg.sender]; + emit UnregisterScheme(msg.sender, msg.sender); + return true; + } + + /** + * @dev add or update Global Constraint + * @param _globalConstraint the address of the global constraint to be added. + * @param _params the constraint parameters hash. + * @return bool which represents a success + */ + function addGlobalConstraint( + address _globalConstraint, + bytes32 _params, + address _avatar + ) external onlyGlobalConstraintsScheme isAvatarValid(_avatar) returns (bool) { + GlobalConstraintInterface.CallPhase when = GlobalConstraintInterface( + _globalConstraint + ).when(); + if ( + (when == GlobalConstraintInterface.CallPhase.Pre) || + (when == GlobalConstraintInterface.CallPhase.PreAndPost) + ) { + if (!globalConstraintsRegisterPre[_globalConstraint].isRegistered) { + globalConstraintsPre.push(GlobalConstraint(_globalConstraint, _params)); + globalConstraintsRegisterPre[ + _globalConstraint + ] = GlobalConstraintRegister(true, globalConstraintsPre.length - 1); + } else { + globalConstraintsPre[ + globalConstraintsRegisterPre[_globalConstraint].index + ].params = _params; + } + } + if ( + (when == GlobalConstraintInterface.CallPhase.Post) || + (when == GlobalConstraintInterface.CallPhase.PreAndPost) + ) { + if (!globalConstraintsRegisterPost[_globalConstraint].isRegistered) { + globalConstraintsPost.push( + GlobalConstraint(_globalConstraint, _params) + ); + globalConstraintsRegisterPost[ + _globalConstraint + ] = GlobalConstraintRegister(true, globalConstraintsPost.length - 1); + } else { + globalConstraintsPost[ + globalConstraintsRegisterPost[_globalConstraint].index + ].params = _params; + } + } + emit AddGlobalConstraint(_globalConstraint, _params, when); + return true; + } + + /** + * @dev remove Global Constraint + * @param _globalConstraint the address of the global constraint to be remove. + * @return bool which represents a success + */ + // solhint-disable-next-line code-complexity + function removeGlobalConstraint( + address _globalConstraint, + address _avatar + ) external onlyGlobalConstraintsScheme isAvatarValid(_avatar) returns (bool) { + GlobalConstraintRegister memory globalConstraintRegister; + GlobalConstraint memory globalConstraint; + GlobalConstraintInterface.CallPhase when = GlobalConstraintInterface( + _globalConstraint + ).when(); + bool retVal = false; + + if ( + (when == GlobalConstraintInterface.CallPhase.Pre) || + (when == GlobalConstraintInterface.CallPhase.PreAndPost) + ) { + globalConstraintRegister = globalConstraintsRegisterPre[ + _globalConstraint + ]; + if (globalConstraintRegister.isRegistered) { + if (globalConstraintRegister.index < globalConstraintsPre.length - 1) { + globalConstraint = globalConstraintsPre[ + globalConstraintsPre.length - 1 + ]; + globalConstraintsPre[ + globalConstraintRegister.index + ] = globalConstraint; + globalConstraintsRegisterPre[globalConstraint.gcAddress] + .index = globalConstraintRegister.index; + } + globalConstraintsPre.length--; + delete globalConstraintsRegisterPre[_globalConstraint]; + retVal = true; + } + } + if ( + (when == GlobalConstraintInterface.CallPhase.Post) || + (when == GlobalConstraintInterface.CallPhase.PreAndPost) + ) { + globalConstraintRegister = globalConstraintsRegisterPost[ + _globalConstraint + ]; + if (globalConstraintRegister.isRegistered) { + if (globalConstraintRegister.index < globalConstraintsPost.length - 1) { + globalConstraint = globalConstraintsPost[ + globalConstraintsPost.length - 1 + ]; + globalConstraintsPost[ + globalConstraintRegister.index + ] = globalConstraint; + globalConstraintsRegisterPost[globalConstraint.gcAddress] + .index = globalConstraintRegister.index; + } + globalConstraintsPost.length--; + delete globalConstraintsRegisterPost[_globalConstraint]; + retVal = true; + } + } + if (retVal) { + emit RemoveGlobalConstraint( + _globalConstraint, + globalConstraintRegister.index, + when == GlobalConstraintInterface.CallPhase.Pre + ); + } + return retVal; + } + + /** + * @dev upgrade the Controller + * The function will trigger an event 'UpgradeController'. + * @param _newController the address of the new controller. + * @return bool which represents a success + */ + function upgradeController( + address _newController, + Avatar _avatar + ) + external + onlyUpgradingScheme + isAvatarValid(address(_avatar)) + returns (bool) + { + require(newController == address(0)); // so the upgrade could be done once for a contract. + require(_newController != address(0)); + newController = _newController; + avatar.transferOwnership(_newController); + require(avatar.owner() == _newController); + if (nativeToken.owner() == address(this)) { + nativeToken.transferOwnership(_newController); + require(nativeToken.owner() == _newController); + } + if (nativeReputation.owner() == address(this)) { + nativeReputation.transferOwnership(_newController); + require(nativeReputation.owner() == _newController); + } + emit UpgradeController(address(this), newController); + return true; + } + + /** + * @dev perform a generic call to an arbitrary contract + * @param _contract the contract's address to call + * @param _data ABI-encoded contract call to call `_contract` address. + * @param _avatar the controller's avatar address + * @param _value value (ETH) to transfer with the transaction + * @return bool -success + * bytes - the return value of the called _contract's function. + */ + function genericCall( + address _contract, + bytes calldata _data, + Avatar _avatar, + uint256 _value + ) + external + onlyGenericCallScheme + onlySubjectToConstraint("genericCall") + isAvatarValid(address(_avatar)) + returns (bool, bytes memory) + { + return avatar.genericCall(_contract, _data, _value); + } + + /** + * @dev send some ether + * @param _amountInWei the amount of ether (in Wei) to send + * @param _to address of the beneficiary + * @return bool which represents a success + */ + function sendEther( + uint256 _amountInWei, + address payable _to, + Avatar _avatar + ) + external + onlyRegisteredScheme + onlySubjectToConstraint("sendEther") + isAvatarValid(address(_avatar)) + returns (bool) + { + return avatar.sendEther(_amountInWei, _to); + } + + /** + * @dev send some amount of arbitrary ERC20 Tokens + * @param _externalToken the address of the Token Contract + * @param _to address of the beneficiary + * @param _value the amount of ether (in Wei) to send + * @return bool which represents a success + */ + function externalTokenTransfer( + IERC20 _externalToken, + address _to, + uint256 _value, + Avatar _avatar + ) + external + onlyRegisteredScheme + onlySubjectToConstraint("externalTokenTransfer") + isAvatarValid(address(_avatar)) + returns (bool) + { + return avatar.externalTokenTransfer(_externalToken, _to, _value); + } + + /** + * @dev transfer token "from" address "to" address + * One must to approve the amount of tokens which can be spend from the + * "from" account.This can be done using externalTokenApprove. + * @param _externalToken the address of the Token Contract + * @param _from address of the account to send from + * @param _to address of the beneficiary + * @param _value the amount of ether (in Wei) to send + * @return bool which represents a success + */ + function externalTokenTransferFrom( + IERC20 _externalToken, + address _from, + address _to, + uint256 _value, + Avatar _avatar + ) + external + onlyRegisteredScheme + onlySubjectToConstraint("externalTokenTransferFrom") + isAvatarValid(address(_avatar)) + returns (bool) + { + return avatar.externalTokenTransferFrom(_externalToken, _from, _to, _value); + } + + /** + * @dev externalTokenApproval approve the spender address to spend a specified amount of tokens + * on behalf of msg.sender. + * @param _externalToken the address of the Token Contract + * @param _spender address + * @param _value the amount of ether (in Wei) which the approval is referring to. + * @return bool which represents a success + */ + function externalTokenApproval( + IERC20 _externalToken, + address _spender, + uint256 _value, + Avatar _avatar + ) + external + onlyRegisteredScheme + onlySubjectToConstraint("externalTokenIncreaseApproval") + isAvatarValid(address(_avatar)) + returns (bool) + { + return avatar.externalTokenApproval(_externalToken, _spender, _value); + } + + /** + * @dev metaData emits an event with a string, should contain the hash of some meta data. + * @param _metaData a string representing a hash of the meta data + * @param _avatar Avatar + * @return bool which represents a success + */ + function metaData( + string calldata _metaData, + Avatar _avatar + ) external onlyMetaDataScheme isAvatarValid(address(_avatar)) returns (bool) { + return avatar.metaData(_metaData); + } + + /** + * @dev getNativeReputation + * @param _avatar the organization avatar. + * @return organization native reputation + */ + function getNativeReputation( + address _avatar + ) external view isAvatarValid(_avatar) returns (address) { + return address(nativeReputation); + } + + function isSchemeRegistered( + address _scheme, + address _avatar + ) external view isAvatarValid(_avatar) returns (bool) { + return _isSchemeRegistered(_scheme); + } + + function getSchemeParameters( + address _scheme, + address _avatar + ) external view isAvatarValid(_avatar) returns (bytes32) { + return schemes[_scheme].paramsHash; + } + + function getSchemePermissions( + address _scheme, + address _avatar + ) external view isAvatarValid(_avatar) returns (bytes4) { + return schemes[_scheme].permissions; + } + + function getGlobalConstraintParameters( + address _globalConstraint, + address + ) external view returns (bytes32) { + GlobalConstraintRegister memory register = globalConstraintsRegisterPre[ + _globalConstraint + ]; + + if (register.isRegistered) { + return globalConstraintsPre[register.index].params; + } + + register = globalConstraintsRegisterPost[_globalConstraint]; + + if (register.isRegistered) { + return globalConstraintsPost[register.index].params; + } + } + + /** + * @dev globalConstraintsCount return the global constraint pre and post count + * @return uint256 globalConstraintsPre count. + * @return uint256 globalConstraintsPost count. + */ + function globalConstraintsCount( + address _avatar + ) external view isAvatarValid(_avatar) returns (uint, uint) { + return (globalConstraintsPre.length, globalConstraintsPost.length); + } + + function isGlobalConstraintRegistered( + address _globalConstraint, + address _avatar + ) external view isAvatarValid(_avatar) returns (bool) { + return (globalConstraintsRegisterPre[_globalConstraint].isRegistered || + globalConstraintsRegisterPost[_globalConstraint].isRegistered); + } + + function _isSchemeRegistered(address _scheme) private view returns (bool) { + return (schemes[_scheme].permissions & bytes4(0x00000001) != bytes4(0)); + } +} diff --git a/scripts/multichain-deploy/flattened/FeeFormula.sol b/scripts/multichain-deploy/flattened/FeeFormula.sol new file mode 100644 index 00000000..503d4fd8 --- /dev/null +++ b/scripts/multichain-deploy/flattened/FeeFormula.sol @@ -0,0 +1,1290 @@ +pragma solidity ^0.5.4; + +/** + * @title Ownable + * @dev The Ownable contract has an owner address, and provides basic authorization control + * functions, this simplifies the implementation of "user permissions". + */ +contract Ownable { + address private _owner; + + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + /** + * @dev The Ownable constructor sets the original `owner` of the contract to the sender + * account. + */ + constructor() internal { + _owner = msg.sender; + emit OwnershipTransferred(address(0), _owner); + } + + /** + * @return the address of the owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(isOwner()); + _; + } + + /** + * @return true if `msg.sender` is the owner of the contract. + */ + function isOwner() public view returns (bool) { + return msg.sender == _owner; + } + + /** + * @dev Allows the current owner to relinquish control of the contract. + * @notice Renouncing to ownership will leave the contract without an owner. + * It will not be possible to call the functions with the `onlyOwner` + * modifier anymore. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) public onlyOwner { + _transferOwnership(newOwner); + } + + /** + * @dev Transfers control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0)); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} + +/* @dev abstract contract for ensuring that schemes have been registered properly + * Allows setting zero Avatar in situations where the Avatar hasn't been created yet + */ +contract SchemeGuard is Ownable { + Avatar avatar; + ControllerInterface internal controller = ControllerInterface(0); + + /** @dev Constructor. only sets controller if given avatar is not null. + * @param _avatar The avatar of the DAO. + */ + constructor(Avatar _avatar) public { + avatar = _avatar; + + if (avatar != Avatar(0)) { + controller = ControllerInterface(avatar.owner()); + } + } + + /** @dev modifier to check if caller is avatar + */ + modifier onlyAvatar() { + require(address(avatar) == msg.sender, "only Avatar can call this method"); + _; + } + + /** @dev modifier to check if scheme is registered + */ + modifier onlyRegistered() { + require(isRegistered(), "Scheme is not registered"); + _; + } + + /** @dev modifier to check if scheme is not registered + */ + modifier onlyNotRegistered() { + require(!isRegistered(), "Scheme is registered"); + _; + } + + /** @dev modifier to check if call is a scheme that is registered + */ + modifier onlyRegisteredCaller() { + require(isRegistered(msg.sender), "Calling scheme is not registered"); + _; + } + + /** @dev Function to set a new avatar and controller for scheme + * can only be done by owner of scheme + */ + function setAvatar(Avatar _avatar) public onlyOwner { + avatar = _avatar; + controller = ControllerInterface(avatar.owner()); + } + + /** @dev function to see if an avatar has been set and if this scheme is registered + * @return true if scheme is registered + */ + function isRegistered() public view returns (bool) { + return isRegistered(address(this)); + } + + /** @dev function to see if an avatar has been set and if this scheme is registered + * @return true if scheme is registered + */ + function isRegistered(address scheme) public view returns (bool) { + require(avatar != Avatar(0), "Avatar is not set"); + + if (!(controller.isSchemeRegistered(scheme, address(avatar)))) { + return false; + } + return true; + } +} + +/** + * @title Fee formula abstract contract + */ +contract AbstractFees is SchemeGuard { + constructor() public SchemeGuard(Avatar(0)) {} + + function getTxFees( + uint256 _value, + address _sender, + address _recipient + ) public view returns (uint256, bool); +} + +/** + * @title Fee formula contract + * contract that provides a function to calculate + * fees as a percentage of any given value + */ +contract FeeFormula is AbstractFees { + using SafeMath for uint256; + + uint256 public percentage; + bool public constant senderPays = false; + + /** + * @dev Constructor. Requires the given percentage parameter + * to be less than 100. + * @param _percentage the percentage to calculate fees of + */ + constructor(uint256 _percentage) public { + require(_percentage < 100, "Percentage should be <100"); + percentage = _percentage; + } + + /** @dev calculates the fee of given value. + * @param _value the value of the transaction to calculate fees from + * @param _sender address sending. + * @param _recipient address receiving. + * @return the transactional fee for given value + */ + function getTxFees( + uint256 _value, + address _sender, + address _recipient + ) public view returns (uint256, bool) { + return (_value.mul(percentage).div(100), senderPays); + } +} + +/** + * Utility library of inline functions on addresses + */ +library Address { + /** + * Returns whether the target address is a contract + * @dev This function will return false if invoked during the constructor of a contract, + * as the code is not actually created until after the constructor finishes. + * @param account address of the account to check + * @return whether the target address is a contract + */ + function isContract(address account) internal view returns (bool) { + uint256 size; + // XXX Currently there is no better way to check if there is a contract in an address + // than to check the size of the code at that address. + // See https://ethereum.stackexchange.com/a/14016/36603 + // for more details about how this works. + // TODO Check this again before the Serenity release, because all addresses will be + // contracts then. + // solhint-disable-next-line no-inline-assembly + assembly { + size := extcodesize(account) + } + return size > 0; + } +} + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +interface IERC20 { + function transfer(address to, uint256 value) external returns (bool); + + function approve(address spender, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + function totalSupply() external view returns (uint256); + + function balanceOf(address who) external view returns (uint256); + + function allowance( + address owner, + address spender + ) external view returns (uint256); + + event Transfer(address indexed from, address indexed to, uint256 value); + + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +/** + * @title Standard ERC20 token + * + * @dev Implementation of the basic standard token. + * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md + * Originally based on code by FirstBlood: + * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol + * + * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for + * all accounts just by listening to said events. Note that this isn't required by the specification, and other + * compliant implementations may not do it. + */ +contract ERC20 is IERC20 { + using SafeMath for uint256; + + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowed; + + uint256 private _totalSupply; + + /** + * @dev Total number of tokens in existence + */ + function totalSupply() public view returns (uint256) { + return _totalSupply; + } + + /** + * @dev Gets the balance of the specified address. + * @param owner The address to query the balance of. + * @return An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address owner) public view returns (uint256) { + return _balances[owner]; + } + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + * @param owner address The address which owns the funds. + * @param spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance( + address owner, + address spender + ) public view returns (uint256) { + return _allowed[owner][spender]; + } + + /** + * @dev Transfer token for a specified address + * @param to The address to transfer to. + * @param value The amount to be transferred. + */ + function transfer(address to, uint256 value) public returns (bool) { + _transfer(msg.sender, to, value); + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * Beware that changing an allowance with this method brings the risk that someone may use both the old + * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * @param spender The address which will spend the funds. + * @param value The amount of tokens to be spent. + */ + function approve(address spender, uint256 value) public returns (bool) { + require(spender != address(0)); + + _allowed[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); + return true; + } + + /** + * @dev Transfer tokens from one address to another. + * Note that while this function emits an Approval event, this is not required as per the specification, + * and other compliant implementations may not emit the event. + * @param from address The address which you want to send tokens from + * @param to address The address which you want to transfer to + * @param value uint256 the amount of tokens to be transferred + */ + function transferFrom( + address from, + address to, + uint256 value + ) public returns (bool) { + _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); + _transfer(from, to, value); + emit Approval(from, msg.sender, _allowed[from][msg.sender]); + return true; + } + + /** + * @dev Increase the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed_[_spender] == 0. To increment + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * Emits an Approval event. + * @param spender The address which will spend the funds. + * @param addedValue The amount of tokens to increase the allowance by. + */ + function increaseAllowance( + address spender, + uint256 addedValue + ) public returns (bool) { + require(spender != address(0)); + + _allowed[msg.sender][spender] = _allowed[msg.sender][spender].add( + addedValue + ); + emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); + return true; + } + + /** + * @dev Decrease the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed_[_spender] == 0. To decrement + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * Emits an Approval event. + * @param spender The address which will spend the funds. + * @param subtractedValue The amount of tokens to decrease the allowance by. + */ + function decreaseAllowance( + address spender, + uint256 subtractedValue + ) public returns (bool) { + require(spender != address(0)); + + _allowed[msg.sender][spender] = _allowed[msg.sender][spender].sub( + subtractedValue + ); + emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); + return true; + } + + /** + * @dev Transfer token for a specified addresses + * @param from The address to transfer from. + * @param to The address to transfer to. + * @param value The amount to be transferred. + */ + function _transfer(address from, address to, uint256 value) internal { + require(to != address(0)); + + _balances[from] = _balances[from].sub(value); + _balances[to] = _balances[to].add(value); + emit Transfer(from, to, value); + } + + /** + * @dev Internal function that mints an amount of the token and assigns it to + * an account. This encapsulates the modification of balances such that the + * proper events are emitted. + * @param account The account that will receive the created tokens. + * @param value The amount that will be created. + */ + function _mint(address account, uint256 value) internal { + require(account != address(0)); + + _totalSupply = _totalSupply.add(value); + _balances[account] = _balances[account].add(value); + emit Transfer(address(0), account, value); + } + + /** + * @dev Internal function that burns an amount of the token of a given + * account. + * @param account The account whose tokens will be burnt. + * @param value The amount that will be burnt. + */ + function _burn(address account, uint256 value) internal { + require(account != address(0)); + + _totalSupply = _totalSupply.sub(value); + _balances[account] = _balances[account].sub(value); + emit Transfer(account, address(0), value); + } + + /** + * @dev Internal function that burns an amount of the token of a given + * account, deducting from the sender's allowance for said account. Uses the + * internal burn function. + * Emits an Approval event (reflecting the reduced allowance). + * @param account The account whose tokens will be burnt. + * @param value The amount that will be burnt. + */ + function _burnFrom(address account, uint256 value) internal { + _allowed[account][msg.sender] = _allowed[account][msg.sender].sub(value); + _burn(account, value); + emit Approval(account, msg.sender, _allowed[account][msg.sender]); + } +} + +/** + * @title Burnable Token + * @dev Token that can be irreversibly burned (destroyed). + */ +contract ERC20Burnable is ERC20 { + /** + * @dev Burns a specific amount of tokens. + * @param value The amount of token to be burned. + */ + function burn(uint256 value) public { + _burn(msg.sender, value); + } + + /** + * @dev Burns a specific amount of tokens from the target address and decrements allowance + * @param from address The address which you want to send tokens from + * @param value uint256 The amount of token to be burned + */ + function burnFrom(address from, uint256 value) public { + _burnFrom(from, value); + } +} + +/** + * @title SafeMath + * @dev Unsigned math operations with safety checks that revert on error + */ +library SafeMath { + /** + * @dev Multiplies two unsigned integers, reverts on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b); + + return c; + } + + /** + * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a); + uint256 c = a - b; + + return c; + } + + /** + * @dev Adds two unsigned integers, reverts on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a); + + return c; + } + + /** + * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), + * reverts when dividing by zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b != 0); + return a % b; + } +} + +/** + * @title Reputation system + * @dev A DAO has Reputation System which allows peers to rate other peers in order to build trust . + * A reputation is use to assign influence measure to a DAO'S peers. + * Reputation is similar to regular tokens but with one crucial difference: It is non-transferable. + * The Reputation contract maintain a map of address to reputation value. + * It provides an onlyOwner functions to mint and burn reputation _to (or _from) a specific address. + */ + +contract Reputation is Ownable { + uint8 public decimals = 18; //Number of decimals of the smallest unit + // Event indicating minting of reputation to an address. + event Mint(address indexed _to, uint256 _amount); + // Event indicating burning of reputation for an address. + event Burn(address indexed _from, uint256 _amount); + + /// @dev `Checkpoint` is the structure that attaches a block number to a + /// given value, the block number attached is the one that last changed the + /// value + struct Checkpoint { + // `fromBlock` is the block number that the value was generated from + uint128 fromBlock; + // `value` is the amount of reputation at a specific block number + uint128 value; + } + + // `balances` is the map that tracks the balance of each address, in this + // contract when the balance changes the block number that the change + // occurred is also included in the map + mapping(address => Checkpoint[]) balances; + + // Tracks the history of the `totalSupply` of the reputation + Checkpoint[] totalSupplyHistory; + + /// @notice Constructor to create a Reputation + constructor() public {} + + /// @dev This function makes it easy to get the total number of reputation + /// @return The total number of reputation + function totalSupply() public view returns (uint256) { + return totalSupplyAt(block.number); + } + + //////////////// + // Query balance and totalSupply in History + //////////////// + /** + * @dev return the reputation amount of a given owner + * @param _owner an address of the owner which we want to get his reputation + */ + function balanceOf(address _owner) public view returns (uint256 balance) { + return balanceOfAt(_owner, block.number); + } + + /// @dev Queries the balance of `_owner` at a specific `_blockNumber` + /// @param _owner The address from which the balance will be retrieved + /// @param _blockNumber The block number when the balance is queried + /// @return The balance at `_blockNumber` + function balanceOfAt( + address _owner, + uint256 _blockNumber + ) public view returns (uint256) { + if ( + (balances[_owner].length == 0) || + (balances[_owner][0].fromBlock > _blockNumber) + ) { + return 0; + // This will return the expected balance during normal situations + } else { + return getValueAt(balances[_owner], _blockNumber); + } + } + + /// @notice Total amount of reputation at a specific `_blockNumber`. + /// @param _blockNumber The block number when the totalSupply is queried + /// @return The total amount of reputation at `_blockNumber` + function totalSupplyAt(uint256 _blockNumber) public view returns (uint256) { + if ( + (totalSupplyHistory.length == 0) || + (totalSupplyHistory[0].fromBlock > _blockNumber) + ) { + return 0; + // This will return the expected totalSupply during normal situations + } else { + return getValueAt(totalSupplyHistory, _blockNumber); + } + } + + /// @notice Generates `_amount` reputation that are assigned to `_owner` + /// @param _user The address that will be assigned the new reputation + /// @param _amount The quantity of reputation generated + /// @return True if the reputation are generated correctly + function mint( + address _user, + uint256 _amount + ) public onlyOwner returns (bool) { + uint256 curTotalSupply = totalSupply(); + require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow + uint256 previousBalanceTo = balanceOf(_user); + require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow + updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount); + updateValueAtNow(balances[_user], previousBalanceTo + _amount); + emit Mint(_user, _amount); + return true; + } + + /// @notice Burns `_amount` reputation from `_owner` + /// @param _user The address that will lose the reputation + /// @param _amount The quantity of reputation to burn + /// @return True if the reputation are burned correctly + function burn( + address _user, + uint256 _amount + ) public onlyOwner returns (bool) { + uint256 curTotalSupply = totalSupply(); + uint256 amountBurned = _amount; + uint256 previousBalanceFrom = balanceOf(_user); + if (previousBalanceFrom < amountBurned) { + amountBurned = previousBalanceFrom; + } + updateValueAtNow(totalSupplyHistory, curTotalSupply - amountBurned); + updateValueAtNow(balances[_user], previousBalanceFrom - amountBurned); + emit Burn(_user, amountBurned); + return true; + } + + //////////////// + // Internal helper functions to query and set a value in a snapshot array + //////////////// + + /// @dev `getValueAt` retrieves the number of reputation at a given block number + /// @param checkpoints The history of values being queried + /// @param _block The block number to retrieve the value at + /// @return The number of reputation being queried + function getValueAt( + Checkpoint[] storage checkpoints, + uint256 _block + ) internal view returns (uint256) { + if (checkpoints.length == 0) { + return 0; + } + + // Shortcut for the actual value + if (_block >= checkpoints[checkpoints.length - 1].fromBlock) { + return checkpoints[checkpoints.length - 1].value; + } + if (_block < checkpoints[0].fromBlock) { + return 0; + } + + // Binary search of the value in the array + uint256 min = 0; + uint256 max = checkpoints.length - 1; + while (max > min) { + uint256 mid = (max + min + 1) / 2; + if (checkpoints[mid].fromBlock <= _block) { + min = mid; + } else { + max = mid - 1; + } + } + return checkpoints[min].value; + } + + /// @dev `updateValueAtNow` used to update the `balances` map and the + /// `totalSupplyHistory` + /// @param checkpoints The history of data being updated + /// @param _value The new number of reputation + function updateValueAtNow( + Checkpoint[] storage checkpoints, + uint256 _value + ) internal { + require(uint128(_value) == _value); //check value is in the 128 bits bounderies + if ( + (checkpoints.length == 0) || + (checkpoints[checkpoints.length - 1].fromBlock < block.number) + ) { + Checkpoint storage newCheckPoint = checkpoints[checkpoints.length++]; + newCheckPoint.fromBlock = uint128(block.number); + newCheckPoint.value = uint128(_value); + } else { + Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length - 1]; + oldCheckPoint.value = uint128(_value); + } + } +} + +/* + +SafeERC20 by daostack. +The code is based on a fix by SECBIT Team. + +USE WITH CAUTION & NO WARRANTY + +REFERENCE & RELATED READING +- https://github.com/ethereum/solidity/issues/4116 +- https://medium.com/@chris_77367/explaining-unexpected-reverts-starting-with-solidity-0-4-22-3ada6e82308c +- https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca +- https://gist.github.com/BrendanChou/88a2eeb80947ff00bcf58ffdafeaeb61 + +*/ + +library SafeERC20 { + using Address for address; + + bytes4 private constant TRANSFER_SELECTOR = + bytes4(keccak256(bytes("transfer(address,uint256)"))); + bytes4 private constant TRANSFERFROM_SELECTOR = + bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); + bytes4 private constant APPROVE_SELECTOR = + bytes4(keccak256(bytes("approve(address,uint256)"))); + + function safeTransfer( + address _erc20Addr, + address _to, + uint256 _value + ) internal { + // Must be a contract addr first! + require(_erc20Addr.isContract()); + + ( + bool success, + bytes memory returnValue // solhint-disable-next-line avoid-low-level-calls + ) = _erc20Addr.call(abi.encodeWithSelector(TRANSFER_SELECTOR, _to, _value)); + // call return false when something wrong + require(success); + //check return value + require( + returnValue.length == 0 || + (returnValue.length == 32 && (returnValue[31] != 0)) + ); + } + + function safeTransferFrom( + address _erc20Addr, + address _from, + address _to, + uint256 _value + ) internal { + // Must be a contract addr first! + require(_erc20Addr.isContract()); + + ( + bool success, + bytes memory returnValue // solhint-disable-next-line avoid-low-level-calls + ) = _erc20Addr.call( + abi.encodeWithSelector(TRANSFERFROM_SELECTOR, _from, _to, _value) + ); + // call return false when something wrong + require(success); + //check return value + require( + returnValue.length == 0 || + (returnValue.length == 32 && (returnValue[31] != 0)) + ); + } + + function safeApprove( + address _erc20Addr, + address _spender, + uint256 _value + ) internal { + // Must be a contract addr first! + require(_erc20Addr.isContract()); + + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. + require( + (_value == 0) || + (IERC20(_erc20Addr).allowance(address(this), _spender) == 0) + ); + + ( + bool success, + bytes memory returnValue // solhint-disable-next-line avoid-low-level-calls + ) = _erc20Addr.call( + abi.encodeWithSelector(APPROVE_SELECTOR, _spender, _value) + ); + // call return false when something wrong + require(success); + //check return value + require( + returnValue.length == 0 || + (returnValue.length == 32 && (returnValue[31] != 0)) + ); + } +} + +contract GlobalConstraintInterface { + enum CallPhase { + Pre, + Post, + PreAndPost + } + + function pre( + address _scheme, + bytes32 _params, + bytes32 _method + ) public returns (bool); + + function post( + address _scheme, + bytes32 _params, + bytes32 _method + ) public returns (bool); + + /** + * @dev when return if this globalConstraints is pre, post or both. + * @return CallPhase enum indication Pre, Post or PreAndPost. + */ + function when() public returns (CallPhase); +} + +/** + * @title DAOToken, base on zeppelin contract. + * @dev ERC20 compatible token. It is a mintable, burnable token. + */ + +contract DAOToken is ERC20, ERC20Burnable, Ownable { + string public name; + string public symbol; + // solhint-disable-next-line const-name-snakecase + uint8 public constant decimals = 18; + uint256 public cap; + + /** + * @dev Constructor + * @param _name - token name + * @param _symbol - token symbol + * @param _cap - token cap - 0 value means no cap + */ + constructor(string memory _name, string memory _symbol, uint256 _cap) public { + name = _name; + symbol = _symbol; + cap = _cap; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. + */ + function mint(address _to, uint256 _amount) public onlyOwner returns (bool) { + if (cap > 0) require(totalSupply().add(_amount) <= cap); + _mint(_to, _amount); + return true; + } +} + +/** + * @title Controller contract + * @dev A controller controls the organizations tokens ,reputation and avatar. + * It is subject to a set of schemes and constraints that determine its behavior. + * Each scheme has it own parameters and operation permissions. + */ +interface ControllerInterface { + /** + * @dev Mint `_amount` of reputation that are assigned to `_to` . + * @param _amount amount of reputation to mint + * @param _to beneficiary address + * @return bool which represents a success + */ + function mintReputation( + uint256 _amount, + address _to, + address _avatar + ) external returns (bool); + + /** + * @dev Burns `_amount` of reputation from `_from` + * @param _amount amount of reputation to burn + * @param _from The address that will lose the reputation + * @return bool which represents a success + */ + function burnReputation( + uint256 _amount, + address _from, + address _avatar + ) external returns (bool); + + /** + * @dev mint tokens . + * @param _amount amount of token to mint + * @param _beneficiary beneficiary address + * @param _avatar address + * @return bool which represents a success + */ + function mintTokens( + uint256 _amount, + address _beneficiary, + address _avatar + ) external returns (bool); + + /** + * @dev register or update a scheme + * @param _scheme the address of the scheme + * @param _paramsHash a hashed configuration of the usage of the scheme + * @param _permissions the permissions the new scheme will have + * @param _avatar address + * @return bool which represents a success + */ + function registerScheme( + address _scheme, + bytes32 _paramsHash, + bytes4 _permissions, + address _avatar + ) external returns (bool); + + /** + * @dev unregister a scheme + * @param _avatar address + * @param _scheme the address of the scheme + * @return bool which represents a success + */ + function unregisterScheme( + address _scheme, + address _avatar + ) external returns (bool); + + /** + * @dev unregister the caller's scheme + * @param _avatar address + * @return bool which represents a success + */ + function unregisterSelf(address _avatar) external returns (bool); + + /** + * @dev add or update Global Constraint + * @param _globalConstraint the address of the global constraint to be added. + * @param _params the constraint parameters hash. + * @param _avatar the avatar of the organization + * @return bool which represents a success + */ + function addGlobalConstraint( + address _globalConstraint, + bytes32 _params, + address _avatar + ) external returns (bool); + + /** + * @dev remove Global Constraint + * @param _globalConstraint the address of the global constraint to be remove. + * @param _avatar the organization avatar. + * @return bool which represents a success + */ + function removeGlobalConstraint( + address _globalConstraint, + address _avatar + ) external returns (bool); + + /** + * @dev upgrade the Controller + * The function will trigger an event 'UpgradeController'. + * @param _newController the address of the new controller. + * @param _avatar address + * @return bool which represents a success + */ + function upgradeController( + address _newController, + Avatar _avatar + ) external returns (bool); + + /** + * @dev perform a generic call to an arbitrary contract + * @param _contract the contract's address to call + * @param _data ABI-encoded contract call to call `_contract` address. + * @param _avatar the controller's avatar address + * @param _value value (ETH) to transfer with the transaction + * @return bool -success + * bytes - the return value of the called _contract's function. + */ + function genericCall( + address _contract, + bytes calldata _data, + Avatar _avatar, + uint256 _value + ) external returns (bool, bytes memory); + + /** + * @dev send some ether + * @param _amountInWei the amount of ether (in Wei) to send + * @param _to address of the beneficiary + * @param _avatar address + * @return bool which represents a success + */ + function sendEther( + uint256 _amountInWei, + address payable _to, + Avatar _avatar + ) external returns (bool); + + /** + * @dev send some amount of arbitrary ERC20 Tokens + * @param _externalToken the address of the Token Contract + * @param _to address of the beneficiary + * @param _value the amount of ether (in Wei) to send + * @param _avatar address + * @return bool which represents a success + */ + function externalTokenTransfer( + IERC20 _externalToken, + address _to, + uint256 _value, + Avatar _avatar + ) external returns (bool); + + /** + * @dev transfer token "from" address "to" address + * One must to approve the amount of tokens which can be spend from the + * "from" account.This can be done using externalTokenApprove. + * @param _externalToken the address of the Token Contract + * @param _from address of the account to send from + * @param _to address of the beneficiary + * @param _value the amount of ether (in Wei) to send + * @param _avatar address + * @return bool which represents a success + */ + function externalTokenTransferFrom( + IERC20 _externalToken, + address _from, + address _to, + uint256 _value, + Avatar _avatar + ) external returns (bool); + + /** + * @dev externalTokenApproval approve the spender address to spend a specified amount of tokens + * on behalf of msg.sender. + * @param _externalToken the address of the Token Contract + * @param _spender address + * @param _value the amount of ether (in Wei) which the approval is referring to. + * @return bool which represents a success + */ + function externalTokenApproval( + IERC20 _externalToken, + address _spender, + uint256 _value, + Avatar _avatar + ) external returns (bool); + + /** + * @dev metaData emits an event with a string, should contain the hash of some meta data. + * @param _metaData a string representing a hash of the meta data + * @param _avatar Avatar + * @return bool which represents a success + */ + function metaData( + string calldata _metaData, + Avatar _avatar + ) external returns (bool); + + /** + * @dev getNativeReputation + * @param _avatar the organization avatar. + * @return organization native reputation + */ + function getNativeReputation(address _avatar) external view returns (address); + + function isSchemeRegistered( + address _scheme, + address _avatar + ) external view returns (bool); + + function getSchemeParameters( + address _scheme, + address _avatar + ) external view returns (bytes32); + + function getGlobalConstraintParameters( + address _globalConstraint, + address _avatar + ) external view returns (bytes32); + + function getSchemePermissions( + address _scheme, + address _avatar + ) external view returns (bytes4); + + /** + * @dev globalConstraintsCount return the global constraint pre and post count + * @return uint256 globalConstraintsPre count. + * @return uint256 globalConstraintsPost count. + */ + function globalConstraintsCount( + address _avatar + ) external view returns (uint, uint); + + function isGlobalConstraintRegistered( + address _globalConstraint, + address _avatar + ) external view returns (bool); +} + +/** + * @title An Avatar holds tokens, reputation and ether for a controller + */ +contract Avatar is Ownable { + using SafeERC20 for address; + + string public orgName; + DAOToken public nativeToken; + Reputation public nativeReputation; + + event GenericCall( + address indexed _contract, + bytes _data, + uint _value, + bool _success + ); + event SendEther(uint256 _amountInWei, address indexed _to); + event ExternalTokenTransfer( + address indexed _externalToken, + address indexed _to, + uint256 _value + ); + event ExternalTokenTransferFrom( + address indexed _externalToken, + address _from, + address _to, + uint256 _value + ); + event ExternalTokenApproval( + address indexed _externalToken, + address _spender, + uint256 _value + ); + event ReceiveEther(address indexed _sender, uint256 _value); + event MetaData(string _metaData); + + /** + * @dev the constructor takes organization name, native token and reputation system + and creates an avatar for a controller + */ + constructor( + string memory _orgName, + DAOToken _nativeToken, + Reputation _nativeReputation + ) public { + orgName = _orgName; + nativeToken = _nativeToken; + nativeReputation = _nativeReputation; + } + + /** + * @dev enables an avatar to receive ethers + */ + function() external payable { + emit ReceiveEther(msg.sender, msg.value); + } + + /** + * @dev perform a generic call to an arbitrary contract + * @param _contract the contract's address to call + * @param _data ABI-encoded contract call to call `_contract` address. + * @param _value value (ETH) to transfer with the transaction + * @return bool success or fail + * bytes - the return bytes of the called contract's function. + */ + function genericCall( + address _contract, + bytes memory _data, + uint256 _value + ) public onlyOwner returns (bool success, bytes memory returnValue) { + // solhint-disable-next-line avoid-call-value + (success, returnValue) = _contract.call.value(_value)(_data); + emit GenericCall(_contract, _data, _value, success); + } + + /** + * @dev send ethers from the avatar's wallet + * @param _amountInWei amount to send in Wei units + * @param _to send the ethers to this address + * @return bool which represents success + */ + function sendEther( + uint256 _amountInWei, + address payable _to + ) public onlyOwner returns (bool) { + _to.transfer(_amountInWei); + emit SendEther(_amountInWei, _to); + return true; + } + + /** + * @dev external token transfer + * @param _externalToken the token contract + * @param _to the destination address + * @param _value the amount of tokens to transfer + * @return bool which represents success + */ + function externalTokenTransfer( + IERC20 _externalToken, + address _to, + uint256 _value + ) public onlyOwner returns (bool) { + address(_externalToken).safeTransfer(_to, _value); + emit ExternalTokenTransfer(address(_externalToken), _to, _value); + return true; + } + + /** + * @dev external token transfer from a specific account + * @param _externalToken the token contract + * @param _from the account to spend token from + * @param _to the destination address + * @param _value the amount of tokens to transfer + * @return bool which represents success + */ + function externalTokenTransferFrom( + IERC20 _externalToken, + address _from, + address _to, + uint256 _value + ) public onlyOwner returns (bool) { + address(_externalToken).safeTransferFrom(_from, _to, _value); + emit ExternalTokenTransferFrom(address(_externalToken), _from, _to, _value); + return true; + } + + /** + * @dev externalTokenApproval approve the spender address to spend a specified amount of tokens + * on behalf of msg.sender. + * @param _externalToken the address of the Token Contract + * @param _spender address + * @param _value the amount of ether (in Wei) which the approval is referring to. + * @return bool which represents a success + */ + function externalTokenApproval( + IERC20 _externalToken, + address _spender, + uint256 _value + ) public onlyOwner returns (bool) { + address(_externalToken).safeApprove(_spender, _value); + emit ExternalTokenApproval(address(_externalToken), _spender, _value); + return true; + } + + /** + * @dev metaData emits an event with a string, should contain the hash of some meta data. + * @param _metaData a string representing a hash of the meta data + * @return bool which represents a success + */ + function metaData(string memory _metaData) public onlyOwner returns (bool) { + emit MetaData(_metaData); + return true; + } +} diff --git a/scripts/multichain-deploy/helpers.ts b/scripts/multichain-deploy/helpers.ts index 2cf53d88..943f2a77 100644 --- a/scripts/multichain-deploy/helpers.ts +++ b/scripts/multichain-deploy/helpers.ts @@ -1,22 +1,20 @@ +import { readFileSync } from "fs"; import { Contract, ContractFactory, Signer } from "ethers"; -import { network, ethers, upgrades, run } from "hardhat"; -import * as safeethers from "ethers"; +import { network, ethers } from "hardhat"; import { TransactionResponse, TransactionReceipt } from "@ethersproject/providers"; -// import Safe from "@gnosis.pm/safe-core-sdk"; -import EthersAdapter from "@gnosis.pm/safe-ethers-lib"; -// import { MetaTransactionData } from "@gnosis.pm/safe-core-sdk-types"; -// import SafeClient from "@gnosis.pm/safe-service-client"; +import { getImplementationAddress } from "@openzeppelin/upgrades-core"; import SafeApiKit from "@safe-global/api-kit"; -import Safe, { Eip1193Provider } from "@safe-global/protocol-kit"; -import { MetaTransactionData, OperationType } from "@safe-global/types-kit"; +import Safe from "@safe-global/protocol-kit"; +import { MetaTransactionData } from "@safe-global/types-kit"; import util from "util"; import dao from "../../releases/deployment.json"; +import { ProxyFactory1967 } from "../../types"; const exec = util.promisify(require("child_process").exec); -const networkName = network.name === "localhost" ? "production-mainnet" : network.name; +const networkName = network.name; let totalGas = 0; const gasUsage = {}; const GAS_SETTINGS = { gasLimit: 10000000 }; @@ -60,6 +58,7 @@ export const deploySuperGoodDollar = async ( { superfluidHost, superfluidInflowNFTLogic, superfluidOutflowNFTLogic }, tokenArgs ) => { + const withNFTs = !!(superfluidInflowNFTLogic && superfluidOutflowNFTLogic); const SuperGoodDollar = (await deployDeterministic( { name: "SuperGoodDollar", @@ -79,59 +78,67 @@ export const deploySuperGoodDollar = async ( await GoodDollarProxy.initializeProxy(SuperGoodDollar.address).then(printDeploy); - const OutFlowNFT = (await deployDeterministic( - { - name: "SuperGoodDollar OutflowNFT", - factory: uupsFactory - }, - [] - ).then(printDeploy)) as Contract; - - const InFlowNFT = (await deployDeterministic( - { - name: "SuperGoodDollar InflowNFT", - factory: uupsFactory - }, - [] - ).then(printDeploy)) as Contract; - - await OutFlowNFT.initializeProxy(superfluidOutflowNFTLogic); - await InFlowNFT.initializeProxy(superfluidInflowNFTLogic); - + let OutFlowNFT = await ethers.getContractAt("ConstantOutflowNFT", ethers.constants.AddressZero); + let InFlowNFT = await ethers.getContractAt("ConstantOutflowNFT", ethers.constants.AddressZero); + + if (withNFTs) { + OutFlowNFT = (await deployDeterministic( + { + name: "SuperGoodDollar OutflowNFT", + factory: uupsFactory + }, + [] + ).then(printDeploy)) as Contract; + + InFlowNFT = (await deployDeterministic( + { + name: "SuperGoodDollar InflowNFT", + factory: uupsFactory + }, + [] + ).then(printDeploy)) as Contract; + + console.log("initializing superfluid nfts proxies"); + await OutFlowNFT.initializeProxy(superfluidOutflowNFTLogic); + await InFlowNFT.initializeProxy(superfluidInflowNFTLogic); + } + console.log("initializing supergooddollar"); await SuperGoodDollar.attach(GoodDollarProxy.address)[ - "initialize(string,string,uint256,address,address,address,address,address,address)" - ](...tokenArgs, OutFlowNFT.address, InFlowNFT.address); + "initialize(string,string,uint256,address,address,address,address)" + ](...tokenArgs); const GoodDollar = await ethers.getContractAt("ISuperGoodDollar", GoodDollarProxy.address); - const constantInflowNFT = await ethers.getContractAt("ConstantInflowNFT", InFlowNFT.address); + if (withNFTs) { + const constantInflowNFT = await ethers.getContractAt("ConstantInflowNFT", InFlowNFT.address); - const constantOutflowNFT = await ethers.getContractAt("ConstantOutflowNFT", OutFlowNFT.address); - - await constantOutflowNFT - .attach(OutFlowNFT.address) - .initialize( - GoodDollarProxy.address, - (await GoodDollar.symbol()) + " Outflow NFT", - (await GoodDollar.symbol()) + " COF" - ); - await constantInflowNFT - .attach(InFlowNFT.address) - .initialize( - GoodDollarProxy.address, - (await GoodDollar.symbol()) + " Inflow NFT", - (await GoodDollar.symbol()) + " CIF" - ); + const constantOutflowNFT = await ethers.getContractAt("ConstantOutflowNFT", OutFlowNFT.address); + console.log("initializing inflow and outflow nfts"); + await constantOutflowNFT + .attach(OutFlowNFT.address) + .initialize( + GoodDollarProxy.address, + (await GoodDollar.symbol()) + " Outflow NFT", + (await GoodDollar.symbol()) + " COF" + ); + await constantInflowNFT + .attach(InFlowNFT.address) + .initialize( + GoodDollarProxy.address, + (await GoodDollar.symbol()) + " Inflow NFT", + (await GoodDollar.symbol()) + " CIF" + ); + } return GoodDollar; }; export const deployDeterministic = async (contract, args: any[], factoryOpts = {}, redeployProxyFactory = false) => { try { - let proxyFactory; + let proxyFactory: ProxyFactory1967; if (networkName.startsWith("develop") && redeployProxyFactory) { - proxyFactory = await (await ethers.getContractFactory("ProxyFactory1967")).deploy(); - } else proxyFactory = await ethers.getContractAt("ProxyFactory1967", release.ProxyFactory); + proxyFactory = (await (await ethers.getContractFactory("ProxyFactory1967")).deploy()) as ProxyFactory1967; + } else proxyFactory = (await ethers.getContractAt("ProxyFactory1967", release.ProxyFactory)) as ProxyFactory1967; const Contract = (contract.factory as ContractFactory) || (await ethers.getContractFactory(contract.name, factoryOpts)); @@ -140,39 +147,73 @@ export const deployDeterministic = async (contract, args: any[], factoryOpts = { ); if (contract.isUpgradeable === true) { + const proxyAddr = await proxyFactory["getDeploymentAddress(uint256,address)"]( + salt, + await proxyFactory.signer.getAddress() + ); + const code = await ethers.provider.getCode(proxyAddr); + if (code && code !== "0x") { + const implAddr = await getImplementationAddress(ethers.provider, proxyAddr).catch(e => "0x"); + if (implAddr && implAddr !== "0x") { + console.log("proxy exists and impl set already:", contract.name, proxyAddr, implAddr); + return Contract.attach(proxyAddr); + } + } + console.log("Deploying:", contract.name, "using proxyfactory", { args, + proxyAddr, proxyFactory: proxyFactory.address }); + const encoded = Contract.interface.encodeFunctionData(contract.initializer || "initialize", args); const tx = await Contract.deploy(GAS_SETTINGS); const impl = await tx.deployed(); console.log("implementation deployed:", contract.name, impl.address); await countTotalGas(tx, contract.name); + let tx2; + try { + if (contract.withProxyFactory === false) { + console.log("deploying proxy without proxy factory", { salt, impl: impl.address, encoded }); + const proxy = await ethers.getContractFactory("contracts/utils/ProxyFactory1967.sol:ERC1967Proxy"); + tx2 = await proxyFactory.deployCode(salt, proxy.bytecode, GAS_SETTINGS); + await tx2.wait(); + console.log("proxy deployed initializing...", contract.name, proxyAddr, tx2.hash); + tx2 = await proxy.attach(proxyAddr).initialize(impl.address, encoded); + console.log("proxy initialized:", contract.name, proxyAddr, tx2.hash); + } else { + console.log("deploying proxy with proxyfactory", { salt, impl: impl.address, encoded }); + tx2 = await proxyFactory.deployProxy(salt, impl.address, encoded, GAS_SETTINGS); + } + await countTotalGas(tx2, contract.name); + const deployTx = await tx2.wait(); + } catch (e) { + console.error("failed to deploy proxy, assuming it exists...", e); + } - const tx2 = await proxyFactory.deployProxy(salt, impl.address, encoded, GAS_SETTINGS); - await countTotalGas(tx2, contract.name); - const deployTx = await tx2.wait().catch(e => console.error("failed to deploy proxy, assuming it exists...", e)); - const proxyAddr = await proxyFactory["getDeploymentAddress(uint256,address)"]( - salt, - await proxyFactory.signer.getAddress() - ); console.log("proxy deployed:", contract.name, proxyAddr); return Contract.attach(proxyAddr); } else { - console.log("Deploying:", contract.name, "using proxyfactory code", { - proxyFactory: proxyFactory.address, - args - }); const constructor = Contract.interface.encodeDeploy(args); const bytecode = ethers.utils.solidityPack(["bytes", "bytes"], [Contract.bytecode, constructor]); - const deployTx = await (await proxyFactory.deployCode(salt, bytecode, GAS_SETTINGS)).wait(); - const proxyAddr = await proxyFactory["getDeploymentAddress(uint256,address,bytes32)"]( salt, await proxyFactory.signer.getAddress(), ethers.utils.keccak256(bytecode) ); + const code = await ethers.provider.getCode(proxyAddr); + if (code && code !== "0x") { + console.log("contract already exists:", contract.name, proxyAddr); + return Contract.attach(proxyAddr); + } + + console.log("Deploying:", contract.name, "using proxyfactory code", { + proxyFactory: proxyFactory.address, + args + }); + + const deployTx = await (await proxyFactory.deployCode(salt, bytecode, GAS_SETTINGS)).wait(); + console.log("proxy deployed:", contract.name, proxyAddr); return Contract.attach(proxyAddr); @@ -422,16 +463,108 @@ export const verifyContract = async ( address, contractPath, networkName = network.name, - proxyName?: string, + maybeProxy: boolean = true, forcedConstructorArguments?: string ) => { - const cmd = `yarn hardhat verify --contract ${contractPath} ${address} ${ + const impl = await getImplementationAddress(ethers.provider, address).catch(e => ""); + const contractAddress = maybeProxy ? impl || address : address; + console.log("verifying contract:", { contractPath, address, impl, networkName, forcedConstructorArguments }); + const cmd = `yarn hardhat verify --contract ${contractPath} ${contractAddress} ${ forcedConstructorArguments ?? "" } --network ${networkName}`; console.log("running...:", cmd); - await exec(cmd).then(({ stdout, stderr }) => { - console.log("Result for:", cmd); - console.log(stdout); - console.log(stderr); - }); + await exec(cmd) + .then(({ stdout, stderr }) => { + console.log("Result for:", cmd); + console.log(stdout); + console.log(stderr); + }) + .catch(e => { + console.error("Error verifying contract:", e); + }); + if (maybeProxy && impl) { + const chainid = network.config.chainId.toString(); + console.log("linking proxy to impl", { impl, address }); + const body = new URLSearchParams({ + chaini: network.config.chainId.toString(), + apikey: process.env.ETHERSCAN_KEY, + module: "contract", + action: "verifyproxycontract", + address + }); + + const response = await fetch(`https://api.etherscan.io/v2/api?chainid=${chainid}`, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body + }); + + const data = await response.json(); + if (data.status !== "1") { + return console.log(`Proxy Verification submission failed: ${data.result}`); + } + + const guid = data.result; + console.log(`Proxy Verification submitted. GUID: ${guid}`); + } }; + +/** + * Verify a flattened contract on Etherscan + * @param {string} contractPath - Path to the contract (e.g., contracts/MyContract.sol) + * @param {string} contractName - Contract name (e.g., MyContract) + * @param {string} contractAddress - Deployed contract address + * @param {string} compilerVersion - Compiler version (e.g., v0.8.24+commit.e11b9ed9) + * @param {Array} constructorArgs - Object with {types:[], values:[]} for encoding + */ +export async function verifyOnEtherscan( + chainid, + contractPath, + contractName, + contractAddress, + compilerVersion, + constructorArgs = { types: [], values: [] } +) { + const apiKey = process.env.ETHERSCAN_KEY; + if (!apiKey) { + throw new Error("ETHERSCAN_API_KEY not set in .env"); + } + + const sourceCode = readFileSync(contractPath, "utf8"); + + let constructorEncoded = ""; + if (constructorArgs.types && constructorArgs.values) { + constructorEncoded = ethers.utils.defaultAbiCoder.encode(constructorArgs.types, constructorArgs.values).slice(2); // Remove '0x' + } + + console.log("Submitting verification request to Etherscan...", { chainid, contractAddress, contractName }); + + const body = new URLSearchParams({ + chainid, + apikey: apiKey, + module: "contract", + action: "verifysourcecode", + contractaddress: contractAddress, + sourceCode: sourceCode, + codeformat: "solidity-single-file", + contractname: contractName, + compilerversion: compilerVersion, + optimizationUsed: "1", + runs: "200", + constructorArguements: constructorEncoded + }); + + const response = await fetch(`https://api.etherscan.io/v2/api?chainid=${chainid}`, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body + }); + + const data = await response.json(); + if (data.status !== "1") { + return console.log(`Verification submission failed: ${data.result}`); + } + + const guid = data.result; + console.log(`Verification submitted. GUID: ${guid}`); +} diff --git a/test/governance/ClaimersDistribution.test.ts b/test/governance/ClaimersDistribution.test.ts index f16fe692..7d11d538 100644 --- a/test/governance/ClaimersDistribution.test.ts +++ b/test/governance/ClaimersDistribution.test.ts @@ -240,7 +240,7 @@ describe("ClaimersDistribution", () => { // console.log({ totalGas }, tx.gasUsed.toNumber(), tx2.gasUsed.toNumber()); } console.log(Object.keys(gasCosts)); - expect(totalGas / 30).gt(480000); + expect(totalGas / 30).gt(470000); }); it("should not cost alot of gas to claim with reputation distribution with regular token [@skip-on-coverage]", async () => { diff --git a/test/helpers.ts b/test/helpers.ts index 66bdec79..3338b01b 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -58,53 +58,67 @@ export const deploySuperGoodDollar = async (sfContracts, tokenArgs) => { "contracts/token/superfluid/UUPSProxy.sol:UUPSProxy" ); const GoodDollarProxy = await GoodDollarProxyFactory.deploy(); + console.log("deployed supergooddollar proxy, initializing proxy..."); + await GoodDollarProxy.initializeProxy(SuperGoodDollar.address); - console.log("deploying flow nfts..."); - - const outNftProxy = await GoodDollarProxyFactory.deploy(); - const inNftProxy = await GoodDollarProxyFactory.deploy(); - - const constantInflowNFT = await ethers.deployContract("ConstantInflowNFT", [ - sfContracts.host, - outNftProxy.address - ]); + if (sfContracts.host !== ethers.constants.AddressZero) { + console.log("deploying flow nfts..."); - const constantOutflowNFT = await ethers.deployContract("ConstantOutflowNFT", [ - sfContracts.host, - inNftProxy.address - ]); - await outNftProxy.initializeProxy(constantOutflowNFT.address); - await inNftProxy.initializeProxy(constantInflowNFT.address); + const outNftProxy = await GoodDollarProxyFactory.deploy(); + const inNftProxy = await GoodDollarProxyFactory.deploy(); - console.log("deployed supergooddollar proxy, initializing proxy..."); - await GoodDollarProxy.initializeProxy(SuperGoodDollar.address); + const constantInflowNFT = await ethers.deployContract("ConstantInflowNFT", [ + sfContracts.host, + outNftProxy.address + ]); - console.log("initializing supergooddollar...."); - await SuperGoodDollar.attach(GoodDollarProxy.address)[ - "initialize(string,string,uint256,address,address,address,address,address,address)" - ](...tokenArgs, outNftProxy.address, inNftProxy.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" + const constantOutflowNFT = await ethers.deployContract( + "ConstantOutflowNFT", + [sfContracts.host, inNftProxy.address] ); - await constantInflowNFT - .attach(inNftProxy.address) - .initialize( - (await GoodDollar.symbol()) + " Inflow NFT", - (await GoodDollar.symbol()) + " CIF" + await outNftProxy.initializeProxy(constantOutflowNFT.address); + await inNftProxy.initializeProxy(constantInflowNFT.address); + + 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 ); + console.log("supergooddollar created successfully"); - return GoodDollar; + await constantOutflowNFT + .attach(outNftProxy.address) + .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" + ); + 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 + ); + console.log("supergooddollar created successfully"); + return GoodDollar; + } }; -export const createDAO = async (tokenType: "super" | "regular" = "super") => { +export const createDAO = async ( + tokenType: "super" | "regular" = "super", + identity: "v2" | "v3" = "v3" +) => { let [root, ...signers] = await ethers.getSigners(); const sfContracts = await deploySuperFluid(); @@ -122,7 +136,9 @@ export const createDAO = async (tokenType: "super" | "regular" = "super") => { root ); - const IdentityFactory = await ethers.getContractFactory("IdentityV2"); + const IdentityFactory = await ethers.getContractFactory( + identity === "v2" ? "IdentityV2" : "IdentityV3" + ); const FeeFormulaFactory = new ethers.ContractFactory( FeeFormulaABI.abi, @@ -441,10 +457,7 @@ export const createDAO = async (tokenType: "super" | "regular" = "super") => { await setDAOAddress("REPUTATION", reputation.address); console.log("setting reserve token..."); - await cDAI["mint(address,uint256)"]( - goodReserve.address, - 10000 - ); + await cDAI["mint(address,uint256)"](goodReserve.address, 10000); await setReserveToken( cDAI.address, "100", //1gd @@ -511,8 +524,8 @@ export const deployUBI = async (deployedDAO, withFirstClaim = true) => { }); let ubiScheme = await upgrades.deployProxy( - await ethers.getContractFactory("UBIScheme"), - [nameService.address, firstClaim.address, 14], + await ethers.getContractFactory("UBISchemeV2"), + [nameService.address], { kind: "uups" } ); diff --git a/test/identity/IdentityV2.test.ts b/test/identity/IdentityV2.test.ts index 838ee403..03d66cc6 100644 --- a/test/identity/IdentityV2.test.ts +++ b/test/identity/IdentityV2.test.ts @@ -16,6 +16,9 @@ describe("Identity", () => { let avatar, gd: IGoodDollar, Controller, id: IIdentity; + const createDAOv2 = async () => { + return createDAO("super", "v2"); + }; before(async () => { [founder, ...signers] = await ethers.getSigners(); let { @@ -24,7 +27,7 @@ describe("Identity", () => { gd: gooddollar, identity: idv2, genericCall: gc - } = await loadFixture(createDAO); + } = await loadFixture(createDAOv2); genericCall = gc; identity = (await ethers.getContractAt("IdentityV2", idv2)) as IdentityV2; diff --git a/test/identity/IdentityV3.test.ts b/test/identity/IdentityV3.test.ts new file mode 100644 index 00000000..b198d8d2 --- /dev/null +++ b/test/identity/IdentityV3.test.ts @@ -0,0 +1,449 @@ +import hre, { ethers, upgrades } from "hardhat"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { IGoodDollar, IIdentity, IdentityV3 } from "../../types"; +import { createDAO, increaseTime, advanceBlocks } from "../helpers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +const BN = ethers.BigNumber; + +describe("IdentityV3", () => { + let identity: IdentityV3, 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("IdentityV3", idv2)) as IdentityV3; + 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("IdentityV3"); + let newid = (await upgrades.deployProxy( + f, + [signers[0].address, ethers.constants.AddressZero], + { kind: "uups" } + )) as IdentityV2; + 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("IdentityV3"); + let newid = (await upgrades.deployProxy( + f, + [signers[0].address, ethers.constants.AddressZero], + { kind: "uups" } + )) as IdentityV2; + 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("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); + 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; + }); +}); diff --git a/test/invite/InvitesV2.test.ts b/test/invite/InvitesV2.test.ts index 1c4d5764..6d0d81f4 100644 --- a/test/invite/InvitesV2.test.ts +++ b/test/invite/InvitesV2.test.ts @@ -42,7 +42,7 @@ describe("InvitesV2", () => { })) as InvitesV2; gd = (await ethers.getContractAt("IGoodDollar", gooddollar, founder)) as IGoodDollar; - id = (await ethers.getContractAt("IdentityV2", identity, founder)) as IdentityV2; + id = (await ethers.getContractAt("IdentityV3", identity, founder)) as IdentityV2; await gd["mint(address,uint256)"](invites.address, BN.from(5000)); await loadFixture(initialState); diff --git a/test/token/GoodDollar.test.ts b/test/token/GoodDollar.test.ts index 513b14db..a9b217f3 100644 --- a/test/token/GoodDollar.test.ts +++ b/test/token/GoodDollar.test.ts @@ -55,7 +55,7 @@ describe("GoodDollar Token", () => { superFluid = sfContracts; avatar = av; - identity = await ethers.getContractAt("IdentityV2", idv2); + identity = await ethers.getContractAt("IdentityV3", idv2); const FeeFormulaFactory = new ethers.ContractFactory( FeeFormulaABI.abi, diff --git a/test/token/SuperGoodDollar.nohost.test.ts b/test/token/SuperGoodDollar.nohost.test.ts new file mode 100644 index 00000000..845c8e9c --- /dev/null +++ b/test/token/SuperGoodDollar.nohost.test.ts @@ -0,0 +1,383 @@ +import { ethers } from "hardhat"; +import { assert, expect } from "chai"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import TransferAndCallMockABI from "@gooddollar/goodcontracts/build/contracts/TransferAndCallMock.json"; +import { createDAO, deploySuperGoodDollar } from "../helpers"; +import { ISuperGoodDollar } from "../../types"; + +let sfHost, + founder, + alice, + bob, + eve, + newHost, + identityMock, + receiverMock, + sgd: ISuperGoodDollar, // stands for "SuperGoodDollar" + feesFormula10PctMock; + +const alotOfDollars = ethers.utils.parseEther("100000"); +const tenDollars = ethers.utils.parseEther("10"); +const oneDollar = ethers.utils.parseEther("1"); +const tenDollarsPerDay = "124378109452730"; // flowrate per second + +const initialState = async () => {}; + +before(async function () { + //get accounts from hardhat + [founder, alice, bob, eve, newHost] = await ethers.getSigners(); + + await createDAO(); + + const sfContracts = { + host: ethers.constants.AddressZero + }; + sfHost = sfContracts.host; + + // GoodDollar specific init + const FeesFormulaMockFactory = await ethers.getContractFactory( + "FeesFormulaMock", + founder + ); + + const feesFormula0PctMock = await FeesFormulaMockFactory.deploy(0); + + feesFormula10PctMock = await FeesFormulaMockFactory.deploy(100000); + + // the zero address is a placeholder for the dao contract + const IdentityMockFactory = await ethers.getContractFactory( + "IdentityMock", + founder + ); + identityMock = await IdentityMockFactory.deploy( + "0x0000000000000000000000000000000000000000" + ); + + receiverMock = await new ethers.ContractFactory( + TransferAndCallMockABI.abi, + TransferAndCallMockABI.bytecode, + founder + ).deploy(); + + console.log("deploying test supergooddollar..."); + sgd = (await deploySuperGoodDollar(sfContracts, [ + "SuperGoodDollar", + "SGD", + 0, // cap + feesFormula0PctMock.address, + identityMock.address, + receiverMock.address, + founder.address + ])) as ISuperGoodDollar; + + await sgd.mint(founder.address, alotOfDollars); +}); + +describe("SuperGoodDollar No Host", async function () { + it("check superfluid host", async () => { + expect(await sgd.getHost()).equal(sfHost); + }); + + it("check ERC20 metadata", async function () { + await loadFixture(initialState); + const symbol = await sgd.symbol(); + const name = await sgd.name(); + assert.equal(symbol, "SGD", "symbol mismatch"); + assert.equal(name, "SuperGoodDollar", "name mismatch"); + }); + + it("mint to alice", async function () { + await loadFixture(initialState); + await sgd.mint(alice.address, alotOfDollars); + const balAfter = await sgd.balanceOf(alice.address); + + assert.equal( + balAfter.toString(), + alotOfDollars.toString(), + "wrong balance after mint" + ); + }); + + it("do ERC20 transfer", async function () { + await loadFixture(initialState); + await sgd.mint(alice.address, tenDollars); + await sgd.connect(alice).transfer(bob.address, tenDollars); + const balAfter = await sgd.balanceOf(bob.address); + + assert.equal( + balAfter.toString(), + tenDollars.toString(), + "wrong balance after transfer" + ); + }); + + it("do ERC20 transferFrom", async function () { + await loadFixture(initialState); + await sgd.approve(alice.address, tenDollars); + await sgd + .connect(alice) + .transferFrom(founder.address, bob.address, tenDollars); + assert.equal( + (await sgd.balanceOf(bob.address)).toString(), + tenDollars.toString(), + "wrong balance after transferFrom" + ); + }); + + it("pauseable", async function () { + await loadFixture(initialState); + await sgd.connect(founder).pause(); + + await expect(sgd.transfer(bob.address, tenDollars)).revertedWithCustomError( + sgd, + "SUPER_GOODDOLLAR_PAUSED" + ); + + await sgd.connect(founder).unpause(); + + await sgd.transfer(bob.address, tenDollars); + }); + + it("non-zero fees are applied", async function () { + await loadFixture(initialState); + + await sgd.connect(founder).mint(alice.address, tenDollars); + await sgd.setFormula(feesFormula10PctMock.address); + + await expect( + sgd.connect(alice).transfer(bob.address, tenDollars) + ).revertedWith(/Not enough balance to pay TX fee/); + + // mint the extra amount needed for 10% fees + await sgd.mint(alice.address, oneDollar); + await sgd.connect(alice).transfer(bob.address, tenDollars); + + // since the sender pays the fee, alice should have spent 11$ and bob received 10$ + assert.equal( + (await sgd.balanceOf(alice.address)).toString(), + "0", + "alice: wrong balance after transfer" + ); + assert.equal( + (await sgd.balanceOf(bob.address)).toString(), + tenDollars.toString(), + "bob: wrong balance after transfer" + ); + }); + + it("non-zero fees are applied for transferFrom (verify override of _transferForm)", async function () { + await loadFixture(initialState); + + await sgd.connect(founder).mint(alice.address, tenDollars); + await sgd.setFormula(feesFormula10PctMock.address); + + await sgd.connect(alice).approve(founder.address, tenDollars.mul(2)); + + await expect( + sgd.connect(founder).transferFrom(alice.address, bob.address, tenDollars) + ).revertedWith(/Not enough balance to pay TX fee/); + + // mint the extra amount needed for 10% fees + await sgd.connect(founder).mint(alice.address, oneDollar); + assert.equal( + (await sgd.balanceOf(alice.address)).toString(), + tenDollars.add(oneDollar).toString(), + "alice: wrong balance after mint" + ); + + await sgd + .connect(founder) + .transferFrom(alice.address, bob.address, tenDollars); + + // since the sender pays the fee, alice should have spent 11$ and bob received 10$ + assert.equal( + (await sgd.balanceOf(alice.address)).toString(), + "0", + "alice: wrong balance after transfer" + ); + assert.equal( + (await sgd.balanceOf(bob.address)).toString(), + tenDollars.toString(), + "bob: wrong balance after transfer" + ); + }); + + it("should not be able to initialize again", async () => { + await loadFixture(initialState); + await expect( + sgd["initialize(string,string,uint256,address,address,address,address)"]( + "x", + "y", + 1, + ethers.constants.AddressZero, + ethers.constants.AddressZero, + ethers.constants.AddressZero, + ethers.constants.AddressZero + ) + ).revertedWith(/Initializable: contract is already initialized/); + + await expect( + sgd["initialize(address,uint8,string,string)"]( + ethers.constants.AddressZero, + 2, + "GD", + "GD" + ) + ).revertedWith(/Initializable: contract is not initializing/); + }); + + it("update the GoodDollar logic", async function () { + await loadFixture(initialState); + const sgdProxiable = await ethers.getContractAt( + "contracts/token/superfluid/UUPSProxiable.sol:UUPSProxiable", + sgd.address, + founder.signer + ); + + const auxCodeAddrBefore = await sgdProxiable.getCodeAddress(); + + const newLogic = await ( + await ethers.getContractFactory("SuperGoodDollar", founder) + ).deploy(newHost.address); + + await expect( + sgdProxiable.connect(eve).updateCode(newLogic.address) + ).revertedWith(/not owner/); + + await sgdProxiable.connect(founder).updateCode(newLogic.address); + + const auxCodeAddrAfter = await sgdProxiable.getCodeAddress(); + + assert.notEqual( + auxCodeAddrBefore, + auxCodeAddrAfter, + "code address unchanged" + ); + + expect(await sgd.getHost()).equal(newHost.address); + await expect( + sgd["initialize(string,string,uint256,address,address,address,address)"]( + "x", + "y", + 1, + ethers.constants.AddressZero, + ethers.constants.AddressZero, + ethers.constants.AddressZero, + ethers.constants.AddressZero + ) + ).revertedWith(/Initializable: contract is already initialized/); + }); + + describe("ERC20Permit", () => { + const name = "SuperGoodDollar"; + const version = "1"; + + const Permit = [ + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + { name: "value", type: "uint256" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" } + ]; + + it("initial nonce is 0", async function () { + expect(await sgd.nonces(alice.address)).to.equal(0); + }); + + it("domain separator", async function () { + const hashedDomain = await ethers.utils._TypedDataEncoder.hashDomain({ + name, + version, + chainId: 4447, + verifyingContract: sgd.address + }); + expect(await sgd.DOMAIN_SEPARATOR()).to.equal(hashedDomain); + }); + + describe("permit", function () { + const wallet = ethers.Wallet.createRandom(); + + const chainId = 4447; + const owner = wallet.address; + const value = 42; + const nonce = 0; + const maxDeadline = ethers.constants.MaxUint256; + + const buildData = ( + chainId, + verifyingContract, + deadline = maxDeadline + ) => ({ + primaryType: "Permit", + types: { Permit }, + domain: { name, version, chainId, verifyingContract }, + message: { owner, spender: bob.address, value, nonce, deadline } + }); + + it("accepts owner signature", async function () { + const data = buildData(chainId, sgd.address); + const signature = await wallet._signTypedData( + data.domain, + data.types, + data.message + ); + const { v, r, s } = ethers.utils.splitSignature(signature); + + await sgd.permit(owner, bob.address, value, maxDeadline, v, r, s); + + expect(await sgd.nonces(owner)).to.equal(1); + expect(await sgd.allowance(owner, bob.address)).to.equal(value); + }); + + it("rejects reused signature", async function () { + await loadFixture(initialState); + + const data = buildData(chainId, sgd.address); + const signature = await wallet._signTypedData( + data.domain, + data.types, + data.message + ); + const { v, r, s } = ethers.utils.splitSignature(signature); + + await sgd.permit(owner, bob.address, value, maxDeadline, v, r, s); + + await expect( + sgd.permit(owner, bob.address, value, maxDeadline, v, r, s) + ).revertedWith(/ERC20Permit: invalid signature/); + }); + + it("rejects other signature", async function () { + const otherWallet = ethers.Wallet.createRandom(); + const data = buildData(chainId, sgd.address); + const signature = await otherWallet._signTypedData( + data.domain, + data.types, + data.message + ); + const { v, r, s } = ethers.utils.splitSignature(signature); + + await expect( + sgd.permit(owner, bob.address, value, maxDeadline, v, r, s) + ).revertedWith(/ERC20Permit: invalid signature/); + }); + + it("rejects expired permit", async function () { + const block = await ethers.provider.getBlock("latest"); + const deadline = ethers.BigNumber.from(block.timestamp.toFixed(0)); + + const data = buildData(chainId, sgd.address, deadline); + const signature = await wallet._signTypedData( + data.domain, + data.types, + data.message + ); + const { v, r, s } = ethers.utils.splitSignature(signature); + + await expect( + sgd.permit(owner, bob.address, value, deadline, v, r, s) + ).revertedWith(/ERC20Permit: expired deadline/); + }); + }); + }); +}); diff --git a/test/token/SuperGoodDollar.test.ts b/test/token/SuperGoodDollar.test.ts index b2666034..48c7679b 100644 --- a/test/token/SuperGoodDollar.test.ts +++ b/test/token/SuperGoodDollar.test.ts @@ -249,17 +249,13 @@ describe("SuperGoodDollar", async function () { it("should not be able to initialize again", async () => { await loadFixture(initialState); await expect( - sgd[ - "initialize(string,string,uint256,address,address,address,address,address,address)" - ]( + sgd["initialize(string,string,uint256,address,address,address,address)"]( "x", "y", 1, ethers.constants.AddressZero, ethers.constants.AddressZero, ethers.constants.AddressZero, - ethers.constants.AddressZero, - ethers.constants.AddressZero, ethers.constants.AddressZero ) ).revertedWith(/Initializable: contract is already initialized/); @@ -304,17 +300,13 @@ describe("SuperGoodDollar", async function () { expect(await sgd.getHost()).equal(newHost.address); await expect( - sgd[ - "initialize(string,string,uint256,address,address,address,address,address,address)" - ]( + sgd["initialize(string,string,uint256,address,address,address,address)"]( "x", "y", 1, ethers.constants.AddressZero, ethers.constants.AddressZero, ethers.constants.AddressZero, - ethers.constants.AddressZero, - ethers.constants.AddressZero, ethers.constants.AddressZero ) ).revertedWith(/Initializable: contract is already initialized/); diff --git a/test/ubi/UBIScheme.e2e.test.ts b/test/ubi/UBIScheme.e2e.test.ts index 54876490..78e632dd 100644 --- a/test/ubi/UBIScheme.e2e.test.ts +++ b/test/ubi/UBIScheme.e2e.test.ts @@ -235,7 +235,7 @@ describe("UBIScheme - network e2e tests", () => { ); }); - it("should not be able to fish an active user", async () => { + it.skip("should not be able to fish an active user", async () => { let error = await ubi .connect(fisherman) .fish(claimer.address) @@ -244,7 +244,7 @@ describe("UBIScheme - network e2e tests", () => { expect(error.message).to.have.string("can't fish"); }); - it("should be able to fish inactive user", async () => { + it.skip("should be able to fish inactive user", async () => { await increaseTime(MAX_INACTIVE_DAYS * 86700); let balance1 = await goodDollar.balanceOf(fisherman.address); await ubi.connect(fisherman).fish(claimer.address); @@ -257,7 +257,7 @@ describe("UBIScheme - network e2e tests", () => { ); }); - it("should not be able to fish the same user twice", async () => { + it.skip("should not be able to fish the same user twice", async () => { let error = await ubi .connect(fisherman) .fish(claimer.address) @@ -265,7 +265,7 @@ describe("UBIScheme - network e2e tests", () => { expect(error.message).to.have.string("can't fish"); }); - it("should recieves a claim reward when call claim after being fished", async () => { + it.skip("should recieves a claim reward when call claim after being fished", async () => { let activeUsersCountBefore = await ubi.activeUsersCount(); let claimerBalanceBefore = await goodDollar.balanceOf(claimer.address); await ubi.connect(claimer).claim(); @@ -279,7 +279,7 @@ describe("UBIScheme - network e2e tests", () => { ).to.be.equal(1000); }); - it("should be able to fish by calling fishMulti", async () => { + it.skip("should be able to fish by calling fishMulti", async () => { await increaseTime(MAX_INACTIVE_DAYS * 86700); let amount = 1e8; await dai["mint(address,uint256)"]( diff --git a/test/ubi/UBISchemeCycle.test.ts b/test/ubi/UBISchemeCycle.test.ts index 7e3a89cd..591f2328 100644 --- a/test/ubi/UBISchemeCycle.test.ts +++ b/test/ubi/UBISchemeCycle.test.ts @@ -13,10 +13,19 @@ const ONE_DAY = 86400; describe("UBIScheme cycle", () => { let goodDollar, firstClaimPool; let reputation; - let root, acct, claimer1, claimer2, claimer3, signers, nameService, genericCall, ubiScheme: UBIScheme; + let root, + acct, + claimer1, + claimer2, + claimer3, + signers, + nameService, + genericCall, + ubiScheme: UBIScheme; before(async () => { - [root, acct, claimer1, claimer2, claimer3, ...signers] = await ethers.getSigners(); + [root, acct, claimer1, claimer2, claimer3, ...signers] = + await ethers.getSigners(); const deployedDAO = await loadFixture(createDAO); let { @@ -55,7 +64,9 @@ describe("UBIScheme cycle", () => { it("should be able to change cycleLength if avatar", async () => { // initializing the ubi - let encodedCall = ubiScheme.interface.encodeFunctionData("setCycleLength", [8]); + let encodedCall = ubiScheme.interface.encodeFunctionData("setCycleLength", [ + 8 + ]); await genericCall(ubiScheme.address, encodedCall); expect(await ubiScheme.cycleLength()).to.be.equal(8); }); @@ -70,7 +81,10 @@ describe("UBIScheme cycle", () => { it("should set ubischeme", async () => { // initializing the ubi - let encodedCall = firstClaimPool.interface.encodeFunctionData("setUBIScheme", [ubiScheme.address]); + let encodedCall = firstClaimPool.interface.encodeFunctionData( + "setUBIScheme", + [ubiScheme.address] + ); await genericCall(firstClaimPool.address, encodedCall); // await firstClaimPool.start(); @@ -78,13 +92,21 @@ describe("UBIScheme cycle", () => { it("should calculate cycle on first day", async () => { await increaseTime(2 * ONE_DAY); //make sure + + let balance = await goodDollar.balanceOf(ubiScheme.address); let transaction = await (await ubiScheme.connect(claimer1).claim()).wait(); - await ubiScheme.connect(claimer2).claim(); let cycleLength = await ubiScheme.cycleLength(); let currentCycle = await ubiScheme.currentCycleLength(); - let balance = await goodDollar.balanceOf(ubiScheme.address); + let dailyAmount = await ubiScheme.dailyUbi(); expect(currentCycle.toNumber()).to.be.gt(0); - const cycleEvent = transaction.events.find(e => e.event === "UBICycleCalculated"); + const cycleEvent = transaction.events.find( + e => e.event === "UBICycleCalculated" + ); + console.log({ + dailyAmount: dailyAmount.toString(), + event: cycleEvent.args + }); + expect(cycleEvent.args.day.toNumber()).to.be.a("number"); expect(cycleEvent.args.pool).to.be.equal(balance); expect(cycleEvent.args.cycleLength).to.be.equal(cycleLength); @@ -92,35 +114,53 @@ describe("UBIScheme cycle", () => { }); it("should have calculated dailyCyclePool and dailyUbi correctly", async () => { + await ubiScheme.connect(claimer2).claim(); increaseTime(ONE_DAY); let transaction = await ubiScheme.connect(claimer2).claim(); - expect(await goodDollar.balanceOf(claimer2.address).then(_ => _.toNumber())).to.be.equal( - 1000 + (await ubiScheme.dailyUbi().then(_ => _.toNumber())) - ); //first day 10G$ (1000 wei), second claim 125000 wei daily pool divided by 2 active users = 625000 - expect(await ubiScheme.dailyCyclePool().then(_ => _.toNumber())).to.be.equal(125000); - expect(await ubiScheme.currentDayInCycle().then(_ => _.toNumber())).to.be.equal(1); //1 day passed + expect( + await goodDollar.balanceOf(claimer2.address).then(_ => _.toNumber()) + ).to.be.equal(125 + (await ubiScheme.dailyUbi().then(_ => _.toNumber()))); //first day 125 , second claim 125000 wei daily pool divided by 1000 active users = 125 + expect( + await ubiScheme.dailyCyclePool().then(_ => _.toNumber()) + ).to.be.equal(125000); + expect( + await ubiScheme.currentDayInCycle().then(_ => _.toNumber()) + ).to.be.equal(1); //1 day passed }); it("should calculate next cycle even if day passed without claims(setDay)", async () => { increaseTime(ONE_DAY * 9); - expect(await ubiScheme.currentDayInCycle().then(_ => _.toNumber())).to.be.equal(10); //10 days passed total + expect( + await ubiScheme.currentDayInCycle().then(_ => _.toNumber()) + ).to.be.equal(10); //10 days passed total let transaction = await (await ubiScheme.connect(claimer1).claim()).wait(); //claims in new ubi cycle let dailyClaimAmount = (await ubiScheme.dailyCyclePool()).div(1000); //initialy we have by default min 1000 active users - expect(await goodDollar.balanceOf(claimer1.address)).to.be.equal(dailyClaimAmount.add(1000)); //intial 10 from first claim pool + daily - const cycleEvent = transaction.events.find(e => e.event === "UBICycleCalculated"); + expect(await goodDollar.balanceOf(claimer1.address)).to.be.equal( + dailyClaimAmount.add(125) + ); //intial 10 from first claim pool + daily + const cycleEvent = transaction.events.find( + e => e.event === "UBICycleCalculated" + ); expect(cycleEvent).to.be.not.empty; - expect(await ubiScheme.currentDayInCycle().then(_ => _.toNumber())).to.be.equal(0); //new cycle started - //intial balance on cycle start 1000000 - 125(one user that claimed) = 999875, divide by cycle length (8) = 124984 - expect(cycleEvent.args.dailyUBIPool).to.be.equal(124984); + expect( + await ubiScheme.currentDayInCycle().then(_ => _.toNumber()) + ).to.be.equal(0); //new cycle started + //intial balance on cycle start 1000000 - 375(3 user that claimed in previous tests) = 99625, divide by cycle length (8) = 124953 + expect(cycleEvent.args.dailyUBIPool).to.be.equal( + (BigInt(1000000) - BigInt(375)) / BigInt(8) + ); }); it("should calculate cycle early if we can increase current daily pool", async () => { //increase ubi pool balance - let encoded = goodDollar.interface.encodeFunctionData("mint", [ubiScheme.address, 400000]); + let encoded = goodDollar.interface.encodeFunctionData("mint", [ + ubiScheme.address, + 400000 + ]); await genericCall(goodDollar.address, encoded); let balance = await goodDollar.balanceOf(ubiScheme.address); @@ -133,7 +173,9 @@ describe("UBIScheme cycle", () => { const estimated = await ubiScheme.estimateNextDailyUBI(); await increaseTime(ONE_DAY); //make sure let transaction = await (await ubiScheme.connect(claimer1).claim()).wait(); - const cycleEvent = transaction.events.find(e => e.event === "UBICycleCalculated"); + const cycleEvent = transaction.events.find( + e => e.event === "UBICycleCalculated" + ); const dailyUBI = await ubiScheme.dailyUbi(); expect(dailyUBI).to.eq(estimated); //the estimated before actual calculation should be correct, ie equal to actual dailyUBI calculated after first claim. expect(cycleEvent).to.be.not.empty; @@ -145,7 +187,10 @@ describe("UBIScheme cycle", () => { it("should not calculate cycle early if not possible to increase daily ubi pool", async () => { //increase ubi pool balance - let encoded = goodDollar.interface.encodeFunctionData("mint", [ubiScheme.address, 100]); + let encoded = goodDollar.interface.encodeFunctionData("mint", [ + ubiScheme.address, + 100 + ]); await genericCall(goodDollar.address, encoded); let balance = await goodDollar.balanceOf(ubiScheme.address); const curCycleLen = await ubiScheme.cycleLength(); @@ -155,7 +200,9 @@ describe("UBIScheme cycle", () => { await increaseTime(ONE_DAY); //make sure let transaction = await (await ubiScheme.connect(claimer1).claim()).wait(); - const cycleEvent = transaction.events.find(e => e.event === "UBICycleCalculated"); + const cycleEvent = transaction.events.find( + e => e.event === "UBICycleCalculated" + ); expect(cycleEvent).to.be.undefined; }); }); diff --git a/test/ubi/UBISchemeV2.test.ts b/test/ubi/UBISchemeV2.test.ts new file mode 100644 index 00000000..9ea7a3f3 --- /dev/null +++ b/test/ubi/UBISchemeV2.test.ts @@ -0,0 +1,636 @@ +import { ethers, upgrades } from "hardhat"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { UBIScheme, UBISchemeV2 } from "../../types"; +import { createDAO, deployUBI, advanceBlocks, increaseTime } from "../helpers"; +import FirstClaimPool from "@gooddollar/goodcontracts/stakingModel/build/contracts/FirstClaimPool.json"; +import { flowedAmountSinceUpdatedAt } from "@superfluid-finance/sdk-core"; +const BN = ethers.BigNumber; +export const NULL_ADDRESS = "0x0000000000000000000000000000000000000000"; + +const MAX_INACTIVE_DAYS = 3; +const ONE_DAY = 86400; +const ONE_HOUR = 3600; + +describe("UBISchemeV2", () => { + let goodDollar, + identity, + formula, + avatar, + ubi: UBISchemeV2, + controller, + firstClaimPool, + setSchemes, + addWhitelisted; + let reputation; + let root, + claimer1, + claimer2, + claimer3, + claimer4, + claimer5, + claimer6, + claimer7, + claimer8, + signers, + fisherman, + nameService, + genericCall, + ubiScheme; + + before(async () => { + [ + root, + claimer1, + claimer2, + claimer3, + claimer4, + claimer5, + claimer6, + claimer7, + claimer8, + fisherman, + ...signers + ] = await ethers.getSigners(); + const fcFactory = new ethers.ContractFactory( + FirstClaimPool.abi, + FirstClaimPool.bytecode, + (await ethers.getSigners())[0] + ); + const deployedDAO = await loadFixture(createDAO); + let { + nameService: ns, + genericCall: gn, + reputation: rep, + setDAOAddress, + setSchemes: sc, + identityDeployed: id, + addWhitelisted: aw, + gd, + avatar: av + } = deployedDAO; + nameService = ns; + genericCall = gn; + reputation = rep; + setSchemes = sc; + avatar = av; + addWhitelisted = aw; + goodDollar = await ethers.getContractAt("IGoodDollar", gd); + firstClaimPool = await fcFactory.deploy( + await nameService.getAddress("AVATAR"), + await nameService.getAddress("IDENTITY"), + 100 + ); + identity = id; + // setDAOAddress("GDAO_CLAIMERS", cd.address); + //addWhitelisted(claimer1.address, "claimer1"); + //await addWhitelisted(claimer2.address, "claimer2"); + // await increaseTime(60 * 60 * 24); + }); + + async function deployNewUbi() { + return await upgrades.deployProxy( + await ethers.getContractFactory("UBISchemeV2"), + [nameService.address] + ); + } + + it("should not accept 0 inactive days in the constructor", async () => { + let ubi1 = await (await ethers.getContractFactory("UBIScheme")).deploy(); + + await expect( + ubi1.initialize(nameService.address, firstClaimPool.address, 0) + ).revertedWith(/Max inactive days cannot be zero/); + }); + + it("should deploy the ubi", async () => { + const block = await ethers.provider.getBlock("latest"); + const startUBI = block.timestamp; + ubi = await upgrades.deployProxy( + await ethers.getContractFactory("UBISchemeV2"), + [nameService.address] + ); + const periodStart = await ubi.periodStart(); + // initializing the ubi + let encodedCall = ubi.interface.encodeFunctionData("setCycleLength", [1]); + await genericCall(ubi.address, encodedCall); // we should set cyclelength to one cause this tests was implemented according to it + expect(periodStart.mod(60 * 60 * 24)).to.equal(60 * 60 * 12); + }); + + it.skip("should not be able to set the claim amount if the sender is not the avatar", async () => { + let error = await firstClaimPool.setClaimAmount(200).catch(e => e); + expect(error.message).to.have.string("only Avatar"); + }); + + it.skip("should not be able to set the ubi scheme if the sender is not the avatar", async () => { + let error = await firstClaimPool.setUBIScheme(ubi.address).catch(e => e); + expect(error.message).to.have.string("only Avatar"); + }); + + it("should return zero entitlement before UBI started", async () => { + let blockTimestamp = (await ethers.provider.getBlock("latest")).timestamp; + const timeInDay = blockTimestamp % ONE_DAY; + // Move to before 12pm of the current day + if (timeInDay > 12 * ONE_HOUR) { + blockTimestamp += 12 * ONE_HOUR; + await ethers.provider.send("evm_setNextBlockTimestamp", [blockTimestamp]); + await ethers.provider.send("evm_mine", []); + } + const ubiNew = await deployNewUbi(); + let amount = await ubiNew.connect(claimer1)["checkEntitlement()"](); + expect(amount).to.equal(0); + }); + + it("should start the ubi", async () => { + await setSchemes([ubi.address]); + // await ubi.start(); + const block = await ethers.provider.getBlock("latest"); + const startUBI = block.timestamp; + let periodStart = await ubi.periodStart().then(_ => _.toNumber()); + let startDate = new Date(periodStart * 1000); + expect(startDate.toISOString()).to.have.string("T12:00:00.000Z"); //contract set itself to start at noon GMT + await increaseTime(ONE_DAY / 2); // increase time half of the day to make sure ubi period started + }); + + it("should not be able to execute claiming when the caller is not whitelisted", async () => { + let error = await ubi.claim().catch(e => e); + expect(error.message).to.have.string("UBIScheme: not whitelisted"); + }); + + it.skip("should not be able to claim when the claim pool is not active", async () => { + await addWhitelisted(claimer1.address, "claimer1"); + let error = await ubi + .connect(claimer1) + .claim() + .catch(e => e); + expect(error.message).to.have.string("is not active"); + }); + + it.skip("should set the ubi scheme by avatar", async () => { + let encodedCall = firstClaimPool.interface.encodeFunctionData( + "setUBIScheme", + [NULL_ADDRESS] + ); + await genericCall(firstClaimPool.address, encodedCall); + const newUbi = await firstClaimPool.ubi(); + expect(newUbi.toString()).to.be.equal(NULL_ADDRESS); + }); + + it.skip("should not be able to claim when the ubi is not initialized", async () => { + await setSchemes([firstClaimPool.address]); + await firstClaimPool.start(); + let error = await ubi + .connect(claimer1) + .claim() + .catch(e => e); + expect(error.message).to.have.string("ubi has not initialized"); + + // initializing the ubi + let encodedCall = firstClaimPool.interface.encodeFunctionData( + "setUBIScheme", + [ubi.address] + ); + await genericCall(firstClaimPool.address, encodedCall); + }); + + it.skip("should not be able to call award user if the caller is not the ubi", async () => { + let error = await firstClaimPool.awardUser(claimer1.address).catch(e => e); + expect(error.message).to.have.string("Only UBIScheme can call this method"); + }); + + it("should estimate next daily UBI to according to min value when no claimers and not using first claim pool", async () => { + const nextDailyUBIBefore = await ubi.estimateNextDailyUBI(); + const minActiveUsers = await ubi.minActiveUsers(); + const cycleLength = await ubi.cycleLength(); + const pool = await goodDollar.balanceOf(ubi.address); + const dailyPool = pool.div(cycleLength); + const defaultDailyClaim = dailyPool.div(minActiveUsers); + // let encodedCall = ubi.interface.encodeFunctionData("setUseFirstClaimPool", [ + // false + // ]); + // await genericCall(ubi.address, encodedCall); + const nextDailyUBIAfter = await ubi.estimateNextDailyUBI(); + + expect(nextDailyUBIBefore.eq(0)); + expect(nextDailyUBIAfter.gt(nextDailyUBIBefore)); + expect(nextDailyUBIAfter.eq(defaultDailyClaim)); + + // encodedCall = ubi.interface.encodeFunctionData("setUseFirstClaimPool", [ + // true + // ]); + // await genericCall(ubi.address, encodedCall); + }); + + it.skip("should award a new user with 0 on first time execute claim if the first claim contract has no balance", async () => { + let tx = await (await ubi.connect(claimer1).claim()).wait(); + let claimer1Balance = await goodDollar.balanceOf(claimer1.address); + expect(claimer1Balance.toNumber()).to.be.equal(0); + + expect(tx.events.find(_ => _.event === "ActivatedUser")).to.be.not.empty; + expect(tx.events.find(_ => _.event === "UBIClaimed")).to.be.not.empty; + }); + + it.skip("should award a new user with the award amount on first time execute claim", async () => { + await goodDollar.mint(firstClaimPool.address, "10000000"); + await addWhitelisted(claimer2.address, "claimer2"); + let transaction = await (await ubi.connect(claimer2).claim()).wait(); + let activeUsersCount = await ubi.activeUsersCount(); + let claimer2Balance = await goodDollar.balanceOf(claimer2.address); + const [claimersCount, amountClaimed] = await ubi.getDailyStats(); + expect(claimer2Balance.toNumber()).to.be.equal(100); + expect(amountClaimed.eq(100)); + expect(activeUsersCount.toNumber()).to.be.equal(2); + expect(claimersCount.eq(2)); + expect(transaction.events.find(_ => _.event === "ActivatedUser")).to.be.not + .empty; + }); + + it.skip("should not be able to fish a new user", async () => { + let error = await ubi + .connect(fisherman) + .fish(claimer1.address) + .catch(e => e); + expect(error.message).to.have.string("can't fish"); + }); + + it("should not initiate the scheme balance and distribution formula when a new user execute claim", async () => { + let balance = await goodDollar.balanceOf(ubi.address); + let dailyUbi = await ubi.dailyUbi(); + expect(balance.toString()).to.be.equal("0"); + expect(dailyUbi.toString()).to.be.equal("0"); + }); + + it("should returns a valid distribution calculation when the current balance is lower than the number of daily claimers", async () => { + await addWhitelisted(claimer1.address, "claimer1"); + await addWhitelisted(claimer2.address, "claimer2"); + // there is 0.01 gd and 2 claimers + // this is an edge case + await goodDollar.mint(ubi.address, "1"); + await increaseTime(ONE_DAY); + await ubi.connect(claimer1).claim(); + await ubi.connect(claimer2).claim(); + let ubiBalance = await goodDollar.balanceOf(ubi.address); + await increaseTime(ONE_DAY); + let dailyUbi = await ubi.dailyUbi(); + let claimer1Balance = await goodDollar.balanceOf(claimer1.address); + expect(ubiBalance.toString()).to.be.equal("1"); + expect(dailyUbi.toString()).to.be.equal("0"); + expect(claimer1Balance.toString()).to.be.equal("0"); + }); + + it("should updates the daily stats when claiming", async () => { + await goodDollar.mint(ubi.address, 1000); + await addWhitelisted(claimer3.address, "claimer3"); + const [claimersBefore, amountBefore] = await ubi.getDailyStats(); + + await ubi.connect(claimer3).claim(); + + const [claimersAfter, amountAfter] = await ubi.getDailyStats(); + expect(claimersAfter.sub(claimersBefore).toString()).to.be.equal("1"); + expect(amountAfter).to.be.gt(amountBefore); + }); + + it.skip("should calculate the daily distribution and withdraw balance from the dao when an active user executes claim", async () => { + // checking that the distirbution works ok also when not all claimers claim + // achieving that goal by leaving the claimed amount of the second claimer + // in the ubi and in the next day after transferring the balances from the + // dao, making sure that the tokens that have not been claimed are + // taken by the formula as expected. + let encoded = goodDollar.interface.encodeFunctionData("transfer", [ + signers[0].address, + "1000" + ]); + + await genericCall(goodDollar.address, encoded); // There is 10gd initially allocated to avatar so I send it to another address for further transactions + let encodedCall = ubi.interface.encodeFunctionData( + "setShouldWithdrawFromDAO", + [true] + ); + await genericCall(ubi.address, encodedCall); // we should set cyclelength to one cause this tests was implemented according to it + const currentDay = await ubi.currentDayInCycle().then(_ => _.toNumber()); + await increaseTime(ONE_DAY); + await goodDollar.mint(avatar, "300002"); + //ubi will have 902GD in pool so daily ubi is now 300002/1(cycle)/1000(min claimers) = 300 + await ubi.connect(claimer1).claim(); + await increaseTime(ONE_DAY); + await goodDollar.mint(avatar, "1"); + //daily ubi is 0 since only 1 GD is in pool and can't be divided + // an edge case + await ubi.connect(claimer1).claim(); + let avatarBalance = await goodDollar.balanceOf(avatar); + let claimer1Balance = await goodDollar.balanceOf(claimer1.address); + expect(avatarBalance.toString()).to.be.equal("0"); + // 300 GD from first day and 299 (30002 - 300 /1 / 1000) from the second day claimed in this test + expect(claimer1Balance.toString()).to.be.equal("599"); + }); + + it.skip("should return the reward value for entitlement user", async () => { + let amount = await ubi.connect(claimer4)["checkEntitlement()"](); + let claimAmount = await firstClaimPool.claimAmount(); + expect(amount.toString()).to.be.equal(claimAmount.toString()); + }); + + it.skip("should return that a new user is not an active user", async () => { + let isActiveUser = await ubi.isActiveUser(claimer7.address); + expect(isActiveUser).to.be.false; + }); + + it.skip("should not be able to fish an active user", async () => { + await addWhitelisted(claimer4.address, "claimer4"); + await ubi.connect(claimer3).claim(); + await ubi.connect(claimer4).claim(); + let isActiveUser = await ubi.isActiveUser(claimer4.address); + let error = await ubi + .connect(fisherman) + .fish(claimer4.address) + .catch(e => e); + expect(isActiveUser).to.be.true; + expect(error.message).to.have.string("can't fish"); + }); + + it("should not be able to execute claim twice a day", async () => { + await goodDollar.mint(avatar, "20"); + await addWhitelisted(claimer4.address, "claimer4"); + await increaseTime(ONE_DAY); + let claimer4Balance1 = await goodDollar.balanceOf(claimer4.address); + await ubi.connect(claimer4).claim(); + let claimer4Balance2 = await goodDollar.balanceOf(claimer4.address); + let dailyUbi = await ubi.dailyUbi(); + await ubi.connect(claimer4).claim(); + let claimer4Balance3 = await goodDollar.balanceOf(claimer4.address); + expect( + claimer4Balance2.toNumber() - claimer4Balance1.toNumber() + ).to.be.equal(dailyUbi.toNumber()); + expect( + claimer4Balance3.toNumber() - claimer4Balance1.toNumber() + ).to.be.equal(dailyUbi.toNumber()); + }); + + it("should return the daily ubi for entitlement user", async () => { + // claimer3 hasn't claimed during that interval so that user + // may have the dailyUbi + let amount = await ubi.connect(claimer3)["checkEntitlement()"](); + let dailyUbi = await ubi.dailyUbi(); + expect(amount.toString()).to.be.equal(dailyUbi.toString()); + }); + it("should return 0 for entitlement if the user has already claimed for today", async () => { + await ubi.connect(claimer4).claim(); + let amount = await ubi.connect(claimer4)["checkEntitlement()"](); + expect(amount.toString()).to.be.equal("0"); + }); + + it.skip("should be able to fish inactive user", async () => { + await goodDollar.mint(avatar, "20"); + await increaseTime(MAX_INACTIVE_DAYS * ONE_DAY * 14); + let claimer4BalanceBefore = await goodDollar.balanceOf(claimer4.address); + let isFishedBefore = await ubi.fishedUsersAddresses(claimer1.address); + let tx = await (await ubi.connect(claimer4).fish(claimer1.address)).wait(); + let isFishedAfter = await ubi.fishedUsersAddresses(claimer1.address); + let claimer4BalanceAfter = await goodDollar.balanceOf(claimer4.address); + let dailyUbi = await ubi.dailyUbi(); + expect(isFishedBefore).to.be.false; + expect(isFishedAfter).to.be.true; + expect(tx.events.find(_ => _.event === "InactiveUserFished")).to.be.not + .empty; + expect( + claimer4BalanceAfter.toNumber() - claimer4BalanceBefore.toNumber() + ).to.be.equal(dailyUbi.toNumber()); + }); + + it.skip("should not be able to fish the same user twice", async () => { + await goodDollar.mint(avatar, "200"); + await increaseTime(MAX_INACTIVE_DAYS * ONE_DAY); + let claimer4BalanceBefore = await goodDollar.balanceOf(claimer4.address); + let isFishedBefore = await ubi.fishedUsersAddresses(claimer1.address); + let error = await ubi + .connect(claimer4) + .fish(claimer1.address) + .catch(e => e); + let isFishedAfter = await ubi.fishedUsersAddresses(claimer1.address); + let claimer4BalanceAfter = await goodDollar.balanceOf(claimer4.address); + expect(error.message).to.have.string("can't fish"); + expect(isFishedBefore).to.be.true; + expect(isFishedAfter).to.be.true; + expect(claimer4BalanceAfter.toNumber()).to.be.equal( + claimer4BalanceBefore.toNumber() + ); + }); + it.skip("should be able to fish multiple user", async () => { + await goodDollar.mint(avatar, "20"); + await increaseTime(MAX_INACTIVE_DAYS * ONE_DAY); + let claimer4BalanceBefore = await goodDollar.balanceOf(claimer4.address); + let tx = await ( + await ubi + .connect(claimer4) + .fishMulti([claimer2.address, claimer3.address]) + ).wait(); + let claimer4BalanceAfter = await goodDollar.balanceOf(claimer4.address); + let dailyUbi = await ubi.dailyUbi(); + const totalFishedEvent = tx.events.find(e => e.event === "TotalFished"); + expect(tx.events.find(e => e.event === "InactiveUserFished")).to.be.not + .empty; + expect( + claimer4BalanceAfter.toNumber() - claimer4BalanceBefore.toNumber() + ).to.be.equal(2 * dailyUbi.toNumber()); + expect(totalFishedEvent.args.total.toNumber() === 2).to.be.true; + }); + + it.skip("should not be able to remove an active user that no longer whitelisted", async () => { + await goodDollar.mint(avatar, "20"); + await ubi.connect(claimer2).claim(); // makes sure that the user is active + await identity.removeWhitelisted(claimer2.address); + let claimer4BalanceBefore = await goodDollar.balanceOf(claimer4.address); + let isFishedBefore = await ubi.fishedUsersAddresses(claimer2.address); + let error = await ubi + .connect(claimer4) + .fish(claimer2.address) + .catch(e => e); + let isFishedAfter = await ubi.fishedUsersAddresses(claimer2.address); + let claimer4BalanceAfter = await goodDollar.balanceOf(claimer4.address); + expect(error.message).to.have.string("can't fish"); + expect(isFishedBefore).to.be.false; + expect(isFishedAfter).to.be.false; + expect(claimer4BalanceAfter.toNumber()).to.be.equal( + claimer4BalanceBefore.toNumber() + ); + }); + + it.skip("should be able to remove an inactive user that no longer whitelisted", async () => { + await goodDollar.mint(avatar, "20"); + await increaseTime(MAX_INACTIVE_DAYS * ONE_DAY * 14); + let claimer4BalanceBefore = await goodDollar.balanceOf(claimer4.address); + let isFishedBefore = await ubi.fishedUsersAddresses(claimer2.address); + let tx = await (await ubi.connect(claimer4).fish(claimer2.address)).wait(); + let isFishedAfter = await ubi.fishedUsersAddresses(claimer2.address); + let claimer4BalanceAfter = await goodDollar.balanceOf(claimer4.address); + let dailyUbi = await ubi.dailyUbi(); + expect(isFishedBefore).to.be.false; + expect(isFishedAfter).to.be.true; + expect(tx.events.find(e => e.event === "InactiveUserFished")).to.be.not + .empty; + expect( + claimer4BalanceAfter.toNumber() - claimer4BalanceBefore.toNumber() + ).to.be.equal(dailyUbi.toNumber()); + }); + + it.skip("should be able to fish user that removed from the whitelist", async () => { + await goodDollar.mint(avatar, "20"); + await identity.addWhitelisted(claimer2.address); + await ubi.connect(claimer2).claim(); + await increaseTime(MAX_INACTIVE_DAYS * ONE_DAY * 14); + await identity.removeWhitelisted(claimer2.address); + let claimer4BalanceBefore = await goodDollar.balanceOf(claimer4.address); + let isFishedBefore = await ubi.fishedUsersAddresses(claimer2.address); + let tx = await (await ubi.connect(claimer4).fish(claimer2.address)).wait(); + let isFishedAfter = await ubi.fishedUsersAddresses(claimer2.address); + let claimer4BalanceAfter = await goodDollar.balanceOf(claimer4.address); + let dailyUbi = await ubi.dailyUbi(); + expect(isFishedBefore).to.be.false; + expect(isFishedAfter).to.be.true; + expect(tx.events.find(e => e.event === "InactiveUserFished")).to.be.not + .empty; + expect( + claimer4BalanceAfter.toNumber() - claimer4BalanceBefore.toNumber() + ).to.be.equal(dailyUbi.toNumber()); + }); + + it.skip("should recieves a claim reward on claim after removed and added again to the whitelist", async () => { + let isFishedBefore = await ubi.fishedUsersAddresses(claimer2.address); + let activeUsersCountBefore = await ubi.activeUsersCount(); + await identity.addWhitelisted(claimer2.address); + let claimerBalanceBefore = await goodDollar.balanceOf(claimer2.address); + await ubi.connect(claimer2).claim(); + let claimerBalanceAfter = await goodDollar.balanceOf(claimer2.address); + let isFishedAfter = await ubi.fishedUsersAddresses(claimer2.address); + let activeUsersCountAfter = await ubi.activeUsersCount(); + expect(isFishedBefore).to.be.true; + expect(isFishedAfter).to.be.false; + expect( + activeUsersCountAfter.toNumber() - activeUsersCountBefore.toNumber() + ).to.be.equal(1); + expect( + claimerBalanceAfter.toNumber() - claimerBalanceBefore.toNumber() + ).to.be.equal(100); + }); + + it("distribute formula should return correct value", async () => { + await goodDollar.mint(avatar, "20000"); + await increaseTime(ONE_DAY); + let ubiBalance = await goodDollar.balanceOf(ubi.address); + let activeUsersCount = await ubi.minActiveUsers(); // await ubi.activeUsersCount(); + let claimer4BalanceBefore = await goodDollar.balanceOf(claimer2.address); + await ubi.connect(claimer2).claim(); + let claimer4BalanceAfter = await goodDollar.balanceOf(claimer2.address); + expect(ubiBalance.div(activeUsersCount).toNumber()).to.be.equal( + claimer4BalanceAfter.toNumber() - claimer4BalanceBefore.toNumber() + ); + console.log({ + ubiBalance, + activeUsersCount, + claimer4BalanceBefore, + claimer4BalanceAfter + }); + }); + + it("distribute formula should return correct value while gd has transferred directly to the ubi", async () => { + await goodDollar.mint(ubi.address, "200"); + await increaseTime(ONE_DAY); + let ubiBalance = await goodDollar.balanceOf(ubi.address); + let activeUsersCount = await ubi.minActiveUsers(); // await ubi.activeUsersCount(); + let claimer4BalanceBefore = await goodDollar.balanceOf(claimer2.address); + await ubi.connect(claimer2).claim(); + let claimer4BalanceAfter = await goodDollar.balanceOf(claimer2.address); + let dailyUbi = await ubi.dailyUbi(); + expect(ubiBalance.div(activeUsersCount).toNumber()).to.be.equal( + claimer4BalanceAfter.toNumber() - claimer4BalanceBefore.toNumber() + ); + expect(ubiBalance.div(activeUsersCount).toNumber()).to.be.equal( + dailyUbi.toNumber() + ); + }); + + it("should calcualte the correct distribution formula and transfer the correct amount when the ubi has a large amount of tokens", async () => { + await increaseTime(ONE_DAY); + await goodDollar.mint(avatar, "948439324829"); // checking claim with a random number + await increaseTime(ONE_DAY * 2); + await identity.authenticate(claimer1.address); + // first claim + await ubi.connect(claimer1).claim(); + await increaseTime(ONE_DAY * 2); + let claimer1Balance1 = await goodDollar.balanceOf(claimer1.address); + // regular claim + await ubi.connect(claimer1).claim(); + let claimer1Balance2 = await goodDollar.balanceOf(claimer1.address); + let dailyCyclePool = await ubi.dailyCyclePool(); + let activeUsersCount = await ubi.minActiveUsers(); // await ubi.activeUsersCount(); + // the dailyCyclePool divided by max(activeUser,minActiveUsers) should give the daily claim (diff between ubipool balances) + expect(claimer1Balance2.sub(claimer1Balance1).toString()).to.be.equal( + dailyCyclePool.div(activeUsersCount) + ); + }); + + it.skip("should be able to iterate over all accounts if enough gas in fishMulti", async () => { + //should not reach fishin first user because atleast 150k gas is required + let tx = await ubi + .connect(fisherman) + .fishMulti([claimer5.address, claimer6.address, claimer1.address], { + gasLimit: 100000 + }) + .then(_ => true) + .catch(e => console.log({ e })); + expect(tx).to.be.true; + //should loop over all users when enough gas without exceptions + let res = await ubi + .fishMulti([claimer5.address, claimer6.address, claimer1.address], { + gasLimit: 1000000 + }) + .then(_ => true) + .catch(e => console.log({ e })); + expect(res).to.be.true; + }); + + it("should return the estimated claim value for entitlement user before anyone claimed", async () => { + await increaseTime(ONE_DAY); + await ubi.connect(claimer1).claim(); + await increaseTime(ONE_DAY); + let amount = await ubi.connect(claimer1)["checkEntitlement()"](); + let balance2 = await goodDollar.balanceOf(ubi.address); + let estimated = await ubi.estimateNextDailyUBI(); + expect(amount).to.be.equal(estimated); + }); + + it.skip("should set the ubi claim amount by avatar", async () => { + let encodedCall = firstClaimPool.interface.encodeFunctionData( + "setClaimAmount", + [200] + ); + + await genericCall(firstClaimPool.address, encodedCall); + const claimAmount = await firstClaimPool.claimAmount(); + expect(claimAmount.toString()).to.be.equal("200"); + }); + + it.skip("should set if withdraw from the dao or not", async () => { + let encodedCall = ubi.interface.encodeFunctionData( + "setShouldWithdrawFromDAO", + [false] + ); + await genericCall(ubi.address, encodedCall); // we should set cyclelength to one cause this tests was implemented according to it + const shouldWithdrawFromDAO = await ubi.shouldWithdrawFromDAO(); + expect(shouldWithdrawFromDAO).to.be.equal(false); + }); + + it("should award first claimer with default value when not using first claim pool", async () => { + const ubiNew = await deployNewUbi(); + await goodDollar.mint(ubiNew.address, 1000); + const expectedDailyUbi = 1; // 1000 divided by minActiveUsers which is also 1000 by default + await addWhitelisted(claimer8.address, "claimer8"); + + const claimerBalanceBefore = await goodDollar.balanceOf(claimer8.address); + await (await ubiNew.connect(claimer8).claim()).wait(); + const claimerBalanceAfter = await goodDollar.balanceOf(claimer8.address); + + expect(claimerBalanceAfter.gt(claimerBalanceBefore)); + expect(claimerBalanceAfter.sub(claimerBalanceBefore).eq(expectedDailyUbi)); + }); +}); diff --git a/test/utils/AdminWallet.test.ts b/test/utils/AdminWallet.test.ts index f8e1257e..6793232d 100644 --- a/test/utils/AdminWallet.test.ts +++ b/test/utils/AdminWallet.test.ts @@ -42,7 +42,7 @@ describe("AdminWallet", () => { toWhitelist ] = signers.slice(10); let { identity: id, nameService, gd } = await loadFixture(createDAO); - identity = await ethers.getContractAt("IdentityV2", id); + identity = await ethers.getContractAt("IdentityV3", id); gooddollar = await ethers.getContractAt("GoodDollar", gd); adminWallet = (await upgrades.deployProxy( await ethers.getContractFactory("AdminWallet"), diff --git a/test/utils/ProxyFactory.test.ts b/test/utils/ProxyFactory.test.ts index de5d3f6a..ee274301 100644 --- a/test/utils/ProxyFactory.test.ts +++ b/test/utils/ProxyFactory.test.ts @@ -115,7 +115,10 @@ describe("proxyfactory", () => { ).wait(); const proxyAddr = deployTX.events.find(_ => _.event === "ProxyCreated").args .proxy; - let proxy = await ethers.getContractAt("ERC1967Proxy", proxyAddr); + let proxy = await ethers.getContractAt( + "contracts/utils/ProxyFactory1967.sol:ERC1967Proxy", + proxyAddr + ); const c2 = await ( await ethers.getContractFactory("UpgradableMock") diff --git a/yarn.lock b/yarn.lock index ab6323dd..ab95123c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1658,6 +1658,23 @@ __metadata: languageName: node linkType: hard +"@ethersproject/abi@npm:5.8.0, @ethersproject/abi@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/abi@npm:5.8.0" + dependencies: + "@ethersproject/address": ^5.8.0 + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/constants": ^5.8.0 + "@ethersproject/hash": ^5.8.0 + "@ethersproject/keccak256": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + "@ethersproject/strings": ^5.8.0 + checksum: cdab990d520fdbfd63d4a8829e78a2d2d2cc110dc3461895bd9014a49d3a9028c2005a11e2569c3fd620cb7780dcb5c71402630a8082a9ca5f85d4f8700d4549 + languageName: node + linkType: hard + "@ethersproject/abi@npm:^5.0.0-beta.146, @ethersproject/abi@npm:^5.1.2": version: 5.4.0 resolution: "@ethersproject/abi@npm:5.4.0" @@ -1690,6 +1707,21 @@ __metadata: languageName: node linkType: hard +"@ethersproject/abstract-provider@npm:5.8.0, @ethersproject/abstract-provider@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/abstract-provider@npm:5.8.0" + dependencies: + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/networks": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + "@ethersproject/transactions": ^5.8.0 + "@ethersproject/web": ^5.8.0 + checksum: 4fd00d770552af53be297c676f31d938f5dc44d73c24970036a11237a53f114cc1c551fd95937b9eca790f77087da1ed3ec54f97071df088d5861f575fd4f9be + languageName: node + linkType: hard + "@ethersproject/abstract-provider@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/abstract-provider@npm:5.4.0" @@ -1718,6 +1750,19 @@ __metadata: languageName: node linkType: hard +"@ethersproject/abstract-signer@npm:5.8.0, @ethersproject/abstract-signer@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/abstract-signer@npm:5.8.0" + dependencies: + "@ethersproject/abstract-provider": ^5.8.0 + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + checksum: 3f7a98caf7c01e58da45d879c08449d1443bced36ac81938789c90d8f9ff86a1993655bae9805fc7b31a723b7bd7b4f1f768a9ec65dff032d0ebdc93133c14f3 + languageName: node + linkType: hard + "@ethersproject/abstract-signer@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/abstract-signer@npm:5.4.0" @@ -1744,6 +1789,19 @@ __metadata: languageName: node linkType: hard +"@ethersproject/address@npm:5.8.0, @ethersproject/address@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/address@npm:5.8.0" + dependencies: + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/keccak256": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/rlp": ^5.8.0 + checksum: fa48e16403b656207f996ee7796f0978a146682f10f345b75aa382caa4a70fbfdc6ff585e9955e4779f4f15a31628929b665d288b895cea5df206c070266aea1 + languageName: node + linkType: hard + "@ethersproject/address@npm:^5.0.2, @ethersproject/address@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/address@npm:5.4.0" @@ -1766,6 +1824,15 @@ __metadata: languageName: node linkType: hard +"@ethersproject/base64@npm:5.8.0, @ethersproject/base64@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/base64@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + checksum: f0c2136c99b2fd2f93b7e110958eacc5990e88274b1f38eb73d8eaa31bdead75fc0c4608dac23cb5718ae455b965de9dc5023446b96de62ef1fa945cbf212096 + languageName: node + linkType: hard + "@ethersproject/base64@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/base64@npm:5.4.0" @@ -1785,6 +1852,16 @@ __metadata: languageName: node linkType: hard +"@ethersproject/basex@npm:5.8.0, @ethersproject/basex@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/basex@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + checksum: 7b502b91011d3aac9bf38d77aad113632440a1eab6a966ffbe2c23f9e3758a4dcb2a4189ab2948d6996250d0cb716d7445e7e2103d03b94097a77c0e128f9ab7 + languageName: node + linkType: hard + "@ethersproject/bignumber@npm:5.7.0, @ethersproject/bignumber@npm:>=5.0.0-beta.130, @ethersproject/bignumber@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bignumber@npm:5.7.0" @@ -1796,6 +1873,17 @@ __metadata: languageName: node linkType: hard +"@ethersproject/bignumber@npm:5.8.0, @ethersproject/bignumber@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/bignumber@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + bn.js: ^5.2.1 + checksum: c87017f466b32d482e4b39370016cfc3edafc2feb89377011c54cd2e7dd011072ef4f275df59cd9fe080a187066082c1808b2682d97547c4fb9e6912331200c3 + languageName: node + linkType: hard + "@ethersproject/bignumber@npm:^5.4.0": version: 5.4.1 resolution: "@ethersproject/bignumber@npm:5.4.1" @@ -1816,6 +1904,15 @@ __metadata: languageName: node linkType: hard +"@ethersproject/bytes@npm:5.8.0, @ethersproject/bytes@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/bytes@npm:5.8.0" + dependencies: + "@ethersproject/logger": ^5.8.0 + checksum: 507e8ef1f1559590b4e78e3392a2b16090e96fb1091e0b08d3a8491df65976b313c29cdb412594454f68f9f04d5f77ea5a400b489d80a3e46a608156ef31b251 + languageName: node + linkType: hard + "@ethersproject/bytes@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/bytes@npm:5.4.0" @@ -1834,6 +1931,15 @@ __metadata: languageName: node linkType: hard +"@ethersproject/constants@npm:5.8.0, @ethersproject/constants@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/constants@npm:5.8.0" + dependencies: + "@ethersproject/bignumber": ^5.8.0 + checksum: 74830c44f4315a1058b905c73be7a9bb92850e45213cb28a957447b8a100f22a514f4500b0ea5ac7a995427cecef9918af39ae4e0e0ecf77aa4835b1ea5c3432 + languageName: node + linkType: hard + "@ethersproject/constants@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/constants@npm:5.4.0" @@ -1861,6 +1967,24 @@ __metadata: languageName: node linkType: hard +"@ethersproject/contracts@npm:5.8.0": + version: 5.8.0 + resolution: "@ethersproject/contracts@npm:5.8.0" + dependencies: + "@ethersproject/abi": ^5.8.0 + "@ethersproject/abstract-provider": ^5.8.0 + "@ethersproject/abstract-signer": ^5.8.0 + "@ethersproject/address": ^5.8.0 + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/constants": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + "@ethersproject/transactions": ^5.8.0 + checksum: cb181012bd55cc19c08f136e56e28e922f1ca66af66747a1b3f58a2aea5b3332bc7ecfe2d23fa14245e7fd45a4fdc4f3427a345c2e9873a9792838cdfe4c62d5 + languageName: node + linkType: hard + "@ethersproject/hash@npm:5.7.0, @ethersproject/hash@npm:>=5.0.0-beta.128, @ethersproject/hash@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/hash@npm:5.7.0" @@ -1878,6 +2002,23 @@ __metadata: languageName: node linkType: hard +"@ethersproject/hash@npm:5.8.0, @ethersproject/hash@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/hash@npm:5.8.0" + dependencies: + "@ethersproject/abstract-signer": ^5.8.0 + "@ethersproject/address": ^5.8.0 + "@ethersproject/base64": ^5.8.0 + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/keccak256": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + "@ethersproject/strings": ^5.8.0 + checksum: e1feb47a98c631548b0f98ef0b1eb1b964bc643d5dea12a0eeb533165004cfcfe6f1d2bb32f31941f0b91e6a82212ad5c8577d6d465fba62c38fc0c410941feb + languageName: node + linkType: hard + "@ethersproject/hash@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/hash@npm:5.4.0" @@ -1914,6 +2055,26 @@ __metadata: languageName: node linkType: hard +"@ethersproject/hdnode@npm:5.8.0, @ethersproject/hdnode@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/hdnode@npm:5.8.0" + dependencies: + "@ethersproject/abstract-signer": ^5.8.0 + "@ethersproject/basex": ^5.8.0 + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/pbkdf2": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + "@ethersproject/sha2": ^5.8.0 + "@ethersproject/signing-key": ^5.8.0 + "@ethersproject/strings": ^5.8.0 + "@ethersproject/transactions": ^5.8.0 + "@ethersproject/wordlists": ^5.8.0 + checksum: 72cc6bd218dbe3565b915f3fd8654562003b1b160a5ace8c8959e319333712a0951887641f6888ef91017a39bb804204fc09fb7e5064e3acf76ad701c2ff1266 + languageName: node + linkType: hard + "@ethersproject/json-wallets@npm:5.7.0, @ethersproject/json-wallets@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/json-wallets@npm:5.7.0" @@ -1935,6 +2096,27 @@ __metadata: languageName: node linkType: hard +"@ethersproject/json-wallets@npm:5.8.0, @ethersproject/json-wallets@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/json-wallets@npm:5.8.0" + dependencies: + "@ethersproject/abstract-signer": ^5.8.0 + "@ethersproject/address": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/hdnode": ^5.8.0 + "@ethersproject/keccak256": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/pbkdf2": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + "@ethersproject/random": ^5.8.0 + "@ethersproject/strings": ^5.8.0 + "@ethersproject/transactions": ^5.8.0 + aes-js: 3.0.0 + scrypt-js: 3.0.1 + checksum: 8e0f8529f683d0a3fab1c76173bfccf7fc03a27e291344c86797815872722770be787e91f8fa83c37b0abfc47d5f2a2d0eca0ab862effb5539ad545e317f8d83 + languageName: node + linkType: hard + "@ethersproject/keccak256@npm:5.7.0, @ethersproject/keccak256@npm:>=5.0.0-beta.127, @ethersproject/keccak256@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/keccak256@npm:5.7.0" @@ -1945,6 +2127,16 @@ __metadata: languageName: node linkType: hard +"@ethersproject/keccak256@npm:5.8.0, @ethersproject/keccak256@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/keccak256@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + js-sha3: 0.8.0 + checksum: af3621d2b18af6c8f5181dacad91e1f6da4e8a6065668b20e4c24684bdb130b31e45e0d4dbaed86d4f1314d01358aa119f05be541b696e455424c47849d81913 + languageName: node + linkType: hard + "@ethersproject/keccak256@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/keccak256@npm:5.4.0" @@ -1962,6 +2154,13 @@ __metadata: languageName: node linkType: hard +"@ethersproject/logger@npm:5.8.0, @ethersproject/logger@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/logger@npm:5.8.0" + checksum: 6249885a7fd4a5806e4c8700b76ffcc8f1ff00d71f31aa717716a89fa6b391de19fbb0cb5ae2560b9f57ec0c2e8e0a11ebc2099124c73d3b42bc58e3eedc41d1 + languageName: node + linkType: hard + "@ethersproject/logger@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/logger@npm:5.4.0" @@ -1978,6 +2177,15 @@ __metadata: languageName: node linkType: hard +"@ethersproject/networks@npm:5.8.0, @ethersproject/networks@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/networks@npm:5.8.0" + dependencies: + "@ethersproject/logger": ^5.8.0 + checksum: b1d43fdab13e32be74b5547968c7e54786915a1c3543025c628f634872038750171bef15db0cf42a27e568175b185ac9c185a9aae8f93839452942c5a867c908 + languageName: node + linkType: hard + "@ethersproject/networks@npm:^5.4.0": version: 5.4.1 resolution: "@ethersproject/networks@npm:5.4.1" @@ -1997,6 +2205,16 @@ __metadata: languageName: node linkType: hard +"@ethersproject/pbkdf2@npm:5.8.0, @ethersproject/pbkdf2@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/pbkdf2@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/sha2": ^5.8.0 + checksum: 79e06ec6063e745a714c7c3f8ecfb7a8d2db2d19d45ad0e84e59526f685a2704f06e8c8fbfaf3aca85d15037bead7376d704529aac783985e1ff7b90c2d6e714 + languageName: node + linkType: hard + "@ethersproject/properties@npm:5.7.0, @ethersproject/properties@npm:>=5.0.0-beta.131, @ethersproject/properties@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/properties@npm:5.7.0" @@ -2006,6 +2224,15 @@ __metadata: languageName: node linkType: hard +"@ethersproject/properties@npm:5.8.0, @ethersproject/properties@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/properties@npm:5.8.0" + dependencies: + "@ethersproject/logger": ^5.8.0 + checksum: 2bb0369a3c25a7c1999e990f73a9db149a5e514af253e3945c7728eaea5d864144da6a81661c0c414b97be75db7fb15c34f719169a3adb09e585a3286ea78b9c + languageName: node + linkType: hard + "@ethersproject/properties@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/properties@npm:5.4.0" @@ -2043,6 +2270,34 @@ __metadata: languageName: node linkType: hard +"@ethersproject/providers@npm:5.8.0, @ethersproject/providers@npm:^5.7.2": + version: 5.8.0 + resolution: "@ethersproject/providers@npm:5.8.0" + dependencies: + "@ethersproject/abstract-provider": ^5.8.0 + "@ethersproject/abstract-signer": ^5.8.0 + "@ethersproject/address": ^5.8.0 + "@ethersproject/base64": ^5.8.0 + "@ethersproject/basex": ^5.8.0 + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/constants": ^5.8.0 + "@ethersproject/hash": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/networks": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + "@ethersproject/random": ^5.8.0 + "@ethersproject/rlp": ^5.8.0 + "@ethersproject/sha2": ^5.8.0 + "@ethersproject/strings": ^5.8.0 + "@ethersproject/transactions": ^5.8.0 + "@ethersproject/web": ^5.8.0 + bech32: 1.1.4 + ws: 8.18.0 + checksum: 2970ee03fe61bc941555b57075d4a12fbb6342ee56181ad75250a75e9418403e85821bbea1b6e17b25ef35e9eaa1c2b2c564dad7d20af2c1f28ba6db9d0c7ce3 + languageName: node + linkType: hard + "@ethersproject/random@npm:5.7.0, @ethersproject/random@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/random@npm:5.7.0" @@ -2053,6 +2308,16 @@ __metadata: languageName: node linkType: hard +"@ethersproject/random@npm:5.8.0, @ethersproject/random@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/random@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + checksum: c3bec10516b433eca7ddbd5d97cf2c24153f8fb9615225ea2e3b7fab95a6d6434ab8af55ce55527c3aeb00546ee4363a43aecdc0b5a9970a207ab1551783ddef + languageName: node + linkType: hard + "@ethersproject/rlp@npm:5.7.0, @ethersproject/rlp@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/rlp@npm:5.7.0" @@ -2063,6 +2328,16 @@ __metadata: languageName: node linkType: hard +"@ethersproject/rlp@npm:5.8.0, @ethersproject/rlp@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/rlp@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + checksum: 9d6f646072b3dd61de993210447d35744a851d24d4fe6262856e372f47a1e9d90976031a9fa28c503b1a4f39dd5ab7c20fc9b651b10507a09b40a33cb04a19f1 + languageName: node + linkType: hard + "@ethersproject/rlp@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/rlp@npm:5.4.0" @@ -2084,6 +2359,17 @@ __metadata: languageName: node linkType: hard +"@ethersproject/sha2@npm:5.8.0, @ethersproject/sha2@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/sha2@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + hash.js: 1.1.7 + checksum: ef8916e3033502476fba9358ba1993722ac3bb99e756d5681e4effa3dfa0f0bf0c29d3fa338662830660b45dd359cccb06ba40bc7b62cfd44f4a177b25829404 + languageName: node + linkType: hard + "@ethersproject/signing-key@npm:5.7.0, @ethersproject/signing-key@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/signing-key@npm:5.7.0" @@ -2098,6 +2384,20 @@ __metadata: languageName: node linkType: hard +"@ethersproject/signing-key@npm:5.8.0, @ethersproject/signing-key@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/signing-key@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + bn.js: ^5.2.1 + elliptic: 6.6.1 + hash.js: 1.1.7 + checksum: 8c07741bc8275568130d97da5d37535c813c842240d0b3409d5e057321595eaf65660c207abdee62e2d7ba225d9b82f0b711ac0324c8c9ceb09a815b231b9f55 + languageName: node + linkType: hard + "@ethersproject/signing-key@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/signing-key@npm:5.4.0" @@ -2126,6 +2426,20 @@ __metadata: languageName: node linkType: hard +"@ethersproject/solidity@npm:5.8.0": + version: 5.8.0 + resolution: "@ethersproject/solidity@npm:5.8.0" + dependencies: + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/keccak256": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/sha2": ^5.8.0 + "@ethersproject/strings": ^5.8.0 + checksum: 305166f3f8e8c2f5ad7b0b03ab96d52082fc79b5136601175e1c76d7abd8fd8e3e4b56569dea745dfa2b7fcbfd180c5d824b03fea7e08dd53d515738a35e51dd + languageName: node + linkType: hard + "@ethersproject/strings@npm:5.7.0, @ethersproject/strings@npm:>=5.0.0-beta.130, @ethersproject/strings@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/strings@npm:5.7.0" @@ -2137,6 +2451,17 @@ __metadata: languageName: node linkType: hard +"@ethersproject/strings@npm:5.8.0, @ethersproject/strings@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/strings@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/constants": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + checksum: 997396cf1b183ae66ebfd97b9f98fd50415489f9246875e7769e57270ffa1bffbb62f01430eaac3a0c9cb284e122040949efe632a0221012ee47de252a44a483 + languageName: node + linkType: hard + "@ethersproject/strings@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/strings@npm:5.4.0" @@ -2165,6 +2490,23 @@ __metadata: languageName: node linkType: hard +"@ethersproject/transactions@npm:5.8.0, @ethersproject/transactions@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/transactions@npm:5.8.0" + dependencies: + "@ethersproject/address": ^5.8.0 + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/constants": ^5.8.0 + "@ethersproject/keccak256": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + "@ethersproject/rlp": ^5.8.0 + "@ethersproject/signing-key": ^5.8.0 + checksum: e867516ccc692c3642bfbd34eab6d2acecabb3b964d8e1cced8e450ec4fa490bcf2513efb6252637bc3157ecd5e0250dadd1a08d3ec3150c14478b9ec7715570 + languageName: node + linkType: hard + "@ethersproject/transactions@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/transactions@npm:5.4.0" @@ -2193,6 +2535,17 @@ __metadata: languageName: node linkType: hard +"@ethersproject/units@npm:5.8.0": + version: 5.8.0 + resolution: "@ethersproject/units@npm:5.8.0" + dependencies: + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/constants": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + checksum: cc7180c85f695449c20572602971145346fc5c169ee32f23d79ac31cc8c9c66a2049e3ac852b940ddccbe39ab1db3b81e3e093b604d9ab7ab27639ecb933b270 + languageName: node + linkType: hard + "@ethersproject/wallet@npm:5.7.0": version: 5.7.0 resolution: "@ethersproject/wallet@npm:5.7.0" @@ -2216,6 +2569,29 @@ __metadata: languageName: node linkType: hard +"@ethersproject/wallet@npm:5.8.0, @ethersproject/wallet@npm:^5.7.0": + version: 5.8.0 + resolution: "@ethersproject/wallet@npm:5.8.0" + dependencies: + "@ethersproject/abstract-provider": ^5.8.0 + "@ethersproject/abstract-signer": ^5.8.0 + "@ethersproject/address": ^5.8.0 + "@ethersproject/bignumber": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/hash": ^5.8.0 + "@ethersproject/hdnode": ^5.8.0 + "@ethersproject/json-wallets": ^5.8.0 + "@ethersproject/keccak256": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + "@ethersproject/random": ^5.8.0 + "@ethersproject/signing-key": ^5.8.0 + "@ethersproject/transactions": ^5.8.0 + "@ethersproject/wordlists": ^5.8.0 + checksum: d2921c3212c30a49048e0cba7a8287e0d53a5346ad5a15d46d9932991dc54e541a3da063c47addc1347a4b65142d7239f7056c8716d6f85c8ec4a1bf6b5d2f69 + languageName: node + linkType: hard + "@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.0": version: 5.7.1 resolution: "@ethersproject/web@npm:5.7.1" @@ -2229,6 +2605,19 @@ __metadata: languageName: node linkType: hard +"@ethersproject/web@npm:5.8.0, @ethersproject/web@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/web@npm:5.8.0" + dependencies: + "@ethersproject/base64": ^5.8.0 + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + "@ethersproject/strings": ^5.8.0 + checksum: d8ca89bde8777ed1eec81527f8a989b939b4625b2f6c275eea90031637a802ad68bf46911fdd43c5e84ea2962b8a3cb4801ab51f5393ae401a163c17c774123f + languageName: node + linkType: hard + "@ethersproject/web@npm:^5.4.0": version: 5.4.0 resolution: "@ethersproject/web@npm:5.4.0" @@ -2255,6 +2644,19 @@ __metadata: languageName: node linkType: hard +"@ethersproject/wordlists@npm:5.8.0, @ethersproject/wordlists@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/wordlists@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + "@ethersproject/hash": ^5.8.0 + "@ethersproject/logger": ^5.8.0 + "@ethersproject/properties": ^5.8.0 + "@ethersproject/strings": ^5.8.0 + checksum: ba24300927a3c9cb85ae8ace84a6be73f3c43aac6eab7a3abe58a7dfd3b168caf3f01a4528efa8193e269dd3d5efe9d4533bdf3b29d5c55743edcb2e864d25d9 + languageName: node + linkType: hard + "@gnosis.pm/safe-core-sdk-types@npm:^1.7.0": version: 1.7.0 resolution: "@gnosis.pm/safe-core-sdk-types@npm:1.7.0" @@ -2360,7 +2762,7 @@ __metadata: "@mean-finance/uniswap-v3-oracle": ^1.0.3 "@nomicfoundation/hardhat-chai-matchers": 1 "@nomicfoundation/hardhat-network-helpers": ^1.0.8 - "@nomicfoundation/hardhat-verify": 2 + "@nomicfoundation/hardhat-verify": ^2.1.0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.6 "@openzeppelin/contracts": ^4.8.0 @@ -2395,6 +2797,7 @@ __metadata: graphql-request: ^3.4.0 hardhat: ^2.* hardhat-contract-sizer: ^2.6.1 + hardhat-deploy: ^1.0.4 hardhat-gas-reporter: ^1.0.8 hardhat-storage-layout: ^0.1.7 lodash: ^4.17.21 @@ -2870,9 +3273,9 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/hardhat-verify@npm:2": - version: 2.0.13 - resolution: "@nomicfoundation/hardhat-verify@npm:2.0.13" +"@nomicfoundation/hardhat-verify@npm:^2.1.0": + version: 2.1.0 + resolution: "@nomicfoundation/hardhat-verify@npm:2.1.0" dependencies: "@ethersproject/abi": ^5.1.2 "@ethersproject/address": ^5.0.2 @@ -2884,8 +3287,8 @@ __metadata: table: ^6.8.0 undici: ^5.14.0 peerDependencies: - hardhat: ^2.0.4 - checksum: 59a4d0f1fb93fcce91a4c318aaa69c8de6a857deb983e3976e9c20f09ce204f5a4e8a4e2ccae63018ee83ce5dbaa954b1f182a2c02e29d1a77f8673b03b498f9 + hardhat: ^2.26.0 + checksum: 5ab51b67df3bd95c6fdb06d8df8d7d9c3e2dcd541b0216254c7049c62ba8bd577ffb76195ac0d3522867189f18c90df96276577532ea59be0d104159733d1d97 languageName: node linkType: hard @@ -5880,6 +6283,15 @@ __metadata: languageName: node linkType: hard +"axios@npm:^0.21.1": + version: 0.21.4 + resolution: "axios@npm:0.21.4" + dependencies: + follow-redirects: ^1.14.0 + checksum: 44245f24ac971e7458f3120c92f9d66d1fc695e8b97019139de5b0cc65d9b8104647db01e5f46917728edfc0cfd88eb30fc4c55e6053eef4ace76768ce95ff3c + languageName: node + linkType: hard + "axios@npm:^0.26.1": version: 0.26.1 resolution: "axios@npm:0.26.1" @@ -7801,6 +8213,25 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^3.5.2": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: ~3.1.2 + braces: ~3.0.2 + fsevents: ~2.3.2 + glob-parent: ~5.1.2 + is-binary-path: ~2.1.0 + is-glob: ~4.0.1 + normalize-path: ~3.0.0 + readdirp: ~3.6.0 + dependenciesMeta: + fsevents: + optional: true + checksum: d2f29f499705dcd4f6f3bbed79a9ce2388cf530460122eed3b9c48efeab7a4e28739c6551fd15bec9245c6b9eeca7a32baa64694d64d9b6faeb74ddb8c4a413d + languageName: node + linkType: hard + "chownr@npm:^1.1.1": version: 1.1.4 resolution: "chownr@npm:1.1.4" @@ -8818,6 +9249,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.3.2": + version: 4.4.1 + resolution: "debug@npm:4.4.1" + dependencies: + ms: ^2.1.3 + peerDependenciesMeta: + supports-color: + optional: true + checksum: a43826a01cda685ee4cec00fb2d3322eaa90ccadbef60d9287debc2a886be3e835d9199c80070ede75a409ee57828c4c6cd80e4b154f2843f0dc95a570dc0729 + languageName: node + linkType: hard + "decamelize@npm:^1.1.1, decamelize@npm:^1.2.0": version: 1.2.0 resolution: "decamelize@npm:1.2.0" @@ -9477,6 +9920,21 @@ __metadata: languageName: node linkType: hard +"elliptic@npm:6.6.1": + version: 6.6.1 + resolution: "elliptic@npm:6.6.1" + dependencies: + bn.js: ^4.11.9 + brorand: ^1.1.0 + hash.js: ^1.0.0 + hmac-drbg: ^1.0.1 + inherits: ^2.0.4 + minimalistic-assert: ^1.0.1 + minimalistic-crypto-utils: ^1.0.1 + checksum: 27b14a52f68bbbc0720da259f712cb73e953f6d2047958cd02fb0d0ade2e83849dc39fb4af630889c67df8817e24237428cf59c4f4c07700f755b401149a7375 + languageName: node + linkType: hard + "emittery@npm:0.10.0": version: 0.10.0 resolution: "emittery@npm:0.10.0" @@ -9512,6 +9970,13 @@ __metadata: languageName: node linkType: hard +"encode-utf8@npm:^1.0.2": + version: 1.0.3 + resolution: "encode-utf8@npm:1.0.3" + checksum: 550224bf2a104b1d355458c8a82e9b4ea07f9fc78387bc3a49c151b940ad26473de8dc9e121eefc4e84561cb0b46de1e4cd2bc766f72ee145e9ea9541482817f + languageName: node + linkType: hard + "encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" @@ -9580,6 +10045,16 @@ __metadata: languageName: node linkType: hard +"enquirer@npm:^2.3.6": + version: 2.4.1 + resolution: "enquirer@npm:2.4.1" + dependencies: + ansi-colors: ^4.1.1 + strip-ansi: ^6.0.1 + checksum: f080f11a74209647dbf347a7c6a83c8a47ae1ebf1e75073a808bc1088eb780aa54075bfecd1bcdb3e3c724520edb8e6ee05da031529436b421b71066fcc48cb5 + languageName: node + linkType: hard + "entities@npm:^4.2.0, entities@npm:^4.3.0, entities@npm:^4.4.0": version: 4.4.0 resolution: "entities@npm:4.4.0" @@ -10584,7 +11059,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^5.0.0, ethers@npm:^5.0.1, ethers@npm:^5.0.13, ethers@npm:^5.0.2, ethers@npm:^5.5.2, ethers@npm:^5.7.2": +"ethers@npm:^5.0.0, ethers@npm:^5.0.1, ethers@npm:^5.0.13, ethers@npm:^5.0.2, ethers@npm:^5.5.2, ethers@npm:^5.7.2, ethers@npm:~5.7.0": version: 5.7.2 resolution: "ethers@npm:5.7.2" dependencies: @@ -10622,6 +11097,44 @@ __metadata: languageName: node linkType: hard +"ethers@npm:^5.7.0": + version: 5.8.0 + resolution: "ethers@npm:5.8.0" + dependencies: + "@ethersproject/abi": 5.8.0 + "@ethersproject/abstract-provider": 5.8.0 + "@ethersproject/abstract-signer": 5.8.0 + "@ethersproject/address": 5.8.0 + "@ethersproject/base64": 5.8.0 + "@ethersproject/basex": 5.8.0 + "@ethersproject/bignumber": 5.8.0 + "@ethersproject/bytes": 5.8.0 + "@ethersproject/constants": 5.8.0 + "@ethersproject/contracts": 5.8.0 + "@ethersproject/hash": 5.8.0 + "@ethersproject/hdnode": 5.8.0 + "@ethersproject/json-wallets": 5.8.0 + "@ethersproject/keccak256": 5.8.0 + "@ethersproject/logger": 5.8.0 + "@ethersproject/networks": 5.8.0 + "@ethersproject/pbkdf2": 5.8.0 + "@ethersproject/properties": 5.8.0 + "@ethersproject/providers": 5.8.0 + "@ethersproject/random": 5.8.0 + "@ethersproject/rlp": 5.8.0 + "@ethersproject/sha2": 5.8.0 + "@ethersproject/signing-key": 5.8.0 + "@ethersproject/solidity": 5.8.0 + "@ethersproject/strings": 5.8.0 + "@ethersproject/transactions": 5.8.0 + "@ethersproject/units": 5.8.0 + "@ethersproject/wallet": 5.8.0 + "@ethersproject/web": 5.8.0 + "@ethersproject/wordlists": 5.8.0 + checksum: fb107bf28dc3aedde4729f9553be066c699e0636346c095b4deeb5349a0c0c8538f48a58b5c8cbefced008706919739c5f7b8f4dd506bb471a31edee18cda228 + languageName: node + linkType: hard + "ethjs-unit@npm:0.1.6": version: 0.1.6 resolution: "ethjs-unit@npm:0.1.6" @@ -11302,6 +11815,15 @@ __metadata: languageName: node linkType: hard +"fmix@npm:^0.1.0": + version: 0.1.0 + resolution: "fmix@npm:0.1.0" + dependencies: + imul: ^1.0.0 + checksum: c465344d4f169eaf10d45c33949a1e7a633f09dba2ac7063ce8ae8be743df5979d708f7f24900163589f047f5194ac5fc2476177ce31175e8805adfa7b8fb7a4 + languageName: node + linkType: hard + "follow-redirects@npm:1.5.10": version: 1.5.10 resolution: "follow-redirects@npm:1.5.10" @@ -11321,6 +11843,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.14.0": + version: 1.15.9 + resolution: "follow-redirects@npm:1.15.9" + peerDependenciesMeta: + debug: + optional: true + checksum: 859e2bacc7a54506f2bf9aacb10d165df78c8c1b0ceb8023f966621b233717dab56e8d08baadc3ad3b9db58af290413d585c999694b7c146aaf2616340c3d2a6 + languageName: node + linkType: hard + "follow-redirects@npm:^1.14.8": version: 1.15.2 resolution: "follow-redirects@npm:1.15.2" @@ -11518,6 +12050,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^10.0.0": + version: 10.1.0 + resolution: "fs-extra@npm:10.1.0" + dependencies: + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^2.0.0 + checksum: dc94ab37096f813cc3ca12f0f1b5ad6744dfed9ed21e953d72530d103cea193c2f81584a39e9dee1bea36de5ee66805678c0dddc048e8af1427ac19c00fffc50 + languageName: node + linkType: hard + "fs-extra@npm:^4.0.2, fs-extra@npm:^4.0.3": version: 4.0.3 resolution: "fs-extra@npm:4.0.3" @@ -12302,6 +12845,37 @@ __metadata: languageName: node linkType: hard +"hardhat-deploy@npm:^1.0.4": + version: 1.0.4 + resolution: "hardhat-deploy@npm:1.0.4" + dependencies: + "@ethersproject/abi": ^5.7.0 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/contracts": ^5.7.0 + "@ethersproject/providers": ^5.7.2 + "@ethersproject/solidity": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/wallet": ^5.7.0 + axios: ^0.21.1 + chalk: ^4.1.2 + chokidar: ^3.5.2 + debug: ^4.3.2 + enquirer: ^2.3.6 + ethers: ^5.7.0 + form-data: ^4.0.0 + fs-extra: ^10.0.0 + match-all: ^1.2.6 + murmur-128: ^0.2.1 + neoqs: ^6.13.0 + zksync-ethers: ^5.0.0 + checksum: d91410a82b0a37c64eef235c4a8d457dee52ea67a67d4d9e32b710070d054e94defba5d60e034c6e10190e3c1c8183c36f576e83d88cfee539decd3dab801853 + languageName: node + linkType: hard + "hardhat-gas-reporter@npm:^1.0.8": version: 1.0.8 resolution: "hardhat-gas-reporter@npm:1.0.8" @@ -12928,6 +13502,13 @@ __metadata: languageName: node linkType: hard +"imul@npm:^1.0.0": + version: 1.0.1 + resolution: "imul@npm:1.0.1" + checksum: 6c2af3d5f09e2135e14d565a2c108412b825b221eb2c881f9130467f2adccf7ae201773ae8bcf1be169e2d090567a1fdfa9cf20d3b7da7b9cecb95b920ff3e52 + languageName: node + linkType: hard + "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -14939,6 +15520,13 @@ __metadata: languageName: node linkType: hard +"match-all@npm:^1.2.6": + version: 1.2.7 + resolution: "match-all@npm:1.2.7" + checksum: 2932da38c05124ba90d1f558109d04bf41a5b1a632f9c957562338e2b86037335534ed0157fe2e42c810d175ce026429c8cf2e3411b661bffeb3a68cfc01652a + languageName: node + linkType: hard + "md5.js@npm:^1.3.4": version: 1.3.5 resolution: "md5.js@npm:1.3.5" @@ -15688,7 +16276,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -15745,6 +16333,17 @@ __metadata: languageName: node linkType: hard +"murmur-128@npm:^0.2.1": + version: 0.2.1 + resolution: "murmur-128@npm:0.2.1" + dependencies: + encode-utf8: ^1.0.2 + fmix: ^0.1.0 + imul: ^1.0.0 + checksum: 94ff8b39bf1a1a7bde83b6d13f656bbe591e0a5b5ffe4384c39470120ab70e9eadf0af38557742a30d24421ddc63aea6bba1028a1d6b66553038ee86a660dd92 + languageName: node + linkType: hard + "mute-stream@npm:0.0.7": version: 0.0.7 resolution: "mute-stream@npm:0.0.7" @@ -15854,6 +16453,13 @@ __metadata: languageName: node linkType: hard +"neoqs@npm:^6.13.0": + version: 6.13.0 + resolution: "neoqs@npm:6.13.0" + checksum: 2b1ba202dbe839f80fae3065b08f3fdcb8993af720340ffd71a4136c5c084c81f691bcc04594e1547b1ff2562f458367427da7f4bcca0c6b8a8d214a329a377e + languageName: node + linkType: hard + "next-tick@npm:1, next-tick@npm:^1.1.0": version: 1.1.0 resolution: "next-tick@npm:1.1.0" @@ -23508,6 +24114,21 @@ patch-package@latest: languageName: node linkType: hard +"ws@npm:8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 91d4d35bc99ff6df483bdf029b9ea4bfd7af1f16fc91231a96777a63d263e1eabf486e13a2353970efc534f9faa43bdbf9ee76525af22f4752cbc5ebda333975 + languageName: node + linkType: hard + "ws@npm:8.18.1": version: 8.18.1 resolution: "ws@npm:8.18.1" @@ -23851,3 +24472,14 @@ patch-package@latest: checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard + +"zksync-ethers@npm:^5.0.0": + version: 5.10.0 + resolution: "zksync-ethers@npm:5.10.0" + dependencies: + ethers: ~5.7.0 + peerDependencies: + ethers: ~5.7.0 + checksum: 52079b973bbf74eb92c3126ec9c87010eb3dbb8f079db6714c78d2606ab874b100a2c25aabc24143099a4f2d87265a8b6924ac6032b0608322213cd65751e6d2 + languageName: node + linkType: hard