From 3e56d05a16c87fd770b846394241a8fbc5c7ec6d Mon Sep 17 00:00:00 2001 From: sirpy Date: Wed, 21 Jan 2026 16:25:01 +0200 Subject: [PATCH 1/6] add: identity events. improve best pool --- contracts/identity/IdentityV3.sol | 5 +++++ contracts/reserve/GenericDistributionHelper.sol | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/contracts/identity/IdentityV3.sol b/contracts/identity/IdentityV3.sol index 3c37186f..42a907a2 100644 --- a/contracts/identity/IdentityV3.sol +++ b/contracts/identity/IdentityV3.sol @@ -58,6 +58,9 @@ contract IdentityV3 is event ContractAdded(address indexed account); event ContractRemoved(address indexed account); + event AccountConnected(address indexed connected, address indexed to); + event AccountDisconnected(address indexed disconnected, address indexed from); + function initialize( address _owner, IIdentity _oldIdentity @@ -422,6 +425,7 @@ contract IdentityV3 is require(connectedAccounts[account] == address(0x0), "already connected"); connectedAccounts[account] = msg.sender; + emit AccountConnected(account, msg.sender); } /** @@ -434,6 +438,7 @@ contract IdentityV3 is "unauthorized" ); delete connectedAccounts[connected]; + emit AccountDisconnected(connected, msg.sender); } /** diff --git a/contracts/reserve/GenericDistributionHelper.sol b/contracts/reserve/GenericDistributionHelper.sol index c9d193ab..f25e48f2 100644 --- a/contracts/reserve/GenericDistributionHelper.sol +++ b/contracts/reserve/GenericDistributionHelper.sol @@ -308,13 +308,21 @@ contract GenericDistributionHelper is ); uint24 gasFee = IUniswapV3Pool(gasPools[0]).fee(); uint24 gdFee = IUniswapV3Pool(gdPools[0]).fee(); + + // find the pool with the best liquidity for gasToken and reserveToken + uint256 gasBalance = ERC20(gasToken).balanceOf(gasPools[0]); + uint256 stableBalance = ERC20(reserveToken).balanceOf(gdPools[0]); for (uint i = 1; i < gasPools.length; i++) { - uint24 fee = IUniswapV3Pool(gasPools[i]).fee(); - gasFee = gasFee < fee ? gasFee : fee; + uint256 balance = ERC20(gasToken).balanceOf(gasPools[i]); + gasFee = gasBalance < balance + ? IUniswapV3Pool(gasPools[i]).fee() + : gasFee; } for (uint i = 1; i < gdPools.length; i++) { - uint24 fee = IUniswapV3Pool(gdPools[i]).fee(); - gdFee = gdFee < fee ? gdFee : fee; + uint256 balance = ERC20(reserveToken).balanceOf(gdPools[i]); + gdFee = stableBalance < balance + ? IUniswapV3Pool(gdPools[i]).fee() + : gdFee; } ERC20(nativeToken()).approve(address(ROUTER), amountToSell); uint256 amountOutMinimum = (minReceived * (100 - feeSettings.maxSlippage)) / From 963b4c9b0e7d155feb2374a09536ebd807dbd6c7 Mon Sep 17 00:00:00 2001 From: sirpy Date: Wed, 21 Jan 2026 16:27:48 +0200 Subject: [PATCH 2/6] add: deployment scripts --- contracts/MentoInterfaces.sol | 440 ++++++----- contracts/utils/UpdateReserveRatio.sol | 67 ++ releases/deploy-settings.json | 3 + .../multichain-deploy/8_disthelper-deploy.ts | 20 +- .../createUniswapTestPools.ts | 138 ++-- scripts/multichain-deploy/helpers.ts | 13 +- .../proposals/gip-25-xdc-deploy-reserve.ts | 707 ++++++++++++++++++ 7 files changed, 1106 insertions(+), 282 deletions(-) create mode 100644 contracts/utils/UpdateReserveRatio.sol create mode 100644 scripts/proposals/gip-25-xdc-deploy-reserve.ts diff --git a/contracts/MentoInterfaces.sol b/contracts/MentoInterfaces.sol index 8a9b2749..e6fc7131 100644 --- a/contracts/MentoInterfaces.sol +++ b/contracts/MentoInterfaces.sol @@ -218,6 +218,8 @@ interface IBancorExchangeProvider { /* ------- Functions ------- */ + function AVATAR() external view returns (address); + /** * @notice Retrieves the pool with the specified exchangeId. * @param exchangeId The id of the pool to be retrieved. @@ -245,6 +247,11 @@ interface IBancorExchangeProvider { PoolExchange calldata exchange ) external returns (bytes32 exchangeId); + function updateExchange( + bytes32 exchangeId, + PoolExchange calldata exchange + ) external returns (bool updated); + /** * @notice Delete a PoolExchange. * @param exchangeId The PoolExchange to be created. @@ -533,39 +540,39 @@ interface IGoodDollarExchangeProvider { */ interface ITradingLimits { - /** - * @dev The State struct contains the current state of a trading limit config. - * @param lastUpdated0 The timestamp of the last reset of netflow0. - * @param lastUpdated1 The timestamp of the last reset of netflow1. - * @param netflow0 The current netflow of the asset for limit0. - * @param netflow1 The current netflow of the asset for limit1. - * @param netflowGlobal The current netflow of the asset for limitGlobal. - */ - struct State { - uint32 lastUpdated0; - uint32 lastUpdated1; - int48 netflow0; - int48 netflow1; - int48 netflowGlobal; - } - - /** - * @dev The Config struct contains the configuration of trading limits. - * @param timestep0 The time window in seconds for limit0. - * @param timestep1 The time window in seconds for limit1. - * @param limit0 The limit0 for the asset. - * @param limit1 The limit1 for the asset. - * @param limitGlobal The global limit for the asset. - * @param flags A bitfield of flags to enable/disable the individual limits. - */ - struct Config { - uint32 timestep0; - uint32 timestep1; - int48 limit0; - int48 limit1; - int48 limitGlobal; - uint8 flags; - } + /** + * @dev The State struct contains the current state of a trading limit config. + * @param lastUpdated0 The timestamp of the last reset of netflow0. + * @param lastUpdated1 The timestamp of the last reset of netflow1. + * @param netflow0 The current netflow of the asset for limit0. + * @param netflow1 The current netflow of the asset for limit1. + * @param netflowGlobal The current netflow of the asset for limitGlobal. + */ + struct State { + uint32 lastUpdated0; + uint32 lastUpdated1; + int48 netflow0; + int48 netflow1; + int48 netflowGlobal; + } + + /** + * @dev The Config struct contains the configuration of trading limits. + * @param timestep0 The time window in seconds for limit0. + * @param timestep1 The time window in seconds for limit1. + * @param limit0 The limit0 for the asset. + * @param limit1 The limit1 for the asset. + * @param limitGlobal The global limit for the asset. + * @param flags A bitfield of flags to enable/disable the individual limits. + */ + struct Config { + uint32 timestep0; + uint32 timestep1; + int48 limit0; + int48 limit1; + int48 limitGlobal; + uint8 flags; + } } /* @@ -573,174 +580,201 @@ interface ITradingLimits { * @notice The broker is responsible for executing swaps and keeping track of trading limits. */ interface IBroker { - /** - * @notice Emitted when a swap occurs. - * @param exchangeProvider The exchange provider used. - * @param exchangeId The id of the exchange used. - * @param trader The user that initiated the swap. - * @param tokenIn The address of the token that was sold. - * @param tokenOut The address of the token that was bought. - * @param amountIn The amount of token sold. - * @param amountOut The amount of token bought. - */ - event Swap( - address exchangeProvider, - bytes32 indexed exchangeId, - address indexed trader, - address indexed tokenIn, - address tokenOut, - uint256 amountIn, - uint256 amountOut - ); - - /** - * @notice Emitted when a new trading limit is configured. - * @param exchangeId the exchangeId to target. - * @param token the token to target. - * @param config the new trading limits config. - */ - event TradingLimitConfigured(bytes32 exchangeId, address token, ITradingLimits.Config config); - - /** - * @notice Allows the contract to be upgradable via the proxy. - * @param _exchangeProviders The addresses of the ExchangeProvider contracts. - * @param _reserves The address of the Reserve contract. - */ - function initialize(address[] calldata _exchangeProviders, address[] calldata _reserves) external; - - /** - * @notice Set the reserves for the exchange providers. - * @param _exchangeProviders The addresses of the ExchangeProvider contracts. - * @param _reserves The addresses of the Reserve contracts. - */ - function setReserves(address[] calldata _exchangeProviders, address[] calldata _reserves) external; - - /** - * @notice Add an exchange provider to the list of providers. - * @param exchangeProvider The address of the exchange provider to add. - * @param reserve The address of the reserve used by the exchange provider. - * @return index The index of the newly added specified exchange provider. - */ - function addExchangeProvider(address exchangeProvider, address reserve) external returns (uint256 index); - - /** - * @notice Remove an exchange provider from the list of providers. - * @param exchangeProvider The address of the exchange provider to remove. - * @param index The index of the exchange provider being removed. - */ - function removeExchangeProvider(address exchangeProvider, uint256 index) external; - - /** - * @notice Calculate amountIn of tokenIn needed for a given amountOut of tokenOut. - * @param exchangeProvider the address of the exchange provider for the pair. - * @param exchangeId The id of the exchange to use. - * @param tokenIn The token to be sold. - * @param tokenOut The token to be bought. - * @param amountOut The amount of tokenOut to be bought. - * @return amountIn The amount of tokenIn to be sold. - */ - function getAmountIn( - address exchangeProvider, - bytes32 exchangeId, - address tokenIn, - address tokenOut, - uint256 amountOut - ) external view returns (uint256 amountIn); - - /** - * @notice Calculate amountOut of tokenOut received for a given amountIn of tokenIn. - * @param exchangeProvider the address of the exchange provider for the pair. - * @param exchangeId The id of the exchange to use. - * @param tokenIn The token to be sold. - * @param tokenOut The token to be bought. - * @param amountIn The amount of tokenIn to be sold. - * @return amountOut The amount of tokenOut to be bought. - */ - function getAmountOut( - address exchangeProvider, - bytes32 exchangeId, - address tokenIn, - address tokenOut, - uint256 amountIn - ) external view returns (uint256 amountOut); - - /** - * @notice Execute a token swap with fixed amountIn. - * @param exchangeProvider the address of the exchange provider for the pair. - * @param exchangeId The id of the exchange to use. - * @param tokenIn The token to be sold. - * @param tokenOut The token to be bought. - * @param amountIn The amount of tokenIn to be sold. - * @param amountOutMin Minimum amountOut to be received - controls slippage. - * @return amountOut The amount of tokenOut to be bought. - */ - function swapIn( - address exchangeProvider, - bytes32 exchangeId, - address tokenIn, - address tokenOut, - uint256 amountIn, - uint256 amountOutMin - ) external returns (uint256 amountOut); - - /** - * @notice Execute a token swap with fixed amountOut. - * @param exchangeProvider the address of the exchange provider for the pair. - * @param exchangeId The id of the exchange to use. - * @param tokenIn The token to be sold. - * @param tokenOut The token to be bought. - * @param amountOut The amount of tokenOut to be bought. - * @param amountInMax Maximum amount of tokenIn that can be traded. - * @return amountIn The amount of tokenIn to be sold. - */ - function swapOut( - address exchangeProvider, - bytes32 exchangeId, - address tokenIn, - address tokenOut, - uint256 amountOut, - uint256 amountInMax - ) external returns (uint256 amountIn); - - /** - * @notice Permissionless way to burn stables from msg.sender directly. - * @param token The token getting burned. - * @param amount The amount of the token getting burned. - * @return True if transaction succeeds. - */ - function burnStableTokens(address token, uint256 amount) external returns (bool); - - /** - * @notice Configure trading limits for an (exchangeId, token) tuple. - * @dev Will revert if the configuration is not valid according to the TradingLimits library. - * Resets existing state according to the TradingLimits library logic. - * Can only be called by owner. - * @param exchangeId the exchangeId to target. - * @param token the token to target. - * @param config the new trading limits config. - */ - function configureTradingLimit(bytes32 exchangeId, address token, ITradingLimits.Config calldata config) external; - - /** - * @notice Get the list of registered exchange providers. - * @dev This can be used by UI or clients to discover all pairs. - * @return exchangeProviders the addresses of all exchange providers. - */ - function getExchangeProviders() external view returns (address[] memory); - - /** - * @notice Get the address of the exchange provider at a given index. - * @dev Auto-generated getter for the exchangeProviders array. - * @param index The index of the exchange provider. - * @return exchangeProvider The address of the exchange provider. - */ - function exchangeProviders(uint256 index) external view returns (address exchangeProvider); - - /** - * @notice Check if a given address is an exchange provider. - * @dev Auto-generated getter for the isExchangeProvider mapping. - * @param exchangeProvider The address to check. - * @return isExchangeProvider True if the address is an exchange provider, false otherwise. - */ - function isExchangeProvider(address exchangeProvider) external view returns (bool); + /** + * @notice Emitted when a swap occurs. + * @param exchangeProvider The exchange provider used. + * @param exchangeId The id of the exchange used. + * @param trader The user that initiated the swap. + * @param tokenIn The address of the token that was sold. + * @param tokenOut The address of the token that was bought. + * @param amountIn The amount of token sold. + * @param amountOut The amount of token bought. + */ + event Swap( + address exchangeProvider, + bytes32 indexed exchangeId, + address indexed trader, + address indexed tokenIn, + address tokenOut, + uint256 amountIn, + uint256 amountOut + ); + + /** + * @notice Emitted when a new trading limit is configured. + * @param exchangeId the exchangeId to target. + * @param token the token to target. + * @param config the new trading limits config. + */ + event TradingLimitConfigured( + bytes32 exchangeId, + address token, + ITradingLimits.Config config + ); + + /** + * @notice Allows the contract to be upgradable via the proxy. + * @param _exchangeProviders The addresses of the ExchangeProvider contracts. + * @param _reserves The address of the Reserve contract. + */ + function initialize( + address[] calldata _exchangeProviders, + address[] calldata _reserves + ) external; + + /** + * @notice Set the reserves for the exchange providers. + * @param _exchangeProviders The addresses of the ExchangeProvider contracts. + * @param _reserves The addresses of the Reserve contracts. + */ + function setReserves( + address[] calldata _exchangeProviders, + address[] calldata _reserves + ) external; + + /** + * @notice Add an exchange provider to the list of providers. + * @param exchangeProvider The address of the exchange provider to add. + * @param reserve The address of the reserve used by the exchange provider. + * @return index The index of the newly added specified exchange provider. + */ + function addExchangeProvider( + address exchangeProvider, + address reserve + ) external returns (uint256 index); + + /** + * @notice Remove an exchange provider from the list of providers. + * @param exchangeProvider The address of the exchange provider to remove. + * @param index The index of the exchange provider being removed. + */ + function removeExchangeProvider( + address exchangeProvider, + uint256 index + ) external; + + /** + * @notice Calculate amountIn of tokenIn needed for a given amountOut of tokenOut. + * @param exchangeProvider the address of the exchange provider for the pair. + * @param exchangeId The id of the exchange to use. + * @param tokenIn The token to be sold. + * @param tokenOut The token to be bought. + * @param amountOut The amount of tokenOut to be bought. + * @return amountIn The amount of tokenIn to be sold. + */ + function getAmountIn( + address exchangeProvider, + bytes32 exchangeId, + address tokenIn, + address tokenOut, + uint256 amountOut + ) external view returns (uint256 amountIn); + + /** + * @notice Calculate amountOut of tokenOut received for a given amountIn of tokenIn. + * @param exchangeProvider the address of the exchange provider for the pair. + * @param exchangeId The id of the exchange to use. + * @param tokenIn The token to be sold. + * @param tokenOut The token to be bought. + * @param amountIn The amount of tokenIn to be sold. + * @return amountOut The amount of tokenOut to be bought. + */ + function getAmountOut( + address exchangeProvider, + bytes32 exchangeId, + address tokenIn, + address tokenOut, + uint256 amountIn + ) external view returns (uint256 amountOut); + + /** + * @notice Execute a token swap with fixed amountIn. + * @param exchangeProvider the address of the exchange provider for the pair. + * @param exchangeId The id of the exchange to use. + * @param tokenIn The token to be sold. + * @param tokenOut The token to be bought. + * @param amountIn The amount of tokenIn to be sold. + * @param amountOutMin Minimum amountOut to be received - controls slippage. + * @return amountOut The amount of tokenOut to be bought. + */ + function swapIn( + address exchangeProvider, + bytes32 exchangeId, + address tokenIn, + address tokenOut, + uint256 amountIn, + uint256 amountOutMin + ) external returns (uint256 amountOut); + + /** + * @notice Execute a token swap with fixed amountOut. + * @param exchangeProvider the address of the exchange provider for the pair. + * @param exchangeId The id of the exchange to use. + * @param tokenIn The token to be sold. + * @param tokenOut The token to be bought. + * @param amountOut The amount of tokenOut to be bought. + * @param amountInMax Maximum amount of tokenIn that can be traded. + * @return amountIn The amount of tokenIn to be sold. + */ + function swapOut( + address exchangeProvider, + bytes32 exchangeId, + address tokenIn, + address tokenOut, + uint256 amountOut, + uint256 amountInMax + ) external returns (uint256 amountIn); + + /** + * @notice Permissionless way to burn stables from msg.sender directly. + * @param token The token getting burned. + * @param amount The amount of the token getting burned. + * @return True if transaction succeeds. + */ + function burnStableTokens( + address token, + uint256 amount + ) external returns (bool); + + /** + * @notice Configure trading limits for an (exchangeId, token) tuple. + * @dev Will revert if the configuration is not valid according to the TradingLimits library. + * Resets existing state according to the TradingLimits library logic. + * Can only be called by owner. + * @param exchangeId the exchangeId to target. + * @param token the token to target. + * @param config the new trading limits config. + */ + function configureTradingLimit( + bytes32 exchangeId, + address token, + ITradingLimits.Config calldata config + ) external; + + /** + * @notice Get the list of registered exchange providers. + * @dev This can be used by UI or clients to discover all pairs. + * @return exchangeProviders the addresses of all exchange providers. + */ + function getExchangeProviders() external view returns (address[] memory); + + /** + * @notice Get the address of the exchange provider at a given index. + * @dev Auto-generated getter for the exchangeProviders array. + * @param index The index of the exchange provider. + * @return exchangeProvider The address of the exchange provider. + */ + function exchangeProviders( + uint256 index + ) external view returns (address exchangeProvider); + + /** + * @notice Check if a given address is an exchange provider. + * @dev Auto-generated getter for the isExchangeProvider mapping. + * @param exchangeProvider The address to check. + * @return isExchangeProvider True if the address is an exchange provider, false otherwise. + */ + function isExchangeProvider( + address exchangeProvider + ) external view returns (bool); } diff --git a/contracts/utils/UpdateReserveRatio.sol b/contracts/utils/UpdateReserveRatio.sol new file mode 100644 index 00000000..f4aaf731 --- /dev/null +++ b/contracts/utils/UpdateReserveRatio.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import "../utils/NameService.sol"; +import "../Interfaces.sol"; +import "../DAOStackInterfaces.sol"; +import "../MentoInterfaces.sol"; + +import "hardhat/console.sol"; + +interface MentoExchange { + function reserve() external view returns (address); +} + +/** + * @notice set the new reserve ratio on Celo for xdc reserve deploy + */ +contract UpdateReserveRatio { + address owner; + + constructor(address _owner) { + owner = _owner; + } + + function upgrade( + Controller _controller, + address _mentoExchange, + bytes32 _exchangeId, + uint32 _reserveRatio, + uint256 _verifyCurrentSupply, + uint256 _newTotalSupply + ) external { + require(msg.sender == owner, "only owner can call this"); + address avatar = _controller.avatar(); + //verify that the total supply the new reserve ratio was based on still applies + require( + ERC20(Avatar(avatar).nativeToken()).totalSupply() == _verifyCurrentSupply, + "total supply mismatch" + ); + + IBancorExchangeProvider.PoolExchange + memory _exchange = IBancorExchangeProvider(_mentoExchange) + .getPoolExchange(_exchangeId); + + _exchange.reserveRatio = _reserveRatio; + _exchange.tokenSupply = _newTotalSupply; + + (bool ok, bytes memory result) = _controller.genericCall( + address(_mentoExchange), + abi.encodeCall( + IBancorExchangeProvider.updateExchange, + (_exchangeId, _exchange) + ), + address(avatar), + 0 + ); + + require(ok, "update failed"); + console.log("update done"); + bool updated = abi.decode(result, (bool)); + require(updated, "not updated"); + + owner = address(0); //mark as run; + // prevent executing again + require(_controller.unregisterSelf(avatar), "unregistering failed"); + } +} diff --git a/releases/deploy-settings.json b/releases/deploy-settings.json index 0a9bdfe8..63ae0436 100644 --- a/releases/deploy-settings.json +++ b/releases/deploy-settings.json @@ -311,6 +311,9 @@ "production-xdc": { "gasPrice": 12.5e9, "superfluidHost": "0x0000000000000000000000000000000000000000", + "reserve": { + "gasToken": "0x951857744785e80e2de051c32ee7b25f9c458c42" + }, "ubi": { "maxInactiveDays": 14, "minActiveUsers": 30000 diff --git a/scripts/multichain-deploy/8_disthelper-deploy.ts b/scripts/multichain-deploy/8_disthelper-deploy.ts index 163656ed..73e4e41e 100644 --- a/scripts/multichain-deploy/8_disthelper-deploy.ts +++ b/scripts/multichain-deploy/8_disthelper-deploy.ts @@ -33,15 +33,15 @@ const printDeploy = async (c: Contract | TransactionResponse): Promise { +export const deployHelpers = async (networkName: string = network.name) => { const viaGuardians = false; - let protocolSettings = defaultsDeep({}, ProtocolSettings[network.name], ProtocolSettings["default"]); - let release: { [key: string]: any } = dao[network.name]; + let protocolSettings = defaultsDeep({}, ProtocolSettings[networkName], ProtocolSettings["default"]); + let release: { [key: string]: any } = dao[networkName]; let [root, ...signers] = await ethers.getSigners(); - const isProduction = network.name.includes("production"); - let networkEnv = network.name.split("-")[0]; + const isProduction = networkName.includes("production"); + let networkEnv = networkName.split("-")[0]; const celoNetwork = networkEnv + "-celo"; if (isProduction) verifyProductionSigner(root); @@ -66,9 +66,9 @@ export const deployHelpers = async () => { release.NameService, release.StaticOracle, protocolSettings.reserve.gasToken, - protocolSettings.reserve.reserveToken, + release.ReserveToken, release.UniswapV3Router, - [ethers.utils.parseEther("100"), ethers.utils.parseEther("100"), 5, 5] + [ethers.utils.parseEther("20"), ethers.utils.parseEther("20"), 5, 5] ] ).then(printDeploy)) as Contract; @@ -79,7 +79,7 @@ export const deployHelpers = async () => { ...release, ...torelease }; - await releaser(torelease, network.name, "deployment", false); + await releaser(torelease, networkName, "deployment", false); console.log("setting nameservice addresses via guardian"); const proposalActions = [ @@ -109,7 +109,7 @@ export const deployHelpers = async () => { "addOrUpdateRecipient((uint32,uint32,address,uint8))", ethers.utils.defaultAbiCoder.encode( ["uint32", "uint32", "address", "uint8"], - [1000, 42220, dao[celoNetwork].CommunitySafe, network.name === celoNetwork ? 1 : 0] //10% to celo community safe, use LZ bridge if not on celo + [1000, 42220, dao[celoNetwork].CommunitySafe, networkName === celoNetwork ? 1 : 0] //10% to celo community safe, use LZ bridge if not on celo ), 0 ]); @@ -143,7 +143,7 @@ export const deployHelpers = async () => { } let impl = await getImplementationAddress(ethers.provider, DistHelper.address); - await verifyContract(impl, "contracts/reserve/GenericDistributionHelper.sol:GenericDistributionHelper", network.name); + await verifyContract(impl, "contracts/reserve/GenericDistributionHelper.sol:GenericDistributionHelper", networkName); }; export const main = async (networkName = name) => { diff --git a/scripts/multichain-deploy/createUniswapTestPools.ts b/scripts/multichain-deploy/createUniswapTestPools.ts index ed9e9cda..7b9ced47 100644 --- a/scripts/multichain-deploy/createUniswapTestPools.ts +++ b/scripts/multichain-deploy/createUniswapTestPools.ts @@ -8,7 +8,9 @@ const V3Factory = "0x30f317a9ec0f0d06d5de0f8d248ec3506b7e4a8a"; const NFPM = "0x6d22833398772d7da9a7cbcfdee98342cce154d4"; const STABLE_DECIMALS = 6; //USDC const GASPRICE_STABLE = 0.08; -const POOL_FEE = 100; // 0.01% +const GDPRICE_STABLE = 0.0001 +const GAS_POOL_FEE = 3000; // 0.01% +const GD_POOL_FEE = 500; // 0.05% const main = async () => { let protocolSettings = defaultsDeep({}, ProtocolSettings[network.name], ProtocolSettings["default"]); let release: { [key: string]: any } = dao[network.name]; @@ -20,103 +22,104 @@ const main = async () => { const v3Factory = (await ethers.getContractAt("IUniswapV3Factory", V3Factory)) as IUniswapV3Factory; const nfpm = (await ethers.getContractAt("INonfungiblePositionManager", NFPM)) as INonfungiblePositionManager; + const stable = (await ethers.getContractAt("IERC20", release.ReserveToken)) as IERC20; + const gd = (await ethers.getContractAt("IERC20", release.GoodDollar)) as IERC20; // create G$<>Stable pool console.log("creating G$<>Stable pool", { gd: release.GoodDollar, - stable: protocolSettings.reserve.reserveToken, - fee: POOL_FEE + stable: release.ReserveToken, + fee: GAS_POOL_FEE, + gdFee: GD_POOL_FEE }); - const poolExists = await v3Factory.getPool(protocolSettings.reserve.reserveToken, release.GoodDollar, POOL_FEE); + const poolExists = await v3Factory.getPool(release.ReserveToken, release.GoodDollar, GD_POOL_FEE); if (poolExists !== ethers.constants.AddressZero) { console.log("pool already exists at:", poolExists); } else { await ( - await v3Factory.createPool(protocolSettings.reserve.reserveToken, release.GoodDollar, POOL_FEE, { + await v3Factory.createPool(release.ReserveToken, release.GoodDollar, GD_POOL_FEE, { gasLimit: 5000000 }) ).wait(); } const gdstablePool = (await ethers.getContractAt( "IUniswapV3Pool", - await v3Factory.getPool(release.GoodDollar, protocolSettings.reserve.reserveToken, POOL_FEE) + await v3Factory.getPool(release.GoodDollar, release.ReserveToken, GD_POOL_FEE) )) as IUniswapV3Pool; //get pool price const { sqrtPriceX96 } = await gdstablePool.slot0(); - const existingPrice = (Number(sqrtPriceX96.toString()) / 2 ** 96) ** 2; + const existingPrice = (10 ** 12) * (Number(sqrtPriceX96.toString()) / 2 ** 96) ** 2 //convert to G$ price considering decimals diff console.log("created pool at:", gdstablePool.address, { existingPrice }); - let price = BigInt(Math.sqrt(10000 * (10 ** (18 - STABLE_DECIMALS))) * 2 ** 96); //1 G$ = 0.0001 Stable + let price = BigInt(Math.sqrt((1 / GDPRICE_STABLE) * (10 ** (18 - STABLE_DECIMALS))) * 2 ** 96); //1 G$ = 0.0001 Stable let amount1 = ethers.utils.parseUnits("50000", 18); let amount0 = ethers.utils.parseUnits("5", STABLE_DECIMALS); if ((await gdstablePool.token0()).toLowerCase() === release.GoodDollar.toLowerCase()) { - price = BigInt(Math.sqrt(0.0001 * (10 ** (STABLE_DECIMALS - 18))) * 2 ** 96); + price = BigInt(Math.sqrt(GDPRICE_STABLE * (10 ** (STABLE_DECIMALS - 18))) * 2 ** 96); amount0 = ethers.utils.parseUnits("50000", 18); amount1 = ethers.utils.parseUnits("5", STABLE_DECIMALS); } if (existingPrice > 0) { - console.log("pool already initialized"); - price = BigInt(Math.sqrt(existingPrice) * 2 ** 96); //1 G$ = 0.0001 Stable + console.log("pool already initialized", { existingPrice }); } else { await (await gdstablePool.initialize(price)).wait(); console.log("initialized pool with price:", price.toString(), { amount0, amount1 }); - } + console.log("creating first position") - // print allowance for nfpm - const stable = (await ethers.getContractAt("IERC20", protocolSettings.reserve.reserveToken)) as IERC20; - const gd = (await ethers.getContractAt("IERC20", release.GoodDollar)) as IERC20; - let stableAllowance = await stable.allowance(root.address, NFPM); - const gdAllowance = await gd.allowance(root.address, NFPM); - console.log("stable allowance for NFPM:", ethers.utils.formatUnits(stableAllowance, STABLE_DECIMALS)); - console.log("G$ allowance for NFPM:", ethers.utils.formatUnits(gdAllowance, 18)); - - await ( - await nfpm.mint({ - token0: await gdstablePool.token0(), - token1: await gdstablePool.token1(), - fee: POOL_FEE, - tickLower: -887220, - tickUpper: 887220, - amount0Desired: amount0, - amount1Desired: amount1, - amount0Min: amount0.mul(8).div(10), - amount1Min: amount1.mul(8).div(10), - recipient: root.address, - deadline: Math.floor(Date.now() / 1000) + 60 * 10 - }) - ).wait(); + // print allowance for nfpm + let stableAllowance = await stable.allowance(root.address, NFPM); + const gdAllowance = await gd.allowance(root.address, NFPM); + console.log("stable allowance for NFPM:", ethers.utils.formatUnits(stableAllowance, STABLE_DECIMALS)); + console.log("G$ allowance for NFPM:", ethers.utils.formatUnits(gdAllowance, 18)); + + await ( + await nfpm.mint({ + token0: await gdstablePool.token0(), + token1: await gdstablePool.token1(), + fee: GD_POOL_FEE, + tickLower: -887220, + tickUpper: 887220, + amount0Desired: amount0, + amount1Desired: amount1, + amount0Min: amount0.mul(8).div(10), + amount1Min: amount1.mul(8).div(10), + recipient: root.address, + deadline: Math.floor(Date.now() / 1000) + 60 * 10 + }) + ).wait(); + } console.log("creating gastoken<>Stable pool", { gasToken: protocolSettings.reserve.gasToken, - stable: protocolSettings.reserve.reserveToken, - fee: POOL_FEE + stable: release.ReserveToken, + fee: GAS_POOL_FEE }); const gaspoolExists = await v3Factory.getPool( protocolSettings.reserve.gasToken, - protocolSettings.reserve.reserveToken, - POOL_FEE + release.ReserveToken, + GAS_POOL_FEE ); if (gaspoolExists !== ethers.constants.AddressZero) { console.log("pool already exists at:", gaspoolExists); } else { await ( - await v3Factory.createPool(protocolSettings.reserve.gasToken, protocolSettings.reserve.reserveToken, POOL_FEE, { + await v3Factory.createPool(protocolSettings.reserve.gasToken, release.ReserveToken, GAS_POOL_FEE, { gasLimit: 5000000 }) ).wait(); } const gasstablePool = (await ethers.getContractAt( "IUniswapV3Pool", - await v3Factory.getPool(protocolSettings.reserve.gasToken, protocolSettings.reserve.reserveToken, POOL_FEE) + await v3Factory.getPool(protocolSettings.reserve.gasToken, release.ReserveToken, GAS_POOL_FEE) )) as IUniswapV3Pool; //get pool price const { sqrtPriceX96: gasSqrtPriceX96 } = await gasstablePool.slot0(); - const gasexistingPrice = (Number(gasSqrtPriceX96.toString()) / 2 ** 96) ** 2; + const gasexistingPrice = (10 ** 12) * (Number(gasSqrtPriceX96.toString()) / 2 ** 96) ** 2; //considering decimals diff console.log("created pool at:", gasstablePool.address, { gasexistingPrice }); price = BigInt(Math.sqrt(GASPRICE_STABLE * (10 ** (18 - STABLE_DECIMALS))) * 2 ** 96); //1 G$ = 0.0001 Stable amount1 = ethers.utils.parseUnits((5 / GASPRICE_STABLE).toString(), 18); @@ -130,34 +133,35 @@ const main = async () => { } if (gasexistingPrice > 0) { - console.log("pool already initialized"); - price = BigInt(Math.sqrt(gasexistingPrice) * 2 ** 96); //1 G$ = 0.0001 Stable + console.log("pool already initialized", { gasexistingPrice }); } else { await (await gasstablePool.initialize(price)).wait(); console.log("initialized pool with price:", price.toString(), { amount0, amount1 }); - } - // print allowance for nfpm - const gasToken = (await ethers.getContractAt("IERC20", protocolSettings.reserve.gasToken)) as IERC20; - stableAllowance = await stable.allowance(root.address, NFPM); - const gasAllowance = await gasToken.allowance(root.address, NFPM); - console.log("stable allowance for NFPM:", ethers.utils.formatUnits(stableAllowance, STABLE_DECIMALS)); - console.log("weth allowance for NFPM:", ethers.utils.formatUnits(gasAllowance, 18)); - - await ( - await nfpm.mint({ - token0: await gasstablePool.token0(), - token1: await gasstablePool.token1(), - fee: POOL_FEE, - tickLower: -887220, - tickUpper: 887220, - amount0Desired: amount0, - amount1Desired: amount1, - amount0Min: amount0.mul(8).div(10), - amount1Min: amount1.mul(8).div(10), - recipient: root.address, - deadline: Math.floor(Date.now() / 1000) + 60 * 10 - }) - ).wait(); + + // print allowance for nfpm + const gasToken = (await ethers.getContractAt("IERC20", protocolSettings.reserve.gasToken)) as IERC20; + const stableAllowance = await stable.allowance(root.address, NFPM); + const gasAllowance = await gasToken.allowance(root.address, NFPM); + console.log("stable allowance for NFPM:", ethers.utils.formatUnits(stableAllowance, STABLE_DECIMALS)); + console.log("weth allowance for NFPM:", ethers.utils.formatUnits(gasAllowance, 18)); + + + await ( + await nfpm.mint({ + token0: await gasstablePool.token0(), + token1: await gasstablePool.token1(), + fee: GAS_POOL_FEE, + tickLower: -887220, + tickUpper: 887220, + amount0Desired: amount0, + amount1Desired: amount1, + amount0Min: amount0.mul(8).div(10), + amount1Min: amount1.mul(8).div(10), + recipient: root.address, + deadline: Math.floor(Date.now() / 1000) + 60 * 10 + }) + ).wait(); + } }; main(); diff --git a/scripts/multichain-deploy/helpers.ts b/scripts/multichain-deploy/helpers.ts index 32f8796e..a20144ac 100644 --- a/scripts/multichain-deploy/helpers.ts +++ b/scripts/multichain-deploy/helpers.ts @@ -142,8 +142,15 @@ export const deploySuperGoodDollar = async ( return GoodDollar; }; -export const deployDeterministic = async (contract, args: any[], factoryOpts = {}, redeployProxyFactory = false) => { +export const deployDeterministic = async ( + contract, + args: any[], + factoryOpts = {}, + redeployProxyFactory = false, + networkName = network.name +) => { try { + let release: { [key: string]: any } = dao[networkName]; let proxyFactory: ProxyFactory1967; if (networkName.startsWith("develop") && redeployProxyFactory) { proxyFactory = (await (await ethers.getContractFactory("ProxyFactory1967")).deploy()) as ProxyFactory1967; @@ -300,7 +307,8 @@ export const executeViaGuardian = async ( functionSigs, functionInputs, guardian: Signer, - network?: string + network?: string, + simulateOnly?: boolean ) => { let release: { [key: string]: any } = dao[network || networkName]; const ctrl = await (await ethers.getContractAt("Controller", release.Controller)).connect(guardian); @@ -338,6 +346,7 @@ export const executeViaGuardian = async ( simulationResult }); if (simulationResult[0] === false) throw new Error("simulation failed:" + contract); + if (simulateOnly) continue; const tx = await ctrl .genericCall(contract, encoded, release.Avatar, ethValues[i], { gasLimit: 8000000 diff --git a/scripts/proposals/gip-25-xdc-deploy-reserve.ts b/scripts/proposals/gip-25-xdc-deploy-reserve.ts new file mode 100644 index 00000000..86d42c9b --- /dev/null +++ b/scripts/proposals/gip-25-xdc-deploy-reserve.ts @@ -0,0 +1,707 @@ +// Part 2 Reserve +// upgrade bridge + identity on all networks - V +// upgrade celo exchangeprovider so we can update the params - V +// upgrade celo broker so it can support no in limits - V +// set the new limits on the broker - V +// create uniswap pools on xdc - they exists +// calculate how much G$s each reserve is backing - V +// deploy distribution helper on xdc - V +// set distribution helper on xdc expansion controller - V +// create exchange on mento reserve xdc with calculated parameters - V +// give mento broker minting rights on xdc - V +// give expansion controller minting rights - V +// transfer usdc to xdc reserve +// update celo reserve parameters accordingly + +import { network, ethers, upgrades } from "hardhat"; +import { reset } from "@nomicfoundation/hardhat-network-helpers"; +import { defaultsDeep, last } from "lodash"; +import prompt from "prompt"; +import { + deployDeterministic, + executeViaGuardian, + executeViaSafe, + verifyContract, + verifyProductionSigner +} from "../multichain-deploy/helpers"; + +import dao from "../../releases/deployment.json"; +import { + Controller, + IBancorExchangeProvider, + IBroker, + IdentityV3, + IGoodDollar, + IGoodDollarExpansionController, + IMessagePassingBridge, + Ownable, + UBISchemeV2, + UpdateReserveRatio, + UpdateReserveRatioAfterXDC +} from "../../types"; +import releaser from "../releaser"; +import { keccak256, toUtf8Bytes } from "ethers/lib/utils"; +let { name: networkName } = network; +const isSimulation = network.name === "hardhat" || network.name === "fork" || network.name === "localhost"; +const bridgeUpgradeImpl = { + "production-celo": "0xF3eAB7018d74E7Df95A5d8dC70987C0539bDF48f", + production: "0xFB62aA509a7B260b6697B671C969a184d6c39E90", + "production-mainnet": "0x12ab702f015D3302f3cc0c4AbA0626A127D06A07", + "production-xdc": "0xe4CFA18A3d0a7d77fAA42961ee943c9221d61937" +}; +const XDC_INITIAL_USDC = 200000 * 1e6; + +export const upgradeCeloStep2 = async (network, checksOnly) => { + const ExchangeProviderV2Impl = "0xe930CDE20f60d0A4fc9487874861AE259F5Bed48"; + const MentoBrokerV2Impl = "0xc69ae3550E25C7AB28301B9Bf75F20f5AF47B7d2"; + + let [root] = await ethers.getSigners(); + + const isProduction = networkName.includes("production"); + + if (isProduction) verifyProductionSigner(root); + + let networkEnv = networkName; + let guardian = root; + if (isSimulation) { + networkEnv = network; + } + + let release: { [key: string]: any } = dao[networkEnv]; + + console.log("signer:", root.address, { networkEnv, isSimulation, isProduction, release }); + + if (isSimulation) { + networkEnv = network; + guardian = await ethers.getImpersonatedSigner(release.GuardiansSafe); + + await root.sendTransaction({ + value: ethers.utils.parseEther("1"), + to: release.GuardiansSafe + }); + } + + const identityImpl = await ethers.deployContract("IdentityV3"); + const reserveUpdate = (await deployDeterministic( + { name: "UpdateReserveRatio" }, + [root.address], + {}, + false, + networkEnv + )) as UpdateReserveRatio; + + const bridgeImpl = bridgeUpgradeImpl[networkEnv]; + + // Extract the first four bytes as the function selector + console.log("deployed new impls", { identityImpl: identityImpl.address }); + + const proposalActions = [ + [ + release.MentoProxyAdmin, + "upgrade(address,address)", + ethers.utils.defaultAbiCoder.encode( + ["address", "address"], + [release.MentoExchangeProvider, ExchangeProviderV2Impl] + ), + "0" + ], + [ + release.MentoProxyAdmin, + "upgrade(address,address)", + ethers.utils.defaultAbiCoder.encode(["address", "address"], [release.MentoBroker, MentoBrokerV2Impl]), + "0" + ], + [ + release.MentoBroker, + "configureTradingLimit(bytes32,address,(uint32,uint32,int48,int48,int48,int48,int48,uint8))", + ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "tuple(uint32,uint32,int48,int48,int48,int48,int48,uint8)"], + [ + release.CUSDExchangeId, + release.CUSD, + [7 * 86400, 30 * 86400, 140737488355326, 40000, 140737488355327, 80000, 0, 3] + ] + ), + "0" + ], + [ + release.Identity, + "upgradeTo(address)", + ethers.utils.defaultAbiCoder.encode(["address"], [identityImpl.address]), + "0" + ], //upgrade identity + [release.MpbBridge, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [bridgeImpl]), "0"], //upgrade bridge + [ + release.Controller, + "registerScheme(address,bytes32,bytes4,address)", + ethers.utils.defaultAbiCoder.encode( + ["address", "bytes32", "bytes4", "address"], + [reserveUpdate.address, ethers.constants.HashZero, "0x0000001f", release.Avatar] + ), + "0" + ] //give generic call rights to update reserve ratio + ]; + + console.log({ + networkEnv, + guardian: guardian.address, + isSimulation, + isProduction, + release + }); + + const proposalContracts = proposalActions.map(a => a[0]); + const proposalFunctionSignatures = proposalActions.map(a => a[1]); + const proposalFunctionInputs = proposalActions.map(a => a[2]); + const proposalEthValues = proposalActions.map(a => a[3]); + if (isProduction && !checksOnly) { + await executeViaSafe( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + release.GuardiansSafe, + "celo" + ); + } else if (!checksOnly) { + await executeViaGuardian( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + guardian, + networkEnv + ); + } + + if (!isProduction && isSimulation) { + const exchange = (await ethers.getContractAt( + "IBancorExchangeProvider", + release.MentoExchangeProvider + )) as IBancorExchangeProvider; + + const reserveParams = await calculateReserveParams(); + const reserveUpdateResult = await ( + await reserveUpdate.upgrade( + release.Controller, + release.MentoExchangeProvider, + release.CUSDExchangeId, + reserveParams.reserveRatioCelo, + reserveParams.celoSupply, + reserveParams.celoGdSupplyEquivalent + ) + ).wait(); + console.log("Exchange after update", await exchange.getPoolExchange(release.CUSDExchangeId)); + console.log("Price:", await exchange.currentPrice(release.CUSDExchangeId)); + } +}; + +export const upgradeFuseStep2 = async (network, checksOnly) => { + let [root] = await ethers.getSigners(); + + const isProduction = networkName.includes("production"); + + if (isProduction) verifyProductionSigner(root); + + let networkEnv = networkName; + let guardian = root; + if (isSimulation) { + networkEnv = network; + } + + let release: { [key: string]: any } = dao[networkEnv]; + + console.log("signer:", root.address, { networkEnv, isSimulation, isProduction, release }); + + if (isSimulation) { + networkEnv = network; + guardian = await ethers.getImpersonatedSigner(release.GuardiansSafe); + + await root.sendTransaction({ + value: ethers.utils.parseEther("1"), + to: release.GuardiansSafe + }); + } + + const identityImpl = await ethers.deployContract("IdentityV3"); + const bridgeImpl = bridgeUpgradeImpl[networkEnv]; + + // Extract the first four bytes as the function selector + console.log("deployed new impls", { identityImpl: identityImpl.address }); + + const proposalActions = [ + [ + release.Identity, + "upgradeTo(address)", + ethers.utils.defaultAbiCoder.encode(["address"], [identityImpl.address]), + "0" + ], //upgrade identity + [release.MpbBridge, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [bridgeImpl]), "0"] //upgrade bridge + ]; + + console.log({ + networkEnv, + guardian: guardian.address, + isSimulation, + isProduction, + release + }); + + const proposalContracts = proposalActions.map(a => a[0]); + const proposalFunctionSignatures = proposalActions.map(a => a[1]); + const proposalFunctionInputs = proposalActions.map(a => a[2]); + const proposalEthValues = proposalActions.map(a => a[3]); + if (isProduction && !checksOnly) { + await executeViaSafe( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + release.GuardiansSafe, + "fuse" + ); + } else if (!checksOnly) { + await executeViaGuardian( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + guardian, + networkEnv + ); + } +}; + +export const upgradeEthStep2 = async (network, checksOnly) => { + let [root] = await ethers.getSigners(); + + const isProduction = networkName.includes("production"); + + if (isProduction) verifyProductionSigner(root); + + let networkEnv = networkName; + let guardian = root; + if (isSimulation) { + networkEnv = network; + } + + let release: { [key: string]: any } = dao[networkEnv]; + + console.log("signer:", root.address, { networkEnv, isSimulation, isProduction, release }); + + if (isSimulation) { + networkEnv = network; + guardian = await ethers.getImpersonatedSigner(release.GuardiansSafe); + + await root.sendTransaction({ + value: ethers.utils.parseEther("1"), + to: release.GuardiansSafe + }); + } + + const bridgeImpl = bridgeUpgradeImpl[networkEnv]; + + const proposalActions = [ + [release.MpbBridge, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [bridgeImpl]), "0"] //upgrade bridge + ]; + + console.log({ + networkEnv, + guardian: guardian.address, + isSimulation, + isProduction, + release + }); + + const proposalContracts = proposalActions.map(a => a[0]); + const proposalFunctionSignatures = proposalActions.map(a => a[1]); + const proposalFunctionInputs = proposalActions.map(a => a[2]); + const proposalEthValues = proposalActions.map(a => a[3]); + if (isProduction && !checksOnly) { + await executeViaSafe( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + release.GuardiansSafe, + "mainnet" + ); + } else if (!checksOnly) { + await executeViaGuardian( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + guardian, + networkEnv + ); + } +}; + +export const upgradeXdcStep2 = async (network, checksOnly) => { + let [root] = await ethers.getSigners(); + + const isProduction = networkName.includes("production"); + + if (isProduction) verifyProductionSigner(root); + + let networkEnv = networkName; + let guardian = root; + if (isSimulation) { + networkEnv = network; + } + + const celoNetwork = networkEnv.split("-")[0] + "-celo"; + let release: { [key: string]: any } = dao[networkEnv]; + + console.log("signer:", root.address, { networkEnv, isSimulation, isProduction, release, celoNetwork }); + + if (isSimulation) { + networkEnv = network; + guardian = await ethers.getImpersonatedSigner(release.GuardiansSafe); + + await root.sendTransaction({ + value: ethers.utils.parseEther("1"), + to: release.GuardiansSafe + }); + } + + console.log({ + networkEnv, + guardian: guardian.address, + isSimulation, + isProduction, + release + }); + + const identityImpl = await ethers.deployContract("IdentityV3"); + const bridgeImpl = bridgeUpgradeImpl[networkEnv]; + console.log("deploying dist helper"); + + const DistHelper = await deployDeterministic( + { + name: "DistributionHelper", + factory: await ethers.getContractFactory("GenericDistributionHelper"), + isUpgradeable: true + }, + [ + release.NameService, + release.StaticOracle, + release.WXDC, + release.ReserveToken, + release.UniswapV3Router, + [ethers.utils.parseEther("20"), ethers.utils.parseEther("20"), 5, 5] + ], + {}, + false, + networkEnv + ); + + const exchangeId = keccak256(ethers.utils.solidityPack(["string", "string"], ["USDC", "G$"])); + + const torelease = { + DistributionHelper: DistHelper.address, + USDCEXchangeId: exchangeId + }; + release = { + ...release, + ...torelease + }; + await releaser(torelease, networkName, "deployment", false); + + console.log({ exchangeId }); + const reserveParams = undefined; + + const proposalActions = [ + [release.MpbBridge, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [bridgeImpl]), "0"], //upgrade bridge + [ + release.Identity, + "upgradeTo(address)", + ethers.utils.defaultAbiCoder.encode(["address"], [identityImpl.address]), + "0" + ], //upgrade identity + [ + release.NameService, //nameservice + "setAddresses(bytes32[],address[])", //add ubischeme + ethers.utils.defaultAbiCoder.encode( + ["bytes32[]", "address[]"], + [ + [keccak256(toUtf8Bytes("DISTRIBUTION_HELPER")), keccak256(toUtf8Bytes("MPBBRIDGE_CONTRACT"))], + [DistHelper.address, release.MpbBridge] + ] + ), + 0 + ], + [ + DistHelper.address, + "addOrUpdateRecipient((uint32,uint32,address,uint8))", + ethers.utils.defaultAbiCoder.encode( + ["uint32", "uint32", "address", "uint8"], + [dao[celoNetwork].CommunitySafe ? 9000 : 10000, release.networkId, release.UBIScheme, 1] //90% to ubi scheme + ), + 0 + ], + [ + release.MentoExpansionController, + "setDistributionHelper(address)", + ethers.utils.defaultAbiCoder.encode(["address"], [DistHelper.address]), + "0" + ], + [ + release.MentoExchangeProvider, + "createExchange((address,address,uint256,uint256,uint32,uint32))", + ethers.utils.defaultAbiCoder.encode( + ["tuple(address, address, uint256, uint256, uint32, uint32)"], + [ + [ + release.ReserveToken, + release.GoodDollar, + reserveParams.xdcGdSupplyEquivalent, + XDC_INITIAL_USDC, + reserveParams.reserveRatioXdc, + 10000000 + ] + ] + ), + "0" + ], + [ + release.GoodDollar, + "addMinter(address)", + ethers.utils.defaultAbiCoder.encode(["address"], [release.MentoBroker]), + "0" + ], //give minting rights to broker + [ + release.GoodDollar, + "addMinter(address)", + ethers.utils.defaultAbiCoder.encode(["address"], [release.MentoExpansionController]), + "0" + ] //give minting rights to expansion controller + ]; + + if (dao[celoNetwork].CommunitySafe) { + proposalActions.push([ + DistHelper.address, + "addOrUpdateRecipient((uint32,uint32,address,uint8))", + ethers.utils.defaultAbiCoder.encode( + ["uint32", "uint32", "address", "uint8"], + [1000, 42220, dao[celoNetwork].CommunitySafe, networkName === celoNetwork ? 1 : 0] //10% to celo community safe, use LZ bridge if not on celo + ), + 0 + ]); + } + + const proposalContracts = proposalActions.map(a => a[0]); + const proposalFunctionSignatures = proposalActions.map(a => a[1]); + const proposalFunctionInputs = proposalActions.map(a => a[2]); + const proposalEthValues = proposalActions.map(a => a[3]); + if (isProduction && !checksOnly) { + await executeViaSafe( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + release.GuardiansSafe, + "xdc" + ); + } else if (!checksOnly) { + await executeViaGuardian( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + guardian, + networkEnv, + false + ); + } + + if (!isProduction && isSimulation) { + const swapper = networkEnv.includes("production") + ? await ethers.getImpersonatedSigner("0x66582D24FEaD72555adaC681Cc621caCbB208324") + : root; + const USDC_AMOUNT = 5000e6; + const usdc = await ethers.getContractAt("IERC20", release.USDC); + const usdcSwapper = await ethers.getImpersonatedSigner("0xD0Ad6BC1c9E6fd9fC1Be1d674109E1AFcC78B058"); + await usdc.connect(usdcSwapper).transfer(swapper.address, 10000e6); + const gd = (await ethers.getContractAt("IGoodDollar", release.GoodDollar)) as IGoodDollar; + const isBrokerMinter = await gd.isMinter(release.MentoBroker); + const isExpansionMinter = await gd.isMinter(release.MentoExpansionController); + const mentoExchange = (await ethers.getContractAt( + "IBancorExchangeProvider", + release.MentoExchangeProvider + )) as IBancorExchangeProvider; + const mentoBroker = (await ethers.getContractAt("IBroker", release.MentoBroker)) as IBroker; + // const eids = await mentoExchange.getExchangeIds(); + const eids = ["0xf8d028730f58a008390c265fca425bb912e4c7efa370d4cef756a06f5029acd2"]; + const exchange = await mentoExchange.getPoolExchange(eids[0]); + const price = (await mentoExchange.currentPrice(eids[0])).toNumber() / 1e6; + console.log("current price:", price, await mentoExchange.currentPrice(eids[0])); + console.log("Exchange:", exchange, eids[0]); + + console.log("Broker minter check:", isBrokerMinter ? "Success" : "Failed"); + console.log("Expansion minter check:", isExpansionMinter ? "Success" : "Failed"); + await gd.connect(swapper).approve(release.MentoBroker, ethers.utils.parseEther("10000")); + console.log("balance before G$ swap:", await gd.balanceOf(swapper.address), await usdc.balanceOf(swapper.address)); + const shouldFail = await mentoBroker + .connect(swapper) + .swapIn(mentoExchange.address, eids[0], gd.address, usdc.address, ethers.utils.parseEther("10000"), 0) + .then(_ => _.wait()) + .catch(e => e.message); + console.log("initial gd swap should fail:", shouldFail); + console.log( + "balance before usdc swap:", + await gd.balanceOf(swapper.address), + await usdc.balanceOf(swapper.address) + ); + await usdc.connect(swapper).approve(release.MentoBroker, USDC_AMOUNT); + await mentoBroker + .connect(swapper) + .swapIn(mentoExchange.address, eids[0], usdc.address, gd.address, USDC_AMOUNT, 0) + .then(_ => _.wait()); + console.log( + "Balance after swap:", + swapper.address, + await gd.balanceOf(swapper.address), + await usdc.balanceOf(swapper.address) + ); + console.log("price after swap:", (await mentoExchange.currentPrice(eids[0])).toNumber() / 1e6); + + const mentomint = (await ethers.getContractAt( + "IGoodDollarExpansionController", + release.MentoExpansionController + )) as IGoodDollarExpansionController; + await usdc.connect(swapper).approve(mentomint.address, USDC_AMOUNT); + const tx = await (await mentomint.connect(swapper).mintUBIFromInterest(eids[0], USDC_AMOUNT)).wait(); + console.log( + "mint from interest:", + tx.events.find(_ => _.event === "InterestUBIMinted").args.amount.toString() / 1e18 + ); + console.log("price after interest mint:", (await mentoExchange.currentPrice(eids[0])).toNumber() / 1e6); + const distTx = await (await DistHelper.onDistribution(0, { gasLimit: 4000000 })).wait(); + const { distributionRecipients, distributed } = distTx.events.find(_ => _.event === "Distribution").args; + console.log( + "Distribution events:", + distributionRecipients, + distributed, + distTx.events.length, + distTx.events.map(_ => _.address) + ); + } +}; + +const calculateReserveParams = async () => { + // hacker and hacked multichain bridge accounts + const LOCKED_ACCOUNTS = [ + "0xeC577447D314cf1e443e9f4488216651450DBE7c", + "0xD17652350Cfd2A37bA2f947C910987a3B1A1c60d", + "0x6738fA889fF31F82d9Fe8862ec025dbE318f3Fde" + ]; + + const celoProvider = new ethers.providers.JsonRpcProvider("https://forno.celo.org"); + const xdcProvider = new ethers.providers.JsonRpcProvider("https://rpc.ankr.com/xdc"); + const fuseProvider = new ethers.providers.JsonRpcProvider("https://rpc.fuse.io"); + const ethProvider = new ethers.providers.JsonRpcProvider("https://eth.drpc.org"); + const gdCelo = (await ethers.getContractAt("GoodDollar", dao["production-celo"].GoodDollar)).connect(celoProvider); + const gdXdc = (await ethers.getContractAt("GoodDollar", dao["production-xdc"].GoodDollar)).connect(xdcProvider); + const gdFuse = (await ethers.getContractAt("GoodDollar", dao["production"].GoodDollar)).connect(fuseProvider); + const gdEth = (await ethers.getContractAt("GoodDollar", dao["production-mainnet"].GoodDollar)).connect(ethProvider); + const celoCusd = (await ethers.getContractAt("IERC20", dao["production-celo"].CUSD)).connect(celoProvider); + const celoSupply = await gdCelo.totalSupply(); + const totalSupply = [ + await gdCelo.totalSupply(), + await gdXdc.totalSupply(), + (await gdFuse.totalSupply()).mul(ethers.utils.parseEther("0.01")), //scale to 18 decimals + (await gdEth.totalSupply()).mul(ethers.utils.parseEther("0.01")) + ].reduce((acc, cur) => acc.add(cur), ethers.constants.Zero); + const lockedFunds = await Promise.all(LOCKED_ACCOUNTS.map(_ => gdEth.balanceOf(_))); + const totalLocked = lockedFunds + .reduce((acc, cur) => acc.add(cur), ethers.constants.Zero) + .mul(ethers.utils.parseEther("0.01")); //scale to 18 decimals + const realSupply = totalSupply.sub(totalLocked); + const reserveBalance = await celoCusd.balanceOf(dao["production-celo"].MentoReserve); + const xdcReserveBalance = ethers.utils.parseUnits("200000", 18); //200k in xdc + const totalUSD = reserveBalance.add(xdcReserveBalance); //reserve + 200k in xdc + const xdcSupplyShare = xdcReserveBalance.mul(ethers.constants.WeiPerEther).div(totalUSD); + const xdcGdSupplyEquivalent = realSupply.mul(xdcSupplyShare).div(ethers.constants.WeiPerEther); + const price = ethers.utils.parseUnits("0.0001283", 18); + const celoGdSupplyEquivalent = realSupply.sub(xdcGdSupplyEquivalent); + + console.log({ + totalSupply, + totalLocked, + realSupply, + reserveBalance, + totalUSD, + xdcSupplyShare, + xdcGdSupplyEquivalent, + celoGdSupplyEquivalent + }); + + // uint32 reserveRatio = uint32( + // (cUSDBalance * 1e18 * 1e8) / (price * totalGlobalSupply) + // ); + //calculate reserve ratio + const reserveRatioXdc = xdcReserveBalance + .mul(ethers.constants.WeiPerEther) //1e8 + .mul(ethers.constants.WeiPerEther) //1e18 + .div(xdcGdSupplyEquivalent.mul(price)); + console.log( + "recommended reserve ratio for xdc:", + reserveRatioXdc.toString(), + reserveRatioXdc.div(ethers.constants.WeiPerEther).toNumber() / 1e8 + ); + + //calcualte reserve ratio for celo + const reserveRatioCelo = reserveBalance + .mul(ethers.constants.WeiPerEther) //1e8 + .mul(ethers.constants.WeiPerEther) //1e18 + .div(celoGdSupplyEquivalent.mul(price)); + + console.log( + "recommended reserve ratio for celo:", + reserveRatioCelo.toString(), + reserveRatioCelo.div(ethers.constants.WeiPerEther).toNumber() / 1e18 + ); + + const normalizedRatioXdc = reserveRatioXdc.div("10000000000"); //reduce to 1e8 basis points + const normalizedRatioCelo = reserveRatioCelo.div("10000000000"); //reduce to 1e8 basis points + return { + celoSupply, + reserveRatioXdc: normalizedRatioXdc, + xdcGdSupplyEquivalent, + reserveRatioCelo: normalizedRatioCelo, + celoGdSupplyEquivalent + }; +}; + +export const main = async () => { + // await calculateReserveParams(); + // return; + prompt.start(); + const { network } = await prompt.get(["network"]); + + console.log("running step:", { network }); + const chain = last(network.split("-")) || "fuse"; + console.log("detected chain:", chain, network); + switch (chain) { + case "mainnet": + await upgradeEthStep2(network, false); + + break; + case "production": + case "fuse": + await upgradeFuseStep2(network, false); + + break; + case "celo": + await upgradeCeloStep2(network, false); + + break; + case "xdc": + await upgradeXdcStep2(network, false); + break; + } +}; + +main().catch(console.log); From 84fd08a701e7e97e71d46dad95f108ffec2afa52 Mon Sep 17 00:00:00 2001 From: sirpy Date: Wed, 21 Jan 2026 16:29:42 +0200 Subject: [PATCH 3/6] add: xdc contracts --- releases/deployment.json | 44 ++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/releases/deployment.json b/releases/deployment.json index 5c8d930b..827f3262 100644 --- a/releases/deployment.json +++ b/releases/deployment.json @@ -568,12 +568,12 @@ "development-mainnet": { "networkId": 1 }, "test": { "ProxyFactory": "0xCd7c00Ac6dc51e8dCc773971Ac9221cC582F3b1b", - "NameService": "0x421eF26e50B0a9B22BEbEb034ef62409E4982eAf", - "GReputation": "0x89d8aBD52beEE7eE9536083CaF89AE0C5CE00C99", - "CompoundVotingMachine": "0x2a4EF648d211487260b656D9CE8d05e9E86354d1", - "ClaimersDistribution": "0x36544e42e372d1188792088C271dd8076794B217", + "NameService": "0x79a8656f72F7F3F1C658718b5C9Fe808e2D6Ab39", + "GReputation": "0xFde4C515250562688C4E84cBe82Cf9af9F5d46Ae", + "CompoundVotingMachine": "0xd5e9cE20E33818a30e3626aAB3D1db9f09Ea8181", + "ClaimersDistribution": "0xde7FB3BA189eC8Fb278f0B8f1F005526eB489254", "GovernanceStaking": "0xf5C3953Ae4639806fcbCC3196f71dd81B0da4348", - "UBIScheme": "0x160554Da2f84105E879f415917bdd68DEEC17BB4", + "UBIScheme": "0x8f298403715F62f8958Aa1cd1cfDE6cE9B47319e", "ProtocolUpgradeFuse": "0xDDa0648FA8c9cD593416EC37089C2a2E6060B45c", "network": "test", "networkId": 4447, @@ -592,23 +592,23 @@ }, "test-mainnet": { "ProxyFactory": "0x70E5370b8981Abc6e14C91F4AcE823954EFC8eA3", - "NameService": "0x0708e4f1f9F16b185B87f85aCB934A6F13Ea4060", - "GReputation": "0x274d1a996Af9E406fEa1E76321b68581cb7C03AC", - "CompoundVotingMachine": "0x9F34160Fa8c1b296b67599c601249AEd26ef518c", - "GoodMarketMaker": "0x6B7b15dBfDe6B025428106f026Ea6146b2CF1D38", - "GoodReserveCDai": "0x77921dD809a08E8a022eE049fF04A666d6842668", - "ExchangeHelper": "0x1dc21E2f463E87CA0fEc04B90C200727fc407E1E", - "GoodFundManager": "0x17e235C296BBc62CD88c37696aC99a657Ae503b0", - "StakersDistribution": "0x07Eb78E143EFc9cc2b24d22c293b18b7e058f610", + "NameService": "0xA1de3421535CF40A6B341F165F1671878C48f1de", + "GReputation": "0x81099494e5325Bd6237EB67cA116514137E10Ba6", + "CompoundVotingMachine": "0xB7D090220BA506CCe0D2d0de3005893534Dc5f46", + "GoodMarketMaker": "0x31a7e4029bDe7eDd07b497B850474b0c65bd6D3f", + "GoodReserveCDai": "0x879892FbE36f7499Bea8744a01550190c078A68E", + "ExchangeHelper": "0x4fE75c387D27724d0a27b78fE6229E0f1385Db48", + "GoodFundManager": "0x1D98610e8702C66479565F73EBF3289Cff607e95", + "StakersDistribution": "0xde979777C259a24b623b7783be44F5810FC54B51", "ProtocolUpgrade": "0xbe18A1B61ceaF59aEB6A9bC81AB4FB87D56Ba167", "UniswapV2SwapHelper": "0x25C0a2F0A077F537Bd11897F04946794c2f6f1Ef", "CompoundStakingFactory": "0x01cf58e264d7578D4C67022c58A24CbC4C4a304E", "AaveStakingFactory": "0xd038A2EE73b64F30d65802Ad188F27921656f28F", "StakingContracts": [ - ["0xBAb9F0Bfaf3FbC4041e23F50f17A5dd8E52D5866", 13888], - ["0xEdB0C48Ab3d332B5FBA86c83162C734F2acCf8F1", "6944"] + ["0x31553691c8225B730914Ab2FC6b7EC6dbbBf52cd", 13888], + ["0xbfaeAb1e80726804Dcf46653961A538095cCC6AD", "6944"] ], - "DonationsStaking": "0xbE811FF6E7e40Db414ecFA0d18946Df83C0a1ed8", + "DonationsStaking": "0x1935252558E7FE0AF6cDB6897bb5c6dB81620577", "network": "test-mainnet", "networkId": 4447, "ForeignBridge": "0x7B4f352Cd40114f12e82fC675b5BA8C7582FC513", @@ -673,6 +673,7 @@ "ProxyFactory": "0x5BE34022c26FA03a8d6926314a42414c7ca2dF51", "GuardiansSafe": "0xE0c5daa7CC6F88d29505f702a53bb5E67600e7Ec", "CUSD": "0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1", + "USDC": "0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1", "ReserveToken": "0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1", "Identity": "0x27a4a02C9ed591E1a86e2e5D05870292c34622C9", "DAOCreator": "0xa2B9993D198904e4bdCE48379FDff65405607F42", @@ -686,6 +687,15 @@ "Invites": "0x6bd698566632bf2e81e2278f1656CB24aAF06D2e", "UBIScheme": "0x22867567E2D80f2049200E25C6F31CB6Ec2F0faf", "MpbBridge": "0xa3247276DbCC76Dd7705273f766eB3E8a5ecF4a5", - "BulkWhitelist": "0xe8861A20452Db53dF685F1d9e6B7017de3DB0E46" + "BulkWhitelist": "0xe8861A20452Db53dF685F1d9e6B7017de3DB0E46", + "UniswapV3Router": "0x3b9edecc4286ba33ea6e27119c2a4db99829839d", + "UniswapV3RouterOfficial": "0xaa52bB8110fE38D0d2d2AF0B85C3A3eE622CA455", + "StaticOracle": "0x725244458f011551Dde1104c9728746EEBEA19f9", + "WXDC": "0x951857744785e80e2de051c32ee7b25f9c458c42", + "MentoProxyAdmin": "0x8471406C3Bff67Cc6538481C8550364bda6Ca691", + "MentoExchangeProvider": "0x2fFBB49055d487DdBBb0C052Cd7c2a02A7971e41", + "MentoExpansionController": "0x5557E9dEF86ad6564462741a7A3f3679C1223f5d", + "MentoReserve": "0x94A3240f484A04F5e3d524f528d02694c109463b", + "MentoBroker": "0x88de45906D4F5a57315c133620cfa484cB297541" } } From f1d5a95b91d370175b0c11e179e3e3adf497bf13 Mon Sep 17 00:00:00 2001 From: sirpy Date: Sun, 1 Feb 2026 09:42:54 +0200 Subject: [PATCH 4/6] add: v4 --- contracts/identity/IdentityV4.sol | 541 ++++++++++++++++++++++++++++++ 1 file changed, 541 insertions(+) create mode 100644 contracts/identity/IdentityV4.sol diff --git a/contracts/identity/IdentityV4.sol b/contracts/identity/IdentityV4.sol new file mode 100644 index 00000000..42a907a2 --- /dev/null +++ b/contracts/identity/IdentityV4.sol @@ -0,0 +1,541 @@ +// 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)"; + + /* @dev rough estimate of number of whitelisted addresses */ + 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); + + event AccountConnected(address indexed connected, address indexed to); + event AccountDisconnected(address indexed disconnected, address indexed from); + + function initialize( + address _owner, + IIdentity _oldIdentity + ) public initializer { + __AccessControl_init_unchained(); + __Pausable_init_unchained(); + __EIP712_init_unchained("Identity", "1.0.0"); + 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 { + _removeWhitelisted(account); + identities[account].status = 255; + emit BlacklistAdded(account); + } + + /** + * @dev Removes an address from blacklist + * Can only be called by Identity Administrators. + * @param account address to remove as blacklisted + */ + function removeBlacklisted( + address account + ) external onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + if ( + address(oldIdentity) != address(0) && oldIdentity.isBlacklisted(account) + ) oldIdentity.removeBlacklisted(account); + + identities[account].status = 0; + emit BlacklistRemoved(account); + } + + /** + * @dev Function to add a Contract to list of contracts + * @param account The address to add + */ + function addContract( + address account + ) public onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + require(isContract(account), "Given address is not a contract"); + _addWhitelisted(account, _chainId()); + identities[account].status = 2; //this must come after _addWhitelisted + + emit ContractAdded(account); + } + + /** + * @dev Function to remove a Contract from list of contracts + * @param account The address to add + */ + function removeContract( + address account + ) public onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { + if ( + address(oldIdentity) != address(0) && oldIdentity.isDAOContract(account) + ) { + oldIdentity.removeContract(account); + } + _removeWhitelisted(account); + + emit ContractRemoved(account); + } + + /** + * @dev Function to check if given contract is on list of contracts. + * @param account to check + * @return a bool indicating if address is on list of contracts + */ + function isDAOContract(address account) external view returns (bool) { + if (identities[account].status == 2) return true; + if (address(oldIdentity) != address(0)) { + try oldIdentity.isDAOContract(account) returns (bool res) { + return res; + } catch { + return false; + } + } + return false; + } + + /** + * @dev Internal function to add to whitelisted + * @param account the address to add + */ + function _addWhitelisted(address account, uint256 orgChain) internal { + require(identities[account].status == 0, "already has status"); + whitelistedCount += 1; + identities[account].status = 1; + identities[account].dateAdded = block.timestamp; + identities[account].dateAuthenticated = block.timestamp; + identities[account].whitelistedOnChainId = orgChain; + connectedAccounts[account] = address(0); + + if (isContract(account)) { + whitelistedContracts += 1; + } + + emit WhitelistedAdded(account); + } + + /** + * @dev Internal whitelisting with did function. + * @param account the address to add + * @param did the id to register account under + */ + function _addWhitelistedWithDID( + address account, + string memory did, + uint256 orgChain + ) internal { + bytes32 pHash = keccak256(bytes(did)); + require(didHashToAddress[pHash] == address(0), "DID already registered"); + + identities[account].did = did; + didHashToAddress[pHash] = account; + + _addWhitelisted(account, orgChain); + } + + /** + * @dev Internal function to remove from whitelisted + * @param account the address to add + */ + function _removeWhitelisted(address account) internal { + if (identities[account].status == 1 || identities[account].status == 2) { + whitelistedCount -= 1; + + if (isContract(account) && whitelistedContracts > 0) { + whitelistedContracts -= 1; + } + + string memory did = identities[account].did; + bytes32 pHash = keccak256(bytes(did)); + + delete identities[account]; + delete didHashToAddress[pHash]; + + emit WhitelistedRemoved(account); + } + + if ( + address(oldIdentity) != address(0) && oldIdentity.isWhitelisted(account) + ) { + oldIdentity.removeWhitelisted(account); + } + } + + /// @notice helper function to get current chain id + /// @return chainId id + function _chainId() internal view returns (uint256 chainId) { + assembly { + chainId := chainid() + } + } + + /** + * @dev Returns true if given address has been added to the blacklist + * @param account the address to check + * @return a bool indicating weather the address is present in the blacklist + */ + function isBlacklisted(address account) public view returns (bool) { + if (identities[account].status == 255) return true; + if (address(oldIdentity) != address(0)) { + try oldIdentity.isBlacklisted(account) returns (bool res) { + return res; + } catch { + return false; + } + } + return false; + } + + /** + * @dev Function to see if given address is a contract + * @return true if address is a contract + */ + function isContract(address _addr) internal view returns (bool) { + uint256 length; + assembly { + length := extcodesize(_addr) + } + return length > 0; + } + + /** + @dev allows user to connect more accounts to his identity. msg.sender needs to be whitelisted + @param account the account to connect to msg.sender + */ + function connectAccount(address account) external onlyWhitelisted { + require( + !isWhitelisted(account) && !isBlacklisted(account), + "invalid account" + ); + require(connectedAccounts[account] == address(0x0), "already connected"); + + connectedAccounts[account] = msg.sender; + emit AccountConnected(account, msg.sender); + } + + /** + @dev disconnect a connected account from identity. can be performed either by identity or the connected account + @param connected the account to disconnect + */ + function disconnectAccount(address connected) external { + require( + connectedAccounts[connected] == msg.sender || msg.sender == connected, + "unauthorized" + ); + delete connectedAccounts[connected]; + emit AccountDisconnected(connected, msg.sender); + } + + /** + @dev returns the identity in case account is connected or is the identity itself otherwise returns the empty address + @param account address to get its identity + @return whitelisted the identity or address 0 if _account not connected or not identity + **/ + function getWhitelistedRoot( + address account + ) external view returns (address whitelisted) { + if (isWhitelisted(account)) return account; + if (isWhitelisted(connectedAccounts[account])) + return connectedAccounts[account]; + + return address(0x0); + } + + function pause(bool toPause) external onlyRole(PAUSER_ROLE) { + if (toPause) _pause(); + else _unpause(); + } + + /** + @dev modify account did can be called by account owner or identity admin + @param account the account to modify + @param did the did to set + */ + function setDID(address account, string calldata did) external { + require( + msg.sender == account || hasRole(IDENTITY_ADMIN_ROLE, msg.sender), + "not authorized" + ); + _setDID(account, did); + } + + function _setDID(address account, string memory did) internal { + require(isWhitelisted(account), "not whitelisted"); + require(bytes(did).length > 0, "did empty"); + bytes32 pHash = keccak256(bytes(did)); + require(didHashToAddress[pHash] == address(0), "DID already registered"); + + if (address(oldIdentity) != address(0)) { + address oldDIDOwner; + try oldIdentity.didHashToAddress(pHash) returns (address _didOwner) { + oldDIDOwner = _didOwner; + } catch {} + //if owner not the same and doesnt have a new did set then revert + require( + oldDIDOwner == address(0) || + oldDIDOwner == account || + bytes(identities[oldDIDOwner].did).length > 0, + "DID already registered oldIdentity" + ); + } + + bytes32 oldHash = keccak256(bytes(identities[account].did)); + delete didHashToAddress[oldHash]; + identities[account].did = did; + didHashToAddress[pHash] = account; + } + + /** + @dev for backward compatability with V1 + @param account to get DID for + @return did of the account + */ + function addrToDID( + address account + ) external view returns (string memory did) { + did = identities[account].did; + bytes32 pHash = keccak256(bytes(did)); + + //if did was set in this contract return it, otherwise check oldidentity + if (didHashToAddress[pHash] == account) return did; + + if (address(oldIdentity) != address(0)) { + try oldIdentity.addrToDID(account) returns (string memory _did) { + return _did; + } catch { + return ""; + } + } + + return ""; + } + + function getWhitelistedOnChainId( + address account + ) external view returns (uint256 chainId) { + chainId = identities[account].whitelistedOnChainId; + return chainId > 0 ? chainId : _chainId(); + } + + /** + * backward compatability with IdentityV1 that GoodDollar token checks if the identity contract is registered + */ + function isRegistered() external pure returns (bool) { + return true; + } +} From 81563ac7b55b4e2d7474f567150d5e0fc32a0bd1 Mon Sep 17 00:00:00 2001 From: sirpy Date: Sun, 1 Feb 2026 09:50:47 +0200 Subject: [PATCH 5/6] add: basic v4 changes --- contracts/identity/IdentityV4.sol | 67 +++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/contracts/identity/IdentityV4.sol b/contracts/identity/IdentityV4.sol index 42a907a2..e8f87f29 100644 --- a/contracts/identity/IdentityV4.sol +++ b/contracts/identity/IdentityV4.sol @@ -16,7 +16,7 @@ import "../Interfaces.sol"; /* @title Identity contract responsible for whitelisting * and keeping track of amount of whitelisted users */ -contract IdentityV3 is +contract IdentityV4 is DAOUpgradeableContract, AccessControlUpgradeable, PausableUpgradeable, @@ -28,6 +28,7 @@ contract IdentityV3 is string did; uint256 whitelistedOnChainId; uint8 status; //0 nothing, 1 whitelisted, 2 daocontract, 255 blacklisted + uint32 authCount; } bytes32 public constant IDENTITY_ADMIN_ROLE = keccak256("identity_admin"); @@ -38,7 +39,7 @@ contract IdentityV3 is /* @dev rough estimate of number of whitelisted addresses */ uint256 public whitelistedCount; uint256 public whitelistedContracts; - uint256 public authenticationPeriod; + uint256 private unused_authenticationPeriod; mapping(address => Identity) public identities; @@ -48,6 +49,8 @@ contract IdentityV3 is IIdentity public oldIdentity; + uint32[] public reverifyDaysOptions; + event BlacklistAdded(address indexed account); event BlacklistRemoved(address indexed account); @@ -68,7 +71,6 @@ contract IdentityV3 is __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); @@ -77,6 +79,12 @@ contract IdentityV3 is _setupRole(IDENTITY_ADMIN_ROLE, avatar); oldIdentity = _oldIdentity; + + // default reverify schedule (days) + // adjust as needed via setReverifyDaysOptions + reverifyDaysOptions.push(1); + reverifyDaysOptions.push(7); + reverifyDaysOptions.push(180); } /** @@ -96,13 +104,18 @@ contract IdentityV3 is } /** - * @dev Sets a new value for authenticationPeriod. - * Can only be called by Identity Administrators. - * @param period new value for authenticationPeriod + * @dev Set the reverification schedule (days). Only identity admins. + * Provide non-empty array. Values must fit in uint8 (<=255). */ - function setAuthenticationPeriod(uint256 period) external whenNotPaused { - _onlyAvatar(); - authenticationPeriod = period; + function setReverifyDaysOptions( + uint8[] calldata options + ) external onlyRole(IDENTITY_ADMIN_ROLE) { + require(options.length > 0, "empty options"); + // replace storage array + delete reverifyDaysOptions; + for (uint256 i = 0; i < options.length; i++) { + reverifyDaysOptions.push(options[i]); + } } /** @@ -127,7 +140,21 @@ contract IdentityV3 is uint256 timestamp ) public onlyRole(IDENTITY_ADMIN_ROLE) whenNotPaused { require(identities[account].status == 1, "not whitelisted"); + uint daysSinceAuthentication = (timestamp - + identities[account].dateAuthenticated) / 1 days; + + // should happen before updating dateAuthenticated + if (shouldReverify(account, daysSinceAuthentication)) { + identities[account].authCount += 1; + // if authCount has advanced past the last configured reverify step, + // reset back to start (cycle). This ensures we won't index out of bounds + // and allows repeated cycles through the reverify schedule. + if (identities[account].authCount >= reverifyDaysOptions.length) { + identities[account].authCount = 0; + } + } identities[account].dateAuthenticated = timestamp; + emit WhitelistedAuthenticated(account, timestamp); } @@ -191,6 +218,18 @@ contract IdentityV3 is _removeWhitelisted(msg.sender); } + function shouldReverify( + address account, + uint daysSinceAuth + ) public view returns (bool) { + if (identities[account].authCount >= reverifyDaysOptions.length) + return false; + uint reverifyAfterDays = reverifyDaysOptions[identities[account].authCount]; + if (daysSinceAuth >= reverifyAfterDays) return true; + + return false; + } + /** * @dev Returns true if given address has been added to whitelist * @param account the address to check @@ -199,10 +238,11 @@ contract IdentityV3 is 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 (shouldReverify(account, daysSinceAuthentication)) { + return false; + } + if (identities[account].status == 1) return true; if (address(oldIdentity) != address(0)) { try oldIdentity.isWhitelisted(account) returns (bool res) { @@ -320,6 +360,7 @@ contract IdentityV3 is identities[account].dateAdded = block.timestamp; identities[account].dateAuthenticated = block.timestamp; identities[account].whitelistedOnChainId = orgChain; + identities[account].authCount = 0; connectedAccounts[account] = address(0); if (isContract(account)) { From 9a65ddd8738e573d7d83a957ca3f947d737e02ae Mon Sep 17 00:00:00 2001 From: sirpy Date: Mon, 2 Feb 2026 10:34:02 +0200 Subject: [PATCH 6/6] add: identity v4 tested --- contracts/identity/IdentityV4.sol | 5 +- test/governance/ClaimersDistribution.test.ts | 2 +- test/helpers.ts | 271 ++++-------- test/identity/IdentityV3.test.ts | 182 +++----- test/identity/IdentityV4.test.ts | 429 +++++++++++++++++++ 5 files changed, 561 insertions(+), 328 deletions(-) create mode 100644 test/identity/IdentityV4.test.ts diff --git a/contracts/identity/IdentityV4.sol b/contracts/identity/IdentityV4.sol index e8f87f29..1c35374b 100644 --- a/contracts/identity/IdentityV4.sol +++ b/contracts/identity/IdentityV4.sol @@ -239,10 +239,9 @@ contract IdentityV4 is uint256 daysSinceAuthentication = (block.timestamp - identities[account].dateAuthenticated) / 1 days; - if (shouldReverify(account, daysSinceAuthentication)) { - return false; + if (identities[account].status == 1) { + return shouldReverify(account, daysSinceAuthentication) == false; } - if (identities[account].status == 1) return true; if (address(oldIdentity) != address(0)) { try oldIdentity.isWhitelisted(account) returns (bool res) { diff --git a/test/governance/ClaimersDistribution.test.ts b/test/governance/ClaimersDistribution.test.ts index e87c69a3..6b708f3e 100644 --- a/test/governance/ClaimersDistribution.test.ts +++ b/test/governance/ClaimersDistribution.test.ts @@ -257,6 +257,6 @@ describe("ClaimersDistribution", () => { // console.log({ totalGas }, tx.gasUsed.toNumber(), tx2.gasUsed.toNumber()); } console.log(Object.keys(gasCosts)); - expect(totalGas / 30).lt(316000); + expect(totalGas / 30).lt(319000); }); }); diff --git a/test/helpers.ts b/test/helpers.ts index 04a7dd80..c7e73f31 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -12,7 +12,7 @@ import IUniswapV2Pair from "@uniswap/v2-core/build/IUniswapV2Pair.json"; import UniswapV2Factory from "@uniswap/v2-core/build/UniswapV2Factory.json"; import WETH9 from "@uniswap/v2-periphery/build/WETH9.json"; import UniswapV2Router02 from "@uniswap/v2-periphery/build/UniswapV2Router02.json"; -import { GoodMarketMaker, CompoundVotingMachine } from "../types"; +import { GoodMarketMaker, CompoundVotingMachine, IdentityV4 } from "../types"; import { Contract } from "ethers"; import testDeployer from "@superfluid-finance/ethereum-contracts/dev-scripts/deploy-test-framework"; @@ -24,9 +24,7 @@ export const getStakingFactory = async ( | "GoodAaveStakingV2" | "GoodCompoundStakingV2" ) => { - let swapHelper = await ethers - .getContractFactory("UniswapV2SwapHelper") - .then(_ => _.deploy()); + let swapHelper = await ethers.getContractFactory("UniswapV2SwapHelper").then(_ => _.deploy()); const simpleStakingFactory = await ethers.getContractFactory(factory, { libraries: { @@ -46,17 +44,13 @@ export const deploySuperFluid = async () => { }; export const deploySuperGoodDollar = async (sfContracts, tokenArgs) => { - const SuperGoodDollarFactory = await ethers.getContractFactory( - "SuperGoodDollar" - ); + const SuperGoodDollarFactory = await ethers.getContractFactory("SuperGoodDollar"); console.log("deploying supergooddollar logic"); const SuperGoodDollar = await SuperGoodDollarFactory.deploy(sfContracts.host); console.log("deploying supergooddollar proxy"); - const GoodDollarProxyFactory = await ethers.getContractFactory( - "contracts/token/superfluid/UUPSProxy.sol:UUPSProxy" - ); + const GoodDollarProxyFactory = await ethers.getContractFactory("contracts/token/superfluid/UUPSProxy.sol:UUPSProxy"); const GoodDollarProxy = await GoodDollarProxyFactory.deploy(); console.log("deployed supergooddollar proxy, initializing proxy..."); await GoodDollarProxy.initializeProxy(SuperGoodDollar.address); @@ -67,15 +61,12 @@ export const deploySuperGoodDollar = async (sfContracts, tokenArgs) => { const outNftProxy = await GoodDollarProxyFactory.deploy(); const inNftProxy = await GoodDollarProxyFactory.deploy(); - const constantInflowNFT = await ethers.deployContract("ConstantInflowNFT", [ + const constantInflowNFT = await ethers.deployContract("ConstantInflowNFT", [sfContracts.host, outNftProxy.address]); + + const constantOutflowNFT = await ethers.deployContract("ConstantOutflowNFT", [ sfContracts.host, - outNftProxy.address + inNftProxy.address ]); - - const constantOutflowNFT = await ethers.deployContract( - "ConstantOutflowNFT", - [sfContracts.host, inNftProxy.address] - ); await outNftProxy.initializeProxy(constantOutflowNFT.address); await inNftProxy.initializeProxy(constantInflowNFT.address); @@ -83,42 +74,27 @@ export const deploySuperGoodDollar = async (sfContracts, tokenArgs) => { await SuperGoodDollar.attach(GoodDollarProxy.address)[ "initialize(string,string,uint256,address,address,address,address)" ](...tokenArgs); - const GoodDollar = await ethers.getContractAt( - "SuperGoodDollar", - GoodDollarProxy.address - ); + const GoodDollar = await ethers.getContractAt("SuperGoodDollar", GoodDollarProxy.address); console.log("supergooddollar created successfully"); await constantOutflowNFT .attach(outNftProxy.address) - .initialize( - (await GoodDollar.symbol()) + " Outflow NFT", - (await GoodDollar.symbol()) + " COF" - ); + .initialize((await GoodDollar.symbol()) + " Outflow NFT", (await GoodDollar.symbol()) + " COF"); await constantInflowNFT .attach(inNftProxy.address) - .initialize( - (await GoodDollar.symbol()) + " Inflow NFT", - (await GoodDollar.symbol()) + " CIF" - ); + .initialize((await GoodDollar.symbol()) + " Inflow NFT", (await GoodDollar.symbol()) + " CIF"); return GoodDollar; } else { console.log("initializing supergooddollar...."); await SuperGoodDollar.attach(GoodDollarProxy.address)[ "initialize(string,string,uint256,address,address,address,address)" ](...tokenArgs); - const GoodDollar = await ethers.getContractAt( - "SuperGoodDollar", - GoodDollarProxy.address - ); + const GoodDollar = await ethers.getContractAt("SuperGoodDollar", GoodDollarProxy.address); console.log("supergooddollar created successfully"); return GoodDollar; } }; -export const createDAO = async ( - tokenType: "super" | "regular" = "super", - identity: "v2" | "v3" = "v3" -) => { +export const createDAO = async (tokenType: "super" | "regular" = "super", identity: "v2" | "v3" | "v4" = "v4") => { let [root, ...signers] = await ethers.getSigners(); const sfContracts = await deploySuperFluid(); @@ -130,38 +106,23 @@ export const createDAO = async ( let cDAI = await cdaiFactory.deploy(dai.address); - const DAOCreatorFactory = new ethers.ContractFactory( - DAOCreatorABI.abi, - DAOCreatorABI.bytecode, - root - ); + const DAOCreatorFactory = new ethers.ContractFactory(DAOCreatorABI.abi, DAOCreatorABI.bytecode, root); - const IdentityFactory = await ethers.getContractFactory( - identity === "v2" ? "IdentityV2" : "IdentityV3" - ); + const IdentityFactory = await ethers.getContractFactory("Identity" + identity.toUpperCase()); - const FeeFormulaFactory = new ethers.ContractFactory( - FeeFormulaABI.abi, - FeeFormulaABI.bytecode, - root - ); + const FeeFormulaFactory = new ethers.ContractFactory(FeeFormulaABI.abi, FeeFormulaABI.bytecode, root); - const BancorFormula = await ( - await ethers.getContractFactory("BancorFormula") - ).deploy(); + const BancorFormula = await (await ethers.getContractFactory("BancorFormula")).deploy(); await BancorFormula.init(); console.log("deploy upgradeable identity..."); - const Identity = await upgrades.deployProxy( - IdentityFactory, - [root.address, ethers.constants.AddressZero], - { - kind: "uups" - } - ); - + const Identity = (await upgrades.deployProxy(IdentityFactory, [root.address, ethers.constants.AddressZero], { + kind: "uups" + })) as IdentityV4; + // put reverify far in the future to avoid interference with tests + if (identity === "v4") await Identity.setReverifyDaysOptions([180]); const daoCreator = await DAOCreatorFactory.deploy(); const FeeFormula = await FeeFormulaFactory.deploy(0); const GReputation = await ethers.getContractFactory("GReputation"); @@ -183,19 +144,10 @@ export const createDAO = async ( GoodDollar = await upgrades.deployProxy( GoodDollarFactory, - [ - "GoodDollar", - "G$", - 0, - FeeFormula.address, - Identity.address, - ethers.constants.AddressZero, - daoCreator.address - ], + ["GoodDollar", "G$", 0, FeeFormula.address, Identity.address, ethers.constants.AddressZero, daoCreator.address], { kind: "uups", - initializer: - "initialize(string, string, uint256, address, address, address,address)" + initializer: "initialize(string, string, uint256, address, address, address,address)" } ); } else { @@ -218,20 +170,11 @@ export const createDAO = async ( daoCreator: daoCreator.address }); // await Identity.setAuthenticationPeriod(365); - await daoCreator.forgeOrg( - GoodDollar.address, - reputation.address, - [], - 1000, - [] - ); + await daoCreator.forgeOrg(GoodDollar.address, reputation.address, [], 1000, []); const Avatar = new ethers.Contract( await daoCreator.avatar(), - [ - "function owner() view returns (address)", - "function nativeToken() view returns (address)" - ], + ["function owner() view returns (address)", "function nativeToken() view returns (address)"], root ); @@ -245,11 +188,7 @@ export const createDAO = async ( await GoodDollar.isPauser(Avatar.address) ); - const ccFactory = new ethers.ContractFactory( - ContributionCalculation.abi, - ContributionCalculation.bytecode, - root - ); + const ccFactory = new ethers.ContractFactory(ContributionCalculation.abi, ContributionCalculation.bytecode, root); const contribution = await ccFactory.deploy(Avatar.address, 0, 1e15); @@ -332,13 +271,9 @@ export const createDAO = async ( const MM = await ethers.getContractFactory("GoodMarketMaker"); - let marketMaker = (await upgrades.deployProxy( - MM, - [nameService.address, 999388834642296, 1e15], - { - kind: "uups" - } - )) as unknown as GoodMarketMaker; + let marketMaker = (await upgrades.deployProxy(MM, [nameService.address, 999388834642296, 1e15], { + kind: "uups" + })) as unknown as GoodMarketMaker; await (await reputation.updateDAO(nameService.address)).wait(); @@ -346,28 +281,16 @@ export const createDAO = async ( //generic call permissions let schemeMock = signers[signers.length - 1]; - const ictrl = await ethers.getContractAt( - "Controller", - controller, - schemeMock - ); + const ictrl = await ethers.getContractAt("Controller", controller, schemeMock); const setSchemes = async (addrs, params = []) => { for (let i in addrs) { - await ictrl.registerScheme( - addrs[i], - params[i] || ethers.constants.HashZero, - "0x0000001F", - Avatar.address - ); + await ictrl.registerScheme(addrs[i], params[i] || ethers.constants.HashZero, "0x0000001F", Avatar.address); } }; const setDAOAddress = async (name, addr) => { - const encoded = nameService.interface.encodeFunctionData("setAddress", [ - name, - addr - ]); + const encoded = nameService.interface.encodeFunctionData("setAddress", [name, addr]); await ictrl.genericCall(nameService.address, encoded, Avatar.address, 0); }; @@ -377,21 +300,20 @@ export const createDAO = async ( expect(funcNameEnd).to.be.gt(-1); const functionName = functionAbi.substring(0, funcNameEnd); - await expect(contract[functionAbi](...parameters)).to.revertedWith( - /avatar/ - ); - const encoded = contract.interface.encodeFunctionData(functionName, [ - ...parameters - ]); + await expect(contract[functionAbi](...parameters)).to.revertedWith(/avatar/); + const encoded = contract.interface.encodeFunctionData(functionName, [...parameters]); await ictrl.genericCall(contract.address, encoded, Avatar.address, 0); }; const setReserveToken = async (token, gdReserve, tokenReserve, RR) => { - const encoded = marketMaker.interface.encodeFunctionData( - "initializeToken", - [token, gdReserve, tokenReserve, RR, 0] - ); + const encoded = marketMaker.interface.encodeFunctionData("initializeToken", [ + token, + gdReserve, + tokenReserve, + RR, + 0 + ]); await ictrl.genericCall(marketMaker.address, encoded, Avatar.address, 0); }; @@ -417,9 +339,9 @@ export const createDAO = async ( const gd = await Avatar.nativeToken(); //make GoodCap minter console.log("Setting reserve as minter..."); - const encoded = ( - await ethers.getContractAt("IGoodDollar", gd) - ).interface.encodeFunctionData("addMinter", [goodReserve.address]); + const encoded = (await ethers.getContractAt("IGoodDollar", gd)).interface.encodeFunctionData("addMinter", [ + goodReserve.address + ]); await ictrl.genericCall(gd, encoded, Avatar.address, 0); @@ -427,25 +349,17 @@ export const createDAO = async ( await ictrl.genericCall( goodReserve.address, - goodReserve.interface.encodeFunctionData("setDistributionHelper", [ - distHelper.address - ]), + goodReserve.interface.encodeFunctionData("setDistributionHelper", [distHelper.address]), Avatar.address, 0 ); - const gasFeeMockFactory = await ethers.getContractFactory( - "GasPriceMockOracle" - ); + const gasFeeMockFactory = await ethers.getContractFactory("GasPriceMockOracle"); const gasFeeOracle = await gasFeeMockFactory.deploy(); - const daiEthPriceMockFactory = await ethers.getContractFactory( - "DaiEthPriceMockOracle" - ); + const daiEthPriceMockFactory = await ethers.getContractFactory("DaiEthPriceMockOracle"); const daiEthOracle = await daiEthPriceMockFactory.deploy(); - const ethUsdOracleFactory = await ethers.getContractFactory( - "EthUSDMockOracle" - ); + const ethUsdOracleFactory = await ethers.getContractFactory("EthUSDMockOracle"); const ethUsdOracle = await ethUsdOracleFactory.deploy(); console.log("setting nameservice addrresses..."); @@ -531,15 +445,17 @@ export const deployUBI = async (deployedDAO, withFirstClaim = true) => { const gd = await nameService.getAddress("GOODDOLLAR"); - let encoded = ( - await ethers.getContractAt("IGoodDollar", gd) - ).interface.encodeFunctionData("mint", [firstClaim.address, 1000000]); + let encoded = (await ethers.getContractAt("IGoodDollar", gd)).interface.encodeFunctionData("mint", [ + firstClaim.address, + 1000000 + ]); await genericCall(gd, encoded); - encoded = ( - await ethers.getContractAt("IGoodDollar", gd) - ).interface.encodeFunctionData("mint", [ubiScheme.address, 1000000]); + encoded = (await ethers.getContractAt("IGoodDollar", gd)).interface.encodeFunctionData("mint", [ + ubiScheme.address, + 1000000 + ]); await genericCall(gd, encoded); @@ -547,9 +463,7 @@ export const deployUBI = async (deployedDAO, withFirstClaim = true) => { await setSchemes([firstClaim.address, ubiScheme.address]); if (withFirstClaim) { - encoded = firstClaim.interface.encodeFunctionData("setUBIScheme", [ - ubiScheme.address - ]); + encoded = firstClaim.interface.encodeFunctionData("setUBIScheme", [ubiScheme.address]); await genericCall(firstClaim.address, encoded); await firstClaim.start(); @@ -598,15 +512,17 @@ export const deployOldUBI = async deployedDAO => { const gd = await nameService.getAddress("GOODDOLLAR"); - let encoded = ( - await ethers.getContractAt("IGoodDollar", gd) - ).interface.encodeFunctionData("mint", [firstClaim.address, 1000000]); + let encoded = (await ethers.getContractAt("IGoodDollar", gd)).interface.encodeFunctionData("mint", [ + firstClaim.address, + 1000000 + ]); await genericCall(gd, encoded); - encoded = ( - await ethers.getContractAt("IGoodDollar", gd) - ).interface.encodeFunctionData("mint", [ubiScheme.address, 1000000]); + encoded = (await ethers.getContractAt("IGoodDollar", gd)).interface.encodeFunctionData("mint", [ + ubiScheme.address, + 1000000 + ]); await genericCall(gd, encoded); @@ -651,26 +567,16 @@ export const deployOldVoting = async dao => { SchemeRegistrarF.deploy() ]); console.log("setting parameters"); - const voteParametersHash = await absoluteVote.getParametersHash( - 50, - ethers.constants.AddressZero - ); + const voteParametersHash = await absoluteVote.getParametersHash(50, ethers.constants.AddressZero); console.log("setting params for voting machine and schemes"); await Promise.all([ - schemeRegistrar.setParameters( - voteParametersHash, - voteParametersHash, - absoluteVote.address - ), + schemeRegistrar.setParameters(voteParametersHash, voteParametersHash, absoluteVote.address), absoluteVote.setParameters(50, ethers.constants.AddressZero), upgradeScheme.setParameters(voteParametersHash, absoluteVote.address) ]); - const upgradeParametersHash = await upgradeScheme.getParametersHash( - voteParametersHash, - absoluteVote.address - ); + const upgradeParametersHash = await upgradeScheme.getParametersHash(voteParametersHash, absoluteVote.address); // Deploy SchemeRegistrar const schemeRegisterParams = await schemeRegistrar.getParametersHash( @@ -710,18 +616,10 @@ export const deployUniswap = async (comp, dai) => { UniswapV2Factory.bytecode, (await ethers.getSigners())[0] ); - const wethFactory = new ethers.ContractFactory( - WETH9.abi, - WETH9.bytecode, - (await ethers.getSigners())[0] - ); + const wethFactory = new ethers.ContractFactory(WETH9.abi, WETH9.bytecode, (await ethers.getSigners())[0]); const weth = await wethFactory.deploy(); - const factory = await uniswapFactory.deploy( - ( - await ethers.getSigners() - )[0].address - ); + const factory = await uniswapFactory.deploy((await ethers.getSigners())[0].address); const router = await routerFactory.deploy(factory.address, weth.address); await factory.createPair(comp.address, weth.address); // Create comp and weth pair const compPairAddress = factory.getPair(comp.address, weth.address); @@ -729,28 +627,11 @@ export const deployUniswap = async (comp, dai) => { await factory.createPair(dai.address, weth.address); // Create comp and dai pair const daiPairAddress = factory.getPair(dai.address, weth.address); - const compPair = new Contract( - compPairAddress, - JSON.stringify(IUniswapV2Pair.abi), - staker - ).connect(founder); - const daiPair = new Contract( - daiPairAddress, - JSON.stringify(IUniswapV2Pair.abi), - staker - ).connect(founder); - await dai["mint(address,uint256)"]( - founder.address, - ethers.utils.parseEther("2000000") - ); - await dai["mint(address,uint256)"]( - daiPair.address, - ethers.utils.parseEther("2000000") - ); - await comp["mint(address,uint256)"]( - compPair.address, - ethers.utils.parseEther("200000") - ); + const compPair = new Contract(compPairAddress, JSON.stringify(IUniswapV2Pair.abi), staker).connect(founder); + const daiPair = new Contract(daiPairAddress, JSON.stringify(IUniswapV2Pair.abi), staker).connect(founder); + await dai["mint(address,uint256)"](founder.address, ethers.utils.parseEther("2000000")); + await dai["mint(address,uint256)"](daiPair.address, ethers.utils.parseEther("2000000")); + await comp["mint(address,uint256)"](compPair.address, ethers.utils.parseEther("200000")); console.log("depositing eth to liquidity pools"); await weth.deposit({ value: ethers.utils.parseEther("4000") }); console.log(await weth.balanceOf(founder.address).then(_ => _.toString())); diff --git a/test/identity/IdentityV3.test.ts b/test/identity/IdentityV3.test.ts index b198d8d2..dd38aa11 100644 --- a/test/identity/IdentityV3.test.ts +++ b/test/identity/IdentityV3.test.ts @@ -16,16 +16,12 @@ describe("IdentityV3", () => { let avatar, gd: IGoodDollar, Controller, id: IIdentity; + const createDAOv3 = async () => { + return createDAO("super", "v3"); + }; before(async () => { [founder, ...signers] = await ethers.getSigners(); - let { - controller, - avatar: av, - gd: gooddollar, - identity: idv2, - genericCall: gc - } = await loadFixture(createDAO); - + let { controller, avatar: av, gd: gooddollar, identity: idv2, genericCall: gc } = await loadFixture(createDAOv3); genericCall = gc; identity = (await ethers.getContractAt("IdentityV3", idv2)) as IdentityV3; Controller = controller; @@ -38,34 +34,24 @@ describe("IdentityV3", () => { // "" // ); - gd = (await ethers.getContractAt( - "IGoodDollar", - gooddollar, - founder - )) as IGoodDollar; + gd = (await ethers.getContractAt("IGoodDollar", gooddollar, founder)) as IGoodDollar; }); it("should set DAO by creator", async () => { let f = await ethers.getContractFactory("IdentityV3"); - let newid = (await upgrades.deployProxy( - f, - [signers[0].address, ethers.constants.AddressZero], - { kind: "uups" } - )) as IdentityV2; + let newid = (await upgrades.deployProxy(f, [signers[0].address, ethers.constants.AddressZero], { + kind: "uups" + })) as IdentityV3; expect(await newid.dao()).eq(ethers.constants.AddressZero); - await expect( - newid.connect(signers[0]).initDAO(await identity.nameService()) - ).not.reverted; + await expect(newid.connect(signers[0]).initDAO(await identity.nameService())).not.reverted; expect(await newid.dao()).not.eq(ethers.constants.AddressZero); }); it("should not be able to set DAO by non-creator", async () => { let f = await ethers.getContractFactory("IdentityV3"); - let newid = (await upgrades.deployProxy( - f, - [signers[0].address, ethers.constants.AddressZero], - { kind: "uups" } - )) as IdentityV2; + let newid = (await upgrades.deployProxy(f, [signers[0].address, ethers.constants.AddressZero], { + kind: "uups" + })) as IdentityV3; expect(await newid.dao()).eq(ethers.constants.AddressZero); await expect(newid.initDAO(await identity.nameService())).reverted; }); @@ -96,43 +82,35 @@ describe("IdentityV3", () => { await identity.addWhitelisted(whitelisted.address); - const diffWhitelistedCount = ( - (await identity.whitelistedCount()) as any - ).sub(oldWhitelistedCount); + const diffWhitelistedCount = ((await identity.whitelistedCount()) as any).sub(oldWhitelistedCount); expect(diffWhitelistedCount.toString()).to.be.equal("1"); await identity.removeWhitelisted(whitelisted.address); const whitelistedCount = (await identity.whitelistedCount()) as any; - expect(whitelistedCount.toString()).to.be.equal( - oldWhitelistedCount.toString() - ); + expect(whitelistedCount.toString()).to.be.equal(oldWhitelistedCount.toString()); }); it("should revert when non admin tries to add whitelisted", async () => { let whitelisted = signers[1]; - await expect( - identity.connect(signers[2]).addWhitelisted(whitelisted.address) - ).revertedWith(/AccessControl: account/); + await expect(identity.connect(signers[2]).addWhitelisted(whitelisted.address)).revertedWith( + /AccessControl: account/ + ); }); it("should revert when non admin tries to add blacklist", async () => { let blacklisted = signers[1]; - await expect( - identity.connect(signers[2]).addBlacklisted(blacklisted.address) - ).revertedWith(/AccessControl: account/); + await expect(identity.connect(signers[2]).addBlacklisted(blacklisted.address)).revertedWith( + /AccessControl: account/ + ); }); it("should revert when non admin tries to set the authentication period", async () => { - await expect(identity.connect(signers[2]).setAuthenticationPeriod(10)) - .reverted; + await expect(identity.connect(signers[2]).setAuthenticationPeriod(10)).reverted; }); it("should let owner set auth period", async () => { - const encoded = identity.interface.encodeFunctionData( - "setAuthenticationPeriod", - [10] - ); + const encoded = identity.interface.encodeFunctionData("setAuthenticationPeriod", [10]); await genericCall(identity.address, encoded); expect(await identity.authenticationPeriod()).eq(10); }); @@ -148,9 +126,7 @@ describe("IdentityV3", () => { it("should revert when non admin tries to authentice a user", async () => { let authuser = signers[0].address; - await expect( - identity.connect(signers[2]).authenticate(authuser) - ).revertedWith(/AccessControl: account/); + await expect(identity.connect(signers[2]).authenticate(authuser)).revertedWith(/AccessControl: account/); }); it("should authenticate the user with the correct timestamp", async () => { @@ -167,18 +143,14 @@ describe("IdentityV3", () => { it("should add identity admin", async () => { let outsider = signers[5].address; await identity.grantRole(await identity.IDENTITY_ADMIN_ROLE(), outsider); - expect( - await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider) - ).true; + expect(await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider)).true; }); it("should remove identity admin", async () => { let outsider = signers[5].address; await identity.revokeRole(await identity.IDENTITY_ADMIN_ROLE(), outsider); - expect( - await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider) - ).false; + expect(await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider)).false; }); it("should revert when adding to whitelisted twice", async () => { @@ -223,16 +195,14 @@ describe("IdentityV3", () => { it("should not allow adding with used did", async () => { let whitelisted2 = signers[2]; - await expect( - identity.addWhitelistedWithDID(whitelisted2.address, "testString") - ).revertedWith(/DID already registered/); + await expect(identity.addWhitelistedWithDID(whitelisted2.address, "testString")).revertedWith( + /DID already registered/ + ); }); it("should not allow adding non contract to contracts", async () => { let outsider = signers[0]; - await expect(identity.addContract(outsider.address)).revertedWith( - /Given address is not a contract/ - ); + await expect(identity.addContract(outsider.address)).revertedWith(/Given address is not a contract/); }); it("should add contract to contracts", async () => { @@ -255,19 +225,13 @@ describe("IdentityV3", () => { const toconnect = signers[10]; let whitelisted = signers[1]; - expect(await identity.getWhitelistedRoot(toconnect.address)).eq( - ethers.constants.AddressZero - ); + expect(await identity.getWhitelistedRoot(toconnect.address)).eq(ethers.constants.AddressZero); await loadFixture(connectedFixture); - expect(await identity.getWhitelistedRoot(whitelisted.address)).eq( - whitelisted.address - ); + expect(await identity.getWhitelistedRoot(whitelisted.address)).eq(whitelisted.address); - expect(await identity.getWhitelistedRoot(toconnect.address)).eq( - whitelisted.address - ); + expect(await identity.getWhitelistedRoot(toconnect.address)).eq(whitelisted.address); }); it("should not allow to connect account already whitelisted", async () => { @@ -276,9 +240,7 @@ describe("IdentityV3", () => { await identity.addWhitelisted(signers[2].address); let whitelisted = signers[1]; - await expect( - identity.connect(whitelisted).connectAccount(signers[2].address) - ).revertedWith(/invalid account/); + await expect(identity.connect(whitelisted).connectAccount(signers[2].address)).revertedWith(/invalid account/); }); it("should allow to disconnect account by owner or connected", async () => { @@ -287,14 +249,10 @@ describe("IdentityV3", () => { const connected = signers[10]; const whitelisted = signers[1]; await identity.connect(connected).disconnectAccount(connected.address); - expect(await identity.getWhitelistedRoot(connected.address)).eq( - ethers.constants.AddressZero - ); + expect(await identity.getWhitelistedRoot(connected.address)).eq(ethers.constants.AddressZero); await loadFixture(connectedFixture); await identity.connect(whitelisted).disconnectAccount(connected.address); - expect(await identity.getWhitelistedRoot(connected.address)).eq( - ethers.constants.AddressZero - ); + expect(await identity.getWhitelistedRoot(connected.address)).eq(ethers.constants.AddressZero); }); it("should not allow to disconnect account not by owner or by connected", async () => { @@ -302,9 +260,7 @@ describe("IdentityV3", () => { const connected = signers[10]; const whitelisted = signers[1]; - await expect(identity.disconnectAccount(connected.address)).revertedWith( - /unauthorized/ - ); + await expect(identity.disconnectAccount(connected.address)).revertedWith(/unauthorized/); }); it("should not allow to connect to an already connected account", async () => { @@ -314,9 +270,7 @@ describe("IdentityV3", () => { expect(await identity.isWhitelisted(signers[2].address)).true; const connected = signers[10]; - await expect( - identity.connect(signers[2]).connectAccount(connected.address) - ).revertedWith(/already connected/); + await expect(identity.connect(signers[2]).connectAccount(connected.address)).revertedWith(/already connected/); }); it("should return same root for multiple connected accounts", async () => { @@ -335,27 +289,19 @@ describe("IdentityV3", () => { const toWhitelist = signers[2]; const ts = (Date.now() / 1000 - 100000).toFixed(0); - await identity.addWhitelistedWithDIDAndChain( - toWhitelist.address, - "xxx", - 1234, - ts - ); + await identity.addWhitelistedWithDIDAndChain(toWhitelist.address, "xxx", 1234, ts); const record = await identity.identities(toWhitelist.address); expect(record.whitelistedOnChainId).eq(1234); expect(record.dateAuthenticated).eq(ts); }); const oldidFixture = async () => { - const newid = (await upgrades.deployProxy( - await ethers.getContractFactory("IdentityV3"), - [founder.address, identity.address] - )) as IdentityV3; - - await identity.grantRole( - await identity.IDENTITY_ADMIN_ROLE(), - newid.address - ); + const newid = (await upgrades.deployProxy(await ethers.getContractFactory("IdentityV3"), [ + founder.address, + identity.address + ])) as IdentityV3; + + await identity.grantRole(await identity.IDENTITY_ADMIN_ROLE(), newid.address); await identity.addBlacklisted(signers[4].address); await identity.addContract(identity.address); await identity.removeWhitelisted(signers[3].address); @@ -365,9 +311,7 @@ describe("IdentityV3", () => { it("should default to old identity isWhitelisted, isBlacklisted, isContract", async () => { const { newid } = await loadFixture(oldidFixture); - expect(await (await identity.identities(signers[3].address)).did).eq( - "testolddid" - ); + expect(await (await identity.identities(signers[3].address)).did).eq("testolddid"); expect(await (await newid.identities(signers[3].address)).did).eq(""); expect(await identity.addrToDID(signers[3].address)).eq("testolddid"); @@ -396,51 +340,31 @@ describe("IdentityV3", () => { it("should not set did if set in oldidentity", async () => { const { newid } = await loadFixture(oldidFixture); - await expect( - newid - .connect(signers[1]) - ["setDID(address,string)"](signers[1].address, "testolddid") - ).revertedWith(/DID already registered oldIdentity/); + await expect(newid.connect(signers[1])["setDID(address,string)"](signers[1].address, "testolddid")).revertedWith( + /DID already registered oldIdentity/ + ); }); it("should set did if set in oldidentity by same owner", async () => { const { newid } = await loadFixture(oldidFixture); - await expect( - newid - .connect(signers[3]) - ["setDID(address,string)"](signers[3].address, "testolddid") - ).not.reverted; + await expect(newid.connect(signers[3])["setDID(address,string)"](signers[3].address, "testolddid")).not.reverted; expect(await newid.addrToDID(signers[3].address)).eq("testolddid"); }); it("should set did if set in oldidentity by different owner but updated in new identity", async () => { const { newid } = await loadFixture(oldidFixture); - await expect( - newid - .connect(signers[3]) - ["setDID(address,string)"](signers[3].address, "newdid") - ).not.reverted; + await expect(newid.connect(signers[3])["setDID(address,string)"](signers[3].address, "newdid")).not.reverted; expect(await newid.addrToDID(signers[3].address)).eq("newdid"); - await expect( - newid - .connect(signers[1]) - ["setDID(address,string)"](signers[1].address, "testolddid") - ).not.reverted; + await expect(newid.connect(signers[1])["setDID(address,string)"](signers[1].address, "testolddid")).not.reverted; expect(await newid.addrToDID(signers[1].address)).eq("testolddid"); }); it("should let admin setDID", async () => { - await expect( - identity["setDID(address,string)"](signers[1].address, "admindid") - ).not.reverted; + await expect(identity["setDID(address,string)"](signers[1].address, "admindid")).not.reverted; expect(await identity.addrToDID(signers[1].address)).eq("admindid"); - await expect( - identity - .connect(signers[2]) - ["setDID(address,string)"](signers[1].address, "admindid") - ).reverted; + await expect(identity.connect(signers[2])["setDID(address,string)"](signers[1].address, "admindid")).reverted; }); it("should be registered for v1 compatability", async () => { diff --git a/test/identity/IdentityV4.test.ts b/test/identity/IdentityV4.test.ts new file mode 100644 index 00000000..62101ccd --- /dev/null +++ b/test/identity/IdentityV4.test.ts @@ -0,0 +1,429 @@ +import hre, { ethers, upgrades } from "hardhat"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { IGoodDollar, IIdentity, IdentityV4 } from "../../types"; +import { createDAO, increaseTime, advanceBlocks } from "../helpers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +const BN = ethers.BigNumber; + +describe("IdentityV4", () => { + let identity: IdentityV4, founder: SignerWithAddress; + let user1 = ethers.Wallet.createRandom().connect(ethers.provider); + let user2 = ethers.Wallet.createRandom().connect(ethers.provider); + let signers: Array; + let genericCall; + + let avatar, gd: IGoodDollar, Controller, id: IIdentity; + + before(async () => { + [founder, ...signers] = await ethers.getSigners(); + let { controller, avatar: av, gd: gooddollar, identity: idv2, genericCall: gc } = await loadFixture(createDAO); + + genericCall = gc; + identity = (await ethers.getContractAt("IdentityV4", idv2)) as IdentityV4; + Controller = controller; + avatar = av; + // await daoCreator.setSchemes( + // avatar, + // [identity], + // [ethers.constants.HashZero], + // ["0x0000001F"], + // "" + // ); + + gd = (await ethers.getContractAt("IGoodDollar", gooddollar, founder)) as IGoodDollar; + }); + + it("should set DAO by creator", async () => { + let f = await ethers.getContractFactory("IdentityV4"); + let newid = (await upgrades.deployProxy(f, [signers[0].address, ethers.constants.AddressZero], { + kind: "uups" + })) as IdentityV4; + expect(await newid.dao()).eq(ethers.constants.AddressZero); + await expect(newid.connect(signers[0]).initDAO(await identity.nameService())).not.reverted; + expect(await newid.dao()).not.eq(ethers.constants.AddressZero); + }); + + it("should not be able to set DAO by non-creator", async () => { + let f = await ethers.getContractFactory("IdentityV4"); + let newid = (await upgrades.deployProxy(f, [signers[0].address, ethers.constants.AddressZero], { + kind: "uups" + })) as IdentityV4; + expect(await newid.dao()).eq(ethers.constants.AddressZero); + await expect(newid.initDAO(await identity.nameService())).reverted; + }); + + it("should blacklist address", async () => { + let blacklisted = signers[1]; + await identity.addBlacklisted(blacklisted.address); + expect(await identity.isBlacklisted(blacklisted.address)).true; + + await identity.removeBlacklisted(blacklisted.address); + expect(await identity.isBlacklisted(blacklisted.address)).false; + }); + + it("should add, check and remove whitelisted", async () => { + let whitelisted = signers[1]; + await identity.addWhitelisted(whitelisted.address); + expect(await identity.isWhitelisted(whitelisted.address)).true; + const id = await identity.identities(whitelisted.address); + expect(id.whitelistedOnChainId).gt(0); + + await identity.removeWhitelisted(whitelisted.address); + expect(await identity.isWhitelisted(whitelisted.address)).false; + }); + + it("should increment and decrement whitelisteds when adding whitelisted", async () => { + let whitelisted = signers[1]; + const oldWhitelistedCount = (await identity.whitelistedCount()) as any; + + await identity.addWhitelisted(whitelisted.address); + + const diffWhitelistedCount = ((await identity.whitelistedCount()) as any).sub(oldWhitelistedCount); + expect(diffWhitelistedCount.toString()).to.be.equal("1"); + + await identity.removeWhitelisted(whitelisted.address); + + const whitelistedCount = (await identity.whitelistedCount()) as any; + expect(whitelistedCount.toString()).to.be.equal(oldWhitelistedCount.toString()); + }); + + it("should revert when non admin tries to add whitelisted", async () => { + let whitelisted = signers[1]; + await expect(identity.connect(signers[2]).addWhitelisted(whitelisted.address)).revertedWith( + /AccessControl: account/ + ); + }); + + it("should revert when non admin tries to add blacklist", async () => { + let blacklisted = signers[1]; + await expect(identity.connect(signers[2]).addBlacklisted(blacklisted.address)).revertedWith( + /AccessControl: account/ + ); + }); + + // it("should revert when non admin tries to set the authentication period", async () => { + // await expect(identity.connect(signers[2]).setAuthenticationPeriod(10)).reverted; + // }); + + // it("should let owner set auth period", async () => { + // const encoded = identity.interface.encodeFunctionData("setAuthenticationPeriod", [10]); + // await genericCall(identity.address, encoded); + // expect(await identity.authenticationPeriod()).eq(10); + // }); + + it("should revert when non admin tries to pause", async () => { + await expect(identity.connect(signers[2]).pause(true)).reverted; + }); + + it("should let admin pause", async () => { + await expect(identity.pause(true)).not.reverted; + await expect(identity.pause(false)).not.reverted; + }); + + it("should revert when non admin tries to authentice a user", async () => { + let authuser = signers[0].address; + await expect(identity.connect(signers[2]).authenticate(authuser)).revertedWith(/AccessControl: account/); + }); + + it("should authenticate the user with the correct timestamp", async () => { + let authuser = signers[0].address; + await identity.addWhitelisted(authuser); + await identity.authenticate(authuser); + let dateAuthenticated1 = await identity.lastAuthenticated(authuser); + await increaseTime(10); + await identity.authenticate(authuser); + let dateAuthenticated2 = await identity.lastAuthenticated(authuser); + expect(dateAuthenticated2.toNumber() - dateAuthenticated1.toNumber()).gt(0); + }); + + it("should add identity admin", async () => { + let outsider = signers[5].address; + await identity.grantRole(await identity.IDENTITY_ADMIN_ROLE(), outsider); + expect(await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider)).true; + }); + + it("should remove identity admin", async () => { + let outsider = signers[5].address; + await identity.revokeRole(await identity.IDENTITY_ADMIN_ROLE(), outsider); + + expect(await identity.hasRole(await identity.IDENTITY_ADMIN_ROLE(), outsider)).false; + }); + + it("should revert when adding to whitelisted twice", async () => { + let whitelisted = signers[1]; + await identity.addWhitelisted(whitelisted.address); + await expect(identity.addWhitelisted(whitelisted.address)).reverted; + + await identity.removeWhitelisted(whitelisted.address); + }); + + it("should not increment whitelisted counter when adding whitelisted", async () => { + let whitelisted = signers[1]; + await identity.addWhitelisted(whitelisted.address); + let whitelistedCount = await identity.whitelistedCount(); + + await expect(identity.addWhitelisted(whitelisted.address)).reverted; + + let whitelistedCountNew = await identity.whitelistedCount(); + expect(whitelistedCountNew).to.be.equal(whitelistedCount).gt(0); + + await identity.removeWhitelisted(whitelisted.address); + }); + + it("should renounce whitelisted", async () => { + let whitelisted = signers[1]; + await identity.addWhitelisted(whitelisted.address); + expect(await identity.isWhitelisted(whitelisted.address)).true; + await identity.connect(whitelisted).renounceWhitelisted(); + expect(await identity.isWhitelisted(whitelisted.address)).false; + }); + + it("should add with did", async () => { + let whitelisted = signers[1]; + + await identity.addWhitelistedWithDID(whitelisted.address, "testString"); + + const id = await identity.identities(whitelisted.address); + + expect(id.did).to.be.equal("testString"); + }); + + it("should not allow adding with used did", async () => { + let whitelisted2 = signers[2]; + + await expect(identity.addWhitelistedWithDID(whitelisted2.address, "testString")).revertedWith( + /DID already registered/ + ); + }); + + it("should not allow adding non contract to contracts", async () => { + let outsider = signers[0]; + await expect(identity.addContract(outsider.address)).revertedWith(/Given address is not a contract/); + }); + + it("should add contract to contracts", async () => { + await identity.addContract(gd.address); + const wasAdded = await identity.isDAOContract(gd.address); + expect(wasAdded).to.be.true; + }); + + const connectedFixture = async () => { + const toconnect = signers[10]; + const toconnect2 = signers[11]; + let whitelisted = signers[1]; + + await identity.connect(whitelisted).connectAccount(toconnect.address); + await identity.connect(whitelisted).connectAccount(toconnect2.address); + return {}; + }; + + it("should allow to connect account", async () => { + const toconnect = signers[10]; + let whitelisted = signers[1]; + + expect(await identity.getWhitelistedRoot(toconnect.address)).eq(ethers.constants.AddressZero); + + await loadFixture(connectedFixture); + + expect(await identity.getWhitelistedRoot(whitelisted.address)).eq(whitelisted.address); + + expect(await identity.getWhitelistedRoot(toconnect.address)).eq(whitelisted.address); + }); + + it("should not allow to connect account already whitelisted", async () => { + await loadFixture(connectedFixture); + + await identity.addWhitelisted(signers[2].address); + let whitelisted = signers[1]; + + await expect(identity.connect(whitelisted).connectAccount(signers[2].address)).revertedWith(/invalid account/); + }); + + it("should allow to disconnect account by owner or connected", async () => { + await loadFixture(connectedFixture); + + const connected = signers[10]; + const whitelisted = signers[1]; + await identity.connect(connected).disconnectAccount(connected.address); + expect(await identity.getWhitelistedRoot(connected.address)).eq(ethers.constants.AddressZero); + await loadFixture(connectedFixture); + await identity.connect(whitelisted).disconnectAccount(connected.address); + expect(await identity.getWhitelistedRoot(connected.address)).eq(ethers.constants.AddressZero); + }); + + it("should not allow to disconnect account not by owner or by connected", async () => { + await loadFixture(connectedFixture); + + const connected = signers[10]; + const whitelisted = signers[1]; + await expect(identity.disconnectAccount(connected.address)).revertedWith(/unauthorized/); + }); + + it("should not allow to connect to an already connected account", async () => { + await loadFixture(connectedFixture); + + await identity.addWhitelisted(signers[2].address); + expect(await identity.isWhitelisted(signers[2].address)).true; + const connected = signers[10]; + + await expect(identity.connect(signers[2]).connectAccount(connected.address)).revertedWith(/already connected/); + }); + + it("should return same root for multiple connected accounts", async () => { + await loadFixture(connectedFixture); + + const connected = signers[10]; + const connected2 = signers[11]; + const whitelisted = signers[1]; + expect(await identity.getWhitelistedRoot(connected.address)) + .eq(await identity.getWhitelistedRoot(connected2.address)) + .eq(whitelisted.address); + }); + + it("should add whitelisted with orgchain and dateauthenticated", async () => { + await loadFixture(connectedFixture); + const toWhitelist = signers[2]; + + const ts = (Date.now() / 1000 - 100000).toFixed(0); + await identity.addWhitelistedWithDIDAndChain(toWhitelist.address, "xxx", 1234, ts); + const record = await identity.identities(toWhitelist.address); + expect(record.whitelistedOnChainId).eq(1234); + expect(record.dateAuthenticated).eq(ts); + }); + + const oldidFixture = async () => { + const newid = (await upgrades.deployProxy(await ethers.getContractFactory("IdentityV4"), [ + founder.address, + identity.address + ])) as IdentityV4; + + await identity.grantRole(await identity.IDENTITY_ADMIN_ROLE(), newid.address); + await identity.addBlacklisted(signers[4].address); + await identity.addContract(identity.address); + await identity.removeWhitelisted(signers[3].address); + await identity.addWhitelistedWithDID(signers[3].address, "testolddid"); + return { newid }; + }; + + it("should default to old identity isWhitelisted, isBlacklisted, isContract", async () => { + const { newid } = await loadFixture(oldidFixture); + expect(await (await identity.identities(signers[3].address)).did).eq("testolddid"); + expect(await (await newid.identities(signers[3].address)).did).eq(""); + + expect(await identity.addrToDID(signers[3].address)).eq("testolddid"); + expect(await newid.addrToDID(signers[3].address)).eq("testolddid"); + expect(await newid.isBlacklisted(signers[4].address)).true; + expect(await newid.isWhitelisted(signers[3].address)).true; + expect(await newid.isDAOContract(identity.address)).true; + }); + + it("should remove whitelisted,blacklisted,contract from old identity", async () => { + const { newid } = await loadFixture(oldidFixture); + await newid.removeBlacklisted(signers[4].address); + await newid.removeWhitelisted(signers[3].address); + await newid.removeContract(identity.address); + + expect(await newid.addrToDID(signers[3].address)).eq(""); + expect(await newid.isBlacklisted(signers[4].address)).false; + expect(await newid.isWhitelisted(signers[3].address)).false; + expect(await newid.isDAOContract(identity.address)).false; + + expect(await identity.isBlacklisted(signers[4].address)).false; + expect(await identity.isWhitelisted(signers[3].address)).false; + expect(await identity.isDAOContract(identity.address)).false; + }); + + it("should not set did if set in oldidentity", async () => { + const { newid } = await loadFixture(oldidFixture); + + await expect(newid.connect(signers[1])["setDID(address,string)"](signers[1].address, "testolddid")).revertedWith( + /DID already registered oldIdentity/ + ); + }); + + it("should set did if set in oldidentity by same owner", async () => { + const { newid } = await loadFixture(oldidFixture); + + await expect(newid.connect(signers[3])["setDID(address,string)"](signers[3].address, "testolddid")).not.reverted; + expect(await newid.addrToDID(signers[3].address)).eq("testolddid"); + }); + it("should set did if set in oldidentity by different owner but updated in new identity", async () => { + const { newid } = await loadFixture(oldidFixture); + + await expect(newid.connect(signers[3])["setDID(address,string)"](signers[3].address, "newdid")).not.reverted; + expect(await newid.addrToDID(signers[3].address)).eq("newdid"); + + await expect(newid.connect(signers[1])["setDID(address,string)"](signers[1].address, "testolddid")).not.reverted; + expect(await newid.addrToDID(signers[1].address)).eq("testolddid"); + }); + + it("should let admin setDID", async () => { + await expect(identity["setDID(address,string)"](signers[1].address, "admindid")).not.reverted; + expect(await identity.addrToDID(signers[1].address)).eq("admindid"); + await expect(identity.connect(signers[2])["setDID(address,string)"](signers[1].address, "admindid")).reverted; + }); + + it("should be registered for v1 compatability", async () => { + expect(await identity.isRegistered()).true; + }); + + // New tests added below --------------------------------------------------- + + it("should allow identity admin to set reverifyDaysOptions and reject empty", async () => { + // admin (default signer) sets new schedule + await expect(identity.setReverifyDaysOptions([2, 5, 10])).not.reverted; + expect(await identity.reverifyDaysOptions(0)).to.equal(2); + expect(await identity.reverifyDaysOptions(1)).to.equal(5); + expect(await identity.reverifyDaysOptions(2)).to.equal(10); + + // empty options should revert + await expect(identity.setReverifyDaysOptions([])).revertedWith("empty options"); + }); + + it("non-admin should not set reverifyDaysOptions", async () => { + await expect(identity.connect(signers[2]).setReverifyDaysOptions([1, 7, 180])).revertedWith( + /AccessControl: account/ + ); + }); + + it("should follow reverify schedule and cycle authCount", async () => { + const u = signers[12]; + // ensure a fresh account is whitelisted + await identity.addWhitelisted(u.address); + let record = await identity.identities(u.address); + expect(record.authCount).to.equal(0); + + // default reverifyDaysOptions set in initialize: [1,7,180] + // move forward 2 days (past first reverify day) + await increaseTime(2 * 24 * 3600); + expect(await identity.isWhitelisted(u.address)).to.be.false; + + // admin authenticates -> should increment authCount to 1 + await identity.authenticate(u.address); + record = await identity.identities(u.address); + expect(record.authCount).to.equal(1); + + // move forward 8 days (past second reverify day = 7) + await increaseTime(8 * 24 * 3600); + expect(await identity.isWhitelisted(u.address)).to.be.false; + + // authenticate again -> authCount becomes 2 + await identity.authenticate(u.address); + record = await identity.identities(u.address); + expect(record.authCount).to.equal(2); + + // move forward 181 days (past third reverify day = 180) + await increaseTime(181 * 24 * 3600); + expect(await identity.isWhitelisted(u.address)).to.be.false; + + // authenticate again -> authCount should wrap back to 0 (cycle) + await identity.authenticate(u.address); + record = await identity.identities(u.address); + expect(record.authCount).to.equal(0); + + // cleanup (remove whitelisted) to avoid affecting other tests + await identity.removeWhitelisted(u.address); + }); +});