-
Notifications
You must be signed in to change notification settings - Fork 261
Expand file tree
/
Copy pathCFASuperAppBase.sol
More file actions
314 lines (273 loc) · 12 KB
/
CFASuperAppBase.sol
File metadata and controls
314 lines (273 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
// SPDX-License-Identifier: MIT
pragma solidity >= 0.8.11;
import {
ISuperfluid,
ISuperToken,
ISuperApp,
SuperAppDefinitions,
IGeneralDistributionAgreementV1
} from "../interfaces/superfluid/ISuperfluid.sol";
import { SuperTokenV1Library } from "./SuperTokenV1Library.sol";
/**
* @title abstract base contract for SuperApps using CFA callbacks
* @author Superfluid
* @dev This contract provides a more convenient API for implementing CFA callbacks.
* It allows to write more concise and readable SuperApps when the full flexibility
* of the low-level agreement callbacks isn't needed.
* The API is tailored for the most common use cases, with the "beforeX" and "afterX" callbacks being
* abstrated into a single "onX" callback for create|update|delete flows.
* For use cases requiring more flexibility (specifically if more data needs to be provided by the before callbacks)
* it's recommended to implement the low-level callbacks directly instead of using this base contract.
*/
abstract contract CFASuperAppBase is ISuperApp {
using SuperTokenV1Library for ISuperToken;
bytes32 public constant CFAV1_TYPE = keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1");
ISuperfluid public immutable HOST;
/// @dev Thrown when the callback caller is not the host.
error UnauthorizedHost();
/// @dev Thrown if a required callback wasn't implemented (overridden by the SuperApp)
error NotImplemented();
/// @dev Thrown when SuperTokens not accepted by the SuperApp are streamed to it
error NotAcceptedSuperToken();
/**
* @dev Creates the contract tied to the provided Superfluid host
* @param host_ the Superfluid host the SuperApp belongs to
* @notice You also need to register the app with the host in order to enable callbacks.
* This can be done either by calling `selfRegister()` or by calling `host.registerApp()`.
*/
constructor(ISuperfluid host_) {
HOST = host_;
// disable autoconnect for GDA pools
IGeneralDistributionAgreementV1 gda = IGeneralDistributionAgreementV1(
address(
ISuperfluid(host_).getAgreementClass(
keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1")
)
)
);
gda.setConnectPermission(false);
}
/**
* @dev Registers the SuperApp with its Superfluid host contract (self-registration)
* @param activateOnCreated if true, callbacks for `createFlow` will be activated
* @param activateOnUpdated if true, callbacks for `updateFlow` will be activated
* @param activateOnDeleted if true, callbacks for `deleteFlow` will be activated
*
* Note: if the App self-registers on a network with permissioned SuperApp registration,
* self-registration can be used only if the tx.origin (EOA) is whitelisted as deployer.
* If a whitelisted factory is used, it needs to call `host.registerApp()` itself.
* For more details, see https://github.com/superfluid-finance/protocol-monorepo/wiki/Super-App-White-listing-Guide
*/
function selfRegister(
bool activateOnCreated,
bool activateOnUpdated,
bool activateOnDeleted
) public {
HOST.registerApp(getConfigWord(activateOnCreated, activateOnUpdated, activateOnDeleted));
}
/**
* @dev Convenience function to get the `configWord` for app registration when not using self-registration
* @param activateOnCreated if true, callbacks for `createFlow` will be activated
* @param activateOnUpdated if true, callbacks for `updateFlow` will be activated
* @param activateOnDeleted if true, callbacks for `deleteFlow` will be activated
* @return configWord the `configWord` encoding the provided settings
*/
function getConfigWord(
bool activateOnCreated,
bool activateOnUpdated,
bool activateOnDeleted
) public pure returns (uint256 configWord) {
configWord = SuperAppDefinitions.APP_LEVEL_FINAL
| SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP;
if (!activateOnCreated) {
configWord |= SuperAppDefinitions.AFTER_AGREEMENT_CREATED_NOOP;
}
if (!activateOnUpdated) {
configWord |= SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP
| SuperAppDefinitions.AFTER_AGREEMENT_UPDATED_NOOP;
}
if (!activateOnDeleted) {
configWord |= SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP
| SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP;
}
}
/**
* @dev Optional (positive) filter for accepting only specific SuperTokens.
* The default implementation accepts all SuperTokens.
* Can be overridden by the SuperApp in order to apply arbitrary filters.
*/
function isAcceptedSuperToken(ISuperToken /*superToken*/) public view virtual returns (bool) {
return true;
}
// ---------------------------------------------------------------------------------------------
// CFA specific convenience callbacks
// to be overridden and implemented by inheriting SuperApps
/// @dev override if the SuperApp shall have custom logic invoked when a new flow
/// to it is created.
function onFlowCreated(
ISuperToken /*superToken*/,
address /*sender*/,
bytes calldata ctx
) internal virtual returns (bytes memory /*newCtx*/) {
return ctx;
}
/// @dev override if the SuperApp shall have custom logic invoked when an existing flow
/// to it is updated (flowrate change).
function onFlowUpdated(
ISuperToken /*superToken*/,
address /*sender*/,
int96 /*previousFlowRate*/,
uint256 /*lastUpdated*/,
bytes calldata ctx
) internal virtual returns (bytes memory /*newCtx*/) {
return ctx;
}
/// @dev override if the SuperApp shall have custom logic invoked when an existing flow
/// to it is deleted (flowrate set to 0).
/// Unlike the other callbacks, this method is NOT allowed to revert.
/// Failing to satisfy that requirement leads to jailing (defunct SuperApp).
function onFlowDeleted(
ISuperToken /*superToken*/,
address /*sender*/,
address /*receiver*/,
int96 /*previousFlowRate*/,
uint256 /*lastUpdated*/,
bytes calldata ctx
) internal virtual returns (bytes memory /*newCtx*/) {
return ctx;
}
// ---------------------------------------------------------------------------------------------
// Low-level callbacks
// Shall NOT be overriden by SuperApps when inheriting from this contract.
// The before-callbacks are implemented to forward data (flowrate, timestamp),
// the after-callbacks invoke the CFA specific specific convenience callbacks.
// CREATED callback
// Empty implementation to fulfill the interface - is never called because disabled in the app manifest.
function beforeAgreementCreated(
ISuperToken /*superToken*/,
address /*agreementClass*/,
bytes32 /*agreementId*/,
bytes calldata /*agreementData*/,
bytes calldata /*ctx*/
) external pure override returns (bytes memory /*beforeData*/) {
return "0x";
}
function afterAgreementCreated(
ISuperToken superToken,
address agreementClass,
bytes32 /*agreementId*/,
bytes calldata agreementData,
bytes calldata /*cbdata*/,
bytes calldata ctx
) external override returns (bytes memory newCtx) {
if (msg.sender != address(HOST)) revert UnauthorizedHost();
if (!isAcceptedAgreement(agreementClass)) return ctx;
if (!isAcceptedSuperToken(superToken)) revert NotAcceptedSuperToken();
(address sender, ) = abi.decode(agreementData, (address, address));
return
onFlowCreated(
superToken,
sender,
ctx // userData can be acquired with `host.decodeCtx(ctx).userData`
);
}
// UPDATED callbacks
function beforeAgreementUpdated(
ISuperToken superToken,
address agreementClass,
bytes32 /*agreementId*/,
bytes calldata agreementData,
bytes calldata /*ctx*/
) external view override returns (bytes memory /*beforeData*/) {
if (msg.sender != address(HOST)) revert UnauthorizedHost();
if (!isAcceptedAgreement(agreementClass)) return "0x";
if (!isAcceptedSuperToken(superToken)) revert NotAcceptedSuperToken();
(address sender, ) = abi.decode(agreementData, (address, address));
(uint256 lastUpdated, int96 flowRate,,) = superToken.getCFAFlowInfo(sender, address(this));
return abi.encode(
flowRate,
lastUpdated
);
}
function afterAgreementUpdated(
ISuperToken superToken,
address agreementClass,
bytes32 /*agreementId*/,
bytes calldata agreementData,
bytes calldata cbdata,
bytes calldata ctx
) external override returns (bytes memory newCtx) {
if (msg.sender != address(HOST)) revert UnauthorizedHost();
if (!isAcceptedAgreement(agreementClass)) return ctx;
if (!isAcceptedSuperToken(superToken)) revert NotAcceptedSuperToken();
(address sender, ) = abi.decode(agreementData, (address, address));
(int96 previousFlowRate, uint256 lastUpdated) = abi.decode(cbdata, (int96, uint256));
return
onFlowUpdated(
superToken,
sender,
previousFlowRate,
lastUpdated,
ctx // userData can be acquired with `host.decodeCtx(ctx).userData`
);
}
// DELETED callbacks
function beforeAgreementTerminated(
ISuperToken superToken,
address agreementClass,
bytes32 /*agreementId*/,
bytes calldata agreementData,
bytes calldata /*ctx*/
) external view override returns (bytes memory /*beforeData*/) {
// we're not allowed to revert in this callback, thus just return empty beforeData on failing checks
if (msg.sender != address(HOST)
|| !isAcceptedAgreement(agreementClass)
|| !isAcceptedSuperToken(superToken))
{
return "0x";
}
(address sender, address receiver) = abi.decode(agreementData, (address, address));
(uint256 lastUpdated, int96 flowRate,,) = superToken.getCFAFlowInfo(sender, receiver);
return abi.encode(
lastUpdated,
flowRate
);
}
function afterAgreementTerminated(
ISuperToken superToken,
address agreementClass,
bytes32 /*agreementId*/,
bytes calldata agreementData,
bytes calldata cbdata,
bytes calldata ctx
) external override returns (bytes memory newCtx) {
// we're not allowed to revert in this callback, thus just return ctx on failing checks
if (msg.sender != address(HOST)
|| !isAcceptedAgreement(agreementClass)
|| !isAcceptedSuperToken(superToken))
{
return ctx;
}
(address sender, address receiver) = abi.decode(agreementData, (address, address));
(uint256 lastUpdated, int96 previousFlowRate) = abi.decode(cbdata, (uint256, int96));
return
onFlowDeleted(
superToken,
sender,
receiver,
previousFlowRate,
lastUpdated,
ctx
);
}
// ---------------------------------------------------------------------------------------------
// HELPERS
/**
* @dev Expect Super Agreement involved in callback to be an accepted one
* This function can be overridden with custom logic and to revert if desired
* Current implementation expects ConstantFlowAgreement
*/
function isAcceptedAgreement(address agreementClass) internal view virtual returns (bool) {
return agreementClass == address(HOST.getAgreementClass(CFAV1_TYPE));
}
}