diff --git a/.gitignore b/.gitignore index 04d9d7ad..6e3090a2 100644 --- a/.gitignore +++ b/.gitignore @@ -115,4 +115,4 @@ build/* .yarn/install-state.gz scripts/fv.ts scripts/bulkWhitelist.ts -.env.enc \ No newline at end of file +.env.enc diff --git a/contracts/MentoInterfaces.sol b/contracts/MentoInterfaces.sol index 8a9b2749..d845aade 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,41 @@ 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 limit0In; + int48 limit0Out; + int48 limit1In; + int48 limit1Out; + int48 limitGlobal; + uint8 flags; + } } /* @@ -573,174 +582,209 @@ 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); + + function tradingLimitsState( + bytes32 eid + ) external view returns (ITradingLimits.State memory); + + function tradingLimitsConfig( + bytes32 eid + ) external view returns (ITradingLimits.Config memory); } diff --git a/contracts/identity/IdentityV3.sol b/contracts/identity/IdentityV3.sol index 5814ef9b..70edb2db 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 @@ -419,6 +422,7 @@ contract IdentityV3 is require(connectedAccounts[account] == address(0x0), "already connected"); connectedAccounts[account] = msg.sender; + emit AccountConnected(account, msg.sender); } /** @@ -431,6 +435,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..3b802ab1 100644 --- a/contracts/reserve/GenericDistributionHelper.sol +++ b/contracts/reserve/GenericDistributionHelper.sol @@ -298,6 +298,8 @@ contract GenericDistributionHelper is uint256 amountToSell, uint256 minReceived ) internal returns (uint256 nativeBought) { + if (amountToSell == 0) return 0; + address[] memory gdPools = STATIC_ORACLE.getAllPoolsForPair( reserveToken, address(nativeToken()) @@ -306,17 +308,35 @@ contract GenericDistributionHelper is reserveToken, gasToken ); + + if (gdPools.length == 0 || gasPools.length == 0) { + emit BuyNativeFailed("no pools available", amountToSell, minReceived); + return 0; + } + + // initialize with first pool and track max liquidity uint24 gasFee = IUniswapV3Pool(gasPools[0]).fee(); - uint24 gdFee = IUniswapV3Pool(gdPools[0]).fee(); - for (uint i = 1; i < gasPools.length; i++) { - uint24 fee = IUniswapV3Pool(gasPools[i]).fee(); - gasFee = gasFee < fee ? gasFee : fee; + uint256 maxGasBalance = ERC20(gasToken).balanceOf(gasPools[0]); + for (uint256 i = 1; i < gasPools.length; i++) { + uint256 balance = ERC20(gasToken).balanceOf(gasPools[i]); + if (balance > maxGasBalance) { + maxGasBalance = balance; + gasFee = IUniswapV3Pool(gasPools[i]).fee(); + } } - for (uint i = 1; i < gdPools.length; i++) { - uint24 fee = IUniswapV3Pool(gdPools[i]).fee(); - gdFee = gdFee < fee ? gdFee : fee; + + uint24 gdFee = IUniswapV3Pool(gdPools[0]).fee(); + uint256 maxStableBalance = ERC20(reserveToken).balanceOf(gdPools[0]); + for (uint256 i = 1; i < gdPools.length; i++) { + uint256 balance = ERC20(reserveToken).balanceOf(gdPools[i]); + if (balance > maxStableBalance) { + maxStableBalance = balance; + gdFee = IUniswapV3Pool(gdPools[i]).fee(); + } } + ERC20(nativeToken()).approve(address(ROUTER), amountToSell); + uint256 amountOutMinimum = (minReceived * (100 - feeSettings.maxSlippage)) / 100; // 5% slippage ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({ @@ -331,11 +351,19 @@ contract GenericDistributionHelper is amountIn: amountToSell, amountOutMinimum: amountOutMinimum }); + try ROUTER.exactInput(params) returns (uint256 amountOut) { return amountOut; } catch Error(string memory reason) { emit BuyNativeFailed(reason, amountToSell, amountOutMinimum); return 0; + } catch { + emit BuyNativeFailed( + "swap reverted without reason", + amountToSell, + amountOutMinimum + ); + return 0; } } } diff --git a/contracts/ubi/UBISchemeV2.sol b/contracts/ubi/UBISchemeV2.sol index b9279fd6..cd91bc16 100644 --- a/contracts/ubi/UBISchemeV2.sol +++ b/contracts/ubi/UBISchemeV2.sol @@ -386,7 +386,14 @@ contract UBISchemeV2 is DAOUpgradeableContract { if ( currentDay == (block.timestamp - periodStart) / (1 days) && dailyUbi > 0 ) { - return hasClaimed(_member) ? 0 : dailyUbi; + return + hasClaimed( + IIdentityV2(nameService.getAddress("IDENTITY")).getWhitelistedRoot( + _member + ) + ) + ? 0 + : dailyUbi; } return estimateNextDailyUBI(); } diff --git a/contracts/utils/UpdateReserveRatio.sol b/contracts/utils/UpdateReserveRatio.sol new file mode 100644 index 00000000..7c274982 --- /dev/null +++ b/contracts/utils/UpdateReserveRatio.sol @@ -0,0 +1,121 @@ +// 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" + ); + + if (block.chainid == 42220) { + 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"); + } else { + IBancorExchangeProvider.PoolExchange memory _exchange; + + _exchange.reserveRatio = _reserveRatio; + _exchange.tokenSupply = _newTotalSupply; + _exchange.reserveAsset = 0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1; //usdc on xdc + _exchange.tokenAddress = Avatar(avatar).nativeToken(); + _exchange.reserveBalance = 200000 * 1e6; // 200k USDC on xdc + _exchange.exitContribution = 10000000; //10% + + (bool ok, bytes memory result) = _controller.genericCall( + address(_mentoExchange), + abi.encodeCall(IBancorExchangeProvider.createExchange, (_exchange)), + address(avatar), + 0 + ); + + require(ok, "create failed"); + console.log("create done"); + bytes32 created = abi.decode(result, (bytes32)); + console.logBytes32(created); + require(created != bytes32(0), "not created"); + } + owner = address(0); //mark as run; + // prevent executing again + require(_controller.unregisterSelf(avatar), "unregistering failed"); + } +} + +contract CreateReserveExchange { + address owner; + + constructor(address _owner) { + owner = _owner; + } + + function upgrade( + Controller _controller, + address _mentoExchange, + IBancorExchangeProvider.PoolExchange memory _exchange + ) external { + require(msg.sender == owner, "only owner can call this"); + address avatar = _controller.avatar(); + + (bool ok, ) = _controller.genericCall( + address(_mentoExchange), + abi.encodeCall(IBancorExchangeProvider.createExchange, _exchange), + address(avatar), + 0 + ); + + // console.log("createExchange %s", ok); + require(ok, "createExchange failed"); + + owner = address(0); //mark as run; + // prevent executing again + require(_controller.unregisterSelf(avatar), "unregistering failed"); + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 912bb56b..a84c0791 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -29,7 +29,7 @@ envEnc.config(); config(); const mnemonic = process.env.MNEMONIC || "test test test test test test test test test test test junk"; -const deployerPrivateKey = process.env.ADMIN_KEY || ethers.utils.hexZeroPad("0x11", 32); +const deployerPrivateKey = process.env.PRIVATE_KEY || ethers.utils.hexZeroPad("0x11", 32); const infura_api = process.env.INFURA_API; const alchemy_key = process.env.ALCHEMY_KEY; const etherscan_key = process.env.ETHERSCAN_KEY; @@ -125,7 +125,7 @@ const hhconfig: HardhatUserConfig = { initialDate: "2021-12-01", //required for DAO tests like guardian forking: process.env.FORK_CHAIN_ID && { url: "https://eth-mainnet.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY - }, + } }, fork: { chainId: 1, diff --git a/package.json b/package.json index a4c01587..e21c4c9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gooddollar/goodprotocol", - "version": "2.2.0-beta.1", + "version": "2.2.0", "description": "GoodDollar Protocol", "engines": { "node": ">=16.x" 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/releases/deployment.json b/releases/deployment.json index 5c8d930b..c40f72bd 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,17 @@ "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", + "DistributionHelper": "0xB8A0d4b97327a0B6dD0705dC290AE26aA38a7d2b", + "USDCEXchangeId": "0xf8d028730f58a008390c265fca425bb912e4c7efa370d4cef756a06f5029acd2" } } diff --git a/scripts/bulkWhitelist.ts b/scripts/bulkWhitelist.ts index 4f6e41a4..b57856dd 100644 --- a/scripts/bulkWhitelist.ts +++ b/scripts/bulkWhitelist.ts @@ -10,6 +10,8 @@ import farmed9 from "../farming9.json"; import farmed10 from "../farming10.json"; import farmed11 from "../farming11.json"; import farmed12 from "../farming12.json"; +import farmed13 from "../farming13.json"; +import farmed14 from "../farming14.json"; import { ethers, network } from "hardhat"; import { chunk } from "lodash"; @@ -24,9 +26,11 @@ const main = async () => { return; } const whitelist = await ethers.getContractAt("BulkWhitelist", contracts[network.name].BulkWhitelist); - const notReVerified = farmed12.filter( + const notReVerified = farmed14.filter( addr => !( + farmed13.includes(addr) || + farmed12.includes(addr) || farmed11.includes(addr) || farmed10.includes(addr) || farmed9.includes(addr) || @@ -40,7 +44,7 @@ const main = async () => { farmed4.includes(addr) ) ); - console.log(`not re-verified count out of ${farmed12.length}:`, notReVerified.length); + console.log(`not re-verified count out of ${farmed14.length}:`, notReVerified.length); const chunks = chunk(notReVerified, 100).slice(0); let nonce = await signer.getTransactionCount(); let batchSize = 2; diff --git a/scripts/fv.ts b/scripts/fv.ts index 78fb4531..e26172af 100644 --- a/scripts/fv.ts +++ b/scripts/fv.ts @@ -2,7 +2,8 @@ import fs from "fs"; import { chunk, uniqBy } from "lodash"; import delay from "delay"; import { bulkIsWhitelisted, bulkLastAuth } from "./utils"; - +import phash from "sharp-phash"; +import distance from "sharp-phash/distance"; //create tunnel to fv server ssh -L 9090:server:8080 -N user@server -i sshkey const saveImage = async (id, idx) => { @@ -261,3 +262,46 @@ const deleteIdentifier = async (id, walletAddress) => { // deleteIdentifiers(process.env.ADMIN_PASSWORD); // main(); // saveImages(["", "", ""]); +const checkLiveness2d = async id => { + //read jpg image and convert to base64 + const data0 = fs.readFileSync("./scripts/photo2d-2.jpg"); + const data1 = fs.readFileSync("./scripts/photo2d-4.jpg"); + const data2 = fs.readFileSync("./scripts/photo2d-app-1.jpg"); + const data3 = fs.readFileSync("./scripts/photo2d-app-2.jpg"); + const data4 = fs.readFileSync("./scripts/photo2d-3.jpg"); + const data5 = fs.readFileSync("./scripts/photo2d-app-3.jpg"); + const data6 = fs.readFileSync("./scripts/photo2d-app-4.jpg"); + const data7 = fs.readFileSync("./scripts/photo2d-app-4-spoof.png"); + + const imgs = [data0, data1, data2, data3, data4, data5, data6, data7]; + //compare phash of the images to see if they are similar + const phashes = await Promise.all(imgs.map(_ => phash(_))); + console.log("phashes", phashes); + for (let i = 0; i < phashes.length; i++) { + for (let j = i + 1; j < phashes.length; j++) { + console.log(`distance between image ${i} and ${j}:`, distance(phashes[i], phashes[j])); + } + } + const data = fs.readFileSync("./scripts/photo2d-app-4-spoof.png").toString("base64"); + + const res = await fetch("http://localhost:9090/liveness-2d/", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ image: data }) + }).then(_ => _.json()); + console.log(res); +}; +const match2d = async id => { + //read jpg image and convert to base64 + const data = fs.readFileSync("./scripts/photo2d-app-4-spoof.png").toString("base64"); + + const res = await fetch("http://localhost:9090/match-3d-2d-face-portrait/", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ image: data, externalDatabaseRefID: id, minMatchLevel: 5 }) + }).then(_ => _.json()); + console.log(res); +}; + +checkLiveness2d(""); +match2d(""); 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..9c25dc61 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,29 +307,33 @@ 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); const results = []; + let gasUsed = 0; for (let i = 0; i < contracts.length; i++) { const contract = contracts[i]; if (!contract) { console.warn("skipping executing missing contract", i, contracts[i], functionSigs[i], functionInputs[i]); continue; } + console.log("executing:", contracts[i], functionSigs[i], functionInputs[i]); const sigHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(functionSigs[i])).slice(0, 10); const encoded = ethers.utils.solidityPack(["bytes4", "bytes"], [sigHash, functionInputs[i]]); if (contract.toLowerCase().startsWith((await guardian.getAddress()).toLocaleLowerCase())) { const [, target] = contract.split("_"); console.log("executing directly on target contract:", sigHash, encoded); - + gasUsed += await guardian.estimateGas({ to: target, data: encoded }).then(_ => _.toNumber()); const tx = await guardian.sendTransaction({ to: target, data: encoded }).then(printDeploy); results.push(tx); } else if (contract === ctrl.address) { console.log("executing directly on controller:", sigHash, encoded); + gasUsed += await guardian.estimateGas({ to: contract, data: encoded }).then(_ => _.toNumber()); const tx = await guardian.sendTransaction({ to: contract, data: encoded }).then(printDeploy); @@ -331,6 +342,12 @@ export const executeViaGuardian = async ( const simulationResult = await ctrl.callStatic.genericCall(contract, encoded, release.Avatar, ethValues[i], { from: await guardian.getAddress() }); + gasUsed += await ctrl.estimateGas + .genericCall(contract, encoded, release.Avatar, ethValues[i], { + from: await guardian.getAddress() + }) + .then(_ => _.toNumber()); + console.log("executing genericCall:", { sigHash, contract, @@ -338,6 +355,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 @@ -347,6 +365,7 @@ export const executeViaGuardian = async ( results.push(tx); } } + console.log("total gas used:", gasUsed); return results; }; @@ -414,7 +433,7 @@ export const executeViaSafe = async ( const safeService = new SafeApiKit({ chainId: BigInt(chainId), txServiceUrl, - apiKey: process.env.SAFE_TX_SERVICE_API_KEY || "" + apiKey: txServiceUrl ? undefined : process.env.SAFE_TX_SERVICE_API_KEY }); const safeSdk = await Safe.init({ @@ -513,7 +532,10 @@ export const executeViaSafe = async ( } const safeTransaction = await safeSdk.createTransaction({ - transactions: safeTransactionData + transactions: safeTransactionData, + options: { + ...safeOptions + } }); const safeTxHash = await safeSdk.getTransactionHash(safeTransaction); @@ -527,13 +549,63 @@ export const executeViaSafe = async ( senderSignature: signedHash, senderAddress }); - await safeService.proposeTransaction({ - safeAddress, - safeTransactionData: safeTransaction.data, - safeTxHash, - senderSignature: signedHash.data, - senderAddress - }); + + if (chainId === 122) { + const fuseResult = await proposeFuseTx({ + safeAddress, + safeTransactionData: safeTransaction.data, + safeTxHash, + senderSignature: signedHash.data, + senderAddress, + origin: "safe.fuse.io" + }); + } else { + await safeService.proposeTransaction({ + safeAddress, + safeTransactionData: safeTransaction.data, + safeTxHash, + senderSignature: signedHash.data, + senderAddress + }); + } +}; + +const proposeFuseTx = async ({ + safeAddress, + safeTransactionData, + safeTxHash, + senderAddress, + senderSignature, + origin +}) => { + const headers = { + Accept: "application/json", + "Content-Type": "application/json" + }; + + const response = await fetch( + `https://transaction-fuse.safe.fuse.io/api/v1/safes/${safeAddress}/multisig-transactions/`, + { + method: "POST", + headers, + body: JSON.stringify({ + ...safeTransactionData, + contractTransactionHash: safeTxHash, + sender: senderAddress, + signature: senderSignature, + origin + }) + } + ); + let jsonResponse; + try { + jsonResponse = await response.json(); + } catch (error) { + if (!response.ok) { + throw new Error(response.statusText); + } + } + return jsonResponse; }; export const verifyContract = async ( 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..366e1121 --- /dev/null +++ b/scripts/proposals/gip-25-xdc-deploy-reserve.ts @@ -0,0 +1,1011 @@ +// 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 - V +// calculate how much G$s each reserve is backing - V +// deploy distribution helper on xdc - V +// set distribution helper on xdc expansion controller - V +// give mento broker minting rights on xdc - V +// give expansion controller minting rights - V +// give genericcall permissions to circuit breaker on all networks - V +// deploy identity v4 on all chains - V + +// Before upgrade: +// verify bridge impl are the latest - V +// deploy circuti breaker on all chains and update address in deployment.json - V +// verify exchange provider and broker impl on celo are the latest - V +// deploy mento contracts on xdc before upgrade - V + +// Post upgrade: +// run script update celo reserve parameters accordingly - V +// run script to create exchange on xdc with the calculated parameters - V +// transfer usdc to xdc reserve - V +// verify identity, bridge contract - V + +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, + IGoodDollar, + IGoodDollarExpansionController, + UpdateReserveRatio +} 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": "0x3eDD30A1Cd94dd6D3329B5EbafF5Aa4E0a4E4a55", + production: "0xCaC4215c57ef199210E759AF92bcaD012f61E7A1", + "production-mainnet": "0x3A2D0a9EF558b42AF5FF8d27FE438b466Fa7692F", + "production-xdc": "0x00F7f61080EF40d3832C8C06e8A2af757839e1F7" +}; + +const circuitBreaker = ""; +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("IdentityV4"); + const upgradeCall = identityImpl.interface.encodeFunctionData("setReverifyDaysOptions", [[180]]); + 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, + "upgradeToAndCall(address,bytes)", + ethers.utils.defaultAbiCoder.encode(["address", "bytes"], [identityImpl.address, upgradeCall]), + "0" + ], //upgrade identity + [release.MpbBridge, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [bridgeImpl]), "0"], //upgrade bridge + [ + release.MpbBridge, + "setBridgeLimits((uint256,uint256,uint256,uint256,bool))", + ethers.utils.defaultAbiCoder.encode( + ["(uint256,uint256,uint256,uint256,bool)"], + [ + [ + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(10), + false + ] + ] + ), + "0" //set bridge limits + ], + [ + release.Controller, + "registerScheme(address,bytes32,bytes4,address)", + ethers.utils.defaultAbiCoder.encode( + ["address", "bytes32", "bytes4", "address"], + [reserveUpdate.address, ethers.constants.HashZero, "0x00000010", release.Avatar] + ), + "0" + ], //give generic call rights to update reserve ratio + [ + release.Controller, + "registerScheme(address,bytes32,bytes4,address)", + ethers.utils.defaultAbiCoder.encode( + ["address", "bytes32", "bytes4", "address"], + [circuitBreaker, ethers.constants.HashZero, "0x00000010", release.Avatar] + ), + "0" + ] //give generic call rights to circuit breaker + ]; + + 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", + { safeTxGas: "2000000" } + ); + } 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 gd = (await ethers.getContractAt("IGoodDollar", release.GoodDollar)) as IGoodDollar; + + const reserveParams = await calculateReserveParams(); + const reserveUpdateResult = await ( + await reserveUpdate.upgrade( + release.Controller, + release.MentoExchangeProvider, + release.CUSDExchangeId, + reserveParams.reserveRatioCelo, + await gd.totalSupply(), + 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("IdentityV4"); + const upgradeCall = identityImpl.interface.encodeFunctionData("setReverifyDaysOptions", [[180]]); + 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, + "upgradeToAndCall(address,bytes)", + ethers.utils.defaultAbiCoder.encode(["address", "bytes"], [identityImpl.address, upgradeCall]), + "0" + ], //upgrade identity + [release.MpbBridge, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [bridgeImpl]), "0"], //upgrade bridge + [ + release.MpbBridge, + "setBridgeLimits((uint256,uint256,uint256,uint256,bool))", + ethers.utils.defaultAbiCoder.encode( + ["(uint256,uint256,uint256,uint256,bool)"], + [ + [ + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(10), + false + ] + ] + ), + "0" //set bridge limits + ], + [ + release.Controller, + "registerScheme(address,bytes32,bytes4,address)", + ethers.utils.defaultAbiCoder.encode( + ["address", "bytes32", "bytes4", "address"], + [circuitBreaker, ethers.constants.HashZero, "0x00000010", release.Avatar] + ), + "0" + ] //give generic call rights to circuit breaker + ]; + + 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", + { safeTxGas: "2000000" } + ); + } 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 + [ + release.MpbBridge, + "setBridgeLimits((uint256,uint256,uint256,uint256,bool))", + ethers.utils.defaultAbiCoder.encode( + ["(uint256,uint256,uint256,uint256,bool)"], + [ + [ + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(10), + false + ] + ] + ), + "0" //set bridge limits + ], + [ + release.Controller, + "registerScheme(address,bytes32,bytes4,address)", + ethers.utils.defaultAbiCoder.encode( + ["address", "bytes32", "bytes4", "address"], + [circuitBreaker, ethers.constants.HashZero, "0x00000010", release.Avatar] + ), + "0" + ] //give generic call rights to circuit breaker + ]; + + 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", + { safeTxGas: "2000000" } + ); + } else if (!checksOnly) { + await executeViaGuardian( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + guardian, + networkEnv + ); + } +}; + +export const upgradeXdcStep2 = async (network, checksOnly) => { + let [root] = await ethers.getSigners(); + const MentoReserveImpl = "0xBdaA65e8175875340365d76A660bB2A7a4EFE909"; + const ExchangeProviderV2Impl = "0xe930CDE20f60d0A4fc9487874861AE259F5Bed48"; + const MentoBrokerV2Impl = "0xc69ae3550E25C7AB28301B9Bf75F20f5AF47B7d2"; + + const isProduction = networkName.includes("production"); + + if (isProduction) verifyProductionSigner(root); + + let networkEnv = networkName; + let guardian = root; + let reserveParams; + if (isSimulation) { + networkEnv = network; + reserveParams = { + xdcGdSupplyEquivalent: ethers.utils.parseEther("4000000000").toString(), + reserveRatioXdc: 40000000 + }; + } + + 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("IdentityV4"); + const upgradeCall = identityImpl.interface.encodeFunctionData("setReverifyDaysOptions", [[180]]); + const bridgeImpl = bridgeUpgradeImpl[networkEnv]; + const reserveUpdate = checksOnly + ? await ethers.getContractAt("UpdateReserveRatio", "0x2431F53AFda24130722dBEb9F9b1B0b8d2fbB197") + : ((await deployDeterministic( + { name: "UpdateReserveRatio" }, + [root.address], + {}, + false, + networkEnv + )) as UpdateReserveRatio); + + console.log("reserve update deployed at:", reserveUpdate.address); + console.log("deploying dist helper"); + + const DistHelper = checksOnly + ? await ethers.getContractAt("GenericDistributionHelper", release.DistributionHelper) + : 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, DistHelper: DistHelper.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.MentoProxyAdmin, + "upgrade(address,address)", + ethers.utils.defaultAbiCoder.encode(["address", "address"], [release.MentoReserve, MentoReserveImpl]), + "0" + ], + [ + release.Controller, + "registerScheme(address,bytes32,bytes4,address)", + ethers.utils.defaultAbiCoder.encode( + ["address", "bytes32", "bytes4", "address"], + [reserveUpdate.address, ethers.constants.HashZero, "0x00000010", release.Avatar] + ), + "0" + ], //give generic call rights to update reserve ratio + [release.MpbBridge, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [bridgeImpl]), "0"], //upgrade bridge + [ + release.MpbBridge, + "setBridgeLimits((uint256,uint256,uint256,uint256,bool))", + ethers.utils.defaultAbiCoder.encode( + ["(uint256,uint256,uint256,uint256,bool)"], + [ + [ + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(300e6), + ethers.constants.WeiPerEther.mul(10), + false + ] + ] + ), + "0" //set bridge limits + ], + [ + release.Identity, + "upgradeToAndCall(address,bytes)", + ethers.utils.defaultAbiCoder.encode(["address", "bytes"], [identityImpl.address, upgradeCall]), + "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.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 + [ + release.Controller, + "registerScheme(address,bytes32,bytes4,address)", + ethers.utils.defaultAbiCoder.encode( + ["address", "bytes32", "bytes4", "address"], + [circuitBreaker, ethers.constants.HashZero, "0x00000010", release.Avatar] + ), + "0" + ] //give generic call rights to circuit breaker + ]; + + 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", + { safeTxGas: "2000000" } + ); + } else if (!checksOnly) { + await executeViaGuardian( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + guardian, + networkEnv, + false + ); + } + + if (!isProduction && isSimulation) { + const gd = (await ethers.getContractAt("IGoodDollar", release.GoodDollar)) as IGoodDollar; + console.log({ reserveParams }, reserveUpdate.address); + await reserveUpdate + .connect(root) + .upgrade( + release.Controller, + release.MentoExchangeProvider, + release.USDCEXchangeId, + reserveParams.reserveRatioXdc, + await gd.totalSupply(), + reserveParams.xdcGdSupplyEquivalent + ) + .then(_ => _.wait()); + console.log("reserve update executed...."); + 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 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(); + const mintedFromInterest = tx.events.find(_ => _.event === "InterestUBIMinted").args.amount; + console.log("mint from interest:", mintedFromInterest.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 ctrl = (await ethers.getContractAt("Controller", release.Controller)) as Controller; + + await (await ctrl.connect(guardian).mintTokens(mintedFromInterest, swapper.address, release.Avatar)).wait(); + + await gd.connect(swapper).approve(release.MentoBroker, mintedFromInterest); + } +}; + +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.00013", 18); + const celoGdSupplyEquivalent = realSupply.sub(xdcGdSupplyEquivalent); + + console.log({ + totalSupply, + celoSupply, + 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 { + realSupply, + celoSupply, + reserveRatioXdc: normalizedRatioXdc, + xdcGdSupplyEquivalent, + reserveRatioCelo: normalizedRatioCelo, + celoGdSupplyEquivalent + }; +}; + +const testSwap = async () => { + let release: { [key: string]: any } = dao["production-xdc"]; + const swapper = await ethers.getImpersonatedSigner("0x66582D24FEaD72555adaC681Cc621caCbB208324"); + const usdc = await ethers.getContractAt("IERC20", release.USDC); + const usdcSwapper = await ethers.getImpersonatedSigner("0x6E23963b9Ccbfba0e57692Cd6E66A1682fb3B8f6"); + await usdc.connect(usdcSwapper).transfer(release.MentoReserve, await usdc.balanceOf(usdcSwapper.address)); + + // await usdc.connect(usdcSwapper).transfer(swapper.address, 10000e6); + const gd = (await ethers.getContractAt("IGoodDollar", release.GoodDollar)) as IGoodDollar; + const ctrl = (await ethers.getContractAt("Controller", release.Controller)) as Controller; + const guardian = await ethers.getImpersonatedSigner(release.GuardiansSafe); + const mentoBroker = (await ethers.getContractAt("IBroker", release.MentoBroker)) as IBroker; + const mentoExchange = (await ethers.getContractAt( + "IBancorExchangeProvider", + release.MentoExchangeProvider + )) as IBancorExchangeProvider; + const eids = await mentoExchange.getExchangeIds(); + const exchangeId = eids[0]; //keccak256(ethers.utils.solidityPack(["string", "string"], ["USDC", "G$"])); + // uint256(uint160(token)) as bytes32 -> left-pad address to 32 bytes + const tokenAsBytes32 = ethers.utils.hexZeroPad(usdc.address, 32); + + // XOR using BigNumber and return a bytes32 hex string + const limitId = ethers.BigNumber.from(exchangeId).xor(ethers.BigNumber.from(tokenAsBytes32)).toHexString(); + + console.log("reserve balance:", await usdc.balanceOf(release.MentoReserve)); + console.log("trading limits:", await mentoBroker.tradingLimitsState(limitId)); + console.log("trading limits config:", await mentoBroker.tradingLimitsConfig(limitId)); + // const eids = ["0xf8d028730f58a008390c265fca425bb912e4c7efa370d4cef756a06f5029acd2"]; + const mintedFromInterest = ethers.utils.parseEther("90000000"); + await (await ctrl.connect(guardian).mintTokens(mintedFromInterest, swapper.address, release.Avatar)).wait(); + + await gd.connect(swapper).approve(release.MentoBroker, mintedFromInterest); + console.log( + "Balance before mintedFromInterest swap:", + await gd.balanceOf(swapper.address), + await usdc.balanceOf(swapper.address) + ); + const txswap = await mentoBroker + .connect(swapper) + .swapIn(mentoExchange.address, eids[0], gd.address, usdc.address, mintedFromInterest, 0) + .then(_ => _.wait()); + + // console.log( + // "Balance after mintedFromInterest swap:", + // txswap.events, + // swapper.address, + // await gd.balanceOf(swapper.address), + // await usdc.balanceOf(swapper.address) + // ); + // console.log("reserve balance:", await usdc.balanceOf(release.MentoReserve)); + let amountout = await mentoBroker.getAmountOut( + mentoExchange.address, + exchangeId, + gd.address, + usdc.address, + ethers.utils.parseEther("90000000") + ); + console.log("amount out:", amountout); + // const mentomint = (await ethers.getContractAt( + // "IGoodDollarExpansionController", + // release.MentoExpansionController + // )) as IGoodDollarExpansionController; + // await usdc.connect(swapper).approve(mentomint.address, 5000e6); + // const tx = await (await mentomint.connect(swapper).mintUBIFromInterest(eids[0], 5000e6)).wait(); + // amountout = await mentoBroker.getAmountOut( + // mentoExchange.address, + // exchangeId, + // gd.address, + // usdc.address, + // ethers.utils.parseEther("90000000") + // ); + // console.log("amount out:", amountout); +}; + +const celoUpgrade = async () => { + let release: { [key: string]: any } = dao["production-celo"]; + let root = (await ethers.getSigners())[0]; + const exchange = (await ethers.getContractAt( + "IBancorExchangeProvider", + release.MentoExchangeProvider + )) as IBancorExchangeProvider; + if (root.address.toLowerCase() != "0x5128E3C1f8846724cc1007Af9b4189713922E4BB".toLowerCase()) + root = await ethers.getImpersonatedSigner("0x5128E3C1f8846724cc1007Af9b4189713922E4BB"); + const exchangeBefore = await exchange.getPoolExchange(release.CUSDExchangeId); + console.log("Exchange before update", exchangeBefore); + + const reserveUpdate = await ethers.getContractAt("UpdateReserveRatio", "0x1f3d49414E4B7b32a5E23FbCA71778dF760D97A3"); + const reserveParams = await calculateReserveParams(); + const total = reserveParams.celoGdSupplyEquivalent.add(reserveParams.xdcGdSupplyEquivalent); + console.log("supply match:", total == exchangeBefore.tokenSupply, { total, onexchange: exchangeBefore.tokenSupply }); + const reserveUpdateResult = await ( + await reserveUpdate + .connect(root) + .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)); +}; +const xdcUpgrade = async () => { + console.log("network:", await ethers.provider.getNetwork()); + let release: { [key: string]: any } = dao["production-xdc"]; + let root = (await ethers.getSigners())[0]; + const exchange = (await ethers.getContractAt( + "IBancorExchangeProvider", + release.MentoExchangeProvider + )) as IBancorExchangeProvider; + if (root.address.toLowerCase() != "0x5128E3C1f8846724cc1007Af9b4189713922E4BB".toLowerCase()) + root = await ethers.getImpersonatedSigner("0x5128E3C1f8846724cc1007Af9b4189713922E4BB"); + // const exchangeBefore = await exchange.getPoolExchange(release.CUSDExchangeId); + // console.log("Exchange before update", exchangeBefore); + + const reserveUpdate = await ethers.getContractAt("UpdateReserveRatio", "0x2431F53AFda24130722dBEb9F9b1B0b8d2fbB197"); + const reserveUpdateResult = await ( + await reserveUpdate + .connect(root) + .upgrade( + release.Controller, + release.MentoExchangeProvider, + release.USDCEXchangeId, + "41408110", + "102060618533016992274287903", + "3715362840972879360199738714" + ) + ).wait(); + console.log("Exchange after update", await exchange.getPoolExchange(release.USDCEXchangeId)); + console.log("Price:", await exchange.currentPrice(release.USDCEXchangeId)); +}; +export const main = async () => { + await xdcUpgrade(); + 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, true); + break; + } +}; + +// testSwap().catch(console.log); +main().catch(console.log);