diff --git a/README.md b/README.md index 1bfbe40..df07eac 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +# Changes - +tl'dr - The current RenBTC/wBTC Zap contract (`contracts/Zap.sol`) for ibBTC estimates the best mint route from all the available curve pools and byWBTC. Since we want Zaps to always go through the RenWBTC Curve Pool the Zap contract needs to be adjusted to remove the other options + +- `calcMintWithRen`, `calcMintWithWbtc` functions in `Zap.sol` changed to calculate mint amount only from pool[0] ie. renWBTC Curve pool + # Interest-bearing Badger BTC (ibBTC) - [bBTC.sol](./contracts/bBTC.sol) is the interest-bearing bitcoin ERC20 token. diff --git a/contracts/Core.sol b/contracts/Core.sol index c5397b8..55d57d5 100644 --- a/contracts/Core.sol +++ b/contracts/Core.sol @@ -10,8 +10,9 @@ import {IPeak} from "./interfaces/IPeak.sol"; import {IbBTC} from "./interfaces/IbBTC.sol"; import {ICore} from "./interfaces/ICore.sol"; import {GovernableProxy} from "./common/proxy/GovernableProxy.sol"; +import {PausableSlot} from "./common/PausableSlot.sol"; -contract Core is GovernableProxy, ICore { +contract Core is GovernableProxy, PausableSlot, ICore { using SafeERC20 for IERC20; using SafeMath for uint; using Math for uint; @@ -22,7 +23,7 @@ contract Core is GovernableProxy, ICore { BadgerGuestListAPI public guestList; - enum PeakState { Extinct, Active, Dormant } + enum PeakState { Extinct, Active, RedeemOnly, MintOnly } mapping(address => PeakState) public peaks; address[] public peakAddresses; @@ -31,7 +32,10 @@ contract Core is GovernableProxy, ICore { uint public redeemFee; uint public accumulatedFee; - uint256[50] private __gap; + address public guardian; + address constant public badgerGovernance = 0xB65cef03b9B89f99517643226d76e286ee999e77; + + uint256[49] private __gap; // END OF STORAGE VARIABLES @@ -46,6 +50,16 @@ contract Core is GovernableProxy, ICore { bBTC = IbBTC(_bBTC); } + modifier onlyGuardianOrGovernance() { + require(msg.sender == guardian || msg.sender == owner(), "onlyGuardianOrGovernance"); + _; + } + + modifier onlyGovernanceOrBadgerGovernance() { + require(msg.sender == badgerGovernance || msg.sender == owner(), "onlyGovernanceOrBadgerGovernance"); + _; + } + /** * @notice Mint bBTC * @dev Only whitelisted peaks can call this function @@ -55,9 +69,10 @@ contract Core is GovernableProxy, ICore { function mint(uint btc, address account, bytes32[] calldata merkleProof) override external + whenNotPaused returns(uint) { - require(peaks[msg.sender] == PeakState.Active, "PEAK_INACTIVE"); + require(peaks[msg.sender] == PeakState.Active || peaks[msg.sender] == PeakState.MintOnly, "PEAK_INACTIVE_OR_MINTING_DISABLED"); if (address(guestList) != address(0)) { require( guestList.authorized(account, btc, merkleProof), @@ -91,9 +106,9 @@ contract Core is GovernableProxy, ICore { * @param bBtc bBTC amount to redeem * @return btc amount redeemed, scaled by 1e36 */ - function redeem(uint bBtc, address account) override external returns (uint) { + function redeem(uint bBtc, address account) override external whenNotPaused returns (uint) { require(bBtc > 0, "REDEEMING_0_bBTC"); - require(peaks[msg.sender] != PeakState.Extinct, "PEAK_EXTINCT"); + require(peaks[msg.sender] == PeakState.Active || peaks[msg.sender] == PeakState.RedeemOnly, "PEAK_INACTIVE_OR_REDEMPTION_DISABLED"); (uint btc, uint fee) = bBtcToBtc(bBtc); accumulatedFee = accumulatedFee.add(fee); bBTC.burn(account, bBtc); @@ -119,7 +134,7 @@ contract Core is GovernableProxy, ICore { /** * @notice Collect all the accumulated fee (denominated in bBTC) */ - function collectFee() external { + function collectFee() external whenNotPaused { require(feeSink != address(0), "NULL_ADDRESS"); uint _fee = accumulatedFee; require(_fee > 0, "NO_FEE"); @@ -141,15 +156,13 @@ contract Core is GovernableProxy, ICore { } } - /* ##### Governance ##### */ - /** * @notice Whitelist a new peak * @param peak Address of the contract that interfaces with the 3rd-party protocol */ function whitelistPeak(address peak) external - onlyGovernance + onlyGovernanceOrBadgerGovernance { require( peaks[peak] == PeakState.Extinct, @@ -173,7 +186,7 @@ contract Core is GovernableProxy, ICore { */ function setPeakStatus(address peak, PeakState state) external - onlyGovernance + onlyGovernanceOrBadgerGovernance { require( peaks[peak] != PeakState.Extinct, @@ -211,9 +224,21 @@ contract Core is GovernableProxy, ICore { feeSink = _feeSink; } - function setGuestList(address _guestList) external onlyGovernance { + function setGuestList(address _guestList) external onlyGovernanceOrBadgerGovernance { guestList = BadgerGuestListAPI(_guestList); } + + function setGuardian(address _guardian) external onlyGovernanceOrBadgerGovernance { + guardian = _guardian; + } + + function pause() external onlyGuardianOrGovernance { + _pause(); + } + + function unpause() external onlyGovernanceOrBadgerGovernance { + _unpause(); + } } interface BadgerGuestListAPI { diff --git a/contracts/Zap.sol b/contracts/Zap.sol index cf35347..2e7ac9e 100644 --- a/contracts/Zap.sol +++ b/contracts/Zap.sol @@ -164,7 +164,7 @@ contract Zap is Initializable, Pausable, AccessControlDefendedBase { } /** - * @notice Calculate the most optimal route and expected ibbtc amount when minting with renBTC. + * @notice Calculate mint through renWBTC pool route and expected ibbtc amount when minting with renBTC. * @dev Use returned params poolId, idx and bBTC in the call to mint(...) The last param `minOut` in mint(...) should be a bit more than the returned bBTC value. For instance 0.2% - 1% higher depending on slippage tolerange. @@ -180,26 +180,10 @@ contract Zap is Initializable, Pausable, AccessControlDefendedBase { // poolId=0, idx=0 (bBTC, fee) = curveLPToIbbtc(0, pools[0].deposit.calc_token_amount([amount,0], true)); - - (_ibbtc, _fee) = curveLPToIbbtc(1, pools[1].deposit.calc_token_amount([amount,0,0], true)); - if (_ibbtc > bBTC) { - bBTC = _ibbtc; - fee = _fee; - poolId = 1; - // idx=0 - } - - (_ibbtc, _fee) = curveLPToIbbtc(2, pools[2].deposit.calc_token_amount([0,amount,0,0], true)); - if (_ibbtc > bBTC) { - bBTC = _ibbtc; - fee = _fee; - poolId = 2; - idx = 1; - } } /** - * @notice Calculate the most optimal route and expected ibbtc amount when minting with wBTC. + * @notice Calculate mint through renWBTC pool route and expected ibbtc amount when minting with wBTC. * @dev Use returned params poolId, idx and bBTC in the call to mint(...) The last param `minOut` in mint(...) should be a bit more than the returned bBTC value. For instance 0.2% - 1% higher depending on slippage tolerange. @@ -216,31 +200,6 @@ contract Zap is Initializable, Pausable, AccessControlDefendedBase { // poolId=0 (bBTC, fee) = curveLPToIbbtc(0, pools[0].deposit.calc_token_amount([0,amount], true)); idx = 1; - - (_ibbtc, _fee) = curveLPToIbbtc(1, pools[1].deposit.calc_token_amount([0,amount,0], true)); - if (_ibbtc > bBTC) { - bBTC = _ibbtc; - fee = _fee; - poolId = 1; - // idx=1 - } - - (_ibbtc, _fee) = curveLPToIbbtc(2, pools[2].deposit.calc_token_amount([0,0,amount,0], true)); - if (_ibbtc > bBTC) { - bBTC = _ibbtc; - fee = _fee; - poolId = 2; - idx = 2; - } - - // for byvwbtc, sett.pricePerShare returns a wbtc value, as opposed to lpToken amount in setts - (_ibbtc, _fee) = byvWbtcPeak.calcMint(amount.mul(1e8).div(IbyvWbtc(address(pools[3].sett)).pricePerShare())); - if (_ibbtc > bBTC) { - bBTC = _ibbtc; - fee = _fee; - poolId = 3; - // idx value will be ignored anyway - } } /** @@ -279,7 +238,7 @@ contract Zap is Initializable, Pausable, AccessControlDefendedBase { } /** - * @notice Calculate the most optimal route and expected token amount when redeeming ibbtc. + * @notice Calculate redeem through renWBTC pool route and expected token amount when redeeming ibbtc. * @dev Use returned params poolId, idx and out in the call to redeem(...) The last param `redeem` in mint(...) should be a bit less than the returned `out` value. For instance 0.2% - 1% lesser depending on slippage tolerange. @@ -300,7 +259,7 @@ contract Zap is Initializable, Pausable, AccessControlDefendedBase { } /** - * @notice Calculate the most optimal route and expected renbtc amount when redeeming ibbtc. + * @notice Calculate redeem through renWBTC pool route and expected renbtc amount when redeeming ibbtc. * @dev Use returned params poolId, idx and renAmount in the call to redeem(...) The last param `minOut` in redeem(...) should be a bit less than the returned renAmount value. For instance 0.2% - 1% lesser depending on slippage tolerange. @@ -318,24 +277,6 @@ contract Zap is Initializable, Pausable, AccessControlDefendedBase { // poolId=0, idx=0 (_lp, fee) = ibbtcToCurveLP(0, amount); renAmount = pools[0].deposit.calc_withdraw_one_coin(_lp, 0); - - (_lp, _fee) = ibbtcToCurveLP(1, amount); - _ren = pools[1].deposit.calc_withdraw_one_coin(_lp, 0); - if (_ren > renAmount) { - renAmount = _ren; - fee = _fee; - poolId = 1; - // idx=0 - } - - (_lp, _fee) = ibbtcToCurveLP(2, amount); - _ren = pools[2].deposit.calc_withdraw_one_coin(_lp, 1); - if (_ren > renAmount) { - renAmount = _ren; - fee = _fee; - poolId = 2; - idx = 1; - } } /** @@ -358,37 +299,6 @@ contract Zap is Initializable, Pausable, AccessControlDefendedBase { (_lp, fee) = ibbtcToCurveLP(0, amount); wBTCAmount = pools[0].deposit.calc_withdraw_one_coin(_lp, 1); idx = 1; - - (_lp, _fee) = ibbtcToCurveLP(1, amount); - _wbtc = pools[1].deposit.calc_withdraw_one_coin(_lp, 1); - if (_wbtc > wBTCAmount) { - wBTCAmount = _wbtc; - fee = _fee; - poolId = 1; - // idx=1 - } - - (_lp, _fee) = ibbtcToCurveLP(2, amount); - _wbtc = pools[2].deposit.calc_withdraw_one_coin(_lp, 2); - if (_wbtc > wBTCAmount) { - wBTCAmount = _wbtc; - fee = _fee; - poolId = 2; - idx = 2; - } - - uint _byvWbtc; - uint _max; - (_byvWbtc,_fee,_max) = byvWbtcPeak.calcRedeem(amount); - if (amount <= _max) { - uint strategyFee = _byvWbtc.mul(pools[3].sett.withdrawalFee()).div(10000); - _wbtc = _byvWbtc.sub(strategyFee).mul(pools[3].sett.pricePerShare()).div(1e8); - if (_wbtc > wBTCAmount) { - wBTCAmount = _wbtc; - fee = _fee.add(strategyFee); - poolId = 3; - } - } } function ibbtcToCurveLP(uint poolId, uint bBtc) public view returns(uint lp, uint fee) { @@ -401,7 +311,7 @@ contract Zap is Initializable, Pausable, AccessControlDefendedBase { } else { // pesimistically charge 0.5% on the withdrawal. // Actual fee might be lesser if the vault keeps keeps a buffer - uint strategyFee = sett.mul(controller.strategies(pool.lpToken).withdrawalFee()).div(1000); + uint strategyFee = sett.mul(controller.strategies(pool.lpToken).withdrawalFee()).div(10000); lp = sett.sub(strategyFee).mul(pool.sett.getPricePerFullShare()).div(1e18); fee = fee.add(strategyFee); } diff --git a/contracts/common/AccessControlDefended.sol b/contracts/common/AccessControlDefended.sol index 9f000a9..de66a72 100644 --- a/contracts/common/AccessControlDefended.sol +++ b/contracts/common/AccessControlDefended.sol @@ -3,11 +3,11 @@ pragma solidity 0.6.11; import {GovernableProxy} from "./proxy/GovernableProxy.sol"; - +import {PausableSlot} from "../common/PausableSlot.sol"; contract AccessControlDefendedBase { mapping (address => bool) public approved; mapping(address => uint256) public blockLock; - + modifier defend() { require(msg.sender == tx.origin || approved[msg.sender], "ACCESS_DENIED"); _; @@ -17,7 +17,7 @@ contract AccessControlDefendedBase { require(approved[msg.sender] || blockLock[msg.sender] < block.number, "BLOCK_LOCKED"); _; } - + function _lockForBlock(address account) internal { blockLock[account] = block.number; } @@ -31,14 +31,38 @@ contract AccessControlDefendedBase { } } -contract AccessControlDefended is GovernableProxy, AccessControlDefendedBase { - uint256[50] private __gap; +contract AccessControlDefended is GovernableProxy, AccessControlDefendedBase, PausableSlot { + address constant public badgerGovernance = 0xB65cef03b9B89f99517643226d76e286ee999e77; + address public guardian; + uint256[49] private __gap; + + modifier onlyGovernanceOrBadgerGovernance() { + require(msg.sender == badgerGovernance || msg.sender == owner(), "onlyGovernanceOrBadgerGovernance"); + _; + } - function approveContractAccess(address account) external onlyGovernance { + modifier onlyGuardianOrGovernance() { + require(msg.sender == guardian || msg.sender == owner(), "onlyGuardianOrGovernance"); + _; + } + + function approveContractAccess(address account) external onlyGovernanceOrBadgerGovernance { _approveContractAccess(account); } - function revokeContractAccess(address account) external onlyGovernance { + function revokeContractAccess(address account) external onlyGovernanceOrBadgerGovernance { _revokeContractAccess(account); } + + function setGuardian(address _guardian) external onlyGovernanceOrBadgerGovernance { + guardian = _guardian; + } + + function pause() external onlyGuardianOrGovernance { + _pause(); + } + + function unpause() external onlyGovernanceOrBadgerGovernance { + _unpause(); + } } diff --git a/contracts/common/PausableSlot.sol b/contracts/common/PausableSlot.sol new file mode 100644 index 0000000..3fef9de --- /dev/null +++ b/contracts/common/PausableSlot.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.11; + +contract PausableSlot { + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + + bytes32 constant PAUSED_SLOT = keccak256("_paused"); + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view returns (bool _paused) { + bytes32 position = PAUSED_SLOT; + assembly { + _paused := sload(position) + } + } + + function _setPaused(bool _paused) internal { + bytes32 position = PAUSED_SLOT; + assembly { + sstore(position, _paused) + } + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + modifier whenNotPaused() { + require(!paused(), "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. + */ + modifier whenPaused() { + require(paused(), "Pausable: not paused"); + _; + } + + /** + * @dev Triggers stopped state. + * + * Requirements: + * + * - The contract must not be paused. + */ + function _pause() internal virtual whenNotPaused { + _setPaused(true); + emit Paused(msg.sender); + } + + /** + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. + */ + function _unpause() internal virtual whenPaused { + _setPaused(false); + emit Unpaused(msg.sender); + } +} \ No newline at end of file diff --git a/contracts/peaks/BadgerSettPeak.sol b/contracts/peaks/BadgerSettPeak.sol index f56b643..42ec1f7 100644 --- a/contracts/peaks/BadgerSettPeak.sol +++ b/contracts/peaks/BadgerSettPeak.sol @@ -8,6 +8,7 @@ import {SafeERC20, SafeMath} from "@openzeppelin/contracts/token/ERC20/SafeERC20 import {Math} from "@openzeppelin/contracts/math/Math.sol"; import {AccessControlDefended} from "../common/AccessControlDefended.sol"; +import {PausableSlot} from "../common/PausableSlot.sol"; import {ISwap} from "../interfaces/ISwap.sol"; import {ICore} from "../interfaces/ICore.sol"; @@ -29,6 +30,8 @@ contract BadgerSettPeak is AccessControlDefended, IBadgerSettPeak { mapping(uint => CurvePool) public pools; uint public numPools; + + // END OF STORAGE VARIABLES event Mint(address account, uint ibBTC, uint sett); @@ -41,6 +44,9 @@ contract BadgerSettPeak is AccessControlDefended, IBadgerSettPeak { core = ICore(_core); } + + // ===== Pausing Functionality ===== + /** * @notice Mint bBTC with Sett LP token * @dev Invoking pool.sett.safeTransferFrom() before core.mint(), will mess up core.totalSystemAssets() calculation @@ -53,6 +59,7 @@ contract BadgerSettPeak is AccessControlDefended, IBadgerSettPeak { external defend blockLocked + whenNotPaused returns(uint outAmount) { _lockForBlock(msg.sender); @@ -76,6 +83,7 @@ contract BadgerSettPeak is AccessControlDefended, IBadgerSettPeak { external defend blockLocked + whenNotPaused returns (uint outAmount) { _lockForBlock(msg.sender); @@ -183,9 +191,10 @@ contract BadgerSettPeak is AccessControlDefended, IBadgerSettPeak { CurvePool[] calldata _pools ) external - onlyGovernance + onlyGovernanceOrBadgerGovernance { CurvePool memory pool; + for (uint i = 0; i < _pools.length; i++) { pool = _pools[i]; require( @@ -204,4 +213,6 @@ contract BadgerSettPeak is AccessControlDefended, IBadgerSettPeak { } numPools = _pools.length; } + + } diff --git a/contracts/peaks/BadgerYearnWbtcPeak.sol b/contracts/peaks/BadgerYearnWbtcPeak.sol index f1f869e..d33b83e 100644 --- a/contracts/peaks/BadgerYearnWbtcPeak.sol +++ b/contracts/peaks/BadgerYearnWbtcPeak.sol @@ -8,11 +8,12 @@ import {SafeERC20, SafeMath} from "@openzeppelin/contracts/token/ERC20/SafeERC20 import {Math} from "@openzeppelin/contracts/math/Math.sol"; import {AccessControlDefended} from "../common/AccessControlDefended.sol"; +import {PausableSlot} from "../common/PausableSlot.sol"; import {ICore} from "../interfaces/ICore.sol"; import {IbyvWbtc} from "../interfaces/IbyvWbtc.sol"; import {IByvWbtcPeak} from "../interfaces/IPeak.sol"; -contract BadgerYearnWbtcPeak is AccessControlDefended, IByvWbtcPeak { +contract BadgerYearnWbtcPeak is AccessControlDefended, PausableSlot, IByvWbtcPeak { using SafeERC20 for IERC20; using SafeERC20 for IbyvWbtc; @@ -22,6 +23,8 @@ contract BadgerYearnWbtcPeak is AccessControlDefended, IByvWbtcPeak { ICore public immutable core; IbyvWbtc public immutable byvWBTC; + address public guardian; + // END OF STORAGE VARIABLES event Mint(address account, uint ibBTC, uint byvWBTC); @@ -35,6 +38,25 @@ contract BadgerYearnWbtcPeak is AccessControlDefended, IByvWbtcPeak { byvWBTC = IbyvWbtc(_byvWBTC); } + modifier onlyGuardianOrGovernance() { + require(msg.sender == guardian || msg.sender == owner(), "onlyGuardianOrGovernance"); + _; + } + + // ===== Pausing Functionality ===== + function pause() external onlyGuardianOrGovernance { + _pause(); + } + + function unpause() external onlyGovernance { + _unpause(); + } + + // ===== Governance Functionality ===== + function setGuardian(address _guardian) external onlyGovernance { + guardian = _guardian; + } + /** * @notice Mint bBTC with byvWBTC token * @dev Invoking byvWBTC.safeTransferFrom() before core.mint(), will mess up core.totalSystemAssets() calculation @@ -46,6 +68,7 @@ contract BadgerYearnWbtcPeak is AccessControlDefended, IByvWbtcPeak { external defend blockLocked + whenNotPaused returns(uint outAmount) { _lockForBlock(msg.sender); @@ -66,6 +89,7 @@ contract BadgerYearnWbtcPeak is AccessControlDefended, IByvWbtcPeak { external defend blockLocked + whenNotPaused returns (uint outAmount) { _lockForBlock(msg.sender); diff --git a/package.json b/package.json index 2313fba..c12e8af 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.1", - "@nomiclabs/hardhat-etherscan": "^2.1.1", + "@nomiclabs/hardhat-etherscan": "^2.1.7", "@nomiclabs/hardhat-truffle5": "^2.0.0", "@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-web3": "^2.0.0", diff --git a/scripts/deployZapMainnet.js b/scripts/deployZapMainnet.js new file mode 100644 index 0000000..d6a7c61 --- /dev/null +++ b/scripts/deployZapMainnet.js @@ -0,0 +1,35 @@ +async function deployZap() { + + const [deployer] = await ethers.getSigners(); + + console.log("Deploying contracts with the account:", deployer.address); + + console.log("Account balance:", (await deployer.getBalance()).toString()); + + // const [TransparentUpgradeableProxy, Zap] = await Promise.all([ + // ethers.getContractFactory('TransparentUpgradeableProxy'), + // ethers.getContractFactory('Zap') + // ]) + const zapImpl = await Zap.deploy() + + console.log({ zapImpl: zapImpl.address }) + + // const zapImpl = await ethers.getContractAt('Zap', '0x4459A591c61CABd905EAb8486Bf628432b15C8b1') + // const args = [ + // zapImpl.address, + // '0xBf0e27fdf5eF7519A9540DF401cCe0A7a4Cd75Bc', // proxyAdmin + // zapImpl.interface.encodeFunctionData('init', ['0xCF7346A5E41b0821b80D5B3fdc385EEB6Dc59F44' /* ibbtc Metasig */]) + // ] + + // console.log(args) + // const zap = await TransparentUpgradeableProxy.deploy(...args) + // console.log({ zap: zap.address }) + +} + +deployZap() + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + });