diff --git a/contracts/debtAllocators/DebtAllocator.sol b/contracts/debtAllocators/DebtAllocator.sol index 40c149e..2890261 100644 --- a/contracts/debtAllocators/DebtAllocator.sol +++ b/contracts/debtAllocators/DebtAllocator.sol @@ -1,10 +1,15 @@ -// SPDX-License-Identifier: GNU AGPLv3 +// SPDX-License-Identifier: AGPL-3.0 pragma solidity >=0.8.18; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; -import {DebtAllocatorFactory} from "./DebtAllocatorFactory.sol"; + +interface IDebtAllocatorFactory { + function governance() external view returns (address); + function keepers(address) external view returns (bool); + function isCurrentBaseFeeAcceptable() external view returns (bool); +} /** * @title YearnV3 Debt Allocator @@ -98,7 +103,7 @@ contract DebtAllocator { /// @notice Check the Factories governance address. function _isGovernance() internal view virtual { require( - msg.sender == DebtAllocatorFactory(factory).governance(), + msg.sender == IDebtAllocatorFactory(factory).governance(), "!governance" ); } @@ -107,14 +112,14 @@ contract DebtAllocator { function _isManager() internal view virtual { require( managers[msg.sender] || - msg.sender == DebtAllocatorFactory(factory).governance(), + msg.sender == IDebtAllocatorFactory(factory).governance(), "!manager" ); } /// @notice Check is one of the allowed keepers. function _isKeeper() internal view virtual { - require(DebtAllocatorFactory(factory).keepers(msg.sender), "!keeper"); + require(IDebtAllocatorFactory(factory).keepers(msg.sender), "!keeper"); } uint256 internal constant MAX_BPS = 10_000; @@ -221,7 +226,7 @@ contract DebtAllocator { if (!config.added) return (false, bytes("!added")); // Check the base fee isn't too high. - if (!DebtAllocatorFactory(factory).isCurrentBaseFeeAcceptable()) { + if (!IDebtAllocatorFactory(factory).isCurrentBaseFeeAcceptable()) { return (false, bytes("Base Fee")); } diff --git a/contracts/debtAllocators/DebtAllocatorFactory.sol b/contracts/debtAllocators/DebtAllocatorFactory.sol index 5841483..3b9eb20 100644 --- a/contracts/debtAllocators/DebtAllocatorFactory.sol +++ b/contracts/debtAllocators/DebtAllocatorFactory.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GNU AGPLv3 +// SPDX-License-Identifier: AGPL-3.0 pragma solidity >=0.8.18; import {DebtAllocator} from "./DebtAllocator.sol"; diff --git a/contracts/debtAllocators/DebtOptimizerApplicator.sol b/contracts/debtAllocators/DebtOptimizerApplicator.sol index 85eccff..e550390 100644 --- a/contracts/debtAllocators/DebtOptimizerApplicator.sol +++ b/contracts/debtAllocators/DebtOptimizerApplicator.sol @@ -1,9 +1,22 @@ // SPDX-License-Identifier: GNU AGPLv3 pragma solidity >=0.8.18; -import {DebtAllocator, DebtAllocatorFactory} from "./DebtAllocator.sol"; +import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol"; +import {IDebtAllocatorFactory} from "./DebtAllocator.sol"; -contract DebtOptimizerApplicator { +interface IDebtAllocator { + function setStrategyDebtRatio( + address _strategy, + uint256 _targetRatio + ) external; + function setStrategyDebtRatio( + address _strategy, + uint256 _targetRatio, + uint256 _maxRatio + ) external; +} + +contract DebtOptimizerApplicator is Multicall { /// @notice An event emitted when a keeper is added or removed. event UpdateManager(address indexed manager, bool allowed); @@ -30,7 +43,7 @@ contract DebtOptimizerApplicator { function _isGovernance() internal view virtual { require( msg.sender == - DebtAllocatorFactory(debtAllocatorFactory).governance(), + IDebtAllocatorFactory(debtAllocatorFactory).governance(), "!governance" ); } @@ -40,7 +53,7 @@ contract DebtOptimizerApplicator { require( managers[msg.sender] || msg.sender == - DebtAllocatorFactory(debtAllocatorFactory).governance(), + IDebtAllocatorFactory(debtAllocatorFactory).governance(), "!manager" ); } @@ -75,12 +88,12 @@ contract DebtOptimizerApplicator { ) public onlyManagers { for (uint8 i; i < _strategyDebtRatios.length; ++i) { if (_strategyDebtRatios[i].maxRatio == 0) { - DebtAllocator(_debtAllocator).setStrategyDebtRatio( + IDebtAllocator(_debtAllocator).setStrategyDebtRatio( _strategyDebtRatios[i].strategy, _strategyDebtRatios[i].targetRatio ); } else { - DebtAllocator(_debtAllocator).setStrategyDebtRatio( + IDebtAllocator(_debtAllocator).setStrategyDebtRatio( _strategyDebtRatios[i].strategy, _strategyDebtRatios[i].targetRatio, _strategyDebtRatios[i].maxRatio diff --git a/tests/conftest.py b/tests/conftest.py index 7a190ec..f81e591 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -404,13 +404,19 @@ def debt_allocator_factory(deploy_debt_allocator_factory): @pytest.fixture(scope="session") -def debt_allocator(debt_allocator_factory, project, vault, daddy): - tx = debt_allocator_factory.newDebtAllocator(vault, sender=daddy) +def create_debt_allocator(debt_allocator_factory, daddy): + def create_debt_allocator(vault): + tx = debt_allocator_factory.newDebtAllocator(vault, sender=daddy) + event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] + debt_allocator = project.DebtAllocator.at(event.allocator) + return debt_allocator - event = list(tx.decode_logs(debt_allocator_factory.NewDebtAllocator))[0] + yield create_debt_allocator - debt_allocator = project.DebtAllocator.at(event.allocator) +@pytest.fixture(scope="session") +def debt_allocator(create_debt_allocator, vault): + debt_allocator = create_debt_allocator(vault) yield debt_allocator diff --git a/tests/debtAllocators/test_debt_optimizer_applicator.py b/tests/debtAllocators/test_debt_optimizer_applicator.py index 440d39c..565808b 100644 --- a/tests/debtAllocators/test_debt_optimizer_applicator.py +++ b/tests/debtAllocators/test_debt_optimizer_applicator.py @@ -1,6 +1,7 @@ import ape from ape import chain, project from utils.constants import MAX_INT, ROLES +import itertools def test_setup(debt_optimizer_applicator, debt_allocator_factory, brain): @@ -111,3 +112,61 @@ def test_set_ratios( 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) + + +def test_set_ratios_multicall( + debt_optimizer_applicator, + debt_allocator, + brain, + daddy, + asset, + create_vault, + create_strategy, + create_debt_allocator, + user, +): + debt_allocators: dict[str, list[str]] = {} + for _ in range(2): + vault = create_vault(asset) + debt_allocator = create_debt_allocator(vault) + debt_allocator.setManager(debt_optimizer_applicator, True, sender=brain) + debt_allocator.setMinimumChange(1, sender=brain) + debt_allocators[debt_allocator.address] = [] + for _ in range(2): + debt_allocators[debt_allocator.address].append(create_strategy(asset)) + + calldata = [ + debt_optimizer_applicator.setStrategyDebtRatios.encode_input( + allocator, [(strategy, int(5_000), int(0)) for strategy in strategies] + ) + for allocator, strategies in debt_allocators.items() + ] + + with ape.reverts("!manager"): + debt_optimizer_applicator.multicall(calldata, sender=user) + + tx = debt_optimizer_applicator.multicall( + calldata, + sender=brain, + ) + + events = list(tx.decode_logs(debt_allocator.UpdateStrategyDebtRatio)) + strategies = list(itertools.chain(*debt_allocators.values())) + + assert len(events) == 4 + for event in events: + assert event.strategy in strategies + assert event.newTargetRatio == 5_000 + assert event.newMaxRatio == int(5_000 * 1.2) + + for debt_allocator_addr, strategies in debt_allocators.items(): + debt_allocator = project.DebtAllocator.at(debt_allocator_addr) + assert debt_allocator.totalDebtRatio() == 10_000 + for strategy in strategies: + assert debt_allocator.getConfig(strategy) == ( + True, + 5_000, + 5_000 * 1.2, + 0, + 0, + )