1
1
// SPDX-License-Identifier: AGPLv3
2
2
pragma solidity ^ 0.8.23 ;
3
3
4
- import { IConstantFlowAgreementV1 } from "../../../contracts/interfaces/agreements/IConstantFlowAgreementV1.sol " ;
5
- import { FoundrySuperfluidTester, ISuperToken, SuperTokenV1Library, ISuperfluidPool }
6
- from "../FoundrySuperfluidTester.t.sol " ;
4
+ import { IConstantFlowAgreementV1, ISuperfluid, ISuperToken, ISuperfluidPool, ISuperApp }
5
+ from "../../../contracts/interfaces/superfluid/ISuperfluid.sol " ;
6
+ import { CFASuperAppBase } from "../../../contracts/apps/CFASuperAppBase.sol " ;
7
+ import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol " ;
8
+ import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.t.sol " ;
7
9
8
10
/*
9
11
* Note: since libs are used by contracts, not EOAs, do NOT try to use
@@ -314,15 +316,60 @@ contract SuperTokenV1LibraryTest is FoundrySuperfluidTester {
314
316
assert (lastUpdated2 > lastUpdated1);
315
317
}
316
318
319
+ function testFlowWithCtx (bool useACL ) external {
320
+ SuperAppMock superApp = new SuperAppMock (sf.host);
321
+ superApp.selfRegister (true , true , true );
322
+ address superAppAddr = address (superApp);
323
+
324
+ address flowSender = superAppAddr;
325
+ address flowReceiver = address (this );
326
+
327
+ if (useACL) {
328
+ vm.startPrank (alice);
329
+ superApp.setACLFlowSender ();
330
+ sf.host.callAgreement (
331
+ sf.cfa,
332
+ abi.encodeCall (sf.cfa.authorizeFlowOperatorWithFullControl, (superToken, superAppAddr, new bytes (0 ))),
333
+ new bytes (0 ) // userData
334
+ );
335
+ vm.stopPrank ();
336
+ flowSender = alice;
337
+ }
338
+
339
+ // initial createFlow
340
+ superToken.flow (superAppAddr, DEFAULT_FLOWRATE);
341
+ assertEq (_getCFAFlowRate (flowSender, flowReceiver), DEFAULT_FLOWRATE, "createFlow unexpected result " );
342
+
343
+ // double it -> updateFlow
344
+ superToken.flow (superAppAddr, DEFAULT_FLOWRATE * 2 );
345
+ assertEq (_getCFAFlowRate (flowSender, flowReceiver), DEFAULT_FLOWRATE * 2 , "updateFlow unexpected result " );
346
+
347
+ if (! useACL) {
348
+ // delete the mirrored flow (check if remains sticky)
349
+ superToken.deleteFlow (superAppAddr, address (this ));
350
+ assertEq (_getCFAFlowRate (flowSender, flowReceiver), DEFAULT_FLOWRATE * 2 , "flow not sticky " );
351
+ }
352
+
353
+ // set to 0 -> deleteFlow
354
+ superToken.flow (superAppAddr, 0 );
355
+ assertEq (_getCFAFlowRate (flowSender, flowReceiver), 0 , "deleteFlow unexpected result " );
356
+
357
+ // invalid flowrate
358
+ vm.expectRevert (IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE.selector );
359
+ this .__externalflow (flowSender, superAppAddr, - 1 );
360
+
361
+ assertFalse (sf.host.isAppJailed (ISuperApp (superAppAddr)), "superApp is jailed " );
362
+ }
363
+
317
364
// HELPER FUNCTIONS ========================================================================================
318
365
319
- // direct use of the agreement for assertions
320
- function _getCFAFlowRate (address sender , address receiver ) public view returns (int96 flowRate ) {
366
+ // direct use of the agreement for assertions
367
+ function _getCFAFlowRate (address sender , address receiver ) internal view returns (int96 flowRate ) {
321
368
(,flowRate,,) = sf.cfa.getFlow (superToken, sender, receiver);
322
369
}
323
370
324
371
// Note: this is without adjustmentFR
325
- function _getGDAFlowRate (address sender , ISuperfluidPool pool ) public view returns (int96 flowRate ) {
372
+ function _getGDAFlowRate (address sender , ISuperfluidPool pool ) internal view returns (int96 flowRate ) {
326
373
return sf.gda.getFlowRate (superToken, sender, pool);
327
374
}
328
375
@@ -344,4 +391,66 @@ contract SuperTokenV1LibraryTest is FoundrySuperfluidTester {
344
391
superToken.flowFrom (sender, receiver, flowRate);
345
392
vm.stopPrank ();
346
393
}
394
+ }
395
+
396
+ // SuperApp for testing withCtx methods.
397
+ // mirrors (default mode) or matches (ACL mode) the incoming flow
398
+ contract SuperAppMock is CFASuperAppBase {
399
+ using SuperTokenV1Library for ISuperToken;
400
+
401
+ // if not set (0), the SuperApp itself is the flowSender
402
+ address aclFlowSender;
403
+
404
+ constructor (ISuperfluid host ) CFASuperAppBase (host) { }
405
+
406
+ // enable ACL mode by setting a sender
407
+ function setACLFlowSender () external {
408
+ aclFlowSender = msg .sender ;
409
+ }
410
+
411
+ function onFlowCreated (
412
+ ISuperToken superToken ,
413
+ address sender ,
414
+ bytes calldata ctx
415
+ ) internal virtual override returns (bytes memory /*newCtx*/ ) {
416
+ return _mirrorOrMatchIncomingFlow (superToken, sender, ctx);
417
+ }
418
+
419
+ function onFlowUpdated (
420
+ ISuperToken superToken ,
421
+ address sender ,
422
+ int96 /*previousFlowRate*/ ,
423
+ uint256 /*lastUpdated*/ ,
424
+ bytes calldata ctx
425
+ ) internal virtual override returns (bytes memory /*newCtx*/ ) {
426
+ return _mirrorOrMatchIncomingFlow (superToken, sender, ctx);
427
+ }
428
+
429
+ function onFlowDeleted (
430
+ ISuperToken superToken ,
431
+ address sender ,
432
+ address receiver ,
433
+ int96 previousFlowRate ,
434
+ uint256 /*lastUpdated*/ ,
435
+ bytes calldata ctx
436
+ ) internal virtual override returns (bytes memory /*newCtx*/ ) {
437
+ if (receiver == address (this )) {
438
+ return _mirrorOrMatchIncomingFlow (superToken, sender, ctx);
439
+ } else {
440
+ // outflow was deleted by the sender we mirror to,
441
+ // we make it "sticky" by simply restoring it.
442
+ return superToken.flowWithCtx (receiver, previousFlowRate, ctx);
443
+ }
444
+ }
445
+
446
+ function _mirrorOrMatchIncomingFlow (ISuperToken superToken , address senderAndReceiver , bytes memory ctx )
447
+ internal returns (bytes memory newCtx )
448
+ {
449
+ int96 flowRate = superToken.getFlowRate (senderAndReceiver, address (this ));
450
+ if (aclFlowSender == address (0 )) {
451
+ return superToken.flowWithCtx (senderAndReceiver, flowRate, ctx);
452
+ } else {
453
+ return superToken.flowFromWithCtx (aclFlowSender, senderAndReceiver, flowRate, ctx);
454
+ }
455
+ }
347
456
}
0 commit comments