Skip to content

Commit

Permalink
feat: debt optimizer applicator (#46)
Browse files Browse the repository at this point in the history
* feat: debt optimizer applicator

* fix: emit

* fix: bug

* chore: test optimizer applicator

* fix: use calldata
  • Loading branch information
fp-crypto authored Jun 13, 2024
1 parent 26c43a9 commit e648962
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 0 deletions.
91 changes: 91 additions & 0 deletions contracts/debtAllocators/DebtOptimizerApplicator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: GNU AGPLv3
pragma solidity >=0.8.18;

import {DebtAllocator, DebtAllocatorFactory} from "./DebtAllocator.sol";

contract DebtOptimizerApplicator {
/// @notice An event emitted when a keeper is added or removed.
event UpdateManager(address indexed manager, bool allowed);

/// @notice struct for debt ratio changes
struct StrategyDebtRatio {
address strategy;
uint256 targetRatio;
uint256 maxRatio;
}

/// @notice Make sure the caller is governance.
modifier onlyGovernance() {
_isGovernance();
_;
}

/// @notice Make sure the caller is governance or a manager.
modifier onlyManagers() {
_isManager();
_;
}

/// @notice Check the Factories governance address.
function _isGovernance() internal view virtual {
require(
msg.sender ==
DebtAllocatorFactory(debtAllocatorFactory).governance(),
"!governance"
);
}

/// @notice Check is either factories governance or local manager.
function _isManager() internal view virtual {
require(
managers[msg.sender] ||
msg.sender ==
DebtAllocatorFactory(debtAllocatorFactory).governance(),
"!manager"
);
}

/// @notice The address of the debt allocator factory to use for some role checks.
address public immutable debtAllocatorFactory;

/// @notice Mapping of addresses that are allowed to update debt ratios.
mapping(address => bool) public managers;

constructor(address _debtAllocatorFactory) {
debtAllocatorFactory = _debtAllocatorFactory;
}

/**
* @notice Set if a manager can update ratios.
* @param _address The address to set mapping for.
* @param _allowed If the address can call {update_debt}.
*/
function setManager(
address _address,
bool _allowed
) external virtual onlyGovernance {
managers[_address] = _allowed;

emit UpdateManager(_address, _allowed);
}

function setStrategyDebtRatios(
address _debtAllocator,
StrategyDebtRatio[] calldata _strategyDebtRatios
) public onlyManagers {
for (uint8 i; i < _strategyDebtRatios.length; ++i) {
if (_strategyDebtRatios[i].maxRatio == 0) {
DebtAllocator(_debtAllocator).setStrategyDebtRatio(
_strategyDebtRatios[i].strategy,
_strategyDebtRatios[i].targetRatio
);
} else {
DebtAllocator(_debtAllocator).setStrategyDebtRatio(
_strategyDebtRatios[i].strategy,
_strategyDebtRatios[i].targetRatio,
_strategyDebtRatios[i].maxRatio
);
}
}
}
}
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ def debt_allocator(debt_allocator_factory, project, vault, daddy):
yield debt_allocator


@pytest.fixture(scope="session")
def debt_optimizer_applicator(debt_allocator_factory, project, brain):

yield brain.deploy(project.DebtOptimizerApplicator, debt_allocator_factory.address)


@pytest.fixture(scope="session")
def deploy_role_manager(
project, daddy, brain, security, keeper, strategy_manager, registry
Expand Down
113 changes: 113 additions & 0 deletions tests/debtAllocators/test_debt_optimizer_applicator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import ape
from ape import chain, project
from utils.constants import MAX_INT, ROLES


def test_setup(debt_optimizer_applicator, debt_allocator_factory, brain):

assert debt_optimizer_applicator.managers(brain) == False
assert (
debt_optimizer_applicator.debtAllocatorFactory()
== debt_allocator_factory.address
)


def test_set_managers(debt_optimizer_applicator, brain, user):
assert debt_optimizer_applicator.managers(brain) == False
assert debt_optimizer_applicator.managers(user) == False

with ape.reverts("!governance"):
debt_optimizer_applicator.setManager(user, True, sender=user)

tx = debt_optimizer_applicator.setManager(user, True, sender=brain)

event = list(tx.decode_logs(debt_optimizer_applicator.UpdateManager))[0]

assert event.manager == user
assert event.allowed == True
assert debt_optimizer_applicator.managers(user) == True

tx = debt_optimizer_applicator.setManager(user, False, sender=brain)

event = list(tx.decode_logs(debt_optimizer_applicator.UpdateManager))[0]

assert event.manager == user
assert event.allowed == False
assert debt_optimizer_applicator.managers(user) == False


def test_set_ratios(
debt_optimizer_applicator,
debt_allocator,
brain,
daddy,
vault,
strategy,
create_strategy,
user,
):
max = int(6_000)
target = int(5_000)
strategy_debt_ratio = (strategy.address, target, max)

debt_allocator.setManager(debt_optimizer_applicator, True, sender=brain)
debt_allocator.setMinimumChange(1, sender=brain)

with ape.reverts("!manager"):
debt_optimizer_applicator.setStrategyDebtRatios(
debt_allocator, [strategy_debt_ratio], sender=user
)

vault.add_strategy(strategy.address, sender=daddy)

tx = debt_optimizer_applicator.setStrategyDebtRatios(
debt_allocator, [strategy_debt_ratio], sender=brain
)

event = list(tx.decode_logs(debt_allocator.StrategyChanged))[0]

assert event.strategy == strategy
assert event.status == 1

event = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))[0]

assert event.newTargetRatio == target
assert event.newMaxRatio == max
assert event.newTotalDebtRatio == target
assert debt_allocator.totalDebtRatio() == target
assert debt_allocator.getConfig(strategy) == (True, target, max, 0, 0)

new_strategy = create_strategy()
vault.add_strategy(new_strategy, sender=daddy)

with ape.reverts("ratio too high"):
debt_optimizer_applicator.setStrategyDebtRatios(
debt_allocator,
[(new_strategy.address, int(10_000), int(10_000))],
sender=brain,
)

tx = debt_optimizer_applicator.setStrategyDebtRatios(
debt_allocator,
[
(strategy.address, int(8_000), int(9_000)),
(new_strategy.address, int(2_000), int(0)),
],
sender=brain,
)

events = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio))

assert len(events) == 2
for event in events:
assert event.strategy in [strategy, new_strategy]
if event.strategy == strategy:
assert event.newTargetRatio == 8_000
assert event.newMaxRatio == 9_000
else:
assert event.newTargetRatio == 2_000
assert event.newMaxRatio == 2_000 * 1.2

assert debt_allocator.totalDebtRatio() == 10_000
assert debt_allocator.getConfig(strategy) == (True, 8_000, 9_000, 0, 0)
assert debt_allocator.getConfig(new_strategy) == (True, 2_000, 2_000 * 1.2, 0, 0)

0 comments on commit e648962

Please sign in to comment.