diff --git a/args.js b/args.js index 8bc95f7..b254933 100644 --- a/args.js +++ b/args.js @@ -1,4 +1,8 @@ const pancakeRouter = "0xd99d1c33f9fc3444f8101754abc46c52416550d1"; const rewardRouterV2 = "0x26E8F916643fbfF603f2DD7348bA50b63A11b6b7"; const weth = "0x612777Eea37a44F7a95E3B101C39e1E2695fa6C2"; -module.exports = ["0x58CB98A966F62aA6F2190eB3AA03132A0c3de3D5"]; +module.exports = [ + "0x07f3E5DA3f9AaA2ba21b0c2177CD0AE5457CDCaB", + "86400", + "0x07f3E5DA3f9AaA2ba21b0c2177CD0AE5457CDCaB", +]; diff --git a/contracts/peripherals/PriceFeedTimelock.sol b/contracts/peripherals/PriceFeedTimelock.sol new file mode 100644 index 0000000..f8e346d --- /dev/null +++ b/contracts/peripherals/PriceFeedTimelock.sol @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "./interfaces/ITimelockTarget.sol"; +import "./interfaces/IHandlerTarget.sol"; +import "../access/interfaces/IAdmin.sol"; +import "../core/interfaces/IVaultPriceFeed.sol"; +import "../oracle/interfaces/IFastPriceFeed.sol"; +import "../referrals/interfaces/IReferralStorage.sol"; +import "../tokens/interfaces/IYieldToken.sol"; +import "../tokens/interfaces/IBaseToken.sol"; +import "../tokens/interfaces/IMintable.sol"; +import "../tokens/interfaces/IUSDG.sol"; +import "../staking/interfaces/IVester.sol"; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; + +contract PriceFeedTimelock { + using SafeMath for uint256; + + uint256 public constant MAX_BUFFER = 5 days; + + uint256 public buffer; + address public admin; + + address public tokenManager; + + mapping(bytes32 => uint256) public pendingActions; + + mapping(address => bool) public isHandler; + mapping(address => bool) public isKeeper; + + event SignalPendingAction(bytes32 action); + event SignalApprove( + address token, + address spender, + uint256 amount, + bytes32 action + ); + event SignalWithdrawToken( + address target, + address token, + address receiver, + uint256 amount, + bytes32 action + ); + event SignalSetGov(address target, address gov, bytes32 action); + event SignalSetPriceFeedWatcher( + address fastPriceFeed, + address account, + bool isActive + ); + event SignalPriceFeedSetTokenConfig( + address vaultPriceFeed, + address token, + address priceFeed, + uint256 priceDecimals, + bool isStrictStable + ); + event ClearAction(bytes32 action); + + modifier onlyAdmin() { + require(msg.sender == admin, "Timelock: forbidden"); + _; + } + + modifier onlyHandlerAndAbove() { + require( + msg.sender == admin || isHandler[msg.sender], + "Timelock: forbidden" + ); + _; + } + + modifier onlyKeeperAndAbove() { + require( + msg.sender == admin || + isHandler[msg.sender] || + isKeeper[msg.sender], + "Timelock: forbidden" + ); + _; + } + + modifier onlyTokenManager() { + require(msg.sender == tokenManager, "Timelock: forbidden"); + _; + } + + constructor(address _admin, uint256 _buffer, address _tokenManager) public { + require(_buffer <= MAX_BUFFER, "Timelock: invalid _buffer"); + admin = _admin; + buffer = _buffer; + tokenManager = _tokenManager; + } + + function setAdmin(address _admin) external onlyTokenManager { + admin = _admin; + } + + function setExternalAdmin( + address _target, + address _admin + ) external onlyAdmin { + require(_target != address(this), "Timelock: invalid _target"); + IAdmin(_target).setAdmin(_admin); + } + + function setContractHandler( + address _handler, + bool _isActive + ) external onlyAdmin { + isHandler[_handler] = _isActive; + } + + function setKeeper(address _keeper, bool _isActive) external onlyAdmin { + isKeeper[_keeper] = _isActive; + } + + function setBuffer(uint256 _buffer) external onlyAdmin { + require(_buffer <= MAX_BUFFER, "Timelock: invalid _buffer"); + require(_buffer > buffer, "Timelock: buffer cannot be decreased"); + buffer = _buffer; + } + + function setIsAmmEnabled( + address _priceFeed, + bool _isEnabled + ) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setIsAmmEnabled(_isEnabled); + } + + function setIsSecondaryPriceEnabled( + address _priceFeed, + bool _isEnabled + ) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setIsSecondaryPriceEnabled(_isEnabled); + } + + function setMaxStrictPriceDeviation( + address _priceFeed, + uint256 _maxStrictPriceDeviation + ) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setMaxStrictPriceDeviation( + _maxStrictPriceDeviation + ); + } + + function setUseV2Pricing( + address _priceFeed, + bool _useV2Pricing + ) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setUseV2Pricing(_useV2Pricing); + } + + function setAdjustment( + address _priceFeed, + address _token, + bool _isAdditive, + uint256 _adjustmentBps + ) external onlyKeeperAndAbove { + IVaultPriceFeed(_priceFeed).setAdjustment( + _token, + _isAdditive, + _adjustmentBps + ); + } + + function setSpreadBasisPoints( + address _priceFeed, + address _token, + uint256 _spreadBasisPoints + ) external onlyKeeperAndAbove { + IVaultPriceFeed(_priceFeed).setSpreadBasisPoints( + _token, + _spreadBasisPoints + ); + } + + function setPriceSampleSpace( + address _priceFeed, + uint256 _priceSampleSpace + ) external onlyHandlerAndAbove { + require(_priceSampleSpace <= 5, "Invalid _priceSampleSpace"); + IVaultPriceFeed(_priceFeed).setPriceSampleSpace(_priceSampleSpace); + } + + function setVaultPriceFeed( + address _fastPriceFeed, + address _vaultPriceFeed + ) external onlyAdmin { + IFastPriceFeed(_fastPriceFeed).setVaultPriceFeed(_vaultPriceFeed); + } + + function setPriceDuration( + address _fastPriceFeed, + uint256 _priceDuration + ) external onlyHandlerAndAbove { + IFastPriceFeed(_fastPriceFeed).setPriceDuration(_priceDuration); + } + + function setMaxPriceUpdateDelay( + address _fastPriceFeed, + uint256 _maxPriceUpdateDelay + ) external onlyHandlerAndAbove { + IFastPriceFeed(_fastPriceFeed).setMaxPriceUpdateDelay( + _maxPriceUpdateDelay + ); + } + + function setSpreadBasisPointsIfInactive( + address _fastPriceFeed, + uint256 _spreadBasisPointsIfInactive + ) external onlyAdmin { + IFastPriceFeed(_fastPriceFeed).setSpreadBasisPointsIfInactive( + _spreadBasisPointsIfInactive + ); + } + + function setSpreadBasisPointsIfChainError( + address _fastPriceFeed, + uint256 _spreadBasisPointsIfChainError + ) external onlyAdmin { + IFastPriceFeed(_fastPriceFeed).setSpreadBasisPointsIfChainError( + _spreadBasisPointsIfChainError + ); + } + + function setMinBlockInterval( + address _fastPriceFeed, + uint256 _minBlockInterval + ) external onlyAdmin { + IFastPriceFeed(_fastPriceFeed).setMinBlockInterval(_minBlockInterval); + } + + function setIsSpreadEnabled( + address _fastPriceFeed, + bool _isSpreadEnabled + ) external onlyAdmin { + IFastPriceFeed(_fastPriceFeed).setIsSpreadEnabled(_isSpreadEnabled); + } + + function transferIn( + address _sender, + address _token, + uint256 _amount + ) external onlyAdmin { + IERC20(_token).transferFrom(_sender, address(this), _amount); + } + + function signalApprove( + address _token, + address _spender, + uint256 _amount + ) external onlyAdmin { + bytes32 action = keccak256( + abi.encodePacked("approve", _token, _spender, _amount) + ); + _setPendingAction(action); + emit SignalApprove(_token, _spender, _amount, action); + } + + function approve( + address _token, + address _spender, + uint256 _amount + ) external onlyAdmin { + bytes32 action = keccak256( + abi.encodePacked("approve", _token, _spender, _amount) + ); + _validateAction(action); + _clearAction(action); + IERC20(_token).approve(_spender, _amount); + } + + function signalWithdrawToken( + address _target, + address _token, + address _receiver, + uint256 _amount + ) external onlyAdmin { + bytes32 action = keccak256( + abi.encodePacked( + "withdrawToken", + _target, + _token, + _receiver, + _amount + ) + ); + _setPendingAction(action); + emit SignalWithdrawToken(_target, _token, _receiver, _amount, action); + } + + function withdrawToken( + address _target, + address _token, + address _receiver, + uint256 _amount + ) external onlyAdmin { + bytes32 action = keccak256( + abi.encodePacked( + "withdrawToken", + _target, + _token, + _receiver, + _amount + ) + ); + _validateAction(action); + _clearAction(action); + IBaseToken(_target).withdrawToken(_token, _receiver, _amount); + } + + function signalSetGov(address _target, address _gov) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("setGov", _target, _gov)); + _setPendingAction(action); + emit SignalSetGov(_target, _gov, action); + } + + function setGov(address _target, address _gov) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("setGov", _target, _gov)); + _validateAction(action); + _clearAction(action); + ITimelockTarget(_target).setGov(_gov); + } + + function signalSetPriceFeedWatcher( + address _fastPriceFeed, + address _account, + bool _isActive + ) external onlyAdmin { + bytes32 action = keccak256( + abi.encodePacked( + "setPriceFeedWatcher", + _fastPriceFeed, + _account, + _isActive + ) + ); + _setPendingAction(action); + emit SignalSetPriceFeedWatcher(_fastPriceFeed, _account, _isActive); + } + + function setPriceFeedWatcher( + address _fastPriceFeed, + address _account, + bool _isActive + ) external onlyAdmin { + bytes32 action = keccak256( + abi.encodePacked( + "setPriceFeedWatcher", + _fastPriceFeed, + _account, + _isActive + ) + ); + _validateAction(action); + _clearAction(action); + IFastPriceFeed(_fastPriceFeed).setSigner(_account, _isActive); + } + + function signalSetPriceFeedUpdater( + address _fastPriceFeed, + address _account, + bool _isActive + ) external onlyAdmin { + bytes32 action = keccak256( + abi.encodePacked( + "setPriceFeedUpdater", + _fastPriceFeed, + _account, + _isActive + ) + ); + _setPendingAction(action); + emit SignalSetPriceFeedWatcher(_fastPriceFeed, _account, _isActive); + } + + function setPriceFeedUpdater( + address _fastPriceFeed, + address _account, + bool _isActive + ) external onlyAdmin { + bytes32 action = keccak256( + abi.encodePacked( + "setPriceFeedUpdater", + _fastPriceFeed, + _account, + _isActive + ) + ); + _validateAction(action); + _clearAction(action); + IFastPriceFeed(_fastPriceFeed).setUpdater(_account, _isActive); + } + + function signalPriceFeedSetTokenConfig( + address _vaultPriceFeed, + address _token, + address _priceFeed, + uint256 _priceDecimals, + bool _isStrictStable + ) external onlyAdmin { + bytes32 action = keccak256( + abi.encodePacked( + "priceFeedSetTokenConfig", + _vaultPriceFeed, + _token, + _priceFeed, + _priceDecimals, + _isStrictStable + ) + ); + + _setPendingAction(action); + + emit SignalPriceFeedSetTokenConfig( + _vaultPriceFeed, + _token, + _priceFeed, + _priceDecimals, + _isStrictStable + ); + } + + function priceFeedSetTokenConfig( + address _vaultPriceFeed, + address _token, + address _priceFeed, + uint256 _priceDecimals, + bool _isStrictStable + ) external onlyAdmin { + bytes32 action = keccak256( + abi.encodePacked( + "priceFeedSetTokenConfig", + _vaultPriceFeed, + _token, + _priceFeed, + _priceDecimals, + _isStrictStable + ) + ); + + _validateAction(action); + _clearAction(action); + + IVaultPriceFeed(_vaultPriceFeed).setTokenConfig( + _token, + _priceFeed, + _priceDecimals, + _isStrictStable + ); + } + + function cancelAction(bytes32 _action) external onlyAdmin { + _clearAction(_action); + } + + function _setPendingAction(bytes32 _action) private { + pendingActions[_action] = block.timestamp.add(buffer); + emit SignalPendingAction(_action); + } + + function _validateAction(bytes32 _action) private view { + require(pendingActions[_action] != 0, "Timelock: action not signalled"); + require( + pendingActions[_action] < block.timestamp, + "Timelock: action time not yet passed" + ); + } + + function _clearAction(bytes32 _action) private { + require(pendingActions[_action] != 0, "Timelock: invalid _action"); + delete pendingActions[_action]; + emit ClearAction(_action); + } +} diff --git a/scripts/core/tokens.js b/scripts/core/tokens.js index b70352f..037fbf7 100644 --- a/scripts/core/tokens.js +++ b/scripts/core/tokens.js @@ -288,6 +288,25 @@ module.exports = { maxGlobalShortSize: 500 * 1000, maxGlobalLongSize: 500 * 1000, }, + arb: { + name: "arb", + address: "0x912CE59144191C1204E64559FE8253a0e49E6548", + decimals: 18, + priceFeed: "0xb2A824043730FE05F3DA2efaFa1CBbe83fa548D6", + priceDecimals: 8, + fastPricePrecision: 1000, + maxCumulativeDeltaDiff: 0.1 * 10 * 1000 * 1000, // 10% + isStrictStable: false, + tokenWeight: 1000, + minProfitBps: 0, + maxUsdgAmount: 5 * 1000 * 1000, + bufferAmount: 100000, + isStable: false, + isShortable: true, + spreadBasisPoints: 20, + maxGlobalShortSize: 500 * 1000, + maxGlobalLongSize: 500 * 1000, + }, usdt: { name: "usdt", address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", diff --git a/scripts/core/whitelistTokens.js b/scripts/core/whitelistTokens.js index 5a9bd70..2a3f7f9 100644 --- a/scripts/core/whitelistTokens.js +++ b/scripts/core/whitelistTokens.js @@ -9,8 +9,8 @@ async function main() { const vault = await contractAt("Vault", "0xec45801399EB38B75A3bf793051b00bb64fF3eF8") const timelock = await contractAt("Timelock", "0xdD3493dEcAC2bD82391fd6fd2f3a6c983372a015") - const { btc, eth, usdt, usdc} = tokens - const tokenArr = [btc, usdt, eth, usdc] + const { btc, eth, usdt, usdc, arb} = tokens + const tokenArr = [arb] for (const token of tokenArr) { console.log("----------------") diff --git a/scripts/peripherals/deployPriceFeedTimelock.js b/scripts/peripherals/deployPriceFeedTimelock.js index 5149c29..185d501 100644 --- a/scripts/peripherals/deployPriceFeedTimelock.js +++ b/scripts/peripherals/deployPriceFeedTimelock.js @@ -4,7 +4,7 @@ const { expandDecimals } = require("../../test/shared/utilities") const network = (process.env.HARDHAT_NETWORK || 'mainnet'); async function getArbValues() { - const tokenManager = { address: "0xddDc546e07f1374A07b270b7d863371e575EA96A" } + const tokenManager = { address: "0x07f3E5DA3f9AaA2ba21b0c2177CD0AE5457CDCaB" } return { tokenManager } } @@ -37,7 +37,7 @@ async function getValues() { async function main() { - const admin = "0x2CC6D07871A1c0655d6A7c9b0Ad24bED8f940517" + const admin = "0x07f3E5DA3f9AaA2ba21b0c2177CD0AE5457CDCaB" const buffer = network === "testnet" ? 60 : 24 * 60 * 60 const { tokenManager } = await getValues() @@ -51,11 +51,7 @@ async function main() { const deployedTimelock = await contractAt("PriceFeedTimelock", timelock.address) const signers = [ - "0x0EaEA9558eFF1d4b76b347A39f54d8CDf01F990F", // account test 1 - "0x33EDbEc831AD335f26fFC06EB07311cC99F50084", // account test 2 - "0x3134d254202E5dd2d98E4ba10CaE3703199c3FB0", // account test 3 - "0x6f8e190d41c6D5F0Dc18122b01C339761A4deDbe", // account test 4 - "0x5287a0ad42b2Cfdd14265949ab4cb9Ac5867FD27" // account test 5 + "0x5678917FfEb77827Aafc33419E99DaCd707313a9", // deployer ] for (let i = 0; i < signers.length; i++) { @@ -64,8 +60,8 @@ async function main() { } const keepers = [ - "0x2CC6D07871A1c0655d6A7c9b0Ad24bED8f940517", // deployer - "0x9B82B9Ab7570Ae452D9FF5411F1bE2bad08EF4c4" + "0x5678917FfEb77827Aafc33419E99DaCd707313a9", // deployer + "0xe6fd8f16CA620854289571FBBB7eE743437fc027" ] for (let i = 0; i < keepers.length; i++) {