Skip to content

Commit

Permalink
feat: auto health check (#31)
Browse files Browse the repository at this point in the history
* feat: auto health check

* fix: readme
  • Loading branch information
Schlagonia authored Oct 30, 2023
1 parent 81a47eb commit ed73d39
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 109 deletions.
21 changes: 1 addition & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,29 +86,10 @@ Health Checks can be used by a strategy to assure automated reports are not unex
It's important to note that the health check does not stop losses from being reported, rather will require manual intervention from 'management' for out of range losses or gains.

A strategist simply has to inherit the [BaseHealthCheck](https://github.com/yearn/tokenized-strategy-periphery/blob/master/src/HealthCheck/BaseHealthCheck.sol) contract in their strategy, set the profit and loss limit ratios with the needed setters, and then call `_executeHealthCheck(uint256)` with the expected return value as the parameter during `_harvestAndReport`.

EX:

contract Strategy is BaseHealthCheck {
...

function _harvestAndReport() internal override returns (uint256 _totalAssets) {
...

_executeHealthCheck(_totalAssets);

}
}
A strategist simply has to inherit the [BaseHealthCheck](https://github.com/yearn/tokenized-strategy-periphery/blob/master/src/HealthCheck/BaseHealthCheck.sol) contract in their strategy, set the profit and loss limit ratios with the needed setters, and then override `_harvestAndReport()` just as they otherwise would. If the profit or loss that would be recorded is outside the acceptable bounds the tx will revert.
The profit and loss ratios can adjusted by management through their specific setters as well as turning the healthCheck off for a specific report. If turned off the health check will automatically turn back on for the next report.
The Health check contract also comes with a [checkHealth](https://github.com/yearn/tokenized-strategy-periphery/blob/master/src/HealthCheck/BaseHealthCheck.sol#L28) modifier that can be put on functions that will check any strategy specific invariants/checks that can be defined in the [_checkHealth](https://github.com/yearn/tokenized-strategy-periphery/blob/master/src/HealthCheck/BaseHealthCheck.sol#L124) function.

NOTE: This should revert to work properly as a modifier if the check is false.

see [MockHealthCheck](https://github.com/yearn/tokenized-strategy-periphery/blob/master/src/test/mocks/MockHealthCheck.sol) for an example.

## Apr Oracle
For easy integration with on chain debt allocator's as well as off chain interfaces, strategist's can implement their own custom 'AprOracle'.
Expand Down
23 changes: 20 additions & 3 deletions src/HealthCheck/BaseHealthCheck.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import {BaseStrategy, ERC20} from "@tokenized-strategy/BaseStrategy.sol";
* `checkHealth` modifier.
*
* A strategist simply needs to inherit this contract. Set
* the limit ratios to the desired amounts and then call
* `_executeHealthCheck(...)` during the `_harvestAndReport()`
* execution. If the profit or loss that would be recorded is
* the limit ratios to the desired amounts and then
* override `_harvestAndReport()` just as they otherwise
* would. If the profit or loss that would be recorded is
* outside the acceptable bounds the tx will revert.
*
* The healthcheck does not prevent a strategy from reporting
Expand Down Expand Up @@ -109,6 +109,23 @@ abstract contract BaseHealthCheck is BaseStrategy {
doHealthCheck = _doHealthCheck;
}

/**
* @notice OVerrides the default {harvestAndReport} to include a healthcheck.

This comment has been minimized.

Copy link
@spalen0

spalen0 Oct 31, 2023

Typo OVerrides

This comment has been minimized.

Copy link
@Schlagonia

Schlagonia Nov 7, 2023

Author Collaborator

#32

* @return _totalAssets New totalAssets post report.
*/
function harvestAndReport()
external
override
onlySelf
returns (uint256 _totalAssets)
{
// Let the strategy report.
_totalAssets = _harvestAndReport();

// Run the healthcheck on the amount returned.
_executeHealthCheck(_totalAssets);
}

/**
* @dev To be called during a report to make sure the profit
* or loss being recorded is within the acceptable bound.
Expand Down
51 changes: 0 additions & 51 deletions src/test/HealthCheck.sol → src/test/HealthCheck.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -428,55 +428,4 @@ contract HealthCheckTest is Setup {
"doHealthCheck should be true"
);
}

function test__checkHealthModifier(uint256 _amount) public {
vm.assume(_amount >= minFuzzAmount && _amount <= maxFuzzAmount);

// deposit
mintAndDepositIntoStrategy(
IStrategy(address(healthCheck)),
user,
_amount
);

assertEq(healthCheck.healthy(), true);
assertEq(healthCheck.availableDepositLimit(user), type(uint256).max);
assertEq(healthCheck.availableWithdrawLimit(user), type(uint256).max);

healthCheck.setHealthy(false);

assertEq(healthCheck.healthy(), false);
assertEq(healthCheck.availableDepositLimit(user), 0);
assertEq(healthCheck.availableWithdrawLimit(user), 0);

vm.expectRevert("unhealthy");
vm.prank(keeper);
healthCheck.report();

healthCheck.setHealthy(true);

assertEq(healthCheck.healthy(), true);
assertEq(healthCheck.availableDepositLimit(user), type(uint256).max);
assertEq(healthCheck.availableWithdrawLimit(user), type(uint256).max);

vm.prank(keeper);
(uint256 realProfit, ) = healthCheck.report();

// Make sure we reported the correct profit
assertEq(0, realProfit, "Reported profit mismatch");

// Health Check should still be on
assertEq(
healthCheck.doHealthCheck(),
true,
"doHealthCheck should be true"
);

skip(healthCheck.profitMaxUnlockTime());

vm.prank(user);
healthCheck.redeem(_amount, user, user);

assertGe(asset.balanceOf(user), _amount);
}
}
36 changes: 1 addition & 35 deletions src/test/mocks/MockHealthCheck.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,44 +22,10 @@ contract MockHealthCheck is BaseHealthCheck {
override
returns (uint256 _totalAssets)
{
require(_healthy(), "unhealthy");

_totalAssets = asset.balanceOf(address(this));

_executeHealthCheck(_totalAssets);
}

// Can't deposit if its not healthy
function availableDepositLimit(
address _owner
) public view override returns (uint256) {
if (!_healthy()) return 0;

return super.availableDepositLimit(_owner);
}

// Can't Withdraw if not healthy.
function availableWithdrawLimit(
address _owner
) public view override returns (uint256) {
if (!_healthy()) return 0;

return super.availableWithdrawLimit(_owner);
}

function _healthy() internal view returns (bool) {
return healthy;
}

function setHealthy(bool _health) external {
healthy = _health;
}
}

import {IBaseHealthCheck} from "../../HealthCheck/IBaseHealthCheck.sol";

interface IMockHealthCheck is IBaseHealthCheck {
function healthy() external view returns (bool);

function setHealthy(bool _health) external;
}
interface IMockHealthCheck is IBaseHealthCheck {}

0 comments on commit ed73d39

Please sign in to comment.