22pragma solidity ^ 0.8.28 ;
33
44import { ISafe } from "src/interfaces/ISafe.sol " ;
5+ import { IHubV2 } from "src/interfaces/IHub.sol " ;
6+ import { IMultiSend } from "src/interfaces/IMultiSend.sol " ;
57import { CirclesLib } from "src/libs/CirclesLib.sol " ;
68import { Errors } from "src/libs/Errors.sol " ;
7- import { Subscription } from "src/libs/Types.sol " ;
9+ import { Subscription, Category } from "src/libs/Types.sol " ;
810import { SubscriptionLib } from "src/libs/SubscriptionLib.sol " ;
911
1012import { ERC1155 } from "@circles/src/circles/ERC1155.sol " ;
11- import { IHubV2 } from "@circles/src/hub/IHub.sol " ;
1213import { TypeDefinitions } from "@circles/src/hub/TypeDefinitions.sol " ;
1314import { Enum } from "@safe-smart-account/contracts/common/Enum.sol " ;
1415import { EnumerableSetLib } from "@solady/src/utils/EnumerableSetLib.sol " ;
16+ import { LibTransient } from "@solady/src/utils/LibTransient.sol " ;
1517
1618contract SubscriptionModule {
1719 /*//////////////////////////////////////////////////////////////
@@ -26,6 +28,8 @@ contract SubscriptionModule {
2628
2729 using CirclesLib for TypeDefinitions.Stream[];
2830
31+ using LibTransient for LibTransient.TUint256;
32+
2933 /*//////////////////////////////////////////////////////////////
3034 STATE VARIABLES
3135 //////////////////////////////////////////////////////////////*/
@@ -35,11 +39,13 @@ contract SubscriptionModule {
3539
3640 address public constant HUB = 0xc12C1E50ABB450d6205Ea2C3Fa861b3B834d13e8 ;
3741
38- mapping (bytes32 id = > address safe ) public safeFromId;
42+ address public constant MULTISEND = 0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526 ;
43+
44+ bytes32 internal constant T_REDEEMABLE_AMOUNT = 0x70bfbb43a5ce660914e09d1b48fcc488982d5981137b973eac35b0592a414e90 ;
3945
40- mapping (address safe = > mapping ( bytes32 id = > Subscription subscription ) ) internal _subscriptions;
46+ mapping (bytes32 id = > Subscription subscription ) internal _subscriptions;
4147
42- mapping (address safe = > EnumerableSetLib.Bytes32Set) internal ids;
48+ mapping (address subscriber = > EnumerableSetLib.Bytes32Set) internal ids;
4349
4450 /*//////////////////////////////////////////////////////////////
4551 EVENTS
@@ -50,18 +56,11 @@ contract SubscriptionModule {
5056 address indexed subscriber ,
5157 address indexed recipient ,
5258 uint256 amount ,
53- uint256 lastRedeemed ,
54- uint256 frequency ,
55- bool requireTrusted
59+ uint256 nextRedeemAt ,
60+ Category category
5661 );
5762
58- event Redeemed (
59- bytes32 indexed id ,
60- address indexed subscriber ,
61- address indexed recipient ,
62- uint256 lastRedeemed ,
63- bool requireTrusted
64- );
63+ event Redeemed (bytes32 indexed id , address indexed subscriber , address indexed recipient , uint256 nextRedeemAt );
6564
6665 event RecipientUpdated (bytes32 indexed id , address indexed oldRecipient , address indexed newRecipient );
6766
@@ -75,7 +74,7 @@ contract SubscriptionModule {
7574 address recipient ,
7675 uint256 amount ,
7776 uint256 frequency ,
78- bool requireTrusted
77+ Category category
7978 )
8079 external
8180 returns (bytes32 id )
@@ -87,75 +86,40 @@ contract SubscriptionModule {
8786 amount: amount,
8887 lastRedeemed: block .timestamp - frequency,
8988 frequency: frequency,
90- requireTrusted: requireTrusted
89+ category: category
9190 });
9291 id = sub.compute ();
93- _subscribe (msg .sender , id, sub);
94- emit SubscriptionCreated (
95- id, msg .sender , recipient, amount, block .timestamp - frequency, frequency, requireTrusted
96- );
97- }
98-
99- function redeem (
100- bytes32 id ,
101- address [] calldata flowVertices ,
102- TypeDefinitions.FlowEdge[] calldata flow ,
103- TypeDefinitions.Stream[] calldata streams ,
104- bytes calldata packedCoordinates ,
105- uint256 sourceCoordinate
106- )
107- external
108- {
109- (address safe , Subscription memory sub ) = _loadSubscription (id);
110-
111- uint256 periods = _requireRedeemablePeriods (sub);
112-
113- require (flowVertices[sourceCoordinate] == sub.subscriber, Errors.InvalidSubscriber ());
114-
115- require (streams.checkSource (sourceCoordinate), Errors.InvalidStreamSource ());
116-
117- require (streams.checkRecipients (sub.recipient, flowVertices, packedCoordinates), Errors.InvalidRecipient ());
118-
119- require (flow.extractAmount () == periods * sub.amount, Errors.InvalidAmount ());
120-
121- _applyRedemption (safe, id, sub, periods);
122-
123- require (
124- ISafe (safe).execTransactionFromModule (
125- HUB,
126- 0 ,
127- abi.encodeCall (IHubV2.operateFlowMatrix, (flowVertices, flow, streams, packedCoordinates)),
128- Enum.Operation.Call
129- ),
130- Errors.ExecutionFailed ()
131- );
132-
133- emit Redeemed (id, safe, sub.recipient, sub.lastRedeemed, sub.requireTrusted);
92+ _subscribe (id, sub);
93+ emit SubscriptionCreated (id, msg .sender , recipient, amount, block .timestamp , category);
13494 }
13595
136- function redeemUntrusted (bytes32 id ) external {
137- (address safe , Subscription memory sub ) = _loadSubscription (id);
138-
139- require (! sub.requireTrusted, Errors.TrustedPathOnly ());
96+ function redeem (bytes32 id , bytes calldata data ) external {
97+ Subscription memory sub = _subscriptions[id];
98+ require (sub.subscriber != address (0 ), Errors.IdentifierNonexistent ());
14099
141- uint256 periods = _requireRedeemablePeriods (sub);
142-
143- _applyRedemption (safe, id, sub, periods);
144-
145- require (
146- ISafe (safe).execTransactionFromModule (
147- HUB,
148- 0 ,
149- abi.encodeCall (
150- ERC1155 .safeTransferFrom,
151- (sub.subscriber, sub.recipient, uint256 (uint160 (sub.subscriber)), periods * sub.amount, "" )
152- ),
153- Enum.Operation.Call
154- ),
155- Errors.ExecutionFailed ()
156- );
100+ uint256 periods = (block .timestamp - sub.lastRedeemed) / sub.frequency;
101+ require (periods >= 1 , Errors.NotRedeemable ());
157102
158- emit Redeemed (id, safe, sub.recipient, sub.lastRedeemed, sub.requireTrusted);
103+ LibTransient.tUint256 (T_REDEEMABLE_AMOUNT).set (periods * sub.amount);
104+ sub.lastRedeemed += periods * sub.frequency;
105+ _subscriptions[id] = sub;
106+
107+ if (sub.category == Category.group) {
108+ _redeemGroup (id, sub);
109+ } else if (sub.category == Category.trusted) {
110+ (
111+ address [] memory flowVertices ,
112+ TypeDefinitions.FlowEdge[] memory flow ,
113+ TypeDefinitions.Stream[] memory streams ,
114+ bytes memory packedCoordinates ,
115+ uint256 sourceCoordinate
116+ ) = abi.decode (data, (address [], TypeDefinitions.FlowEdge[], TypeDefinitions.Stream[], bytes , uint256 ));
117+ _redeemTrusted (id, sub, flowVertices, flow, streams, packedCoordinates, sourceCoordinate);
118+ } else if (sub.category == Category.untrusted) {
119+ _redeemUntrusted (id, sub);
120+ } else {
121+ revert Errors.InvalidCategory ();
122+ }
159123 }
160124
161125 function unsubscribe (bytes32 id ) external {
@@ -169,8 +133,7 @@ contract SubscriptionModule {
169133 }
170134
171135 function updateRecipient (bytes32 id , address newRecipient ) external {
172- address safe = safeFromId[id];
173- Subscription storage sub = _subscriptions[safe][id];
136+ Subscription storage sub = _subscriptions[id];
174137 require (sub.recipient == msg .sender , Errors.OnlyRecipient ());
175138 sub.recipient = newRecipient;
176139 emit RecipientUpdated (id, msg .sender , newRecipient);
@@ -181,62 +144,152 @@ contract SubscriptionModule {
181144 //////////////////////////////////////////////////////////////*/
182145
183146 function getSubscription (bytes32 id ) external view returns (Subscription memory ) {
184- return _subscriptions[safeFromId[id]][ id];
147+ return _subscriptions[id];
185148 }
186149
187- function getSubscriptionIds (address safe ) external view returns (bytes32 [] memory ) {
188- return ids[safe ].values ();
150+ function getSubscriptionIds (address subscriber ) external view returns (bytes32 [] memory ) {
151+ return ids[subscriber ].values ();
189152 }
190153
191154 function isValidOrRedeemable (bytes32 id ) public view returns (uint256 ) {
192- Subscription memory subscription = _subscriptions[safeFromId[id]][id];
193- return (block .timestamp - subscription.lastRedeemed) / subscription.frequency * subscription.amount;
194- }
195-
196- function isTrustedRequired (bytes32 id ) external view returns (bool ) {
197- return _subscriptions[safeFromId[id]][id].requireTrusted;
155+ if (_subscriptions[id].subscriber == address (0 )) return 0 ;
156+ Subscription memory sub = _subscriptions[id];
157+ return (block .timestamp - sub.lastRedeemed) / sub.frequency * sub.amount;
198158 }
199159
200160 /*//////////////////////////////////////////////////////////////
201161 INTERNAL NON-CONSTANT FUNCTIONS
202162 //////////////////////////////////////////////////////////////*/
203163
204- function _subscribe (address subscriber , bytes32 id , Subscription memory subscription ) internal {
205- require (! _exists (id), Errors.IdentifierExists ());
206- _subscriptions[subscriber][id] = subscription;
207- safeFromId[id] = subscriber;
208- ids[subscriber].add (id);
164+ function _subscribe (bytes32 id , Subscription memory sub ) internal {
165+ require (_subscriptions[id].subscriber == address (0 ), Errors.IdentifierExists ());
166+ _subscriptions[id] = sub;
167+ ids[sub.subscriber].add (id);
209168 }
210169
211- function _unsubscribe (address subscriber , bytes32 id ) internal {
212- require ( _exists (id), Errors. IdentifierNonexistent ()) ;
213- delete _subscriptions[ subscriber][id] ;
214- delete safeFromId [id];
215- ids[subscriber].remove (id);
216- emit Unsubscribed (id, subscriber);
170+ function _unsubscribe (address caller , bytes32 id ) internal {
171+ Subscription memory sub = _subscriptions[id] ;
172+ require (sub. subscriber == caller, Errors. OnlySubscriber ()) ;
173+ delete _subscriptions [id];
174+ ids[sub. subscriber].remove (id);
175+ emit Unsubscribed (id, sub. subscriber);
217176 }
218177
219- /*//////////////////////////////////////////////////////////////
220- INTERNAL CONSTANT FUNCTIONS
221- //////////////////////////////////////////////////////////////*/
178+ function _redeemGroup (bytes32 id , Subscription memory sub ) internal {
179+ address [] memory collateralAvatars = new address [](1 );
180+ collateralAvatars[0 ] = sub.subscriber;
181+
182+ uint256 [] memory amounts = new uint256 [](1 );
183+ amounts[0 ] = sub.amount;
184+
185+ /**
186+ * Steps required to handle group minting for the subscriber
187+ * - pull subscriber CRC tokens
188+ * - mint group (= recipient) tokens to this module using subscriber CRC collateral
189+ * - empty data for now, @todo check group mint policies for requirements
190+ * - transfer minted group tokens to the subscriber
191+ */
192+ bytes memory call0 = abi.encodeCall (
193+ ERC1155 .safeTransferFrom,
194+ (
195+ sub.subscriber,
196+ address (this ),
197+ _toTokenId (sub.subscriber),
198+ LibTransient.tUint256 (T_REDEEMABLE_AMOUNT).get (),
199+ ""
200+ )
201+ );
202+ /// @todo empty data for now -- checkout mint policies of existing groups for data requirements
203+ bytes memory call1 = abi.encodeCall (IHubV2.groupMint, (sub.recipient, collateralAvatars, amounts, "" ));
204+ bytes memory call2 = abi.encodeCall (
205+ ERC1155 .safeTransferFrom,
206+ (
207+ address (this ),
208+ sub.subscriber,
209+ _toTokenId (sub.recipient),
210+ ERC1155 (HUB).balanceOf (address (this ), _toTokenId (sub.recipient)),
211+ ""
212+ )
213+ );
214+ bytes memory transactions = bytes .concat (
215+ abi.encodePacked (uint8 (0 ), HUB, uint256 (0 ), call0.length , call0),
216+ abi.encodePacked (uint8 (0 ), HUB, uint256 (0 ), call1.length , call1),
217+ abi.encodePacked (uint8 (0 ), HUB, uint256 (0 ), call2.length , call2)
218+ );
219+
220+ require (
221+ ISafe (sub.subscriber).execTransactionFromModule (
222+ MULTISEND, 0 , abi.encodeCall (IMultiSend.multiSend, (transactions)), Enum.Operation.DelegateCall
223+ ),
224+ Errors.ExecutionFailed ()
225+ );
222226
223- function _exists ( bytes32 id ) internal view returns ( bool ) {
224- return safeFromId[id] != address ( 0 );
227+ emit Redeemed (id, sub.subscriber, sub.recipient, sub.lastRedeemed + sub.frequency);
228+ LibTransient. tUint256 (T_REDEEMABLE_AMOUNT). clear ( );
225229 }
226230
227- function _loadSubscription (bytes32 id ) internal view returns (address safe , Subscription memory sub ) {
228- require (_exists (id), Errors.IdentifierNonexistent ());
229- safe = safeFromId[id];
230- sub = _subscriptions[safe][id];
231+ function _redeemTrusted (
232+ bytes32 id ,
233+ Subscription memory sub ,
234+ address [] memory flowVertices ,
235+ TypeDefinitions.FlowEdge[] memory flow ,
236+ TypeDefinitions.Stream[] memory streams ,
237+ bytes memory packedCoordinates ,
238+ uint256 sourceCoordinate
239+ )
240+ internal
241+ {
242+ require (flowVertices[sourceCoordinate] == sub.subscriber, Errors.InvalidSubscriber ());
243+
244+ require (streams.checkSource (sourceCoordinate), Errors.InvalidStreamSource ());
245+
246+ require (streams.checkRecipients (sub.recipient, flowVertices, packedCoordinates), Errors.InvalidRecipient ());
247+
248+ require (flow.extractAmount () == LibTransient.tUint256 (T_REDEEMABLE_AMOUNT).get (), Errors.InvalidAmount ());
249+
250+ require (
251+ ISafe (sub.subscriber).execTransactionFromModule (
252+ HUB,
253+ 0 ,
254+ abi.encodeCall (IHubV2.operateFlowMatrix, (flowVertices, flow, streams, packedCoordinates)),
255+ Enum.Operation.Call
256+ ),
257+ Errors.ExecutionFailed ()
258+ );
259+
260+ emit Redeemed (id, sub.subscriber, sub.recipient, sub.lastRedeemed + sub.frequency);
261+ LibTransient.tUint256 (T_REDEEMABLE_AMOUNT).clear ();
231262 }
232263
233- function _requireRedeemablePeriods (Subscription memory sub ) internal view returns (uint256 periods ) {
234- periods = (block .timestamp - sub.lastRedeemed) / sub.frequency;
235- require (periods >= 1 , Errors.NotRedeemable ());
264+ function _redeemUntrusted (bytes32 id , Subscription memory sub ) internal {
265+ require (
266+ ISafe (sub.subscriber).execTransactionFromModule (
267+ HUB,
268+ 0 ,
269+ abi.encodeCall (
270+ ERC1155 .safeTransferFrom,
271+ (
272+ sub.subscriber,
273+ sub.recipient,
274+ _toTokenId (sub.subscriber),
275+ LibTransient.tUint256 (T_REDEEMABLE_AMOUNT).get (),
276+ ""
277+ )
278+ ),
279+ Enum.Operation.Call
280+ ),
281+ Errors.ExecutionFailed ()
282+ );
283+
284+ emit Redeemed (id, sub.subscriber, sub.recipient, sub.lastRedeemed + sub.frequency);
285+ LibTransient.tUint256 (T_REDEEMABLE_AMOUNT).clear ();
236286 }
237287
238- function _applyRedemption (address safe , bytes32 id , Subscription memory sub , uint256 periods ) internal {
239- sub.lastRedeemed += periods * sub.frequency;
240- _subscriptions[safe][id] = sub;
288+ /*//////////////////////////////////////////////////////////////
289+ INTERNAL CONSTANT FUNCTIONS
290+ //////////////////////////////////////////////////////////////*/
291+
292+ function _toTokenId (address _avatar ) internal pure returns (uint256 ) {
293+ return uint256 (uint160 (_avatar));
241294 }
242295}
0 commit comments