@@ -4,6 +4,7 @@ pragma solidity ^0.8.24;
4
4
import {IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol " ;
5
5
import {IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol " ;
6
6
import {IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol " ;
7
+ import {IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol " ;
7
8
import {DynamicArrayLib} from "@solady/utils/DynamicArrayLib.sol " ;
8
9
import {LibTransient} from "@solady/utils/LibTransient.sol " ;
9
10
@@ -13,16 +14,13 @@ import {ReentrancyGuard} from "@solady/utils/ReentrancyGuard.sol";
13
14
14
15
import {BoostCore} from "contracts/BoostCore.sol " ;
15
16
import {BoostError} from "contracts/shared/BoostError.sol " ;
17
+ import {BoostLib} from "contracts/shared/BoostLib.sol " ;
16
18
import {ABudget} from "contracts/budgets/ABudget.sol " ;
17
19
import {ACloneable} from "contracts/shared/ACloneable.sol " ;
20
+ import {AIncentive} from "contracts/incentives/AIncentive.sol " ;
18
21
import {ATransparentBudget} from "contracts/budgets/ATransparentBudget.sol " ;
19
-
20
- /*
21
- TODO
22
- 1. implement clawback logic and tracking on deposits
23
- 2. implement clawback auth
24
- 3. implement permit2 support
25
- */
22
+ import {IClaw} from "contracts/shared/IClaw.sol " ;
23
+ import {IPermit2} from "contracts/shared/IPermit2.sol " ;
26
24
27
25
/// @title Simple ABudget
28
26
/// @notice A minimal budget implementation that simply holds and distributes tokens (ERC20-like and native)
@@ -32,6 +30,8 @@ contract TransparentBudget is ATransparentBudget, ReentrancyGuard {
32
30
using DynamicArrayLib for * ;
33
31
using LibTransient for * ;
34
32
33
+ IPermit2 public constant PERMIT2 = IPermit2 (0x000000000022D473030F116dDEE9F6B43aC78BA3 );
34
+
35
35
/// @dev The total amount of each fungible asset distributed from the budget
36
36
mapping (address => uint256 ) private _distributedFungible;
37
37
@@ -44,10 +44,7 @@ contract TransparentBudget is ATransparentBudget, ReentrancyGuard {
44
44
revert BoostError.NotImplemented ();
45
45
}
46
46
47
- function createBoost (bytes [] calldata _allocations , BoostCore core , bytes calldata _boostPayload )
48
- external
49
- payable
50
- {
47
+ function createBoost (bytes [] calldata _allocations , BoostCore core , bytes calldata _boostPayload ) public payable {
51
48
DynamicArrayLib.DynamicArray memory allocationKeys;
52
49
allocationKeys.resize (_allocations.length );
53
50
@@ -65,6 +62,58 @@ contract TransparentBudget is ATransparentBudget, ReentrancyGuard {
65
62
}
66
63
}
67
64
65
+ function createBoostWithPermit2 (
66
+ bytes [] calldata _allocations ,
67
+ BoostCore core ,
68
+ bytes calldata _boostPayload ,
69
+ bytes calldata _permit2Signature ,
70
+ uint256 nonce ,
71
+ uint256 deadline
72
+ ) external payable {
73
+ DynamicArrayLib.DynamicArray memory allocationKeys;
74
+ allocationKeys.resize (_allocations.length );
75
+
76
+ IPermit2.SignatureTransferDetails[] memory transferDetails =
77
+ new IPermit2.SignatureTransferDetails [](_allocations.length );
78
+ IPermit2.TokenPermissions[] memory permissions = new IPermit2.TokenPermissions [](_allocations.length );
79
+ bytes32 key;
80
+ for (uint256 i = 0 ; i < _allocations.length ; i++ ) {
81
+ Transfer memory request = abi.decode (_allocations[i], (Transfer));
82
+ if (request.assetType == AssetType.ERC20 ) {
83
+ (address asset , uint256 amount , bytes32 tKey ) = _allocateERC20 (request, false );
84
+ key = tKey;
85
+ transferDetails[i] = IPermit2.SignatureTransferDetails ({to: address (this ), requestedAmount: amount});
86
+ permissions[i] = IPermit2.TokenPermissions ({token: IERC20 (asset), amount: amount});
87
+ } else {
88
+ key = _allocate (_allocations[i]);
89
+ }
90
+ allocationKeys.set (i, key);
91
+ }
92
+
93
+ PERMIT2.permitTransferFrom (
94
+ // The permit message. Spender will be inferred as the caller (us).
95
+ IPermit2.PermitBatchTransferFrom ({permitted: permissions, nonce: nonce, deadline: deadline}),
96
+ // The transfer recipients and amounts.
97
+ transferDetails,
98
+ // The owner of the tokens, which must also be
99
+ // the signer of the message, otherwise this call
100
+ // will fail.
101
+ msg .sender ,
102
+ // The packed signature that was the result of signing
103
+ // the EIP712 hash of `permit`.
104
+ _permit2Signature
105
+ );
106
+
107
+ // Transfer `payload.amount` of the token to this contract
108
+
109
+ core.createBoost (_boostPayload);
110
+ bytes32 [] memory keys = allocationKeys.asBytes32Array ();
111
+ for (uint256 i = 0 ; i < keys.length ; i++ ) {
112
+ LibTransient.TUint256 storage p = LibTransient.tUint256 (keys[i]);
113
+ if (p.get () != 0 ) revert BoostError.Unauthorized ();
114
+ }
115
+ }
116
+
68
117
/// @notice Allocates assets to be distributed in the boost
69
118
/// @param data_ The packed data for the {Transfer} request
70
119
/// @return key The key of the amount allocated
@@ -83,16 +132,7 @@ contract TransparentBudget is ATransparentBudget, ReentrancyGuard {
83
132
p.inc (payload.amount);
84
133
key = tKey;
85
134
} else if (request.assetType == AssetType.ERC20 ) {
86
- FungiblePayload memory payload = abi.decode (request.data, (FungiblePayload));
87
-
88
- // Transfer `payload.amount` of the token to this contract
89
- request.asset.safeTransferFrom (request.target, address (this ), payload.amount);
90
- if (request.asset.balanceOf (address (this )) < payload.amount) {
91
- revert InvalidAllocation (request.asset, payload.amount);
92
- }
93
- key = bytes32 (uint256 (uint160 (request.asset)));
94
- (LibTransient.TUint256 storage p , bytes32 tKey ) = getFungibleAmountAndKey (request.asset);
95
- p.inc (payload.amount);
135
+ (,, bytes32 tKey ) = _allocateERC20 (request, true );
96
136
key = tKey;
97
137
} else if (request.assetType == AssetType.ERC1155 ) {
98
138
ERC1155Payload memory payload = abi.decode (request.data, (ERC1155Payload ));
@@ -113,6 +153,24 @@ contract TransparentBudget is ATransparentBudget, ReentrancyGuard {
113
153
}
114
154
}
115
155
156
+ function _allocateERC20 (Transfer memory request , bool transfer )
157
+ internal
158
+ returns (address asset , uint256 amount , bytes32 key )
159
+ {
160
+ FungiblePayload memory payload = abi.decode (request.data, (FungiblePayload));
161
+ if (transfer) {
162
+ request.asset.safeTransferFrom (request.target, address (this ), payload.amount);
163
+ if (request.asset.balanceOf (address (this )) < payload.amount) {
164
+ revert InvalidAllocation (request.asset, payload.amount);
165
+ }
166
+ }
167
+ (LibTransient.TUint256 storage p , bytes32 tKey ) = getFungibleAmountAndKey (request.asset);
168
+ p.inc (payload.amount);
169
+ amount = payload.amount;
170
+ asset = request.asset;
171
+ key = tKey;
172
+ }
173
+
116
174
function isAuthorized (address account_ ) public view virtual override returns (bool ) {
117
175
if (account_ == address (this )) return true ;
118
176
return false ;
@@ -145,6 +203,21 @@ contract TransparentBudget is ATransparentBudget, ReentrancyGuard {
145
203
revert BoostError.NotImplemented ();
146
204
}
147
205
206
+ function clawbackFromTarget (address target , bytes calldata data_ , uint256 boostId , uint256 incentiveId )
207
+ external
208
+ virtual
209
+ override
210
+ returns (uint256 , address )
211
+ {
212
+ BoostLib.Boost memory boost = BoostCore (target).getBoost (boostId);
213
+ if (msg .sender != boost.owner) revert BoostError.Unauthorized ();
214
+ AIncentive.ClawbackPayload memory payload =
215
+ AIncentive.ClawbackPayload ({target: address (msg .sender ), data: data_});
216
+ IClaw incentive = IClaw (target);
217
+ (uint256 amount , address asset ) = incentive.clawback (abi.encode (payload), boostId, incentiveId);
218
+ return (amount, asset);
219
+ }
220
+
148
221
/// @inheritdoc ABudget
149
222
/// @notice Disburses assets from the budget to a single recipient
150
223
/// @param data_ The packed {Transfer} request
0 commit comments