Skip to content

Commit 6f047b4

Browse files
committed
added flowWithCtx and flowWith
1 parent 9e93ba5 commit 6f047b4

File tree

3 files changed

+193
-8
lines changed

3 files changed

+193
-8
lines changed

packages/ethereum-contracts/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
77

88
### Added
99

10-
- `SuperTokenV1Library`: overloaded `claimAll` for the msg.sender to claim for themselves.
10+
- `SuperTokenV1Library`
11+
- overloaded `claimAll` for the msg.sender to claim for themselves
12+
- added `flowWithCtx` and `flowFromWithCtx`
1113

1214
## [v1.12.0]
1315

packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ library SuperTokenV1Library {
264264
} // else no change, do nothing
265265
return true;
266266
} else {
267+
// can't set negative flowrate
267268
revert IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE();
268269
}
269270
}
@@ -802,7 +803,78 @@ library SuperTokenV1Library {
802803
/** CFA With CTX FUNCTIONS ************************************* */
803804

804805
/**
805-
* @dev Create flow with context and userData
806+
* @dev Set CFA flowrate with context
807+
* @param token Super token address
808+
* @param receiver The receiver of the flow
809+
* @param flowRate The wanted flowrate in wad/second. Only positive values are valid here.
810+
* @param ctx Context bytes (see ISuperfluid.sol for Context struct)
811+
* @return newCtx The updated context after the execution of the agreement function
812+
*/
813+
function flowWithCtx(
814+
ISuperToken token,
815+
address receiver,
816+
int96 flowRate,
817+
bytes memory ctx
818+
) internal returns (bytes memory newCtx) {
819+
// note: from the lib's perspective, the caller is "this", NOT "msg.sender"
820+
address sender = address(this);
821+
int96 prevFlowRate = getCFAFlowRate(token, sender, receiver);
822+
823+
if (flowRate > 0) {
824+
if (prevFlowRate == 0) {
825+
return createFlowWithCtx(token, receiver, flowRate, ctx);
826+
} else if (prevFlowRate != flowRate) {
827+
return updateFlowWithCtx(token, receiver, flowRate, ctx);
828+
} // else no change, do nothing
829+
return ctx;
830+
} else if (flowRate == 0) {
831+
if (prevFlowRate > 0) {
832+
return deleteFlowWithCtx(token, sender, receiver, ctx);
833+
} // else no change, do nothing
834+
return ctx;
835+
} else {
836+
// can't set negative flowrate
837+
revert IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE();
838+
}
839+
}
840+
841+
/**
842+
* @notice Like `flowFrom`, with context
843+
* @param token Super token address
844+
* @param sender The sender of the flow
845+
* @param receiver The receiver of the flow
846+
* @param flowRate The wanted flowRate in wad/second. Only positive values are valid here.
847+
* @param ctx Context bytes (see ISuperfluid.sol for Context struct)
848+
* @return newCtx The updated context after the execution of the agreement function
849+
*/
850+
function flowFromWithCtx(
851+
ISuperToken token,
852+
address sender,
853+
address receiver,
854+
int96 flowRate,
855+
bytes memory ctx
856+
) internal returns (bytes memory newCtx) {
857+
int96 prevFlowRate = getCFAFlowRate(token, sender, receiver);
858+
859+
if (flowRate > 0) {
860+
if (prevFlowRate == 0) {
861+
return createFlowFromWithCtx(token, sender, receiver, flowRate, ctx);
862+
} else if (prevFlowRate != flowRate) {
863+
return updateFlowFromWithCtx(token, sender, receiver, flowRate, ctx);
864+
} // else no change, do nothing
865+
return ctx;
866+
} else if (flowRate == 0) {
867+
if (prevFlowRate > 0) {
868+
return deleteFlowFromWithCtx(token, sender, receiver, ctx);
869+
} // else no change, do nothing
870+
return ctx;
871+
} else {
872+
revert IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE();
873+
}
874+
}
875+
876+
/**
877+
* @dev Create flow with context
806878
* @param token The token to flow
807879
* @param receiver The receiver of the flow
808880
* @param flowRate The desired flowRate
@@ -1378,6 +1450,8 @@ library SuperTokenV1Library {
13781450

13791451
/**
13801452
* @dev Tries to distribute flow at `requestedFlowRate` of `token` from `from` to `pool`.
1453+
* Note: the "actual" flowrate set can also be less than `requestedFlowRate, depending on the
1454+
* current total pool units. In order to know beforehand, use `estimateDistributionActualAmount`.
13811455
* NOTE: The ability to set the `from` argument is needed only when liquidating a GDA flow.
13821456
* The GDA currently doesn't have ACL support.
13831457
* @param token The Super Token address.

packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// SPDX-License-Identifier: AGPLv3
22
pragma solidity ^0.8.23;
33

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";
79

810
/*
911
* Note: since libs are used by contracts, not EOAs, do NOT try to use
@@ -314,15 +316,60 @@ contract SuperTokenV1LibraryTest is FoundrySuperfluidTester {
314316
assert(lastUpdated2 > lastUpdated1);
315317
}
316318

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+
317364
// HELPER FUNCTIONS ========================================================================================
318365

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) {
321368
(,flowRate,,) = sf.cfa.getFlow(superToken, sender, receiver);
322369
}
323370

324371
// 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) {
326373
return sf.gda.getFlowRate(superToken, sender, pool);
327374
}
328375

@@ -344,4 +391,66 @@ contract SuperTokenV1LibraryTest is FoundrySuperfluidTester {
344391
superToken.flowFrom(sender, receiver, flowRate);
345392
vm.stopPrank();
346393
}
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+
}
347456
}

0 commit comments

Comments
 (0)