Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: debt optimizer applicator #46

Merged
merged 5 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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[] memory _strategyDebtRatios // TODO: use calldata instead of memory?
fp-crypto marked this conversation as resolved.
Show resolved Hide resolved
) 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
115 changes: 115 additions & 0 deletions tests/debtAllocators/test_debt_optimizer_applicator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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)


Loading