Skip to content

Commit 6b73d4f

Browse files
topocountQuazia
authored andcommitted
feat(evm): implement managed budget V2
This iteration of managed budget tracks BoostCore deployments that the budget targets over time, and allows managers to interact with deprecated versions via overloaded functions. Since much of the security model for boosts is contained in BoostCore, managers cannot access a boost core deployment that was not already set by an admin. management fee payouts on BoostCore against boosts not deployed by a budget fail thanks to pre-existing logic that tracks disbursements and stores the incentive address. If the incentive address in the boost does not match that of the disbursement, it will fail.
1 parent db2806a commit 6b73d4f

File tree

3 files changed

+1816
-0
lines changed

3 files changed

+1816
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity ^0.8.24;
3+
4+
import {AManagedBudgetWithFees} from "contracts/budgets/AManagedBudgetWithFees.sol";
5+
import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
6+
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
7+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
8+
9+
import {OwnableRoles} from "@solady/auth/OwnableRoles.sol";
10+
11+
import {ABudget} from "contracts/budgets/ABudget.sol";
12+
import {AManagedBudget} from "contracts/budgets/AManagedBudget.sol";
13+
import {ACloneable} from "contracts/shared/ACloneable.sol";
14+
import {AIncentive} from "contracts/incentives/AIncentive.sol";
15+
import {BoostCore} from "contracts/BoostCore.sol";
16+
import {IClaw} from "contracts/shared/IClaw.sol";
17+
18+
/// @title Managed Budget with Fees Version 2
19+
/// @notice This contract inherits from AManagedBudgetWithFees and can be extended with additional functionality
20+
abstract contract AManagedBudgetWithFeesV2 is AManagedBudgetWithFees {
21+
/// @dev allowlisted core instances
22+
mapping(BoostCore => bool) public coreAllowed;
23+
24+
/// @dev Emitted when BoostCore changes
25+
event BoostCoreUpdated(address boostCore);
26+
27+
/// @inheritdoc ACloneable
28+
function supportsInterface(bytes4 interfaceId)
29+
public
30+
view
31+
virtual
32+
override(AManagedBudgetWithFees)
33+
returns (bool)
34+
{
35+
return interfaceId == type(AManagedBudgetWithFees).interfaceId || super.supportsInterface(interfaceId);
36+
}
37+
38+
/// @notice Pays the management fee for a specific boost and incentive
39+
/// @param core BoostCore Address to the boost is located on
40+
/// @param boostId The ID of the boost for which the management fee is being paid
41+
/// @param incentiveId The ID of the incentive within the boost
42+
/// @dev The function checks the type of incentive and ensures that the claims have reached the limit
43+
/// or the balance is zero before transferring the management fee to the boost owner
44+
/// @dev Supports management fee payouts for {@link AERC20Incentive} and {@link AERC20VariableIncentive} deployments
45+
function payManagementFee(BoostCore core, uint256 boostId, uint256 incentiveId) external virtual;
46+
47+
/// @notice Claws back assets from a target and pays the management fee for a specific boost and incentive
48+
/// @param target The address of the target from which assets are being clawed back
49+
/// @param data_ The packed data for the clawback request
50+
/// @param core BoostCore Address to the boost is located on
51+
/// @param boostId The ID of the boost
52+
/// @param incentiveId The ID of the incentive within the boost for which the management fee is being paid
53+
/// @return amount The amount of assets clawed back
54+
/// @return asset The address of the asset clawed back
55+
/// @dev This function first claws back assets from the target and then pays
56+
/// the management fee for the specified boost and incentive
57+
function clawbackFromTargetAndPayFee(
58+
address target,
59+
bytes calldata data_,
60+
BoostCore core,
61+
uint256 boostId,
62+
uint256 incentiveId
63+
) external virtual returns (uint256 amount, address asset);
64+
65+
/// @notice sets the current BoostCore instance
66+
/// @param core the address of the new BoostCore instance to default to
67+
/// @dev older boost core instances can be accessed via the override functions with the `core` param
68+
function setCore(address core) external virtual;
69+
70+
/// @inheritdoc ACloneable
71+
function getComponentInterface() public pure virtual override returns (bytes4) {
72+
return type(AManagedBudgetWithFeesV2).interfaceId;
73+
}
74+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity ^0.8.24;
3+
4+
import {AManagedBudgetWithFeesV2} from "contracts/budgets/AManagedBudgetWithFeesV2.sol";
5+
import {ManagedBudgetWithFees} from "contracts/budgets/ManagedBudgetWithFees.sol";
6+
import {ACloneable} from "contracts/shared/ACloneable.sol";
7+
import {BoostCore} from "contracts/BoostCore.sol";
8+
import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
9+
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
10+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
11+
12+
import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol";
13+
import {ReentrancyGuard} from "@solady/utils/ReentrancyGuard.sol";
14+
15+
import {BoostError} from "contracts/shared/BoostError.sol";
16+
import {BoostLib} from "contracts/shared/BoostLib.sol";
17+
import {BoostCore} from "contracts/BoostCore.sol";
18+
import {AManagedBudget} from "contracts/budgets/AManagedBudget.sol";
19+
import {AManagedBudgetWithFees} from "contracts/budgets/AManagedBudgetWithFees.sol";
20+
import {ManagedBudgetWithFees, ManagedBudget} from "contracts/budgets/ManagedBudgetWithFees.sol";
21+
import {ABudget} from "contracts/budgets/ABudget.sol";
22+
import {ACloneable} from "contracts/shared/ACloneable.sol";
23+
import {AERC20Incentive} from "contracts/incentives/AERC20Incentive.sol";
24+
import {AERC20VariableIncentive} from "contracts/incentives/AERC20VariableIncentive.sol";
25+
import {AIncentive} from "contracts/incentives/AIncentive.sol";
26+
import {IClaw} from "contracts/shared/IClaw.sol";
27+
28+
/// @title Managed Budget with Fees Version 2
29+
/// @notice This contract inherits from AManagedBudgetWithFeesV2 and ManagedBudgetWithFees
30+
contract ManagedBudgetWithFeesV2 is AManagedBudgetWithFeesV2, ManagedBudgetWithFees {
31+
using SafeTransferLib for address;
32+
33+
/// @notice Construct a new ManagedBudget
34+
/// @dev Because this contract is a base implementation, it should not be initialized through the constructor. Instead, it should be cloned and initialized using the {initialize} function.
35+
constructor() {
36+
_disableInitializers();
37+
}
38+
39+
/// @inheritdoc ACloneable
40+
/// @param data_ The packed init data for the budget `(address owner, address[] authorized, uint256[] roles)`
41+
function initialize(bytes calldata data_) public virtual override(ACloneable, ManagedBudgetWithFees) initializer {
42+
InitPayloadWithFee memory init_ = abi.decode(data_, (InitPayloadWithFee));
43+
_initializeOwner(init_.owner);
44+
for (uint256 i = 1; i < init_.authorized.length; i++) {
45+
_setRoles(init_.authorized[i], init_.roles[i]);
46+
}
47+
48+
if (init_.authorized.length == 0) revert("no core contract set");
49+
_setCore(init_.authorized[0]);
50+
managementFee = init_.managementFee;
51+
emit ManagementFeeSet(init_.managementFee);
52+
}
53+
54+
function _setCore(address _core) internal {
55+
core = BoostCore(_core);
56+
coreAllowed[BoostCore(_core)] = true;
57+
_setRoles(_core, MANAGER_ROLE);
58+
emit BoostCoreUpdated(_core);
59+
}
60+
61+
/// @inheritdoc AManagedBudgetWithFeesV2
62+
function payManagementFee(BoostCore core, uint256 boostId, uint256 incentiveId) public override {
63+
if (!coreAllowed[core]) revert BoostError.Unauthorized();
64+
BoostLib.Boost memory boost = core.getBoost(boostId);
65+
66+
address validIncentive = address(boost.incentives[incentiveId]);
67+
68+
if (AIncentive(validIncentive).supportsInterface(type(AERC20Incentive).interfaceId)) {
69+
uint256 claims = AERC20Incentive(validIncentive).claims();
70+
uint256 limit = AERC20Incentive(validIncentive).limit();
71+
_transferManagementFee(boostId, incentiveId, boost, claims, limit);
72+
return;
73+
}
74+
if (AIncentive(validIncentive).supportsInterface(type(AERC20VariableIncentive).interfaceId)) {
75+
uint256 totalClaimed = AERC20VariableIncentive(validIncentive).totalClaimed();
76+
uint256 limit = AERC20VariableIncentive(validIncentive).limit();
77+
_transferManagementFee(boostId, incentiveId, boost, totalClaimed, limit);
78+
return;
79+
}
80+
revert BoostError.NotImplemented();
81+
}
82+
83+
/// @inheritdoc ABudget
84+
function clawbackFromTarget(address target, bytes calldata data_, uint256 boostId, uint256 incentiveId)
85+
public
86+
virtual
87+
override(ABudget, ManagedBudgetWithFees)
88+
onlyAuthorized
89+
returns (uint256, address)
90+
{
91+
return ManagedBudgetWithFees.clawbackFromTarget(target, data_, boostId, incentiveId);
92+
}
93+
94+
/// @inheritdoc AManagedBudgetWithFeesV2
95+
function clawbackFromTargetAndPayFee(
96+
address target,
97+
bytes calldata data_,
98+
BoostCore core,
99+
uint256 boostId,
100+
uint256 incentiveId
101+
) external virtual override returns (uint256 amount, address asset) {
102+
(amount, asset) = ManagedBudgetWithFees.clawbackFromTarget(target, data_, boostId, incentiveId);
103+
payManagementFee(core, boostId, incentiveId);
104+
}
105+
106+
function setCore(address core) external virtual override onlyOwnerOrRoles(ADMIN_ROLE) {
107+
_setCore(core);
108+
}
109+
110+
/// @inheritdoc ACloneable
111+
function supportsInterface(bytes4 interfaceId)
112+
public
113+
view
114+
virtual
115+
override(AManagedBudgetWithFeesV2, ManagedBudgetWithFees)
116+
returns (bool)
117+
{
118+
return AManagedBudgetWithFeesV2.supportsInterface(interfaceId);
119+
}
120+
121+
/// @inheritdoc ACloneable
122+
function getComponentInterface()
123+
public
124+
pure
125+
virtual
126+
override(AManagedBudgetWithFeesV2, ManagedBudgetWithFees)
127+
returns (bytes4)
128+
{
129+
return type(AManagedBudgetWithFeesV2).interfaceId;
130+
}
131+
}

0 commit comments

Comments
 (0)