@@ -14,20 +14,27 @@ import { SuperTokenV1Library } from "./SuperTokenV1Library.sol";
1414 * @title abstract base contract for SuperApps using CFA callbacks
1515 * @author Superfluid
1616 * @dev This contract provides a more convenient API for implementing CFA callbacks.
17- * It allows to write more concise and readable SuperApps when the full flexibility
18- * of the low-level agreement callbacks isn't needed.
19- * The API is tailored for the most common use cases, with the "beforeX" and "afterX" callbacks being
17+ * It allows to write more concise and readable SuperApps.
18+ * The API is tailored for common use cases, with the "beforeX" and "afterX" callbacks being
2019 * abstrated into a single "onX" callback for create|update|delete flows.
21- * For use cases requiring more flexibility (specifically if more data needs to be provided by the before callbacks)
22- * it's recommended to implement the low-level callbacks directly instead of using this base contract.
20+ * If the previous state provided by this API (`previousFlowRate` and `lastUpdated`) is not sufficient for you use case,
21+ * you should implement the more generic low-level API of `ISuperApp` instead of using this base contract.
2322 */
2423abstract contract CFASuperAppBase is ISuperApp {
2524 using SuperTokenV1Library for ISuperToken;
2625
26+ /// =================================================================================
27+ /// CONSTANTS & IMMUTABLES
28+ /// =================================================================================
29+
2730 bytes32 public constant CFAV1_TYPE = keccak256 ("org.superfluid-finance.agreements.ConstantFlowAgreement.v1 " );
2831
2932 ISuperfluid public immutable HOST;
3033
34+ /// =================================================================================
35+ /// ERRORS
36+ /// =================================================================================
37+
3138 /// @dev Thrown when the callback caller is not the host.
3239 error UnauthorizedHost ();
3340
@@ -37,6 +44,10 @@ abstract contract CFASuperAppBase is ISuperApp {
3744 /// @dev Thrown when SuperTokens not accepted by the SuperApp are streamed to it
3845 error NotAcceptedSuperToken ();
3946
47+ // =================================================================================
48+ // SETUP
49+ // =================================================================================
50+
4051 /**
4152 * @dev Creates the contract tied to the provided Superfluid host
4253 * @param host_ the Superfluid host the SuperApp belongs to
@@ -65,7 +76,7 @@ abstract contract CFASuperAppBase is ISuperApp {
6576 *
6677 * Note: if the App self-registers on a network with permissioned SuperApp registration,
6778 * self-registration can be used only if the tx.origin (EOA) is whitelisted as deployer.
68- * If a whitelisted factory is used, it needs to call `host.registerApp()` itself .
79+ * If instead a whitelisted factory is used, the factory needs to call `host.registerApp(address app)` .
6980 * For more details, see https://github.com/superfluid-finance/protocol-monorepo/wiki/Super-App-White-listing-Guide
7081 */
7182 function selfRegister (
@@ -88,7 +99,9 @@ abstract contract CFASuperAppBase is ISuperApp {
8899 bool activateOnUpdated ,
89100 bool activateOnDeleted
90101 ) public pure returns (uint256 configWord ) {
102+ // since only 1 level is allowed by the protocol, we can hardcode APP_LEVEL_FINAL
91103 configWord = SuperAppDefinitions.APP_LEVEL_FINAL
104+ // there's no information we want to carry over for create
92105 | SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP;
93106 if (! activateOnCreated) {
94107 configWord |= SuperAppDefinitions.AFTER_AGREEMENT_CREATED_NOOP;
@@ -112,16 +125,16 @@ abstract contract CFASuperAppBase is ISuperApp {
112125 return true ;
113126 }
114127
115-
116- // ---------------------------------------------------------------------------------------------
117- // CFA specific convenience callbacks
118- // to be overridden and implemented by inheriting SuperApps
128+ // =================================================================================
129+ // CFA SPECIFIC CALLBACKS - TO BE OVERRIDDEN BY INHERITING SUPERAPPS
130+ // =================================================================================
119131
120132 /// @dev override if the SuperApp shall have custom logic invoked when a new flow
121133 /// to it is created.
122134 function onFlowCreated (
123135 ISuperToken /*superToken*/ ,
124136 address /*sender*/ ,
137+ int96 /*flowRate*/ ,
125138 bytes calldata ctx
126139 ) internal virtual returns (bytes memory /*newCtx*/ ) {
127140 return ctx;
@@ -132,6 +145,7 @@ abstract contract CFASuperAppBase is ISuperApp {
132145 function onFlowUpdated (
133146 ISuperToken /*superToken*/ ,
134147 address /*sender*/ ,
148+ int96 /*flowRate*/ ,
135149 int96 /*previousFlowRate*/ ,
136150 uint256 /*lastUpdated*/ ,
137151 bytes calldata ctx
@@ -141,11 +155,29 @@ abstract contract CFASuperAppBase is ISuperApp {
141155
142156 /// @dev override if the SuperApp shall have custom logic invoked when an existing flow
143157 /// to it is deleted (flowrate set to 0).
144- /// Unlike the other callbacks, this method is NOT allowed to revert.
158+ /// Unlike the other callbacks, the delete callbacks are NOT allowed to revert.
145159 /// Failing to satisfy that requirement leads to jailing (defunct SuperApp).
146- function onFlowDeleted (
160+ function onInFlowDeleted (
147161 ISuperToken /*superToken*/ ,
148162 address /*sender*/ ,
163+ int96 /*previousFlowRate*/ ,
164+ uint256 /*lastUpdated*/ ,
165+ bytes calldata ctx
166+ ) internal virtual returns (bytes memory /*newCtx*/ ) {
167+ return ctx;
168+ }
169+
170+ /// @dev override if the SuperApp shall have custom logic invoked when an outgoing flow
171+ /// is deleted by the receiver (it's not triggered when deleted by the SuperApp itself).
172+ /// A possible implementation is to make outflows "sticky" by simply reopening it.
173+ /// Like onInFlowDeleted, this method is NOT allowed to revert.
174+ /// It's safe to not override this method if the SuperApp doesn't have outgoing flows,
175+ /// or if it doesn't want/need to know if an outgoing flow is deleted by its receiver.
176+ /// Note: In theory this hook could also be triggered by a liquidation, but this would imply
177+ /// that the SuperApp is insolvent, and would thus be jailed already.
178+ /// Thus in practice this is triggered only when a receiver of an outgoing flow deletes that flow.
179+ function onOutFlowDeleted (
180+ ISuperToken /*superToken*/ ,
149181 address /*receiver*/ ,
150182 int96 /*previousFlowRate*/ ,
151183 uint256 /*lastUpdated*/ ,
@@ -154,12 +186,16 @@ abstract contract CFASuperAppBase is ISuperApp {
154186 return ctx;
155187 }
156188
189+ // =================================================================================
190+ // INTERNAL IMPLEMENTATION
191+ // =================================================================================
157192
158- // ---------------------------------------------------------------------------------------------
159- // Low-level callbacks
160- // Shall NOT be overriden by SuperApps when inheriting from this contract.
161- // The before-callbacks are implemented to forward data (flowrate, timestamp),
162- // the after-callbacks invoke the CFA specific specific convenience callbacks.
193+ // The following methods SHALL NOT BE OVERRIDDEN by SuperApps inheriting from this contract.
194+ // If more fine grained control than provided by the onX callbacks is needed,
195+ // you should implement the more generic low-level API of `ISuperApp` instead of using this base contract.
196+
197+ // The before-callbacks are implemented to relay data (flowrate, timestamp) to the after-callbacks.
198+ // The after-callbacks invoke the more convenient onX callbacks.
163199
164200 // CREATED callback
165201
@@ -183,15 +219,17 @@ abstract contract CFASuperAppBase is ISuperApp {
183219 bytes calldata ctx
184220 ) external override returns (bytes memory newCtx ) {
185221 if (msg .sender != address (HOST)) revert UnauthorizedHost ();
186- if (! isAcceptedAgreement (agreementClass)) return ctx;
222+ if (! _isAcceptedAgreement (agreementClass)) return ctx;
187223 if (! isAcceptedSuperToken (superToken)) revert NotAcceptedSuperToken ();
188224
189225 (address sender , ) = abi.decode (agreementData, (address , address ));
226+ int96 flowRate = superToken.getCFAFlowRate (sender, address (this ));
190227
191228 return
192229 onFlowCreated (
193230 superToken,
194231 sender,
232+ flowRate,
195233 ctx // userData can be acquired with `host.decodeCtx(ctx).userData`
196234 );
197235 }
@@ -206,7 +244,7 @@ abstract contract CFASuperAppBase is ISuperApp {
206244 bytes calldata /*ctx*/
207245 ) external view override returns (bytes memory /*beforeData*/ ) {
208246 if (msg .sender != address (HOST)) revert UnauthorizedHost ();
209- if (! isAcceptedAgreement (agreementClass)) return "0x " ;
247+ if (! _isAcceptedAgreement (agreementClass)) return "0x " ;
210248 if (! isAcceptedSuperToken (superToken)) revert NotAcceptedSuperToken ();
211249
212250 (address sender , ) = abi.decode (agreementData, (address , address ));
@@ -227,20 +265,31 @@ abstract contract CFASuperAppBase is ISuperApp {
227265 bytes calldata ctx
228266 ) external override returns (bytes memory newCtx ) {
229267 if (msg .sender != address (HOST)) revert UnauthorizedHost ();
230- if (! isAcceptedAgreement (agreementClass)) return ctx;
268+ if (! _isAcceptedAgreement (agreementClass)) return ctx;
231269 if (! isAcceptedSuperToken (superToken)) revert NotAcceptedSuperToken ();
232270
271+ return _afterAgreementUpdatedHelper (superToken, agreementData, cbdata, ctx);
272+ }
273+
274+ // workaround to stack-too-deep compiler error
275+ function _afterAgreementUpdatedHelper (
276+ ISuperToken superToken ,
277+ bytes calldata agreementData ,
278+ bytes calldata cbdata ,
279+ bytes calldata ctx
280+ ) private returns (bytes memory ) {
233281 (address sender , ) = abi.decode (agreementData, (address , address ));
234282 (int96 previousFlowRate , uint256 lastUpdated ) = abi.decode (cbdata, (int96 , uint256 ));
283+ int96 flowRate = superToken.getCFAFlowRate (sender, address (this ));
235284
236- return
237- onFlowUpdated (
238- superToken ,
239- sender ,
240- previousFlowRate,
241- lastUpdated,
242- ctx // userData can be acquired with `host.decodeCtx(ctx).userData`
243- );
285+ return onFlowUpdated (
286+ superToken,
287+ sender ,
288+ flowRate ,
289+ previousFlowRate,
290+ lastUpdated,
291+ ctx // userData can be acquired with `host.decodeCtx(ctx).userData`
292+ );
244293 }
245294
246295 // DELETED callbacks
@@ -254,7 +303,7 @@ abstract contract CFASuperAppBase is ISuperApp {
254303 ) external view override returns (bytes memory /*beforeData*/ ) {
255304 // we're not allowed to revert in this callback, thus just return empty beforeData on failing checks
256305 if (msg .sender != address (HOST)
257- || ! isAcceptedAgreement (agreementClass)
306+ || ! _isAcceptedAgreement (agreementClass)
258307 || ! isAcceptedSuperToken (superToken))
259308 {
260309 return "0x " ;
@@ -279,7 +328,7 @@ abstract contract CFASuperAppBase is ISuperApp {
279328 ) external override returns (bytes memory newCtx ) {
280329 // we're not allowed to revert in this callback, thus just return ctx on failing checks
281330 if (msg .sender != address (HOST)
282- || ! isAcceptedAgreement (agreementClass)
331+ || ! _isAcceptedAgreement (agreementClass)
283332 || ! isAcceptedSuperToken (superToken))
284333 {
285334 return ctx;
@@ -288,15 +337,25 @@ abstract contract CFASuperAppBase is ISuperApp {
288337 (address sender , address receiver ) = abi.decode (agreementData, (address , address ));
289338 (uint256 lastUpdated , int96 previousFlowRate ) = abi.decode (cbdata, (uint256 , int96 ));
290339
291- return
292- onFlowDeleted (
293- superToken,
294- sender,
295- receiver,
296- previousFlowRate,
297- lastUpdated,
298- ctx
299- );
340+ if (receiver == address (this )) {
341+ return
342+ onInFlowDeleted (
343+ superToken,
344+ sender,
345+ previousFlowRate,
346+ lastUpdated,
347+ ctx
348+ );
349+ } else {
350+ return
351+ onOutFlowDeleted (
352+ superToken,
353+ receiver,
354+ previousFlowRate,
355+ lastUpdated,
356+ ctx
357+ );
358+ }
300359 }
301360
302361
@@ -308,7 +367,7 @@ abstract contract CFASuperAppBase is ISuperApp {
308367 * This function can be overridden with custom logic and to revert if desired
309368 * Current implementation expects ConstantFlowAgreement
310369 */
311- function isAcceptedAgreement (address agreementClass ) internal view virtual returns (bool ) {
370+ function _isAcceptedAgreement (address agreementClass ) internal view returns (bool ) {
312371 return agreementClass == address (HOST.getAgreementClass (CFAV1_TYPE));
313372 }
314373}
0 commit comments