diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d9956658f..558c99563 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -89,6 +89,9 @@ jobs: - name: Install python dependencies run: pip install -r requirements-dev.txt + - name: Run pre-commmit hooks + run: pre-commit run -a + - name: Run black run: black --check --include "(tests|scripts)" . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..1991225ea --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,6 @@ +repos: +- repo: https://github.com/spinoch/blackadder + rev: master + hooks: + - id: blackadder + language_version: python3 diff --git a/contracts/Vault.vy b/contracts/Vault.vy index d484ed1d6..86fe9d9e4 100644 --- a/contracts/Vault.vy +++ b/contracts/Vault.vy @@ -1,4 +1,4 @@ -#@version 0.2.7 +# @version 0.2.7 API_VERSION: constant(String[28]) = "0.1.2" @@ -8,22 +8,38 @@ from vyper.interfaces import ERC20 implements: ERC20 + interface DetailedERC20: - def name() -> String[42]: view - def symbol() -> String[20]: view - def decimals() -> uint256: view + def name() -> String[42]: + view + + def symbol() -> String[20]: + view + + def decimals() -> uint256: + view + interface Strategy: - def strategist() -> address: view - def estimatedTotalAssets() -> uint256: view - def withdraw(_amount: uint256): nonpayable - def migrate(_newStrategy: address): nonpayable + def strategist() -> address: + view + + def estimatedTotalAssets() -> uint256: + view + + def withdraw(_amount: uint256): + nonpayable + + def migrate(_newStrategy: address): + nonpayable + event Transfer: sender: indexed(address) receiver: indexed(address) value: uint256 + event Approval: owner: indexed(address) spender: indexed(address) @@ -43,6 +59,7 @@ governance: public(address) guardian: public(address) pendingGovernance: address + struct StrategyParams: performanceFee: uint256 # Strategist's fee (basis points) activation: uint256 # Activation block.number @@ -52,12 +69,14 @@ struct StrategyParams: totalDebt: uint256 # Total outstanding debt that Strategy has totalReturns: uint256 # Total returns that Strategy has realized for Vault + event StrategyAdded: strategy: indexed(address) debtLimit: uint256 # Maximum borrow amount rateLimit: uint256 # Increase/decrease per block performanceFee: uint256 # Strategist's fee (basis points) + event StrategyReported: strategy: indexed(address) returnAdded: uint256 @@ -66,6 +85,7 @@ event StrategyReported: totalDebt: uint256 debtLimit: uint256 + # NOTE: Track the total for overhead targeting purposes strategies: public(HashMap[address, StrategyParams]) MAXIMUM_STRATEGIES: constant(uint256) = 20 @@ -88,18 +108,23 @@ totalDebt: public(uint256) # Amount of tokens that all strategies have borrowed lastReport: public(uint256) # Number of blocks since last report rewards: public(address) # Rewards contract where Governance fees are sent to -managementFee: public(uint256) # Governance Fee for management of Vault (given to `rewards`) -performanceFee: public(uint256) # Governance Fee for performance of Vault (given to `rewards`) +managementFee: public( + uint256 +) # Governance Fee for management of Vault (given to `rewards`) +performanceFee: public( + uint256 +) # Governance Fee for performance of Vault (given to `rewards`) FEE_MAX: constant(uint256) = 10_000 # 100%, or 10k basis points BLOCKS_PER_YEAR: constant(uint256) = 2_300_000 + @external def __init__( _token: address, _governance: address, _rewards: address, _nameOverride: String[64], - _symbolOverride: String[32] + _symbolOverride: String[32], ): # TODO: Non-detailed Configuration? self.token = ERC20(_token) @@ -218,15 +243,17 @@ def transfer(_to: address, _value: uint256) -> bool: @external -def transferFrom(_from : address, _to : address, _value : uint256) -> bool: - if self.allowance[_from][msg.sender] < MAX_UINT256: # Unlimited approval (saves an SSTORE) - self.allowance[_from][msg.sender] -= _value +def transferFrom(_from: address, _to: address, _value: uint256) -> bool: + if ( + self.allowance[_from][msg.sender] < MAX_UINT256 + ): # Unlimited approval (saves an SSTORE) + self.allowance[_from][msg.sender] -= _value self._transfer(_from, _to, _value) return True @external -def approve(_spender : address, _value : uint256) -> bool: +def approve(_spender: address, _value: uint256) -> bool: """ @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. Beware that changing an allowance with this method brings the risk @@ -312,7 +339,9 @@ def _issueSharesForAmount(_to: address, _amount: uint256) -> uint256: @external -def deposit(_amount: uint256 = MAX_UINT256, _recipient: address = msg.sender) -> uint256: +def deposit( + _amount: uint256 = MAX_UINT256, _recipient: address = msg.sender +) -> uint256: assert not self.emergencyShutdown # Deposits are locked out amount: uint256 = _amount @@ -389,7 +418,9 @@ def maxAvailableShares() -> uint256: @external -def withdraw(_shares: uint256 = MAX_UINT256, _recipient: address = msg.sender) -> uint256: +def withdraw( + _shares: uint256 = MAX_UINT256, _recipient: address = msg.sender +) -> uint256: shares: uint256 = _shares # May reduce this number below # If _shares not specified, transfer full share balance @@ -488,7 +519,7 @@ def _organizeWithdrawalQueue(): if strategy == ZERO_ADDRESS: offset += 1 # how many values we need to shift, always `<= idx` elif offset > 0: - self.withdrawalQueue[idx-offset] = strategy + self.withdrawalQueue[idx - offset] = strategy self.withdrawalQueue[idx] = ZERO_ADDRESS @@ -501,21 +532,23 @@ def addStrategy( ): assert msg.sender == self.governance assert self.strategies[_strategy].activation == 0 - self.strategies[_strategy] = StrategyParams({ - performanceFee: _performanceFee, - activation: block.number, - debtLimit: _debtLimit, - rateLimit: _rateLimit, - lastReport: block.number, - totalDebt: 0, - totalReturns: 0, - }) + self.strategies[_strategy] = StrategyParams( + { + performanceFee: _performanceFee, + activation: block.number, + debtLimit: _debtLimit, + rateLimit: _rateLimit, + lastReport: block.number, + totalDebt: 0, + totalReturns: 0, + } + ) self.debtLimit += _debtLimit log StrategyAdded(_strategy, _debtLimit, _rateLimit, _performanceFee) # queue is full - assert self.withdrawalQueue[MAXIMUM_STRATEGIES-1] == ZERO_ADDRESS - self.withdrawalQueue[MAXIMUM_STRATEGIES-1] = _strategy + assert self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] == ZERO_ADDRESS + self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] = _strategy self._organizeWithdrawalQueue() @@ -594,15 +627,18 @@ def revokeStrategy(_strategy: address = msg.sender): def addStrategyToQueue(_strategy: address): assert msg.sender == self.governance # Must be a current strategy - assert self.strategies[_strategy].activation > 0 and self.strategies[_strategy].totalDebt > 0 + assert ( + self.strategies[_strategy].activation > 0 + and self.strategies[_strategy].totalDebt > 0 + ) # Check if queue is full - assert self.withdrawalQueue[MAXIMUM_STRATEGIES-1] == ZERO_ADDRESS + assert self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] == ZERO_ADDRESS # Can't already be in the queue for strategy in self.withdrawalQueue: if strategy == ZERO_ADDRESS: break assert strategy != _strategy - self.withdrawalQueue[MAXIMUM_STRATEGIES-1] = _strategy + self.withdrawalQueue[MAXIMUM_STRATEGIES - 1] = _strategy self._organizeWithdrawalQueue() @@ -692,9 +728,11 @@ def _expectedReturn(_strategy: address) -> uint256: strategy_totalReturns: uint256 = self.strategies[_strategy].totalReturns strategy_activation: uint256 = self.strategies[_strategy].activation - blockDelta: uint256 = (block.number - strategy_lastReport) + blockDelta: uint256 = block.number - strategy_lastReport if blockDelta > 0: - return (strategy_totalReturns * blockDelta) / (block.number - strategy_activation) + return (strategy_totalReturns * blockDelta) / ( + block.number - strategy_activation + ) else: return 0 # Covers the scenario when block.number == strategy_activation @@ -731,14 +769,16 @@ def report(_return: uint256) -> uint256: # Issue new shares to cover fees # NOTE: In effect, this reduces overall share price by the combined fee governance_fee: uint256 = ( - self._totalAssets() * (block.number - self.lastReport) * self.managementFee - ) / FEE_MAX / BLOCKS_PER_YEAR + (self._totalAssets() * (block.number - self.lastReport) * self.managementFee) + / FEE_MAX + / BLOCKS_PER_YEAR + ) self.lastReport = block.number strategist_fee: uint256 = 0 # Only applies in certain conditions # NOTE: Applies if strategy is not shutting down, or it is but all debt paid off # NOTE: No fee is taken when a strategy is unwinding it's position, until all debt is paid - if _return > debt: + if _return > debt: strategist_fee = ( (_return - debt) * self.strategies[msg.sender].performanceFee ) / FEE_MAX @@ -834,9 +874,9 @@ def erc20_safe_transfer(_token: address, _to: address, _value: uint256): concat( method_id("transfer(address,uint256)"), convert(_to, bytes32), - convert(_value, bytes32) + convert(_value, bytes32), ), - max_outsize=32 + max_outsize=32, ) if len(_response) > 0: assert convert(_response, bool), "Transfer failed!" diff --git a/requirements-dev.txt b/requirements-dev.txt index 4066ba08b..ac2aa2968 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,3 @@ black==19.10b0 eth-brownie>=1.11.10,<2.0.0 +pre-commit