diff --git a/.github/workflows/test-gauge.yml b/.github/workflows/test-gauge.yml new file mode 100644 index 000000000..ac656111a --- /dev/null +++ b/.github/workflows/test-gauge.yml @@ -0,0 +1,21 @@ +name: Gauge Tests +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + gauge: + runs-on: ubuntu-latest + env: + PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} + WALLET_ADDR: ${{ secrets.WALLET_ADDR }} + SNOWTRACE_KEY: ${{ secrets.SNOWTRACE_KEY }} + NODE_OPTIONS: "--max_old_space_size=32184" + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '16' + - run: npm i + - run: npx hardhat test test/gauge-proxy.ts diff --git a/.gitignore b/.gitignore index 1b23ba46e..323771151 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ typings/ # Typechain generations typechain/ +typechain-types/ # TypeScript cache *.tsbuildinfo diff --git a/contracts/interfaces/ierc20.sol b/contracts/interfaces/ierc20.sol new file mode 100644 index 000000000..6b0603f1b --- /dev/null +++ b/contracts/interfaces/ierc20.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.7; + +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); +} \ No newline at end of file diff --git a/contracts/lib/protocol-governance.sol b/contracts/lib/protocol-governance.sol new file mode 100644 index 000000000..aa5709d3f --- /dev/null +++ b/contracts/lib/protocol-governance.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.7; + +contract ProtocolGovernance { + /// @notice governance address for the governance contract + address public governance; + address public pendingGovernance; + + /** + * @notice modifier to allow for easy gov only control over a function + */ + modifier onlyGovernance() { + require(msg.sender == governance, "unauthorized sender (governance"); + _; + } + + /** + * @notice Allows governance to change governance (for future upgradability) + * @param _governance new governance address to set + */ + function setGovernance(address _governance) external onlyGovernance{ + pendingGovernance = _governance; + } + + /** + * @notice Allows pendingGovernance to accept their role as governance (protection pattern) + */ + function acceptGovernance() external { + require(msg.sender == pendingGovernance, "acceptGovernance: !pendingGov"); + governance = pendingGovernance; + } +} \ No newline at end of file diff --git a/contracts/lib/safe-math.sol b/contracts/lib/safe-math.sol index 31baf623f..b6a97f15a 100644 --- a/contracts/lib/safe-math.sol +++ b/contracts/lib/safe-math.sol @@ -156,4 +156,29 @@ library SafeMath { require(b != 0, errorMessage); return a % b; } +} + +library Math { + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a >= b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow, so we distribute + return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2); + } } \ No newline at end of file diff --git a/contracts/lib/safeerc20.sol b/contracts/lib/safeerc20.sol new file mode 100644 index 000000000..38f4477f6 --- /dev/null +++ b/contracts/lib/safeerc20.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.7; //^0.7.5; + +import "./safe-math.sol"; +import "../interfaces/ierc20.sol"; + +library Address { + function isContract(address account) internal view returns (bool) { + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != 0x0 && codehash != accountHash); + } + function toPayable(address account) internal pure returns (address payable) { + return address(uint160(account)); + } + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-call-value + (bool success, ) = recipient.call{value:amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } +} + +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint256 value) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + function safeApprove(IERC20 token, address spender, uint256 value) internal { + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + function callOptionalReturn(IERC20 token, bytes memory data) private { + require(address(token).isContract(), "SafeERC20: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = address(token).call(data); + require(success, "SafeERC20: low-level call failed"); + + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} \ No newline at end of file diff --git a/contracts/snowcones/gauge-proxy-v3.sol b/contracts/snowcones/gauge-proxy-v3.sol new file mode 100644 index 000000000..d09d7213a --- /dev/null +++ b/contracts/snowcones/gauge-proxy-v3.sol @@ -0,0 +1,802 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.7; //^0.7.5; + +library SafeMath { + function add(uint a, uint b) internal pure returns (uint) { + uint c = a + b; + require(c >= a, "add: +"); + + return c; + } + function add(uint a, uint b, string memory errorMessage) internal pure returns (uint) { + uint c = a + b; + require(c >= a, errorMessage); + + return c; + } + function sub(uint a, uint b) internal pure returns (uint) { + return sub(a, b, "sub: -"); + } + function sub(uint a, uint b, string memory errorMessage) internal pure returns (uint) { + require(b <= a, errorMessage); + uint c = a - b; + + return c; + } + function mul(uint a, uint b) internal pure returns (uint) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint c = a * b; + require(c / a == b, "mul: *"); + + return c; + } + function mul(uint a, uint b, string memory errorMessage) internal pure returns (uint) { + if (a == 0) { + return 0; + } + + uint c = a * b; + require(c / a == b, errorMessage); + + return c; + } + function div(uint a, uint b) internal pure returns (uint) { + return div(a, b, "div: /"); + } + function div(uint a, uint b, string memory errorMessage) internal pure returns (uint) { + require(b > 0, errorMessage); + uint c = a / b; + + return c; + } +} + +library Address { + function isContract(address account) internal view returns (bool) { + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != 0x0 && codehash != accountHash); + } + function toPayable(address account) internal pure returns (address payable) { + return address(uint160(account)); + } + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-call-value + (bool success, ) = recipient.call{value:amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } +} + +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); +} + +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint256 value) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + function safeApprove(IERC20 token, address spender, uint256 value) internal { + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + function callOptionalReturn(IERC20 token, bytes memory data) private { + require(address(token).isContract(), "SafeERC20: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = address(token).call(data); + require(success, "SafeERC20: low-level call failed"); + + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +library Math { + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a >= b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow, so we distribute + return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2); + } +} + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor () public { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + +contract ProtocolGovernance { + /// @notice governance address for the governance contract + address public governance; + address public pendingGovernance; + + /** + * @notice modifier to allow for easy gov only control over a function + */ + modifier onlyGovernance() { + require(msg.sender == governance, "unauthorized sender (governance"); + _; + } + + /** + * @notice Allows governance to change governance (for future upgradability) + * @param _governance new governance address to set + */ + function setGovernance(address _governance) external onlyGovernance{ + pendingGovernance = _governance; + } + + /** + * @notice Allows pendingGovernance to accept their role as governance (protection pattern) + */ + function acceptGovernance() external { + require(msg.sender == pendingGovernance, "acceptGovernance: !pendingGov"); + governance = pendingGovernance; + } +} + +contract Strategist { + /// @notice strategist address for the strategist contract + address public strategist; + address public pendingStrategist; + + /** + * @notice modifier to allow for easy gov only control over a function + */ + modifier onlyStrategist() { + require( + msg.sender == strategist, + "unauthorized sender (strategist)"); + _; + } + + /** + * @notice Allows strategist to change strategist (for future upgradability) + * @param _strategist new strategist address to set + */ + function setStrategist(address _strategist) external onlyStrategist { + pendingStrategist = _strategist; + } + + /** + * @notice Allows pendingStrategist to accept their role as strategist (protection pattern) + */ + function acceptStrategist() external { + require(msg.sender == pendingStrategist, "unauthorized sender (pendingStrategist)"); + strategist = pendingStrategist; + } +} + +contract GaugeV2 is ReentrancyGuard, ProtocolGovernance { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + //FUJI testnet + IERC20 public constant SNOWBALL = IERC20(0xC38f41A296A4493Ff429F1238e030924A1542e50); + IERC20 public constant SNOWCONE = IERC20(0x83952E7ab4aca74ca96217D6F8f7591BEaD6D64E); + address public constant TREASURY = address(0x294aB3200ef36200db84C4128b7f1b4eec71E38a); + + IERC20 public immutable TOKEN; + address public DISTRIBUTION; + uint256 public constant DURATION = 7 days; + + uint256 public periodFinish = 0; + uint256 public rewardRate = 0; + uint256 public lastUpdateTime; + uint256 public rewardPerTokenStored; + + modifier onlyDistribution() { + require(msg.sender == DISTRIBUTION, "Caller is not RewardsDistribution contract"); + _; + } + + mapping(address => uint256) public userRewardPerTokenPaid; + mapping(address => uint256) public rewards; + + uint256 private _totalSupply; + uint public derivedSupply; + mapping(address => uint256) private _balances; + mapping(address => uint256) public derivedBalances; + mapping(address => uint) private _base; + + constructor(address _token, address _governance) public { + TOKEN = IERC20(_token); + DISTRIBUTION = msg.sender; + governance = _governance; + } + + // This function is to allow us to update the gaugeProxy + // without resetting the old gauges. + function changeDistribution(address _distribution) external onlyGovernance { + DISTRIBUTION = _distribution; + } + + function totalSupply() external view returns (uint256) { + return _totalSupply; + } + + function balanceOf(address account) external view returns (uint256) { + return _balances[account]; + } + + function lastTimeRewardApplicable() public view returns (uint256) { + return Math.min(block.timestamp, periodFinish); + } + + function rewardPerToken() public view returns (uint256) { + if (_totalSupply == 0) { + return rewardPerTokenStored; + } + return + rewardPerTokenStored.add( + lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(derivedSupply) + ); + } + + function derivedBalance(address account) public view returns (uint) { + uint _balance = _balances[account]; + uint _derived = _balance.mul(40).div(100); + uint _adjusted = (_totalSupply.mul(SNOWCONE.balanceOf(account)).div(SNOWCONE.totalSupply())).mul(60).div(100); + return Math.min(_derived.add(_adjusted), _balance); + } + + function kick(address account) public { + uint _derivedBalance = derivedBalances[account]; + derivedSupply = derivedSupply.sub(_derivedBalance); + _derivedBalance = derivedBalance(account); + derivedBalances[account] = _derivedBalance; + derivedSupply = derivedSupply.add(_derivedBalance); + } + + function earned(address account) public view returns (uint256) { + return derivedBalances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]); + } + + function getRewardForDuration() external view returns (uint256) { + return rewardRate.mul(DURATION); + } + + function depositAll() external { + _deposit(TOKEN.balanceOf(msg.sender), msg.sender); + } + + function deposit(uint256 amount) external { + _deposit(amount, msg.sender); + } + + function depositFor(uint256 amount, address account) external { + _deposit(amount, account); + } + + function _deposit(uint amount, address account) internal nonReentrant updateReward(account) { + require(amount > 0, "Cannot stake 0"); + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Staked(account, amount); + TOKEN.safeTransferFrom(account, address(this), amount); + } + + function withdrawAll() external { + _withdraw(_balances[msg.sender]); + } + + function withdraw(uint256 amount) external { + _withdraw(amount); + } + + function _withdraw(uint amount) internal nonReentrant updateReward(msg.sender) { + require(amount > 0, "Cannot withdraw 0"); + _totalSupply = _totalSupply.sub(amount); + _balances[msg.sender] = _balances[msg.sender].sub(amount); + TOKEN.safeTransfer(msg.sender, amount); + emit Withdrawn(msg.sender, amount); + } + + function getReward() public nonReentrant updateReward(msg.sender) { + uint256 reward = rewards[msg.sender]; + if (reward > 0) { + rewards[msg.sender] = 0; + SNOWBALL.safeTransfer(msg.sender, reward); + emit RewardPaid(msg.sender, reward); + } + } + + function exit() external { + _withdraw(_balances[msg.sender]); + getReward(); + } + + function notifyRewardAmount(uint256 reward) external onlyDistribution updateReward(address(0)) { + SNOWBALL.safeTransferFrom(DISTRIBUTION, address(this), reward); + if (block.timestamp >= periodFinish) { + rewardRate = reward.div(DURATION); + } else { + uint256 remaining = periodFinish.sub(block.timestamp); + uint256 leftover = remaining.mul(rewardRate); + rewardRate = reward.add(leftover).div(DURATION); + } + + // Ensure the provided reward amount is not more than the balance in the contract. + // This keeps the reward rate in the right range, preventing overflows due to + // very high values of rewardRate in the earned and rewardsPerToken functions; + // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow. + uint balance = SNOWBALL.balanceOf(address(this)); + require(rewardRate <= balance.div(DURATION), "Provided reward too high"); + + lastUpdateTime = block.timestamp; + periodFinish = block.timestamp.add(DURATION); + emit RewardAdded(reward); + } + + modifier updateReward(address account) { + rewardPerTokenStored = rewardPerToken(); + lastUpdateTime = lastTimeRewardApplicable(); + if (account != address(0)) { + rewards[account] = earned(account); + userRewardPerTokenPaid[account] = rewardPerTokenStored; + } + _; + if (account != address(0)) { + kick(account); + } + } + + event RewardAdded(uint256 reward); + event Staked(address indexed user, uint256 amount); + event Withdrawn(address indexed user, uint256 amount); + event RewardPaid(address indexed user, uint256 reward); +} + +interface IceQueen { + function deposit(uint, uint) external; + function withdraw(uint, uint) external; + function userInfo(uint, address) external view returns (uint, uint); +} + +// makes a dummy token +contract MakeSnowCones { + using SafeMath for uint; + + /// @notice EIP-20 token name for this token + string public constant name = "Make SNOWCONES"; + + /// @notice EIP-20 token symbol for this token + string public constant symbol = "mSNOWCONES"; + + /// @notice EIP-20 token decimals for this token + uint8 public constant decimals = 18; + + /// @notice Total number of tokens in circulation + uint public totalSupply = 1e18; + + mapping (address => mapping (address => uint)) internal allowances; + mapping (address => uint) internal balances; + + /// @notice The standard EIP-20 transfer event + event Transfer(address indexed from, address indexed to, uint amount); + + /// @notice The standard EIP-20 approval event + event Approval(address indexed owner, address indexed spender, uint amount); + + constructor() public { + balances[msg.sender] = 1e18; + emit Transfer(address(0x0), msg.sender, 1e18); + } + + /** + * @notice Get the number of tokens `spender` is approved to spend on behalf of `account` + * @param account The address of the account holding the funds + * @param spender The address of the account spending the funds + * @return The number of tokens approved + */ + function allowance(address account, address spender) external view returns (uint) { + return allowances[account][spender]; + } + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param amount The number of tokens that are approved (2^256-1 means infinite) + * @return Whether or not the approval succeeded + */ + function approve(address spender, uint amount) external returns (bool) { + allowances[msg.sender][spender] = amount; + + emit Approval(msg.sender, spender, amount); + return true; + } + + /** + * @notice Get the number of tokens held by the `account` + * @param account The address of the account to get the balance of + * @return The number of tokens held + */ + function balanceOf(address account) external view returns (uint) { + return balances[account]; + } + + /** + * @notice Transfer `amount` tokens from `msg.sender` to `dst` + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transfer(address dst, uint amount) external returns (bool) { + _transferTokens(msg.sender, dst, amount); + return true; + } + + /** + * @notice Transfer `amount` tokens from `src` to `dst` + * @param src The address of the source account + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transferFrom(address src, address dst, uint amount) external returns (bool) { + address spender = msg.sender; + uint spenderAllowance = allowances[src][spender]; + + if (spender != src && spenderAllowance != uint(-1)) { + uint newAllowance = spenderAllowance.sub(amount, "transferFrom: exceeds spender allowance"); + allowances[src][spender] = newAllowance; + + emit Approval(src, spender, newAllowance); + } + + _transferTokens(src, dst, amount); + return true; + } + + function _transferTokens(address src, address dst, uint amount) internal { + require(src != address(0), "_transferTokens: zero address"); + require(dst != address(0), "_transferTokens: zero address"); + + balances[src] = balances[src].sub(amount, "_transferTokens: exceeds balance"); + balances[dst] = balances[dst].add(amount, "_transferTokens: overflows"); + emit Transfer(src, dst, amount); + } +} +import {Gauge} from "./gauge.sol"; + +contract GaugeProxyV3 is ProtocolGovernance, Strategist { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + IceQueen public constant MASTER = IceQueen(0xB12531a2d758c7a8BF09f44FC88E646E1BF9D375); + IERC20 public constant SNOWCONE = IERC20(0x83952E7ab4aca74ca96217D6F8f7591BEaD6D64E); + IERC20 public constant SNOWBALL = IERC20(0xC38f41A296A4493Ff429F1238e030924A1542e50); + + IERC20 public immutable TOKEN; + uint public immutable DISTRIBUTION_DEADLINE = 21600; + + uint public pid; + uint public totalWeight; + uint private lockedTotalWeight; + uint private lockedBalance; + uint private locktime; + + address[] internal _tokens; + mapping(address => address) public gauges; // token => gauge + mapping(address => address) public deprecated; // token => gauge + mapping(address => uint) public weights; // token => weight + mapping(address => uint) private lockedWeights; // token => weight + mapping(address => mapping(address => uint)) public votes; // msg.sender => token => votes + mapping(address => address[]) public tokenVote;// msg.sender => token + mapping(address => uint) public usedWeights; // msg.sender => total voting weight of user + mapping(address => bool) public deployers; + + constructor(address _governance) public { + TOKEN = IERC20(address(new MakeSnowCones())); + governance = _governance; + } + + function addDeployer(address _deployer) external onlyGovernance { + deployers[_deployer] = true; + } + + function removeDeployer(address _deployer) external onlyGovernance { + deployers[_deployer] = false; + } + + modifier onlyBenevolent { + require( + msg.sender == strategist || + msg.sender == governance, "unauthorized sender" + ); + _; + } + + function tokens() external view returns (address[] memory) { + return _tokens; + } + + function getGauge(address _token) external view returns (address) { + return gauges[_token]; + } + + // Reset votes to 0 + function reset() external { + _reset(msg.sender); + } + + // Reset votes to 0 + function _reset(address _owner) internal { + address[] storage _tokenVote = tokenVote[_owner]; + uint256 _tokenVoteCnt = _tokenVote.length; + + for (uint i = 0; i < _tokenVoteCnt; i ++) { + address _token = _tokenVote[i]; + // Get the amount of xSnob this user allocated for a gauge + uint _votes = votes[_owner][_token]; + + if (_votes > 0) { + totalWeight = totalWeight.sub(_votes); + weights[_token] = weights[_token].sub(_votes); + + votes[_owner][_token] = 0; + } + } + + delete tokenVote[_owner]; + } + + // Adjusts _owner's votes according to latest _owner's SNOWCONE balance + function poke(address _owner) public { + address[] memory _tokenVote = tokenVote[_owner]; + uint256 _tokenCnt = _tokenVote.length; + uint256[] memory _weights = new uint[](_tokenCnt); + + for (uint256 i = 0; i < _tokenCnt; i ++) { + _weights[i] = votes[_owner][_tokenVote[i]]; + } + + // _weights no longer total 100 like with the front-end + // But we will minimize gas by not converting + _vote(_owner, _tokenVote, _weights); + } + + function _vote(address _owner, address[] memory _tokenVote, uint256[] memory _weights) internal { + // _weights[i] = percentage * 100 + _reset(_owner); + uint256 _tokenCnt = _tokenVote.length; + uint256 _weight = SNOWCONE.balanceOf(_owner); + uint256 _totalVoteWeight = 0; + uint256 _usedWeight = 0; + + for (uint256 i = 0; i < _tokenCnt; i ++) { + _totalVoteWeight = _totalVoteWeight.add(_weights[i]); + } + + for (uint256 i = 0; i < _tokenCnt; i ++) { + address _token = _tokenVote[i]; + address _gauge = gauges[_token]; + // Calculate quantity of users xSnob to allocate for the gauge + uint256 _tokenWeight = _weights[i].mul(_weight).div(_totalVoteWeight); + + if (_gauge != address(0x0)) { + _usedWeight = _usedWeight.add(_tokenWeight); + totalWeight = totalWeight.add(_tokenWeight); + weights[_token] = weights[_token].add(_tokenWeight); + tokenVote[_owner].push(_token); + votes[_owner][_token] = _tokenWeight; + } + } + // This may be redundant as it simply represents the users total xSnob balance if they have ever voted + usedWeights[_owner] = _usedWeight; + } + + // Vote with SNOWCONE on a gauge, removing any previous votes + // _tokenVote: the array of tokens which will recieve tokens + // _weights: the weights to associate with the tokens listed in _tokenVote + function vote(address[] calldata _tokenVote, uint256[] calldata _weights) external { + require(_tokenVote.length == _weights.length); + _vote(msg.sender, _tokenVote, _weights); + } + + // Add new token gauge + function addGauge(address _token) external onlyBenevolent { + require(gauges[_token] == address(0x0), "exists"); + gauges[_token] = address(new GaugeV2(_token, governance)); + _tokens.push(_token); + } + + // Deprecate existing gauge + function deprecateGauge(address _token) external onlyBenevolent { + require(gauges[_token] != address(0x0), "does not exist"); + deprecated[_token] = gauges[_token]; + delete gauges[_token]; + //totalWeight = totalWeight.sub(weights[_token]); + //delete weights[_token]; + } + + // Bring Deprecated gauge back into use + function renewGauge(address _token) external onlyBenevolent { + require(gauges[_token] == address(0x0), "exists"); + gauges[_token] = deprecated[_token]; + delete deprecated[_token]; + } + + // Add existing gauge + function migrateGauge(address _gauge, address _token) external onlyBenevolent { + require(gauges[_token] == address(0x0), "exists"); + gauges[_token] = _gauge; + _tokens.push(_token); + } + + // Sets IceQueen PID + function setPID(uint _pid) external onlyGovernance { + require(pid == 0, "pid has already been set"); + require(_pid > 0, "invalid pid"); + pid = _pid; + } + + // Deposits mSNOWCONES into IceQueen + function deposit() public { + require(pid > 0, "pid not initialized"); + IERC20 _token = TOKEN; + uint _balance = _token.balanceOf(address(this)); + _token.safeApprove(address(MASTER), 0); + _token.safeApprove(address(MASTER), _balance); + MASTER.deposit(pid, _balance); + } + + // Fetches Snowball TODO: at this point would it only be fetching snowball + function collect() public { + (uint _locked,) = MASTER.userInfo(pid, address(this)); + MASTER.withdraw(pid, _locked); + deposit(); + } + + function length() external view returns (uint) { + return _tokens.length; + } + + //TODO the collect should allow us to get the locked balance of other tokens??? + function preDistribute() external { + require( + deployers[msg.sender] || + msg.sender == strategist || + msg.sender == governance, "unauthorized sender" + ); + lockedTotalWeight = totalWeight; + for (uint i = 0; i < _tokens.length; i++) { + lockedWeights[_tokens[i]] = weights[_tokens[i]]; + } + collect(); + lockedBalance = SNOWBALL.balanceOf(address(this)); + locktime = block.timestamp; + } + + function distribute(uint _start, uint _end) external { + require( + deployers[msg.sender] || + msg.sender == strategist || + msg.sender == governance, "unauthorized sender" + ); + require(_start < _end, "bad _start"); + require(_end <= _tokens.length, "bad _end"); + require(locktime + DISTRIBUTION_DEADLINE >= block.timestamp, "lock expired"); + if (lockedBalance > 0 && lockedTotalWeight > 0) { + for (uint i = _start; i < _end; i++) { + address _token = _tokens[i]; + address _gauge = gauges[_token]; + uint _reward = lockedBalance.mul(lockedWeights[_token]).div(totalWeight); + if (_reward > 0) { + SNOWBALL.safeApprove(_gauge, 0); + SNOWBALL.safeApprove(_gauge, _reward); + GaugeV2(_gauge).notifyRewardAmount(_reward); + } + } + } + } +} \ No newline at end of file diff --git a/contracts/snowcones/gauge.sol b/contracts/snowcones/gauge.sol new file mode 100644 index 000000000..72389bc3f --- /dev/null +++ b/contracts/snowcones/gauge.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.7; //^0.7.5; + + +import "../lib/safeerc20.sol"; +import "../lib/protocol-governance.sol"; +import "../lib/safe-math.sol"; +import "../lib/reentrancy-guard.sol"; + + +contract Gauge is ProtocolGovernance, ReentrancyGuard { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + event Staked(address indexed user, uint256 amount); + event Withdrawn(address indexed user, uint256 amount); + event RewardPaid(address indexed user, uint256 reward, address token); + event RewardAdded(uint256 reward, address token); + + // IERC20 public constant AXIAL = IERC20(0xcF8419A615c57511807236751c0AF38Db4ba3351); + // IERC20 public XAXIAL; + + IERC20 public constant SNOWCONE = IERC20(0x83952E7ab4aca74ca96217D6F8f7591BEaD6D64E); + IERC20 public constant SNOWBALL = IERC20(0xC38f41A296A4493Ff429F1238e030924A1542e50); + + IERC20 public constant TREASURY = IERC20(0x4980AD7cCB304f7d3c5053Aa1131eD1EDaf48809); + + IERC20 public immutable TOKEN; + + address[] public rewardTokens; + address public DISTRIBUTION; + uint256 public constant DURATION = 7 days; + + uint256 public periodFinish = 0; + mapping(address => uint256) public rewardRates; // token => rate + mapping(address => uint256) public rewardPerTokenStored; + + uint256 public lastUpdateTime; + + mapping(address => mapping(address => uint256)) public userRewardPerTokenPaid; + mapping(address => mapping(address => uint256)) public rewards; // user => token => amount + + uint256 private _totalSupply; + uint public derivedSupply; + mapping(address => uint256) private _balances; + mapping(address => uint256) public derivedBalances; + + modifier updateReward(address account) { + for (uint i = 0; i < rewardTokens.length; i++) { + rewardPerTokenStored[rewardTokens[i]] = rewardPerToken(i); + lastUpdateTime = lastTimeRewardApplicable(); + if (account != address(0)) { + rewards[account][rewardTokens[i]] = earned(account, i); + userRewardPerTokenPaid[account][rewardTokens[i]] = rewardPerTokenStored[rewardTokens[i]]; + } + } + _; + if (account != address(0)) { + kick(account); + } + } + + modifier onlyDistribution() { + require(msg.sender == DISTRIBUTION, "Caller is not RewardsDistribution contract"); + _; + } + + modifier validAddress(address _rewardToken) { + require(Address.isContract(_rewardToken), "reward token must be a valid contract"); + _; + } + + constructor( + address _token, + address _governance + ) public { + TOKEN = IERC20(_token); + DISTRIBUTION = msg.sender; + governance = _governance; + } + + + /// @param token Reward token to be added to our rewardTokens array + // adding a reward token to our array + function addRewardToken(address token) public onlyGovernance validAddress(token){ + // adding a new reward token to the array + rewardTokens.push(token); + rewardRates[token] = 0; + } + + // returns the amount of reward tokens for the gauges + function getNumRewardTokens() public view returns (uint256) { + return rewardTokens.length; + } + + // This function is to allow us to update the gaugeProxy without resetting the old gauges. + // this changes where it is receiving the axial tokens, as well as changes the governance + function changeDistribution(address _distribution) external onlyGovernance { + DISTRIBUTION = _distribution; + } + + // total supply of our lp tokens in the gauge (e.g. AC4D tokens present) + function totalSupply() external view returns (uint256) { + return _totalSupply; + } + + // balance of lp tokens that user has in the gauge (e.g. amount of AC4D a user has) + function balanceOf(address account) external view returns (uint256) { + return _balances[account]; + } + + function lastTimeRewardApplicable() public view returns (uint256) { + return Math.min(block.timestamp, periodFinish); + } + + // how many of our reward tokens is the user receiving per lp token + // (e.g. how many teddy or axial is received per AC4D token) + function rewardPerToken(uint256 tokenIndex) public view returns (uint256) { + if (_totalSupply == 0) { + return rewardPerTokenStored[rewardTokens[tokenIndex]]; + } + return + rewardPerTokenStored[rewardTokens[tokenIndex]].add( + lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRates[rewardTokens[tokenIndex]]).mul(1e18).div(derivedSupply) + ); + } + + function derivedBalance(address account) public view returns (uint) { + uint _balance = _balances[account]; + uint _derived = _balance.mul(40).div(100); + uint _adjusted = (_totalSupply.mul(SNOWCONE.balanceOf(account)).div(SNOWCONE.totalSupply())).mul(60).div(100); + return Math.min(_derived.add(_adjusted), _balance); + } + + function kick(address account) public { + uint _derivedBalance = derivedBalances[account]; + derivedSupply = derivedSupply.sub(_derivedBalance); + _derivedBalance = derivedBalance(account); + derivedBalances[account] = _derivedBalance; + derivedSupply = derivedSupply.add(_derivedBalance); + } + + function earned(address account, uint256 tokenIndex) public view returns (uint256) { + return derivedBalances[account].mul(rewardPerToken(tokenIndex).sub(userRewardPerTokenPaid[account][rewardTokens[tokenIndex]])) + .div(1e18).add(rewards[account][rewardTokens[tokenIndex]]); + } + + // getting the reward to be received for each reward's respective staking period + function getRewardForDuration(uint256 tokenIndex) external view returns (uint256) { + return rewardRates[rewardTokens[tokenIndex]].mul(DURATION); + } + + function _deposit(uint amount, address account) internal nonReentrant updateReward(account) { + require(amount > 0, "Cannot stake 0"); + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Staked(account, amount); + TOKEN.safeTransferFrom(account, address(this), amount); + } + + function depositAll() external { + _deposit(TOKEN.balanceOf(msg.sender), msg.sender); + } + + function deposit(uint256 amount) external { + _deposit(amount, msg.sender); + } + + function depositFor(uint256 amount, address account) external { + _deposit(amount, account); + } + + function _withdraw(uint amount) internal nonReentrant updateReward(msg.sender) { + require(amount > 0, "Cannot withdraw 0"); + _totalSupply = _totalSupply.sub(amount); + _balances[msg.sender] = _balances[msg.sender].sub(amount); + TOKEN.safeTransfer(msg.sender, amount); + emit Withdrawn(msg.sender, amount); + } + + function withdrawAll() external { + _withdraw(_balances[msg.sender]); + } + + function withdraw(uint256 amount) external { + _withdraw(amount); + } + + function getReward(uint256 tokenIndex) public nonReentrant updateReward(msg.sender) { + uint256 reward = rewards[msg.sender][rewardTokens[tokenIndex]]; + if (reward > 0) { + rewards[msg.sender][rewardTokens[tokenIndex]] = 0; + IERC20(rewardTokens[tokenIndex]).safeTransfer(msg.sender, reward); + emit RewardPaid(msg.sender, reward, rewardTokens[tokenIndex]); + } + } + + function exit() external { + _withdraw(_balances[msg.sender]); + for (uint256 i = 0; i < rewardTokens.length; i++){ + getReward(i); + } + + } + + function notifyReward(uint256 reward, uint tokenIndex) external updateReward(address(0)) { + IERC20(rewardTokens[tokenIndex]).safeTransferFrom(DISTRIBUTION, address(this), reward); + if (block.timestamp >= periodFinish) { + rewardRates[rewardTokens[tokenIndex]] = reward.div(DURATION); + } else { + uint256 remaining = periodFinish.sub(block.timestamp); + uint256 leftover = remaining.mul(rewardRates[rewardTokens[tokenIndex]]); + rewardRates[rewardTokens[tokenIndex]] = reward.add(leftover).div(DURATION); + } + + // Ensure the provided reward amount is not more than the balance in the contract. + // This keeps the reward rate in the right range, preventing overflows due to + // very high values of rewardRate in the earned and rewardsPerToken functions; + // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow. + uint balance = IERC20(rewardTokens[tokenIndex]).balanceOf(address(this)); + require(rewardRates[rewardTokens[tokenIndex]] <= balance.div(DURATION), "Provided reward too high"); + + lastUpdateTime = block.timestamp; + periodFinish = block.timestamp.add(DURATION); + emit RewardAdded(reward, rewardTokens[tokenIndex]); + } + + // only called by the GaugeProxy and so only deals in the native token + function notifyRewardAmount(uint256 reward) external onlyDistribution updateReward(address(0)) { + IERC20(rewardTokens[0]).safeTransferFrom(DISTRIBUTION, address(this), reward); + if (block.timestamp >= periodFinish) { + rewardRates[rewardTokens[0]] = reward.div(DURATION); + } else { + uint256 remaining = periodFinish.sub(block.timestamp); + uint256 leftover = remaining.mul(rewardRates[rewardTokens[0]]); + rewardRates[rewardTokens[0]] = reward.add(leftover).div(DURATION); + } + + // Ensure the provided reward amount is not more than the balance in the contract. + // This keeps the reward rate in the right range, preventing overflows due to + // very high values of rewardRate in the earned and rewardsPerToken functions; + // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow. + uint balance = IERC20(rewardTokens[0]).balanceOf(address(this)); + require(rewardRates[rewardTokens[0]] <= balance.div(DURATION), "Provided reward too high"); + + lastUpdateTime = block.timestamp; + periodFinish = block.timestamp.add(DURATION); + emit RewardAdded(reward, rewardTokens[0]); + } +} \ No newline at end of file diff --git a/hardhat.config.js b/hardhat.config.js index def10a63a..5c0b77c27 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -21,6 +21,7 @@ module.exports = { }, mainnet: { chainId: 43114, + //url: "https://node.snowapi.net/ext/bc/C/rpc", url: "https://api.avax.network/ext/bc/C/rpc", accounts: [process.env.PRIVATE_KEY] }, diff --git a/hardhat.config.ts b/hardhat.config.ts index fa866fa37..b830dfe88 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -65,7 +65,7 @@ const config: HardhatUserConfig = { outDir: 'typechain', }, mocha: { - timeout: 240000 + timeout: 640000 }, // vyper: { // version: "0.2.4", diff --git a/package-lock.json b/package-lock.json index 6e7dee6f0..20af071a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,12 @@ { - "name": "protocol", + "name": "@snowballfinance/protocol", + "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { + "name": "@snowballfinance/protocol", + "version": "1.0.0", "dependencies": { "remixd": "^0.2.4", "typescript-formatter": "^7.2.2", diff --git a/package.json b/package.json index 277af23bb..5ded21ddd 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,7 @@ { + "name": "@snowballfinance/protocol", + "version": "1.0.0", + "description": "Core smart contracts for the Snowball Finance protocol", "dependencies": { "remixd": "^0.2.4", "typescript-formatter": "^7.2.2", @@ -9,7 +12,7 @@ }, "scripts": { "flatten": "npx sol-merger \"./src/**/*.sol\" ./build", - "test": "ts-node" + "test": "npx hardhat test" }, "devDependencies": { "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers@^0.3.0-beta.13", diff --git a/scripts/deploy-gauge-proxy-v3.js b/scripts/deploy-gauge-proxy-v3.js new file mode 100644 index 000000000..427579424 --- /dev/null +++ b/scripts/deploy-gauge-proxy-v3.js @@ -0,0 +1,279 @@ +/* eslint-disable no-undef */ + +const { readFileSync, writeFileSync } = require("fs"); +require('dotenv').config(); +const { ethers, network } = require("hardhat"); +const {setupSigner, setupSigners, getImpersonatedAccounts, snowballAddr, treasuryAddr, devAddr} = require("./utils/static"); + +const multisig_ABI = [{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address","name":""}],"name":"owners","inputs":[{"type":"uint256","name":""}],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"removeOwner","inputs":[{"type":"address","name":"owner"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"revokeConfirmation","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"isOwner","inputs":[{"type":"address","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"confirmations","inputs":[{"type":"uint256","name":""},{"type":"address","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"calcMaxWithdraw","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"count"}],"name":"getTransactionCount","inputs":[{"type":"bool","name":"pending"},{"type":"bool","name":"executed"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"dailyLimit","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"lastDay","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"addOwner","inputs":[{"type":"address","name":"owner"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"isConfirmed","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"count"}],"name":"getConfirmationCount","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address","name":"destination"},{"type":"uint256","name":"value"},{"type":"bytes","name":"data"},{"type":"bool","name":"executed"}],"name":"transactions","inputs":[{"type":"uint256","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address[]","name":""}],"name":"getOwners","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256[]","name":"_transactionIds"}],"name":"getTransactionIds","inputs":[{"type":"uint256","name":"from"},{"type":"uint256","name":"to"},{"type":"bool","name":"pending"},{"type":"bool","name":"executed"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address[]","name":"_confirmations"}],"name":"getConfirmations","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"transactionCount","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"changeRequirement","inputs":[{"type":"uint256","name":"_required"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"confirmTransaction","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"transactionId"}],"name":"submitTransaction","inputs":[{"type":"address","name":"destination"},{"type":"uint256","name":"value"},{"type":"bytes","name":"data"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"changeDailyLimit","inputs":[{"type":"uint256","name":"_dailyLimit"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"MAX_OWNER_COUNT","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"required","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"replaceOwner","inputs":[{"type":"address","name":"owner"},{"type":"address","name":"newOwner"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"executeTransaction","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"spentToday","inputs":[],"constant":true},{"type":"constructor","stateMutability":"nonpayable","payable":false,"inputs":[{"type":"address[]","name":"_owners"},{"type":"uint256","name":"_required"},{"type":"uint256","name":"_dailyLimit"}]},{"type":"fallback","stateMutability":"payable","payable":true},{"type":"event","name":"DailyLimitChange","inputs":[{"type":"uint256","name":"dailyLimit","indexed":false}],"anonymous":false},{"type":"event","name":"Confirmation","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Revocation","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Submission","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Execution","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"ExecutionFailure","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Deposit","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"value","indexed":false}],"anonymous":false},{"type":"event","name":"OwnerAddition","inputs":[{"type":"address","name":"owner","indexed":true}],"anonymous":false},{"type":"event","name":"OwnerRemoval","inputs":[{"type":"address","name":"owner","indexed":true}],"anonymous":false},{"type":"event","name":"RequirementChange","inputs":[{"type":"uint256","name":"required","indexed":false}],"anonymous":false}]; + +const councilAddr = "0x028933a66DD0cCC239a3d5c2243b2d96672f11F5"; +const iceQueenAddr = "0xB12531a2d758c7a8BF09f44FC88E646E1BF9D375"; +const gaugeProxyV2Addr = "0x215D5eDEb6A6a3f84AE9d72962FEaCCdF815BF27"; +const timelockAddr = "0x3d88b8022142ea2693ba43BA349F89256392d59b"; +const governanceAddr = "0x294aB3200ef36200db84C4128b7f1b4eec71E38a"; + +// Do deployment of GaugeProxyV3, soft migration from GaugeProxyV2 to V3, fixed gauge deprecation and vote reallocation +async function main() { + + // This is where we are going to write the transactions [gauges.json] + const gaugesJSON = readFileSync("./scripts/gauges.json"); // Loaded from files + const gauges = JSON.parse(gaugesJSON); + + const [signer] = await ethers.getSigners(); + + console.log("-- Connecting to IceQueen"); + const IceQueen = await ethers.getContractAt("contracts/yield-farming/icequeen.sol:IceQueen", iceQueenAddr, signer); + + console.log("-- Connecting to Existing GaugeProxyV2 --"); + const gaugeProxyV2ABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DISTRIBUTION_DEADLINE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MASTER","outputs":[{"internalType":"contract IceQueen","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWBALL","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWCONE","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"acceptStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"addDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"addGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collect","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deployers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"deprecateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecated","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_start","type":"uint256"},{"internalType":"uint256","name":"_end","type":"uint256"}],"name":"distribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"gauges","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"getGauge","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"length","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_gauge","type":"address"},{"internalType":"address","name":"_token","type":"address"}],"name":"migrateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pendingGovernance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingStrategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"poke","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"preDistribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"removeDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"renewGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_governance","type":"address"}],"name":"setGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"setPID","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_strategist","type":"address"}],"name":"setStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"strategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenVote","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"usedWeights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokenVote","type":"address[]"},{"internalType":"uint256[]","name":"_weights","type":"uint256[]"}],"name":"vote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"votes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"weights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] + const GaugeProxyV2 = new ethers.Contract(gaugeProxyV2Addr, gaugeProxyV2ABI, signer); + + const GaugeProxyV2Length = await GaugeProxyV2.length(); + console.log(`-- GaugeProxyV2 has ${GaugeProxyV2Length} tokens --`); + + let GaugeProxyV3; + const GaugeProxyV3Factory = await ethers.getContractFactory("GaugeProxyV3"); + + if (!gauges["deployed"]) { + console.log(" -- Getting GaugeProxyV3 Factory --"); + console.log("-- Deploying GaugeProxyV3 contract --"); + GaugeProxyV3 = await GaugeProxyV3Factory.deploy(timelockAddr); // New: V3 Constructor takes governance + await GaugeProxyV3.deployed(); + gauges["deployed"] = GaugeProxyV3.address; + writeFileSync("./scripts/gauges.json", JSON.stringify(gauges)); + console.log(`GaugeProxyV3 deployed at ${GaugeProxyV3.address}`); + } else { + GaugeProxyV3 = new ethers.Contract(gauges[deployed], GaugeProxyV3Factory.interface, signer); + } + + console.log("-- Getting number of pre-existing pools from IceQueen --"); + let numIceQueenPools = await IceQueen.poolLength - 1; + + console.log("-- Adding Snowcones to IceQueen --"); + const IIceQueen = IceQueen.interface; + const IMultiSig = new ethers.utils.Interface(multisig_ABI); + const Council = new ethers.Contract(councilAddr, multisig_ABI, signer); + + if (!gauges["IceQueenAdd"]) { + const SnowconeTokenAddr = await GaugeProxyV3.TOKEN(); + + const first_encoding = IIceQueen.encodeFunctionData("add", [5000000, SnowconeTokenAddr, false]); + const second_encoding = IMultiSig.encodeFunctionData("submitTransaction", [IceQueen.address, 0, first_encoding]); + + // var options = { gasPrice: 25000000000, gasLimit: 8000000 }; + const submission = await Council.submitTransaction(governanceAddr, 0, second_encoding); + const tx_submission = await submission.wait(1); + if (!tx_submission.status) { + console.error("Error submitting transaction executing add for IceQueen"); + return; + } + console.log("transaction submitted for executing add for IceQueen"); + + + console.log("-- Setting current IceQueen pool allocation to zero --"); + gauges["IceQueenAdd"] = true; + writeFileSync("./scripts/gauges.json", JSON.stringify(gauges)); + } + + if (!gauges["IceQueenSet"]) { + const first_encoding = IIceQueen.encodeFunctionData("set", [numIceQueenPools, 0, false]); + const second_encoding = IMultiSig.encodeFunctionData("submitTransaction", [IceQueen.address, 0, first_encoding]); + + // var options = { gasPrice: 25000000000, gasLimit: 8000000 }; + const submission = await Council.submitTransaction(governanceAddr, 0, second_encoding); + const tx_submission = await submission.wait(1); + if (!tx_submission.status) { + console.error("Error submitting transaction executing set for IceQueen"); + return; + } + console.log("transaction submitted for executing set for IceQueen"); + + gauges["IceQueenSet"] = true; + writeFileSync("./scripts/gauges.json", JSON.stringify(gauges)); + } + + // Test 2-3 soft migration + console.log("-- Retrieving GaugeProxyV2 Token List --"); + let tokens = await GaugeProxyV2.tokens(); + + let percentMigrationComplete = 0; + const numTokensToMigrate = tokens.length; + let migratedTokens = []; + let userTestWeights = []; + console.log("-- Beginning Soft Migration from GaugeProxyV2 to GaugeProxyV3 --"); + + let totalTransactionsAllowedPerBlock = 75; + let currentBlock = 0; + let currentNumberOfTransactionsThisBlock = 0; + + let IGaugeProxyV3 = GaugeProxyV3.interface; + let GaugeV2Interface = (await ethers.getContractFactory("contracts/snowcones/gauge-proxy-v3.sol:GaugeV2")).interface; + let IGauge = new ethers.utils.Interface(GaugeV2Interface); + + for (let i = 0; i < numTokensToMigrate; i++) { + // Limit quantity of transactions per block + if (currentNumberOfTransactionsThisBlock == totalTransactionsAllowedPerBlock) { + currentBlock += 1; + currentNumberOfTransactionsThisBlock = 0; + if (!gauges[currentBlock]) { + gauges[currentBlock] = {}; + } + } + + let gauge = await GaugeProxyV2.gauges(tokens[i]); + //console.log(` -- token: ${tokens[i]}, gauge: ${gauge}`); + + if (gauge != 0x0) { + migratedTokens.push(tokens[i]); + + let Gauge = new ethers.Contract(gauge, GaugeV2Interface, governanceSigner); + + let GaugeGovernanceAddr = await Gauge.governance(); + //console.log(`-- Gauge Governance Addr = ${GaugeGovernanceAddr}`); + + let GaugeGovernanceSigner; + + let impersonatedAccounts = await getImpersonatedAccounts(); + if (!impersonatedAccounts.includes(GaugeGovernanceAddr)) { + console.log(`-- Setting up new signer ${GaugeGovernanceAddr} --`); + GaugeGovernanceSigner = setupSigner(GaugeGovernanceAddr); + } + + GaugeGovernanceSigner = await ethers.provider.getSigner(GaugeGovernanceAddr); + + /* Encoding for Set Globe */ + if(gauges[currentBlock].highestNumber < i){ + gauges[currentBlock].targets.push(GaugeProxyV3.address); + gauges[currentBlock].data.push(IGaugeProxyV3.encodeFunctionData("migrateGauge", [gauge, tokens[i]])); + gauges[currentBlock].targets.push(Gauge.address); + gauges[currentBlock].data.push(IGauge.encodeFunctionData("changeDistribution", [GaugeProxyV3.address])); + + gauges[currentBlock].highestNumber = i; + writeFileSync("./scripts/gauges.json", JSON.stringify(gauges)); + console.log(`encoded migrateGauge for ${tokens[i]}`); + } + + } else { + console.log(` -- token: ${tokens[i]}, gauge: ${gauge} are deprecated, skipping migration -- `); + } + + // Display progress + let currentPercentageComplete = Math.round((i / numTokensToMigrate) * 100); + if (currentPercentageComplete >= percentMigrationComplete + 5) { + percentMigrationComplete = currentPercentageComplete; + console.log(` -- Soft Migration is ${percentMigrationComplete}% Complete --`); + } + } + + // Calculate even weights for fun and profit + const numMigratedTokens = migratedTokens.length; + for (let i = 0; i < numMigratedTokens; i++) + { + userTestWeights[i] = Math.floor(10000000 / numMigratedTokens); + } + + console.log("-- Impersonating User --"); + const userAddr = "0xdbc195a0ED72c0B059f8906e97a90636d2B6409F"; + let userSigner = await setupSigner(userAddr); + + console.log("-- Distributing votes evenly across all tokens for fun --"); + await GaugeProxyV3.connect(userSigner).vote(migratedTokens, userTestWeights); + + // Test pre-existing stuff + console.log("-- Depositing Snowcones into IceQueen --") + const pid = (await IceQueen.poolLength()) - 1; + await GaugeProxyV3.connect(governanceSigner).setPID(pid); + await GaugeProxyV3.connect(userSigner).deposit(); + + console.log("-- Wait for 10 blocks to be mined --"); + for (let i = 0; i < 10; i++) { + await hre.network.provider.request({ + method: "evm_mine", + }); + } + + console.log("-- Pre Distribute SNOB to gauges--") + await GaugeProxyV3.connect(governanceSigner).preDistribute(); + + console.log("-- Distribute SNOB to gauges --"); + let chunk = migratedTokens.length > 50 ? 50 : migratedTokens.length - 1; // distribute up to 50 at a time + console.log(`-- chunking up ${migratedTokens.length} gauges --`); + for (let i = 0; i < migratedTokens.length - chunk; i += chunk) { + console.log(`-- distrubution for chunk ${i} to ${i+chunk} --`); + await GaugeProxyV3.connect(governanceSigner).distribute(i, i+chunk); + if (i + chunk + chunk > migratedTokens.length) { + chunk = migratedTokens.length - i + chunk; + } + } + + // Print rewards for all gauges + for (const token of migratedTokens) { + let gauge = await GaugeProxyV3.getGauge(token); + let reward = await SnowBall.balanceOf(gauge); + console.log(`rewards to ${token} gauge: ${reward}`); + } + + // Test new stuff + // Deprecate Gauge, Change Votes, Poke + + // Deprecate the second gauge + let gaugeToDeprecate = migratedTokens[1]; + console.log(`-- Deprecating ${gaugeToDeprecate} --`); + await GaugeProxyV3.connect(governanceSigner).deprecateGauge(gaugeToDeprecate); + + // Use this to simulate voting through the front-end, where deprecated gauges cannot be selected + /* + let migratedTokensAfterDeprecation = migratedTokens; + migratedTokensAfterDeprecation.splice(1,1); // remove the index we deprecated + let userTestWeightsAfterDeprecation = userTestWeights; + userTestWeightsAfterDeprecation.splice(1,1); + */ + + console.log(" -- Poke -- "); + await GaugeProxyV3.poke(userAddr); + + // Test voting for deprecated gauge + console.log(" -- Reallocating votes -- "); + await GaugeProxyV3.connect(userSigner).vote(migratedTokens, userTestWeights); + + console.log(" -- Poke -- "); + await GaugeProxyV3.poke(userAddr); + + console.log("-- Wait for 10 blocks to be mined --"); + for (let i = 0; i < 10; i++) { + await hre.network.provider.request({ + method: "evm_mine", + }); + } + + console.log("-- Pre Distribute SNOB to gauges--") + await GaugeProxyV3.connect(governanceSigner).preDistribute(); + + console.log("-- Distribute SNOB to gauges --"); + chunk = migratedTokens.length > 50 ? 50 : migratedTokens.length - 1; // distribute up to 50 at a time + console.log(`-- chunking up ${migratedTokens.length} gauges --`); + for (let i = 0; i < migratedTokens.length - chunk; i += chunk) { + console.log(`-- distrubution for chunk ${i} to ${i+chunk} --`); + await GaugeProxyV3.connect(governanceSigner).distribute(i, i+chunk); + if (i + chunk + chunk > migratedTokens.length) { + chunk = migratedTokens.length - i + chunk; + } + } + + // Print rewards for all gauges + for (const token of migratedTokens) { + let gauge = await GaugeProxyV3.getGauge(token); + let reward = await SnowBall.balanceOf(gauge); + console.log(`rewards to ${token} gauge: ${reward}`); + } +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/scripts/gauge-proxy-v3-migration/1-deploy-gauge-proxy-v3.js b/scripts/gauge-proxy-v3-migration/1-deploy-gauge-proxy-v3.js new file mode 100644 index 000000000..1eac5a1e9 --- /dev/null +++ b/scripts/gauge-proxy-v3-migration/1-deploy-gauge-proxy-v3.js @@ -0,0 +1,56 @@ +/* eslint-disable no-undef */ + +const { readFileSync, writeFileSync } = require("fs"); +require('dotenv').config(); +const { ethers, network } = require("hardhat"); +const {setupSigner, setupSigners, getImpersonatedAccounts, snowballAddr, treasuryAddr, devAddr} = require("./utils/static"); + +const multisig_ABI = [{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address","name":""}],"name":"owners","inputs":[{"type":"uint256","name":""}],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"removeOwner","inputs":[{"type":"address","name":"owner"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"revokeConfirmation","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"isOwner","inputs":[{"type":"address","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"confirmations","inputs":[{"type":"uint256","name":""},{"type":"address","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"calcMaxWithdraw","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"count"}],"name":"getTransactionCount","inputs":[{"type":"bool","name":"pending"},{"type":"bool","name":"executed"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"dailyLimit","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"lastDay","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"addOwner","inputs":[{"type":"address","name":"owner"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"isConfirmed","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"count"}],"name":"getConfirmationCount","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address","name":"destination"},{"type":"uint256","name":"value"},{"type":"bytes","name":"data"},{"type":"bool","name":"executed"}],"name":"transactions","inputs":[{"type":"uint256","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address[]","name":""}],"name":"getOwners","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256[]","name":"_transactionIds"}],"name":"getTransactionIds","inputs":[{"type":"uint256","name":"from"},{"type":"uint256","name":"to"},{"type":"bool","name":"pending"},{"type":"bool","name":"executed"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address[]","name":"_confirmations"}],"name":"getConfirmations","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"transactionCount","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"changeRequirement","inputs":[{"type":"uint256","name":"_required"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"confirmTransaction","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"transactionId"}],"name":"submitTransaction","inputs":[{"type":"address","name":"destination"},{"type":"uint256","name":"value"},{"type":"bytes","name":"data"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"changeDailyLimit","inputs":[{"type":"uint256","name":"_dailyLimit"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"MAX_OWNER_COUNT","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"required","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"replaceOwner","inputs":[{"type":"address","name":"owner"},{"type":"address","name":"newOwner"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"executeTransaction","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"spentToday","inputs":[],"constant":true},{"type":"constructor","stateMutability":"nonpayable","payable":false,"inputs":[{"type":"address[]","name":"_owners"},{"type":"uint256","name":"_required"},{"type":"uint256","name":"_dailyLimit"}]},{"type":"fallback","stateMutability":"payable","payable":true},{"type":"event","name":"DailyLimitChange","inputs":[{"type":"uint256","name":"dailyLimit","indexed":false}],"anonymous":false},{"type":"event","name":"Confirmation","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Revocation","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Submission","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Execution","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"ExecutionFailure","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Deposit","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"value","indexed":false}],"anonymous":false},{"type":"event","name":"OwnerAddition","inputs":[{"type":"address","name":"owner","indexed":true}],"anonymous":false},{"type":"event","name":"OwnerRemoval","inputs":[{"type":"address","name":"owner","indexed":true}],"anonymous":false},{"type":"event","name":"RequirementChange","inputs":[{"type":"uint256","name":"required","indexed":false}],"anonymous":false}]; + +const councilAddr = "0x028933a66DD0cCC239a3d5c2243b2d96672f11F5"; +const iceQueenAddr = "0xB12531a2d758c7a8BF09f44FC88E646E1BF9D375"; +const gaugeProxyV2Addr = "0x215D5eDEb6A6a3f84AE9d72962FEaCCdF815BF27"; +const timelockAddr = "0x3d88b8022142ea2693ba43BA349F89256392d59b"; +const governanceAddr = "0x294aB3200ef36200db84C4128b7f1b4eec71E38a"; + +// Do deployment of GaugeProxyV3, soft migration from GaugeProxyV2 to V3, fixed gauge deprecation and vote reallocation +async function main() { + + // This is where we are going to write the transactions [gauges.json] + const gaugesJSON = readFileSync("./scripts/gauges.json"); // Loaded from files + const gauges = JSON.parse(gaugesJSON); + + const [signer] = await ethers.getSigners(); + + console.log("-- Connecting to Existing GaugeProxyV2 --"); + const gaugeProxyV2ABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DISTRIBUTION_DEADLINE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MASTER","outputs":[{"internalType":"contract IceQueen","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWBALL","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWCONE","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"acceptStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"addDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"addGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collect","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deployers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"deprecateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecated","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_start","type":"uint256"},{"internalType":"uint256","name":"_end","type":"uint256"}],"name":"distribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"gauges","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"getGauge","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"length","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_gauge","type":"address"},{"internalType":"address","name":"_token","type":"address"}],"name":"migrateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pendingGovernance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingStrategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"poke","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"preDistribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"removeDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"renewGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_governance","type":"address"}],"name":"setGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"setPID","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_strategist","type":"address"}],"name":"setStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"strategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenVote","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"usedWeights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokenVote","type":"address[]"},{"internalType":"uint256[]","name":"_weights","type":"uint256[]"}],"name":"vote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"votes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"weights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] + const GaugeProxyV2 = new ethers.Contract(gaugeProxyV2Addr, gaugeProxyV2ABI, signer); + + const GaugeProxyV2Length = await GaugeProxyV2.length(); + console.log(`-- GaugeProxyV2 has ${GaugeProxyV2Length} tokens --`); + + let GaugeProxyV3; + const GaugeProxyV3Factory = await ethers.getContractFactory("GaugeProxyV3"); + + if (!gauges["deployed"]) { + console.log(" -- Getting GaugeProxyV3 Factory --"); + console.log("-- Deploying GaugeProxyV3 contract --"); + GaugeProxyV3 = await GaugeProxyV3Factory.deploy(timelockAddr); // New: V3 Constructor takes governance + await GaugeProxyV3.deployed(); + gauges["deployed"] = GaugeProxyV3.address; + writeFileSync("./scripts/gauges.json", JSON.stringify(gauges)); + console.log(`GaugeProxyV3 deployed at ${GaugeProxyV3.address}`); + } else { + GaugeProxyV3 = new ethers.Contract(gauges[deployed], GaugeProxyV3Factory.interface, signer); + console.log(`GaugeProxyV3 is already deployed at ${GaugeProxyV3.address}`); + } +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/scripts/gauge-proxy-v3-migration/2-migrate-gauge-proxy-v2-to-v3-governance.js b/scripts/gauge-proxy-v3-migration/2-migrate-gauge-proxy-v2-to-v3-governance.js new file mode 100644 index 000000000..9ca7c4e3d --- /dev/null +++ b/scripts/gauge-proxy-v3-migration/2-migrate-gauge-proxy-v2-to-v3-governance.js @@ -0,0 +1,132 @@ +/* eslint-disable no-undef */ + +const { readFileSync, writeFileSync } = require("fs"); +require('dotenv').config(); +const { ethers, network } = require("hardhat"); +const {setupSigner, setupSigners, getImpersonatedAccounts, snowballAddr, treasuryAddr, devAddr} = require("./utils/static"); + +const multisig_ABI = [{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address","name":""}],"name":"owners","inputs":[{"type":"uint256","name":""}],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"removeOwner","inputs":[{"type":"address","name":"owner"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"revokeConfirmation","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"isOwner","inputs":[{"type":"address","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"confirmations","inputs":[{"type":"uint256","name":""},{"type":"address","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"calcMaxWithdraw","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"count"}],"name":"getTransactionCount","inputs":[{"type":"bool","name":"pending"},{"type":"bool","name":"executed"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"dailyLimit","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"lastDay","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"addOwner","inputs":[{"type":"address","name":"owner"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"isConfirmed","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"count"}],"name":"getConfirmationCount","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address","name":"destination"},{"type":"uint256","name":"value"},{"type":"bytes","name":"data"},{"type":"bool","name":"executed"}],"name":"transactions","inputs":[{"type":"uint256","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address[]","name":""}],"name":"getOwners","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256[]","name":"_transactionIds"}],"name":"getTransactionIds","inputs":[{"type":"uint256","name":"from"},{"type":"uint256","name":"to"},{"type":"bool","name":"pending"},{"type":"bool","name":"executed"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address[]","name":"_confirmations"}],"name":"getConfirmations","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"transactionCount","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"changeRequirement","inputs":[{"type":"uint256","name":"_required"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"confirmTransaction","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"transactionId"}],"name":"submitTransaction","inputs":[{"type":"address","name":"destination"},{"type":"uint256","name":"value"},{"type":"bytes","name":"data"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"changeDailyLimit","inputs":[{"type":"uint256","name":"_dailyLimit"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"MAX_OWNER_COUNT","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"required","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"replaceOwner","inputs":[{"type":"address","name":"owner"},{"type":"address","name":"newOwner"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"executeTransaction","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"spentToday","inputs":[],"constant":true},{"type":"constructor","stateMutability":"nonpayable","payable":false,"inputs":[{"type":"address[]","name":"_owners"},{"type":"uint256","name":"_required"},{"type":"uint256","name":"_dailyLimit"}]},{"type":"fallback","stateMutability":"payable","payable":true},{"type":"event","name":"DailyLimitChange","inputs":[{"type":"uint256","name":"dailyLimit","indexed":false}],"anonymous":false},{"type":"event","name":"Confirmation","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Revocation","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Submission","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Execution","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"ExecutionFailure","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Deposit","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"value","indexed":false}],"anonymous":false},{"type":"event","name":"OwnerAddition","inputs":[{"type":"address","name":"owner","indexed":true}],"anonymous":false},{"type":"event","name":"OwnerRemoval","inputs":[{"type":"address","name":"owner","indexed":true}],"anonymous":false},{"type":"event","name":"RequirementChange","inputs":[{"type":"uint256","name":"required","indexed":false}],"anonymous":false}]; + +const councilAddr = "0x028933a66DD0cCC239a3d5c2243b2d96672f11F5"; +const iceQueenAddr = "0xB12531a2d758c7a8BF09f44FC88E646E1BF9D375"; +const gaugeProxyV2Addr = "0x215D5eDEb6A6a3f84AE9d72962FEaCCdF815BF27"; +const timelockAddr = "0x3d88b8022142ea2693ba43BA349F89256392d59b"; +const governanceAddr = "0x294aB3200ef36200db84C4128b7f1b4eec71E38a"; + +// Do deployment of GaugeProxyV3, soft migration from GaugeProxyV2 to V3, fixed gauge deprecation and vote reallocation +async function main() { + + // This is where we are going to write the transactions [gauges.json] + const gaugesJSON = readFileSync("./scripts/gauges.json"); // Loaded from files + const gauges = JSON.parse(gaugesJSON); + + // Encode step 2 actions here + const gaugesMigrationGovernanceJSON = readFileSync(".scripts/gauges-migration-governance.json"); + const gaugesMigrationGovernance = JSON.parse(gaugesMigrationGovernanceJSON); + + const [signer] = await ethers.getSigners(); + + if (signer.address != governanceAddr) { + console.log("-- Signer is not governance! Terminating script --"); + return; + } + + console.log("-- Connecting to Existing GaugeProxyV2 --"); + const gaugeProxyV2ABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DISTRIBUTION_DEADLINE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MASTER","outputs":[{"internalType":"contract IceQueen","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWBALL","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWCONE","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"acceptStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"addDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"addGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collect","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deployers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"deprecateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecated","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_start","type":"uint256"},{"internalType":"uint256","name":"_end","type":"uint256"}],"name":"distribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"gauges","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"getGauge","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"length","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_gauge","type":"address"},{"internalType":"address","name":"_token","type":"address"}],"name":"migrateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pendingGovernance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingStrategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"poke","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"preDistribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"removeDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"renewGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_governance","type":"address"}],"name":"setGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"setPID","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_strategist","type":"address"}],"name":"setStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"strategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenVote","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"usedWeights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokenVote","type":"address[]"},{"internalType":"uint256[]","name":"_weights","type":"uint256[]"}],"name":"vote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"votes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"weights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] + const GaugeProxyV2 = new ethers.Contract(gaugeProxyV2Addr, gaugeProxyV2ABI, signer); + + const GaugeProxyV2Length = await GaugeProxyV2.length(); + console.log(`-- GaugeProxyV2 has ${GaugeProxyV2Length} tokens --`); + + let GaugeProxyV3; + const GaugeProxyV3Factory = await ethers.getContractFactory("GaugeProxyV3"); + + if (!gauges["deployed"]) { + console.log("-- Gauge Proxy V3 is not deployed yet! Terminating script... --"); + return; + } else { + GaugeProxyV3 = new ethers.Contract(gauges[deployed], GaugeProxyV3Factory.interface, signer); + } + + // Test 2-3 soft migration + console.log("-- Retrieving GaugeProxyV2 Token List --"); + let tokens = await GaugeProxyV2.tokens(); + + let percentMigrationComplete = 0; + const numTokensToMigrate = tokens.length; + let migratedTokens = []; + let userTestWeights = []; + console.log("-- Beginning Soft Migration from GaugeProxyV2 to GaugeProxyV3 --"); + + let totalTransactionsAllowedPerBlock = 75; + let currentBlock = 0; + let currentNumberOfTransactionsThisBlock = 0; + + let IGaugeProxyV3 = GaugeProxyV3.interface; + let GaugeV2Interface = (await ethers.getContractFactory("contracts/snowcones/gauge-proxy-v3.sol:GaugeV2")).interface; + let IGauge = new ethers.utils.Interface(GaugeV2Interface); + + for (let i = 0; i < numTokensToMigrate; i++) { + // Limit quantity of transactions per block + if (currentNumberOfTransactionsThisBlock == totalTransactionsAllowedPerBlock) { + currentBlock += 1; + currentNumberOfTransactionsThisBlock = 0; + if (!gauges[currentBlock]) { + gauges[currentBlock] = {}; + } + } + + let gauge = await GaugeProxyV2.gauges(tokens[i]); + //console.log(` -- token: ${tokens[i]}, gauge: ${gauge}`); + + if (gauge != 0x0) { + migratedTokens.push(tokens[i]); + + let Gauge = new ethers.Contract(gauge, GaugeV2Interface, governanceSigner); + + let GaugeGovernanceAddr = await Gauge.governance(); + //console.log(`-- Gauge Governance Addr = ${GaugeGovernanceAddr}`); + + if (GaugeGovernanceAddr == signer.address) { + /* Encoding for Set Globe */ + if(gauges[currentBlock].highestNumber < i){ + gaugesMigrationGovernance[currentBlock].targets.push(GaugeProxyV3.address); + gaugesMigrationGovernance[currentBlock].data.push(IGaugeProxyV3.encodeFunctionData("migrateGauge", [gauge, tokens[i]])); + gaugesMigrationGovernance[currentBlock].targets.push(Gauge.address); + gaugesMigrationGovernance[currentBlock].data.push(IGauge.encodeFunctionData("changeDistribution", [GaugeProxyV3.address])); + + gauges[currentBlock].highestNumber = i; + gauges["totalMigrated"] = gauges["totalMigrated"] + 1; + // store progress in gauges.json + writeFileSync("./scripts/gauges.json", JSON.stringify(gauges)); + + // split commands between governance/timelock in different files + writeFileSync("./scripts/gauges-migration-governance.json", JSON.stringify(gaugesMigrationGovernance)); + console.log(`encoded migrateGauge for ${tokens[i]}`); + } + } + + } else { + console.log(` -- token: ${tokens[i]}, gauge: ${gauge} are deprecated, skipping migration -- `); + } + + // Display progress + let currentPercentageComplete = Math.round((i / numTokensToMigrate) * 100); + if (currentPercentageComplete >= percentMigrationComplete + 5) { + percentMigrationComplete = currentPercentageComplete; + console.log(` -- Soft Migration is ${percentMigrationComplete}% Complete --`); + } + } + + let totalGaugesMigrated = gauges["totalMigrated"]; + console.log(` -- Total gauges migrated between all signers: ${totalGaugesMigrated} --`); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/scripts/gauge-proxy-v3-migration/3-migrate-gauge-proxy-v2-to-v3-timelock.js b/scripts/gauge-proxy-v3-migration/3-migrate-gauge-proxy-v2-to-v3-timelock.js new file mode 100644 index 000000000..33476244e --- /dev/null +++ b/scripts/gauge-proxy-v3-migration/3-migrate-gauge-proxy-v2-to-v3-timelock.js @@ -0,0 +1,132 @@ +/* eslint-disable no-undef */ + +const { readFileSync, writeFileSync } = require("fs"); +require('dotenv').config(); +const { ethers, network } = require("hardhat"); +const {setupSigner, setupSigners, getImpersonatedAccounts, snowballAddr, treasuryAddr, devAddr} = require("./utils/static"); + +const multisig_ABI = [{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address","name":""}],"name":"owners","inputs":[{"type":"uint256","name":""}],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"removeOwner","inputs":[{"type":"address","name":"owner"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"revokeConfirmation","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"isOwner","inputs":[{"type":"address","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"confirmations","inputs":[{"type":"uint256","name":""},{"type":"address","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"calcMaxWithdraw","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"count"}],"name":"getTransactionCount","inputs":[{"type":"bool","name":"pending"},{"type":"bool","name":"executed"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"dailyLimit","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"lastDay","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"addOwner","inputs":[{"type":"address","name":"owner"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"isConfirmed","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"count"}],"name":"getConfirmationCount","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address","name":"destination"},{"type":"uint256","name":"value"},{"type":"bytes","name":"data"},{"type":"bool","name":"executed"}],"name":"transactions","inputs":[{"type":"uint256","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address[]","name":""}],"name":"getOwners","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256[]","name":"_transactionIds"}],"name":"getTransactionIds","inputs":[{"type":"uint256","name":"from"},{"type":"uint256","name":"to"},{"type":"bool","name":"pending"},{"type":"bool","name":"executed"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address[]","name":"_confirmations"}],"name":"getConfirmations","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"transactionCount","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"changeRequirement","inputs":[{"type":"uint256","name":"_required"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"confirmTransaction","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"transactionId"}],"name":"submitTransaction","inputs":[{"type":"address","name":"destination"},{"type":"uint256","name":"value"},{"type":"bytes","name":"data"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"changeDailyLimit","inputs":[{"type":"uint256","name":"_dailyLimit"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"MAX_OWNER_COUNT","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"required","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"replaceOwner","inputs":[{"type":"address","name":"owner"},{"type":"address","name":"newOwner"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"executeTransaction","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"spentToday","inputs":[],"constant":true},{"type":"constructor","stateMutability":"nonpayable","payable":false,"inputs":[{"type":"address[]","name":"_owners"},{"type":"uint256","name":"_required"},{"type":"uint256","name":"_dailyLimit"}]},{"type":"fallback","stateMutability":"payable","payable":true},{"type":"event","name":"DailyLimitChange","inputs":[{"type":"uint256","name":"dailyLimit","indexed":false}],"anonymous":false},{"type":"event","name":"Confirmation","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Revocation","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Submission","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Execution","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"ExecutionFailure","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Deposit","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"value","indexed":false}],"anonymous":false},{"type":"event","name":"OwnerAddition","inputs":[{"type":"address","name":"owner","indexed":true}],"anonymous":false},{"type":"event","name":"OwnerRemoval","inputs":[{"type":"address","name":"owner","indexed":true}],"anonymous":false},{"type":"event","name":"RequirementChange","inputs":[{"type":"uint256","name":"required","indexed":false}],"anonymous":false}]; + +const councilAddr = "0x028933a66DD0cCC239a3d5c2243b2d96672f11F5"; +const iceQueenAddr = "0xB12531a2d758c7a8BF09f44FC88E646E1BF9D375"; +const gaugeProxyV2Addr = "0x215D5eDEb6A6a3f84AE9d72962FEaCCdF815BF27"; +const timelockAddr = "0x3d88b8022142ea2693ba43BA349F89256392d59b"; +const governanceAddr = "0x294aB3200ef36200db84C4128b7f1b4eec71E38a"; + +// Do deployment of GaugeProxyV3, soft migration from GaugeProxyV2 to V3, fixed gauge deprecation and vote reallocation +async function main() { + + // This is where we are going to write the transactions [gauges.json] + const gaugesJSON = readFileSync("./scripts/gauges.json"); // Loaded from files + const gauges = JSON.parse(gaugesJSON); + + // Encode step 2 actions here + const gaugesMigrationTimelockJSON = readFileSync(".scripts/gauges-migration-timelock.json"); + const gaugesMigrationTimelock = JSON.parse(gaugesMigrationTimelockJSON); + + const [signer] = await ethers.getSigners(); + + if (signer.address != timelockAddr) { + console.log("-- Signer is not timelock! Terminating script --"); + return; +} + + console.log("-- Connecting to Existing GaugeProxyV2 --"); + const gaugeProxyV2ABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DISTRIBUTION_DEADLINE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MASTER","outputs":[{"internalType":"contract IceQueen","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWBALL","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWCONE","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"acceptStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"addDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"addGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collect","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deployers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"deprecateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecated","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_start","type":"uint256"},{"internalType":"uint256","name":"_end","type":"uint256"}],"name":"distribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"gauges","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"getGauge","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"length","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_gauge","type":"address"},{"internalType":"address","name":"_token","type":"address"}],"name":"migrateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pendingGovernance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingStrategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"poke","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"preDistribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"removeDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"renewGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_governance","type":"address"}],"name":"setGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"setPID","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_strategist","type":"address"}],"name":"setStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"strategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenVote","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"usedWeights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokenVote","type":"address[]"},{"internalType":"uint256[]","name":"_weights","type":"uint256[]"}],"name":"vote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"votes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"weights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] + const GaugeProxyV2 = new ethers.Contract(gaugeProxyV2Addr, gaugeProxyV2ABI, signer); + + const GaugeProxyV2Length = await GaugeProxyV2.length(); + console.log(`-- GaugeProxyV2 has ${GaugeProxyV2Length} tokens --`); + + let GaugeProxyV3; + const GaugeProxyV3Factory = await ethers.getContractFactory("GaugeProxyV3"); + + if (!gauges["deployed"]) { + console.log("-- Gauge Proxy V3 is not deployed yet! Terminating script... --"); + return; + } else { + GaugeProxyV3 = new ethers.Contract(gauges[deployed], GaugeProxyV3Factory.interface, signer); + } + + // Test 2-3 soft migration + console.log("-- Retrieving GaugeProxyV2 Token List --"); + let tokens = await GaugeProxyV2.tokens(); + + let percentMigrationComplete = 0; + const numTokensToMigrate = tokens.length; + let migratedTokens = []; + let userTestWeights = []; + console.log("-- Beginning Soft Migration from GaugeProxyV2 to GaugeProxyV3 --"); + + let totalTransactionsAllowedPerBlock = 75; + let currentBlock = 0; + let currentNumberOfTransactionsThisBlock = 0; + + let IGaugeProxyV3 = GaugeProxyV3.interface; + let GaugeV2Interface = (await ethers.getContractFactory("contracts/snowcones/gauge-proxy-v3.sol:GaugeV2")).interface; + let IGauge = new ethers.utils.Interface(GaugeV2Interface); + + for (let i = 0; i < numTokensToMigrate; i++) { + // Limit quantity of transactions per block + if (currentNumberOfTransactionsThisBlock == totalTransactionsAllowedPerBlock) { + currentBlock += 1; + currentNumberOfTransactionsThisBlock = 0; + if (!gauges[currentBlock]) { + gauges[currentBlock] = {}; + } + } + + let gauge = await GaugeProxyV2.gauges(tokens[i]); + //console.log(` -- token: ${tokens[i]}, gauge: ${gauge}`); + + if (gauge != 0x0) { + migratedTokens.push(tokens[i]); + + let Gauge = new ethers.Contract(gauge, GaugeV2Interface, governanceSigner); + + let GaugeGovernanceAddr = await Gauge.governance(); + //console.log(`-- Gauge Governance Addr = ${GaugeGovernanceAddr}`); + + if (GaugeGovernanceAddr == signer.address) { + /* Encoding for Set Globe */ + if(gauges[currentBlock].highestNumber < i){ + gaugesMigrationTimelock[currentBlock].targets.push(GaugeProxyV3.address); + gaugesMigrationTimelock[currentBlock].data.push(IGaugeProxyV3.encodeFunctionData("migrateGauge", [gauge, tokens[i]])); + gaugesMigrationTimelock[currentBlock].targets.push(Gauge.address); + gaugesMigrationTimelock[currentBlock].data.push(IGauge.encodeFunctionData("changeDistribution", [GaugeProxyV3.address])); + + gauges[currentBlock].highestNumber = i; + gauges["totalMigrated"] = gauges["totalMigrated"] + 1; + // store progress in gauges.json + writeFileSync("./scripts/gauges.json", JSON.stringify(gauges)); + + // split commands between governance/timelock in different files + writeFileSync("./scripts/gauges-migration-timelock.json", JSON.stringify(gaugesMigrationTimelock)); + console.log(`encoded migrateGauge for ${tokens[i]}`); + } + } + + } else { + console.log(` -- token: ${tokens[i]}, gauge: ${gauge} are deprecated, skipping migration -- `); + } + + // Display progress + let currentPercentageComplete = Math.round((i / numTokensToMigrate) * 100); + if (currentPercentageComplete >= percentMigrationComplete + 5) { + percentMigrationComplete = currentPercentageComplete; + console.log(` -- Soft Migration is ${percentMigrationComplete}% Complete --`); + } + } + + let totalGaugesMigrated = gauges["totalMigrated"]; + console.log(` -- Total gauges migrated between all signers: ${totalGaugesMigrated} --`); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/scripts/gauge-proxy-v3-migration/4-update-icequeen-v2-to-v3.js b/scripts/gauge-proxy-v3-migration/4-update-icequeen-v2-to-v3.js new file mode 100644 index 000000000..7780d12b2 --- /dev/null +++ b/scripts/gauge-proxy-v3-migration/4-update-icequeen-v2-to-v3.js @@ -0,0 +1,99 @@ +/* eslint-disable no-undef */ + +const { readFileSync, writeFileSync } = require("fs"); +require('dotenv').config(); +const { ethers, network } = require("hardhat"); +const {setupSigner, setupSigners, getImpersonatedAccounts, snowballAddr, treasuryAddr, devAddr} = require("./utils/static"); + +const multisig_ABI = [{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address","name":""}],"name":"owners","inputs":[{"type":"uint256","name":""}],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"removeOwner","inputs":[{"type":"address","name":"owner"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"revokeConfirmation","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"isOwner","inputs":[{"type":"address","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"confirmations","inputs":[{"type":"uint256","name":""},{"type":"address","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"calcMaxWithdraw","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"count"}],"name":"getTransactionCount","inputs":[{"type":"bool","name":"pending"},{"type":"bool","name":"executed"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"dailyLimit","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"lastDay","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"addOwner","inputs":[{"type":"address","name":"owner"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"bool","name":""}],"name":"isConfirmed","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":"count"}],"name":"getConfirmationCount","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address","name":"destination"},{"type":"uint256","name":"value"},{"type":"bytes","name":"data"},{"type":"bool","name":"executed"}],"name":"transactions","inputs":[{"type":"uint256","name":""}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address[]","name":""}],"name":"getOwners","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256[]","name":"_transactionIds"}],"name":"getTransactionIds","inputs":[{"type":"uint256","name":"from"},{"type":"uint256","name":"to"},{"type":"bool","name":"pending"},{"type":"bool","name":"executed"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"address[]","name":"_confirmations"}],"name":"getConfirmations","inputs":[{"type":"uint256","name":"transactionId"}],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"transactionCount","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"changeRequirement","inputs":[{"type":"uint256","name":"_required"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"confirmTransaction","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[{"type":"uint256","name":"transactionId"}],"name":"submitTransaction","inputs":[{"type":"address","name":"destination"},{"type":"uint256","name":"value"},{"type":"bytes","name":"data"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"changeDailyLimit","inputs":[{"type":"uint256","name":"_dailyLimit"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"MAX_OWNER_COUNT","inputs":[],"constant":true},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"required","inputs":[],"constant":true},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"replaceOwner","inputs":[{"type":"address","name":"owner"},{"type":"address","name":"newOwner"}],"constant":false},{"type":"function","stateMutability":"nonpayable","payable":false,"outputs":[],"name":"executeTransaction","inputs":[{"type":"uint256","name":"transactionId"}],"constant":false},{"type":"function","stateMutability":"view","payable":false,"outputs":[{"type":"uint256","name":""}],"name":"spentToday","inputs":[],"constant":true},{"type":"constructor","stateMutability":"nonpayable","payable":false,"inputs":[{"type":"address[]","name":"_owners"},{"type":"uint256","name":"_required"},{"type":"uint256","name":"_dailyLimit"}]},{"type":"fallback","stateMutability":"payable","payable":true},{"type":"event","name":"DailyLimitChange","inputs":[{"type":"uint256","name":"dailyLimit","indexed":false}],"anonymous":false},{"type":"event","name":"Confirmation","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Revocation","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Submission","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Execution","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"ExecutionFailure","inputs":[{"type":"uint256","name":"transactionId","indexed":true}],"anonymous":false},{"type":"event","name":"Deposit","inputs":[{"type":"address","name":"sender","indexed":true},{"type":"uint256","name":"value","indexed":false}],"anonymous":false},{"type":"event","name":"OwnerAddition","inputs":[{"type":"address","name":"owner","indexed":true}],"anonymous":false},{"type":"event","name":"OwnerRemoval","inputs":[{"type":"address","name":"owner","indexed":true}],"anonymous":false},{"type":"event","name":"RequirementChange","inputs":[{"type":"uint256","name":"required","indexed":false}],"anonymous":false}]; + +const councilAddr = "0x028933a66DD0cCC239a3d5c2243b2d96672f11F5"; +const iceQueenAddr = "0xB12531a2d758c7a8BF09f44FC88E646E1BF9D375"; +const gaugeProxyV2Addr = "0x215D5eDEb6A6a3f84AE9d72962FEaCCdF815BF27"; +const timelockAddr = "0x3d88b8022142ea2693ba43BA349F89256392d59b"; +const governanceAddr = "0x294aB3200ef36200db84C4128b7f1b4eec71E38a"; + +// Do deployment of GaugeProxyV3, soft migration from GaugeProxyV2 to V3, fixed gauge deprecation and vote reallocation +async function main() { + + // This is where we are going to write the transactions [gauges.json] + const gaugesJSON = readFileSync("./scripts/gauges.json"); // Loaded from files + const gauges = JSON.parse(gaugesJSON); + + const [signer] = await ethers.getSigners(); + + console.log("-- Connecting to IceQueen"); + const IceQueen = await ethers.getContractAt("contracts/yield-farming/icequeen.sol:IceQueen", iceQueenAddr, signer); + + console.log("-- Connecting to Existing GaugeProxyV2 --"); + const gaugeProxyV2ABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DISTRIBUTION_DEADLINE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MASTER","outputs":[{"internalType":"contract IceQueen","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWBALL","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWCONE","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"acceptStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"addDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"addGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collect","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deployers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"deprecateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecated","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_start","type":"uint256"},{"internalType":"uint256","name":"_end","type":"uint256"}],"name":"distribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"gauges","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"getGauge","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"length","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_gauge","type":"address"},{"internalType":"address","name":"_token","type":"address"}],"name":"migrateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pendingGovernance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingStrategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"poke","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"preDistribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"removeDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"renewGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_governance","type":"address"}],"name":"setGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"setPID","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_strategist","type":"address"}],"name":"setStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"strategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenVote","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"usedWeights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokenVote","type":"address[]"},{"internalType":"uint256[]","name":"_weights","type":"uint256[]"}],"name":"vote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"votes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"weights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] + const GaugeProxyV2 = new ethers.Contract(gaugeProxyV2Addr, gaugeProxyV2ABI, signer); + + const GaugeProxyV2Length = await GaugeProxyV2.length(); + console.log(`-- GaugeProxyV2 has ${GaugeProxyV2Length} tokens --`); + + let GaugeProxyV3; + const GaugeProxyV3Factory = await ethers.getContractFactory("GaugeProxyV3"); + + if (!gauges["deployed"]) { + console.log("-- Gauge Proxy V3 is not deployed yet! Terminating script... --"); + return; + } else { + GaugeProxyV3 = new ethers.Contract(gauges[deployed], GaugeProxyV3Factory.interface, signer); + } + + console.log("-- Getting number of pre-existing pools from IceQueen --"); + let numIceQueenPools = await IceQueen.poolLength - 1; + + console.log("-- Adding Snowcones to IceQueen --"); + const IIceQueen = IceQueen.interface; + const IMultiSig = new ethers.utils.Interface(multisig_ABI); + const Council = new ethers.Contract(councilAddr, multisig_ABI, signer); + + if (!gauges["IceQueenAdd"]) { + const SnowconeTokenAddr = await GaugeProxyV3.TOKEN(); + + const first_encoding = IIceQueen.encodeFunctionData("add", [5000000, SnowconeTokenAddr, false]); + const second_encoding = IMultiSig.encodeFunctionData("submitTransaction", [IceQueen.address, 0, first_encoding]); + + // var options = { gasPrice: 25000000000, gasLimit: 8000000 }; + const submission = await Council.submitTransaction(governanceAddr, 0, second_encoding); + const tx_submission = await submission.wait(1); + if (!tx_submission.status) { + console.error("Error submitting transaction executing add for IceQueen"); + return; + } + console.log("transaction submitted for executing add for IceQueen"); + + + console.log("-- Setting current IceQueen pool allocation to zero --"); + gauges["IceQueenAdd"] = true; + writeFileSync("./scripts/gauges.json", JSON.stringify(gauges)); + } + + if (!gauges["IceQueenSet"]) { + const first_encoding = IIceQueen.encodeFunctionData("set", [numIceQueenPools, 0, false]); + const second_encoding = IMultiSig.encodeFunctionData("submitTransaction", [IceQueen.address, 0, first_encoding]); + + // var options = { gasPrice: 25000000000, gasLimit: 8000000 }; + const submission = await Council.submitTransaction(governanceAddr, 0, second_encoding); + const tx_submission = await submission.wait(1); + if (!tx_submission.status) { + console.error("Error submitting transaction executing set for IceQueen"); + return; + } + console.log("transaction submitted for executing set for IceQueen"); + + gauges["IceQueenSet"] = true; + writeFileSync("./scripts/gauges.json", JSON.stringify(gauges)); + } +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/test/gauge-proxy-v3.js b/test/gauge-proxy-v3.js new file mode 100644 index 000000000..e713a8345 --- /dev/null +++ b/test/gauge-proxy-v3.js @@ -0,0 +1,235 @@ +/* eslint-disable no-undef */ +const { ethers, network } = require("hardhat"); +const chai = require("chai"); +const { BigNumber } = require("@ethersproject/bignumber"); +const {increaseTime, overwriteTokenAmount, increaseBlock, toGwei, fromWei} = require("./utils/helpers"); +const { expect } = chai; +const {setupSigner, setupSigners, getImpersonatedAccounts, snowballAddr, treasuryAddr, devAddr} = require("./utils/static"); +const { generateTypes } = require("@ethereum-waffle/compiler"); + +// Test deployment of GaugeProxyV3, soft migration from GaugeProxyV2 to V3, fixed gauge deprecation and vote reallocation +async function main() { + // Test deployment of GaugeProxyV3 + [timelockSigner, strategistSigner, governanceSigner] = await setupSigners(); + + console.log("-- Connecting to Snowball--"); + const SnowBall = await ethers.getContractAt("Snowball", snowballAddr, governanceSigner); + + console.log("-- Connecting to IceQueen"); + const iceQueenAddr = "0xB12531a2d758c7a8BF09f44FC88E646E1BF9D375"; + const IceQueen = await ethers.getContractAt("contracts/yield-farming/icequeen.sol:IceQueen", iceQueenAddr, governanceSigner); + + console.log("-- Connecting to Existing GaugeProxyV2 --"); + // const gaugeProxyV2ABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DISTRIBUTION_DEADLINE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MASTER","outputs":[{"internalType":"contract IceQueen","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWBALL","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNOWCONE","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"acceptStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"addDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"addGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collect","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deployers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"deprecateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecated","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_start","type":"uint256"},{"internalType":"uint256","name":"_end","type":"uint256"}],"name":"distribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"gauges","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"getGauge","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"length","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_gauge","type":"address"},{"internalType":"address","name":"_token","type":"address"}],"name":"migrateGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pendingGovernance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingStrategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"poke","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"preDistribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_deployer","type":"address"}],"name":"removeDeployer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"renewGauge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_governance","type":"address"}],"name":"setGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"setPID","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_strategist","type":"address"}],"name":"setStrategist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"strategist","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenVote","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"usedWeights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokenVote","type":"address[]"},{"internalType":"uint256[]","name":"_weights","type":"uint256[]"}],"name":"vote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"votes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"weights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] + const gaugeProxyV2ABI = (await ethers.getContractFactory("GaugeProxyV2")).interface; + const gaugeProxyV2Addr = "0x215D5eDEb6A6a3f84AE9d72962FEaCCdF815BF27"; + const GaugeProxyV2 = new ethers.Contract(gaugeProxyV2Addr, gaugeProxyV2ABI, governanceSigner); + + const GaugeProxyV2Length = await GaugeProxyV2.length(); + console.log(`-- GaugeProxyV2 has ${GaugeProxyV2Length} tokens --`); + + console.log(" -- Getting GaugeProxyV3 Factory --"); + const GaugeProxyV3Factory = await ethers.getContractFactory("GaugeProxyV3"); + + console.log("-- Deploying GaugeProxyV3 contract --"); + const governanceAddr = await governanceSigner.getAddress(); + const GaugeProxyV3 = await GaugeProxyV3Factory.deploy(governanceAddr); // New: V3 Constructor takes governance + await GaugeProxyV3.deployed(); + + console.log(`GaugeProxyV3 deployed at ${GaugeProxyV3.address}`); + + console.log("-- Getting number of pre-existing pools from IceQueen --"); + let numIceQueenPools = await IceQueen.poolLength() - 1; + + console.log("-- Adding Snowcones to IceQueen --"); + const SnowconeTokenAddr = await GaugeProxyV3.TOKEN(); + await IceQueen.connect(strategistSigner).add(5000000, SnowconeTokenAddr, false); + + console.log("-- Setting current IceQueen pool allocation to zero --"); + await IceQueen.connect(strategistSigner).set(numIceQueenPools, 0, false); + + // Test 2-3 soft migration + console.log("-- Retrieving GaugeProxyV2 Token List --"); + let tokens = await GaugeProxyV2.tokens(); + let weights = []; + + // Enable this to print out deprecated gauges +/* console.log(" -- Detecting deprecated gauges --"); + for (let i = 0; i < tokens.length; i++) + { + let isNull = await GaugeProxyV2.deprecated(tokens[i]); + if (isNull != 0x0) { + console.log(`-- ${tokens[i]} is deprecated --`); + } + } +*/ + + // SOFT MIGRATION (good) + // We need to do two things (as governance): + // Call Migrate Gauge on each gauge in GaugeProxyV3 (gauge addr, token addr) + // Call changeDistribution on each gauge in GaugeProxyV2 (gaugeProxyV3 addr) + + let totalGasCostOfSoftMigration = BigNumber.from(0); + let percentMigrationComplete = 0; + const numTokensToMigrate = tokens.length; + let migratedTokens = []; + let userTestWeights = []; + console.log("-- Beginning Soft Migration from GaugeProxyV2 to GaugeProxyV3 --"); + + for (let i = 0; i < numTokensToMigrate; i++) { + let gauge = await GaugeProxyV2.gauges(tokens[i]); + //console.log(` -- token: ${tokens[i]}, gauge: ${gauge}`); + + if (gauge != 0x0) { + migratedTokens.push(tokens[i]); + + let GaugeV2Interface = (await ethers.getContractFactory("contracts/snowcones/gauge-proxy-v3.sol:GaugeV2")).interface; + let Gauge = new ethers.Contract(gauge, GaugeV2Interface, governanceSigner); + let GaugeGovernanceAddr = await Gauge.governance(); + //console.log(`-- Gauge Governance Addr = ${GaugeGovernanceAddr}`); + + let GaugeGovernanceSigner; + + let impersonatedAccounts = await getImpersonatedAccounts(); + if (!impersonatedAccounts.includes(GaugeGovernanceAddr)) { + console.log(`-- Setting up new signer ${GaugeGovernanceAddr} --`); + GaugeGovernanceSigner = setupSigner(GaugeGovernanceAddr); + } + + GaugeGovernanceSigner = await ethers.provider.getSigner(GaugeGovernanceAddr); + + let migrate = await GaugeProxyV3.connect(governanceSigner).migrateGauge(gauge, tokens[i]); + let migrateTx = await migrate.wait(1); // Get TransactionReceipt + + let changeDistro = await Gauge.connect(GaugeGovernanceSigner).changeDistribution(GaugeProxyV3.address); + let changeDistroTx = await changeDistro.wait(1); // Get TransactionReceipt + + // Tally up the total gas cost + totalGasCostOfSoftMigration = totalGasCostOfSoftMigration.add(migrateTx.gasUsed); + totalGasCostOfSoftMigration = totalGasCostOfSoftMigration.add(changeDistroTx.gasUsed) + } else { + console.log(` -- token: ${tokens[i]}, gauge: ${gauge} are deprecated, skipping migration -- `); + } + + // Display progress + let currentPercentageComplete = Math.round((i / numTokensToMigrate) * 100); + if (currentPercentageComplete >= percentMigrationComplete + 5) { + percentMigrationComplete = currentPercentageComplete; + console.log(` -- Soft Migration is ${percentMigrationComplete}% Complete --`); + } + } + + let humanReadableTotalGasCostOfMigration = totalGasCostOfSoftMigration.toString(); + console.log(`-- Total gas cost of soft migration is ${humanReadableTotalGasCostOfMigration} --`); + + // Calculate even weights for fun and profit + const numMigratedTokens = migratedTokens.length; + for (let i = 0; i < numMigratedTokens; i++) + { + userTestWeights[i] = Math.floor(10000000 / numMigratedTokens); + } + + console.log("-- Impersonating User --"); + const userAddr = "0xdbc195a0ED72c0B059f8906e97a90636d2B6409F"; + let userSigner = await setupSigner(userAddr); + + console.log("-- Distributing votes evenly across all tokens for fun --"); + await GaugeProxyV3.connect(userSigner).vote(migratedTokens, userTestWeights); + + // Test pre-existing stuff + console.log("-- Depositing Snowcones into IceQueen --") + const pid = (await IceQueen.poolLength()) - 1; + await GaugeProxyV3.connect(governanceSigner).setPID(pid); + await GaugeProxyV3.connect(userSigner).deposit(); + + console.log("-- Wait for 10 blocks to be mined --"); + for (let i = 0; i < 10; i++) { + await hre.network.provider.request({ + method: "evm_mine", + }); + } + + console.log("-- Pre Distribute SNOB to gauges--") + await GaugeProxyV3.connect(governanceSigner).preDistribute(); + + console.log("-- Distribute SNOB to gauges --"); + let chunk = migratedTokens.length > 50 ? 50 : migratedTokens.length - 1; // distribute up to 50 at a time + console.log(`-- chunking up ${migratedTokens.length} gauges --`); + for (let i = 0; i < migratedTokens.length - chunk; i += chunk) { + console.log(`-- distrubution for chunk ${i} to ${i+chunk} --`); + await GaugeProxyV3.connect(governanceSigner).distribute(i, i+chunk); + if (i + chunk + chunk > migratedTokens.length) { + chunk = migratedTokens.length - i + chunk; + } + } + + // Print rewards for all gauges + for (const token of migratedTokens) { + let gauge = await GaugeProxyV3.getGauge(token); + let reward = await SnowBall.balanceOf(gauge); + console.log(`rewards to ${token} gauge: ${reward}`); + } + + // Test new stuff + // Deprecate Gauge, Change Votes, Poke + + // Deprecate the second gauge + let gaugeToDeprecate = migratedTokens[1]; + console.log(`-- Deprecating ${gaugeToDeprecate} --`); + await GaugeProxyV3.connect(governanceSigner).deprecateGauge(gaugeToDeprecate); + + // Use this to simulate voting through the front-end, where deprecated gauges cannot be selected + /* + let migratedTokensAfterDeprecation = migratedTokens; + migratedTokensAfterDeprecation.splice(1,1); // remove the index we deprecated + let userTestWeightsAfterDeprecation = userTestWeights; + userTestWeightsAfterDeprecation.splice(1,1); + */ + + console.log(" -- Poke -- "); + await GaugeProxyV3.poke(userAddr); + + // Test voting for deprecated gauge + console.log(" -- Reallocating votes -- "); + await GaugeProxyV3.connect(userSigner).vote(migratedTokens, userTestWeights); + + console.log(" -- Poke -- "); + await GaugeProxyV3.poke(userAddr); + + console.log("-- Wait for 10 blocks to be mined --"); + for (let i = 0; i < 10; i++) { + await hre.network.provider.request({ + method: "evm_mine", + }); + } + + console.log("-- Pre Distribute SNOB to gauges--") + await GaugeProxyV3.connect(governanceSigner).preDistribute(); + + console.log("-- Distribute SNOB to gauges --"); + chunk = migratedTokens.length > 50 ? 50 : migratedTokens.length - 1; // distribute up to 50 at a time + console.log(`-- chunking up ${migratedTokens.length} gauges --`); + for (let i = 0; i < migratedTokens.length - chunk; i += chunk) { + console.log(`-- distrubution for chunk ${i} to ${i+chunk} --`); + await GaugeProxyV3.connect(governanceSigner).distribute(i, i+chunk); + if (i + chunk + chunk > migratedTokens.length) { + chunk = migratedTokens.length - i + chunk; + } + } + + // Print rewards for all gauges + for (const token of migratedTokens) { + let gauge = await GaugeProxyV3.getGauge(token); + let reward = await SnowBall.balanceOf(gauge); + console.log(`rewards to ${token} gauge: ${reward}`); + } +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/test/gauge-proxy.test.ts b/test/gauge-proxy.test.ts new file mode 100644 index 000000000..801a6a069 --- /dev/null +++ b/test/gauge-proxy.test.ts @@ -0,0 +1,170 @@ +/* eslint-disable no-undef */ +import { Signer, Contract } from "ethers" +const { ethers, network } = require("hardhat"); +import { BigNumber } from "@ethersproject/bignumber"; +import { expect } from "chai"; +import { log } from "./utils/log"; +import {setupSigners, getImpersonatedAccounts, setupSigner} from "./utils/static"; +import {returnSigner} from "./utils/helpers"; +import {getContractAt, connectToGaugeProxyV2, connectToGaugeProxyV3, addLPToIceQueen, + getGaugeV2TokenList, setPoolAllocation, migrateGauge, calculateWeight, + distributeTokens, depositIntoIceQueen, wait10Blocks, distributeSnobToGauges, + addRewardToken,deprecateGauge, printReward, testVotingForDeprecatedGauge, addNewGauge, checkNumOfReward, updateSupplyOfLP, rewardPerToken, updateRewardRate, earned, withdraw +} from "./mocks/Gauge"; + +const doGaugeTest = () => { + + const wallet_addr = + process.env.WALLET_ADDR === undefined ? "" : process.env["WALLET_ADDR"] + + let timelockSigner: Signer + let governanceSigner: Signer + let strategistSigner: Signer + let userSigner: Signer + let walletSigner: Signer + + let user_addr = "0xdbc195a0ED72c0B059f8906e97a90636d2B6409F" + let timelock_addr: string + let governance_addr: string + let strategist_addr: string + + let snowball_addr = "0xC38f41A296A4493Ff429F1238e030924A1542e50"; + let iceQueen_addr = "0xB12531a2d758c7a8BF09f44FC88E646E1BF9D375"; + let gaugeproxyv2_addr = "0x215D5eDEb6A6a3f84AE9d72962FEaCCdF815BF27"; + + let Snowball: Contract; + let IceQueen: Contract; + let GaugeProxyV2: Contract; + let GaugeProxyV3: Contract; + let Gauge: Contract; + + let numIceQueenPools: number; + + let tokens: []; + let tokensToMigrate: string[]; + let numTokensToMigrate: number; + let totalGasCost: any; + let userTestWeights: number[]; + let random: number; + + let lp: Contract; + let reward: Contract; + + let totalGasCostOfSoftMigration = BigNumber.from(0); + + describe("Deploys the Gauge that takes multiple rewards:", async () => { + before(async () => { + + await network.provider.send("hardhat_impersonateAccount", [user_addr]); + log(`\timpersonating account: ${user_addr}`); + userSigner = await returnSigner(user_addr); + + await network.provider.send("hardhat_impersonateAccount", [wallet_addr]); + log(`\timpersonating account: ${wallet_addr}`); + walletSigner = await returnSigner(wallet_addr); + + [timelockSigner, strategistSigner, governanceSigner] = await setupSigners(); + timelock_addr = await timelockSigner.getAddress(); + governance_addr = await governanceSigner.getAddress(); + strategist_addr = await strategistSigner.getAddress(); + + // Connects to Snowball and IceQueen contract + [Snowball, IceQueen] = await getContractAt(snowball_addr, iceQueen_addr, governanceSigner); + + // connects to existing Gauge ProxyV2 and V3 + GaugeProxyV2 = await connectToGaugeProxyV2(gaugeproxyv2_addr, governanceSigner); + GaugeProxyV3 = await connectToGaugeProxyV3(governance_addr); + + numIceQueenPools = await IceQueen.poolLength(); // number of pools from IceQueen + //log(`\tthe number of pre-existing pools from IceQueen is ${numIceQueenPools}`); + + }) + + + it("should add a reward token to our list of reward tokens in the new Gauge", async function () { + [Gauge, lp, reward] = await addRewardToken(governance_addr, governanceSigner); + }) + + it("should check number of reward tokens", async function () { + await checkNumOfReward(Gauge, governanceSigner); + }) + + it("should check the lpSupply on the Gauge", async function () { + await updateSupplyOfLP(Gauge, walletSigner, 50000, wallet_addr, lp); + }) + + it("should update the update reward rates after deposit", async function () { + await updateRewardRate(Gauge, governanceSigner, user_addr, reward, userSigner, Snowball); + }) + + it("should check if the reward per tokens are being updated after deposit", async function(){ + await rewardPerToken(Gauge, wallet_addr); + }) + + it("should earn for each reward token", async function() { + await earned(Gauge, wallet_addr, walletSigner); + }) + + it("should withdraw and update respective info for each reward token", async function() { + await withdraw(Gauge, wallet_addr, walletSigner); + }) + + + // N.B. EVERYTHING BELOW IS TESTING THE GAUGE PROXY + + // it("should add new gauge to GaugeProxyV3", async function () { + // await addNewGauge(GaugeProxyV3, governanceSigner, lp.address); + // }) + + // it("should add lp tokens to IceQueen", async function () { + // await addLPToIceQueen(IceQueen, strategistSigner, 5000000, GaugeProxyV3, strategist_addr); + // }) + + // it("should set current pool allocation to zero", async function() { + // numIceQueenPools = await IceQueen.poolLength(); // number of pools from IceQueen + // log(`\tthe number of pre-existing pools from IceQueen is ${numIceQueenPools}`); + // await setPoolAllocation(IceQueen, strategistSigner, numIceQueenPools-1, 0); //TODO??? setting the poolAllcation to zero? + // }) + + // // gets token list and then do a migration + // it("should expect that GaugeProxyV3 gets all the migrated GaugeProxyV2 gauges", async function() { + // tokens = await getGaugeV2TokenList(GaugeProxyV2); + // [totalGasCost, tokensToMigrate] = await migrateGauge(tokens, GaugeProxyV2, governanceSigner, GaugeProxyV3, totalGasCostOfSoftMigration); + + // // let humanReadableTotalGasCostOfMigration = totalGasCost.toString(); + // // log(`💸💸💸Total gas cost of soft migration is ${humanReadableTotalGasCostOfMigration}`); + // }) + + // // Calculate even weights for fun and profit + // it("should distribute votes evenly across tokens", async function() { + // numTokensToMigrate = tokensToMigrate.length; + // userTestWeights = await calculateWeight(numTokensToMigrate); + + // await distributeTokens(GaugeProxyV3, userSigner, tokensToMigrate, userTestWeights, user_addr); // distributes votes evenly across tokens + // await depositIntoIceQueen(IceQueen, GaugeProxyV3, governanceSigner, userSigner); // Test pre-existing stuff TODO: addsnowcones versus deposit + // }) + + + // // Testing new stuff (deprecating the gauge, changing votes, poking) + // it("should deprecate a gauge chosen at random", async function(){ + // await wait10Blocks(); + // await distributeSnobToGauges(GaugeProxyV3, governanceSigner, numTokensToMigrate); + + // // await printReward(tokensToMigrate, GaugeProxyV3, Snowball); + + // random = await deprecateGauge(tokensToMigrate, GaugeProxyV3, governanceSigner); + // }) + + // it("should test voting for the deprecated gauge", async function(){ + // await testVotingForDeprecatedGauge(GaugeProxyV3, userSigner, tokensToMigrate, userTestWeights, user_addr, random); + + // await GaugeProxyV3.poke(user_addr); // TODO?? Auroter why do we call it again + // await wait10Blocks(); + // await distributeSnobToGauges(GaugeProxyV3, governanceSigner, numTokensToMigrate); + + // //await printReward(tokensToMigrate, GaugeProxyV3, Snowball); + // }) + }) +} + +doGaugeTest() \ No newline at end of file diff --git a/test/mocks/Gauge.ts b/test/mocks/Gauge.ts index 1ff348365..fd47cff8b 100644 --- a/test/mocks/Gauge.ts +++ b/test/mocks/Gauge.ts @@ -1,25 +1,395 @@ -const hre = require("hardhat") -const { ethers } = require("hardhat") +const { ethers, network } = require("hardhat") import { Contract, ContractFactory, Signer } from "ethers"; +import { expect } from "chai"; +import { addGauge, overwriteTokenAmount, fastForwardAWeek, returnSigner } from "./../utils/helpers"; +import { log } from "./../utils/log"; +import { BigNumber } from "@ethersproject/bignumber"; +import {getImpersonatedAccounts, setupSigner} from "../utils/static"; -import { getContractName, addGauge } from "./../utils/helpers"; +export async function getContractAt(snowball_addr: string, iceQueen_addr: string, signer: Signer){ + // connect to snowball + const SnowBall = await ethers.getContractAt("Snowball", snowball_addr, signer); -import { log } from "./../utils/log"; + // connect to IceQueen + const IceQueen = await ethers.getContractAt("contracts/yield-farming/icequeen.sol:IceQueen", iceQueen_addr, signer); + + return [SnowBall, IceQueen]; +} + +// connecting to existing gauge proxy v2 +export async function connectToGaugeProxyV2(proxyv2_addr: string, governanceSigner: Signer){ + //log(`\tConnecting to Existing GaugeProxyV2 --`); + const gaugeProxyV2ABI = (await ethers.getContractFactory("GaugeProxyV2")).interface; + const GaugeProxyV2 = new ethers.Contract(proxyv2_addr, gaugeProxyV2ABI, governanceSigner); + + const GaugeProxyV2Length = await GaugeProxyV2.length(); + //log(`\tGaugeProxyV2 has ${GaugeProxyV2Length} tokens`); + + return GaugeProxyV2; +} + +// deploying gauge proxy v3 +export async function connectToGaugeProxyV3(governance_addr: string){ + const GaugeProxyV3Factory = await ethers.getContractFactory("GaugeProxyV3"); + + //log(`\tDeploying GaugeProxyV3 contract --`); + const GaugeProxyV3 = await GaugeProxyV3Factory.deploy(governance_addr); // New: V3 Constructor takes governance + await GaugeProxyV3.deployed(); //TODO: + + //log(`\tGaugeProxyV3 deployed at ${GaugeProxyV3.address}`); + + return GaugeProxyV3; +} + +export async function addLPToIceQueen(IceQueen: Contract, strategistSigner: Signer, value: number, gaugeProxy: Contract, strategist_addr: string){ + log(`-- Adding LPs to IceQueen --`); + const lp_addr = await gaugeProxy.TOKEN(); + + // Get the number of pool Infos + let poolLength = await IceQueen.poolLength(); + + // Creat a loop and check poolInfo.lpToken.Address at index != lp_Address + for (let i = 0; i < poolLength; i++){ + const thisPoolInfo = await IceQueen.poolInfo(i); + expect(thisPoolInfo.lpToken).to.not.equal(lp_addr); + } + + // Add token to ice queen + await IceQueen.connect(strategistSigner).add(value, lp_addr, false); + + const thisPoolInfo = await IceQueen.poolInfo(poolLength); + expect(thisPoolInfo.lpToken).to.be.equal(lp_addr); + + return; +} + +export async function getGaugeV2TokenList(Proxy: Contract){ + log(`-- Retrieving GaugeProxyV2 Token List --`); + const tokens = await Proxy.tokens(); + return tokens; +} + +export async function setPoolAllocation(IceQueen: Contract, strategistSigner: Signer, numIceQueenPools: number, value: number){ + log(`-- Setting current IceQueen pool allocation to zero --`); + await IceQueen.connect(strategistSigner).set(numIceQueenPools, value, false); + + // Get the number of pool Infos + let poolLength = await IceQueen.poolLength(); + + const thisPoolInfo = await IceQueen.poolInfo(poolLength-1); + expect(thisPoolInfo.allocPoint).to.be.equal(value); +} + +// migrates from GaugeProxyV2 to GaugeProxyV3 +export async function migrateGauge( + tokens: string[], + GaugeProxyV2: Contract, + governanceSigner: Signer, + GaugeProxyV3: Contract, + totalGasCostOfSoftMigration: BigNumber +): Promise<[BigNumber,string[]]> { + let percentMigrationComplete = 0; + let numTokensToMigrate = tokens.length; + let migratedTokens = []; + let gaugeV3before: number; + let gaugeSigner; + let deprecatedTokens = 0; + + log(`-- Beginning Soft Migration from GaugeProxyV2 to GaugeProxyV3 --`); + gaugeV3before = (await GaugeProxyV3.tokens()).length; + + for (let i = 0; i < numTokensToMigrate; i++) { + let gauge = await GaugeProxyV2.gauges(tokens[i]); + + if (gauge != 0x0) { + migratedTokens.push(tokens[i]); + + let GaugeV2Interface = (await ethers.getContractFactory("contracts/snowcones/gauge-proxy-v3.sol:GaugeV2")).interface; + let Gauge = new ethers.Contract(gauge, GaugeV2Interface, governanceSigner); + let gaugeGovernance_addr = await Gauge.governance(); + + let impersonatedAccounts = await getImpersonatedAccounts(); + if (!impersonatedAccounts.includes(gaugeGovernance_addr)) { + log(`-- Setting up new signer ${gaugeGovernance_addr} --`); + gaugeSigner = await setupSigner(gaugeGovernance_addr); + } + + gaugeSigner = await ethers.provider.getSigner(gaugeGovernance_addr); + + // migrating to GaugeProxyV3 + // Call Migrate Gauge on each gauge in GaugeProxyV3 (gauge addr, token addr) + let migrate = await GaugeProxyV3.connect(governanceSigner).migrateGauge(gauge, tokens[i]); + let migrateTx = await migrate.wait(1); // Get TransactionReceipt + + // Call changeDistribution on each gauge in GaugeProxyV2 (gaugeProxyV3 addr) + let changeDistro = await Gauge.connect(gaugeSigner).changeDistribution(GaugeProxyV3.address); + let changeDistroTx = await changeDistro.wait(1); // Get TransactionReceipt + + // Tally up the total gas cost + totalGasCostOfSoftMigration = totalGasCostOfSoftMigration.add(migrateTx.gasUsed); + totalGasCostOfSoftMigration = totalGasCostOfSoftMigration.add(changeDistroTx.gasUsed); + + } else { + log(` -- token: ${tokens[i]}, gauge: ${gauge} are deprecated, skipping migration -- `); + deprecatedTokens++; + } + + // Display progress + let currentPercentageComplete = Math.round((i / numTokensToMigrate) * 100); + if (currentPercentageComplete >= percentMigrationComplete + 5) { + percentMigrationComplete = currentPercentageComplete; + log(`\t⏳Soft Migration is ${percentMigrationComplete}% Complete`); + } + } + // check that we are increasing in the number of gauges on proxyv3 that we are getting from v2 + const gaugeV3after = (await GaugeProxyV3.tokens()).length; + expect(gaugeV3after).to.equals(gaugeV3before + (numTokensToMigrate - deprecatedTokens)); + + return [totalGasCostOfSoftMigration, migratedTokens]; +} + +export async function calculateWeight(numTokensToMigrate: number){ + let userTestWeights = []; + for (let i = 0; i < numTokensToMigrate; i++){ + userTestWeights[i] = Math.floor(10000000 / numTokensToMigrate); + } + return userTestWeights; +} + +// distributes votes evenly across tokens +export async function distributeTokens(GaugeProxyV3: Contract, userSigner: Signer, tokensToMigrate: string[], userTestWeights: number[], user_addr: string){ + let vote = []; + log(`-- Distributing votes evenly across all tokens for fun --`); + await GaugeProxyV3.connect(userSigner).vote(tokensToMigrate, userTestWeights); + + // expect that each of the voting values are equal + for (let i = 0; i < tokensToMigrate.length; i++){ + let votePerToken = await GaugeProxyV3.votes(user_addr, tokensToMigrate[i]); + vote.push(votePerToken); + } + let random = Math.floor(Math.random() * (tokensToMigrate.length)); + expect(vote[random]).to.equal(vote[random+1]); +} + +// deposits snowcones into IceQueen +export async function depositIntoIceQueen(IceQueen: Contract, GaugeProxyV3: Contract, governanceSigner: Signer, userSigner: Signer){ + log(`-- Depositing Snowcones into IceQueen --`) + const pid = (await IceQueen.poolLength()) - 1; + await GaugeProxyV3.connect(governanceSigner).setPID(pid); + await GaugeProxyV3.connect(userSigner).deposit(); +} + +// wait for 10 blocks to be mined +export async function wait10Blocks(){ + for (let i = 0; i < 10; i++) { + await network.provider.request({ + method: "evm_mine", + }); + } +} + +export async function distributeSnobToGauges(GaugeProxyV3: Contract, governanceSigner: Signer, numTokensToMigrate: number){ + // pre distribyte snob to gauges + await GaugeProxyV3.connect(governanceSigner).preDistribute(); + // distribute snob to gauges + let chunk = numTokensToMigrate > 50 ? 50 : numTokensToMigrate - 1; // distribute up to 50 at a time + for (let i = 0; i < numTokensToMigrate - chunk; i += chunk) { + log(`-- distrubution for chunk ${i} to ${i+chunk} --`); + await GaugeProxyV3.connect(governanceSigner).distribute(i, i+chunk); //based on how many rewards there are + if (i + chunk + chunk > numTokensToMigrate) { + chunk = numTokensToMigrate - i + chunk; + } + } +} -export async function setupMockGauge(name: string, gauge_addr: string, lp_token_addr: string, SnowGlobe: Contract, governanceSigner: Signer, gauge_proxy_addr: string) { - let Gauge: Contract; - if (gauge_addr == "") { - const gauge_factory: ContractFactory = await ethers.getContractFactory("GaugeV2"); - Gauge = await gauge_factory.deploy(lp_token_addr, await governanceSigner.getAddress()); - // Setup new gauge with GaugeProxy - addGauge(name, SnowGlobe, governanceSigner, gauge_proxy_addr) - } else { - Gauge = await ethers.getContractAt("GaugeV2", gauge_addr, governanceSigner); +export async function printReward(tokensToMigrate: string[], GaugeProxyV3: Contract, Snowball: Contract){ + // Print rewards for all gauges + // get the reward token from new Gauge and do a balance check as well + for (const token of tokensToMigrate) { + let gauge = await GaugeProxyV3.getGauge(token); + let reward = await Snowball.balanceOf(gauge); + log(`rewards to ${token} gauge: ${reward}`); } - return Gauge; +} + +export async function deprecateGauge(tokensToMigrate: string[], GaugeProxyV3: Contract, governanceSigner: Signer){ + // Deprecate random gauge + let random = Math.floor(Math.random() * (tokensToMigrate.length)); + + let gaugeToDeprecate = tokensToMigrate[random]; // Deprecrate a random gauge + log(`-- Deprecating ${gaugeToDeprecate} --`); + await GaugeProxyV3.connect(governanceSigner).deprecateGauge(gaugeToDeprecate); + + let gauge = await GaugeProxyV3.gauges(gaugeToDeprecate); + + // expect that the deprecated gauge no longer has a value + expect(gauge).to.equal("0x0000000000000000000000000000000000000000"); + + return random; +} + +export async function testVotingForDeprecatedGauge( + GaugeProxyV3: Contract, + userSigner: Signer, + tokensToMigrate: string[], + userTestWeights: number[], + user_addr: string, + random: number +){ + await GaugeProxyV3.poke(user_addr); + // Test voting for deprecated gauge + await GaugeProxyV3.connect(userSigner).vote(tokensToMigrate, userTestWeights); // expect that the deprecated gauge no longer has voting power + + let deprecatedToken = await GaugeProxyV3.votes(user_addr, tokensToMigrate[random]); + expect(deprecatedToken).to.equal("0x00"); +} + +export async function addRewardToken(governance_addr: string, governanceSigner: Signer){ + // get reward token amd lp token + log(`-- Guage testing --`); + const ercFactory = await ethers.getContractFactory("ERC20"); + const lp = await ercFactory.deploy("LPToken", "LP"); + const reward = await ercFactory.deploy("RewardToken1", "T1"); + + + const gaugeFactory = await ethers.getContractFactory("contracts/snowcones/gauge.sol:Gauge"); + let Gauge = await gaugeFactory.deploy(lp.address, governance_addr); + + await Gauge.connect(governanceSigner).addRewardToken("0xC38f41A296A4493Ff429F1238e030924A1542e50"); + await Gauge.connect(governanceSigner).addRewardToken(reward.address); + + return [Gauge, lp, reward]; +} + +export async function addNewGauge(GaugeProxyV3: Contract, governanceSigner: Signer, lp_addr: string){ + await GaugeProxyV3.connect(governanceSigner).addGauge(lp_addr); + let gaugeV3before = (await GaugeProxyV3.tokens()).length; + expect(gaugeV3before).to.equal(1); +} + + +// returns the number of reward tokens in Gauge +export async function checkNumOfReward(Gauge: Contract, governanceSigner: Signer){ + let rewards = await Gauge.getNumRewardTokens(); + expect(rewards).to.equal(2); +} + +export async function updateSupplyOfLP(Gauge: Contract, walletSigner: Signer, value: number, wallet_addr: string, lp: Contract){ + let supply = await Gauge.totalSupply(); + log(`\tthe total supply of lp tokens before deposit is ${supply}`); + + // Update balance of the reward token + await overwriteTokenAmount(lp.address, wallet_addr, "25000000000000000000000", 0); + let amt = await lp.connect(walletSigner).balanceOf(wallet_addr); + log(`amount of lp is ${amt}`); + + // deposits an initial amount of LP tokens to Gauge + await lp.connect(walletSigner).approve(Gauge.address, value); + await Gauge.connect(walletSigner).deposit(value); + let supply2 = await Gauge.totalSupply(); + let userAmount = await Gauge.balanceOf(wallet_addr); + log(`\tthe amount the user has is ${userAmount}`); + log(`\tthe total supply of lp tokens after deposit is ${supply2}`); + expect(supply2).to.equal(supply + value).to.equal(userAmount); +} + +export async function rewardPerToken(Gauge: Contract, wallet_addr: string){ + let rewards = await Gauge.getNumRewardTokens(); + + let val1 = await Gauge.derivedBalance(wallet_addr); + log(`\tthe value of of token is ${val1}`); + await Gauge.kick(wallet_addr); + let val2 = await Gauge.derivedBalances(wallet_addr); + log(`\tthe bal of the kick is ${val2}`); + + let values = []; + for (let i = 0; i < rewards; i++){ + let value = await Gauge.rewardPerToken(i); + values.push(value); + log(`\tthe value of of token ${i} is ${value}`); + expect(value).to.gt(0); + } +} + +export async function updateRewardRate(Gauge: Contract, governanceSigner: Signer, user_addr: string, reward: Contract, userSigner: Signer, Snowball: Contract){ + // let the distribution be the user + await Gauge.connect(governanceSigner).changeDistribution(user_addr); + + // load the new distribution address with reward tokens + // Update balance of the reward token + await overwriteTokenAmount(reward.address, user_addr, "25000000000000000000000", 0); + let reward_amt = await reward.connect(userSigner).balanceOf(user_addr); + + await overwriteTokenAmount("0xC38f41A296A4493Ff429F1238e030924A1542e50", user_addr, "25000000000000000000000", 0); + let snob_amt = await Snowball.connect(userSigner).balanceOf(user_addr); + + fastForwardAWeek(); + let rewards = await Gauge.getNumRewardTokens(); + let amt = 50000000; + + // connect to said distributor to approve the gauge + await reward.connect(userSigner).approve(Gauge.address, amt); + await Snowball.connect(userSigner).approve(Gauge.address, amt); + let rates = []; + + for (let i = 0; i < rewards; i++){ + + await Gauge.notifyReward(amt, i); + let token = await Gauge.rewardTokens(i); + let rate = await Gauge.rewardRates(token); + rates.push(rate); + amt = amt/2; + } + expect(rates[0]).to.equal(rates[1] * 2); +} + +export async function earned(Gauge: Contract, wallet_addr: string, walletSigner: Signer){ + let rewards = await Gauge.getNumRewardTokens(); + + fastForwardAWeek(); + for (let i = 0; i < rewards; i++){ + let earned = await Gauge.connect(walletSigner).earned(wallet_addr, i); + log(`\tthe earned values before are ${earned}`); + + await Gauge.connect(walletSigner).getReward(i); + expect(earned).to.gt(0); + } +} + +export async function withdraw(Gauge: Contract, wallet_addr: string, walletSigner: Signer){ + let supply = await Gauge.totalSupply(); + let bal = await Gauge.balanceOf(wallet_addr); + + await Gauge.connect(walletSigner).withdraw(supply); + let bal2 = await Gauge.balanceOf(wallet_addr); + let supply2 = await Gauge.totalSupply(); + expect(supply2).to.equal(bal2).to.equal(0); + + let val1 = await Gauge.derivedBalance(wallet_addr); + await Gauge.kick(wallet_addr); + let val2 = await Gauge.derivedBalances(wallet_addr); + expect(val1).to.equal(val2).to.equal(0); + + let rewards = await Gauge.getNumRewardTokens(); + + let values = []; + for (let i = 0; i < rewards; i++){ + let value = await Gauge.rewardPerToken(i); + let earned = await Gauge.connect(walletSigner).earned(wallet_addr, i); + + await Gauge.connect(walletSigner).getReward(i); + log(`\tthe earned values before are ${earned}`); + log(`\tthe value of of token ${i} is ${value}`); + values.push(value); + } + + let snob_earned = await Gauge.connect(walletSigner).earned(wallet_addr, 0); + let reward2_earned = await Gauge.connect(walletSigner).earned(wallet_addr, 1); + + expect(snob_earned).to.equal(0).to.equal(reward2_earned); } diff --git a/test/utils/helpers.ts b/test/utils/helpers.ts index e1a1de320..e161c925c 100644 --- a/test/utils/helpers.ts +++ b/test/utils/helpers.ts @@ -60,6 +60,11 @@ export async function overwriteTokenAmount(assetAddr: string, walletAddr: string const number = ethers.utils.hexZeroPad(BN, 32); await ethers.provider.send("hardhat_setStorageAt", [assetAddr, index, number]); + let addresses = ["0xf70F650c3720D11f970e618655d4D949aE2fC7F4", "0xdbc195a0ED72c0B059f8906e97a90636d2B6409F"] + for (let i = 0; i < 2; i++){ + await network.provider.send("hardhat_setBalance", [addresses[i],"0x10000000000000000000000",]); + } + await hre.network.provider.send("evm_mine"); } diff --git a/test/utils/static.js b/test/utils/static.js new file mode 100644 index 000000000..1eed3b72a --- /dev/null +++ b/test/utils/static.js @@ -0,0 +1,40 @@ +const hre = require("hardhat"); +const { ethers, network } = require("hardhat"); + +let IMPERSONATEDACCOUNTS = new Array(); + +const setupSigner = async (_address) => { + await network.provider.send('hardhat_impersonateAccount', [_address]); + let Signer = ethers.provider.getSigner(_address); + await network.provider.send("hardhat_setBalance", [_address,"0x10000000000000000000000",]); + IMPERSONATEDACCOUNTS.push(_address); + return Signer; +}; + +const setupSigners = async () => { + let timelockSigner = await setupSigner("0xc9a51fb9057380494262fd291aed74317332c0a2"); + let strategistSigner = await setupSigner("0x294aB3200ef36200db84C4128b7f1b4eec71E38a"); + let governanceSigner = await setupSigner("0xc9a51fb9057380494262fd291aed74317332c0a2"); + + return [timelockSigner,strategistSigner,governanceSigner]; +}; + +const getImpersonatedAccounts = async () => { + return IMPERSONATEDACCOUNTS; +} + +const snowballAddr = "0xC38f41A296A4493Ff429F1238e030924A1542e50"; +const treasuryAddr="0x028933a66dd0ccc239a3d5c2243b2d96672f11f5"; +const devAddr= "0x0aa5cb6f365259524f7ece8e09cce9a7b394077a"; + +const MAX_UINT256= ethers.constants.MaxUint256; + +module.exports = { + setupSigner, + setupSigners, + getImpersonatedAccounts, + snowballAddr, + treasuryAddr, + devAddr, + MAX_UINT256 +}; \ No newline at end of file diff --git a/test/utils/static.ts b/test/utils/static.ts index 979f85f92..bcb996f82 100644 --- a/test/utils/static.ts +++ b/test/utils/static.ts @@ -1,6 +1,7 @@ const hre = "hardhat"; const { ethers, network } = require("hardhat"); import { Signer, BigNumber } from "ethers"; +import { overwriteTokenAmount } from "./helpers"; export const BLACKHOLE = "0x0000000000000000000000000000000000000000" @@ -11,6 +12,12 @@ export const MAX_UINT256: BigNumber = ethers.constants.MaxUint256; export const WAVAX_ADDR = "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7"; export const BENQI_ADDR = "0x8729438EB15e2C8B576fCc6AeCdA6A148776C0F5"; +let IMPERSONATEDACCOUNTS = new Array(); + +export async function getImpersonatedAccounts (){ + return IMPERSONATEDACCOUNTS; +} + /*** * NOTE: Single Staking expects the timelock signer to have the address of the strategist. */ @@ -38,3 +45,12 @@ export async function setupSigners(timelockIsStrategist: boolean = false) { return [timelockSigner, strategistSigner, governanceSigner]; }; + +export async function setupSigner(_address: string){ + await network.provider.send('hardhat_impersonateAccount', [_address]); + let Signer = ethers.provider.getSigner(_address); + await network.provider.send("hardhat_setBalance", [_address,"0x10000000000000000000000",]); + IMPERSONATEDACCOUNTS.push(_address); + return Signer; + }; +