Skip to content

Commit

Permalink
setFlowrate -> setCFAFlowRate
Browse files Browse the repository at this point in the history
  • Loading branch information
d10r committed Oct 28, 2024
1 parent 549e5c7 commit 4c08c7d
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 72 deletions.
3 changes: 2 additions & 1 deletion packages/ethereum-contracts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [UNRELEASED]

### Added
- Added functionality to `SuperTokenV1Library`.
- Added functionality to `SuperTokenV1Library`, most notably `flowX` and `transferX`.

### Breaking
- Removed `CFAv1Library`, superseded by `SuperTokenV1Library`.
- Removed `IDAv1Library` and IDA functionality from `SuperTokenV1Library`. IDA shall not be used anymore, the GDA covers all its functionality.
- Removed some methods from `SuperTokenV1Library` which don't belong to the token interface.

## [v1.11.1]

Expand Down
56 changes: 27 additions & 29 deletions packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1423,78 +1423,76 @@ library SuperTokenV1Library {

// ************** Higher-Level Abstractions ***************

error INVALID_FLOW_RATE();

/**
* @notice Sets the given flowrate between the caller and a given receiver.
* If there's no pre-existing flow and `flowrate` non-zero, a new flow is created.
* If there's an existing flow and `flowrate` non-zero, the flowrate of that flow is updated.
* If there's an existing flow and `flowrate` zero, the flow is deleted.
* If the existing and given flowrate are equal, no action is taken.
* @notice Sets the given CFA flowrate between the caller and a given receiver.
* If there's no pre-existing flow and `flowRate` non-zero, a new flow is created.
* If there's an existing flow and `flowRate` non-zero, the flowRate of that flow is updated.
* If there's an existing flow and `flowRate` zero, the flow is deleted.
* If the existing and given flowRate are equal, no action is taken.
* On creation of a flow, a "buffer" amount is automatically detracted from the sender account's available balance.
* If the sender account is solvent when the flow is deleted, this buffer is redeemed to it.
* @param token Super token address
* @param receiver The receiver of the flow
* @param flowrate The wanted flowrate in wad/second. Only positive values are valid here.
* @param flowRate The wanted flowrate in wad/second. Only positive values are valid here.
* @return bool
*/
function setFlowrate(
function setCFAFlowRate(
ISuperToken token,
address receiver,
int96 flowrate
int96 flowRate
) internal returns (bool) {
// note: from the lib's perspective, the caller is "this", NOT "msg.sender"
address sender = address(this);
int96 prevFlowRate = getCFAFlowRate(token, sender, receiver);

if (flowrate > 0) {
if (flowRate > 0) {
if (prevFlowRate == 0) {
return createFlow(token, receiver, flowrate, new bytes(0));
} else if (prevFlowRate != flowrate) {
return updateFlow(token, receiver, flowrate, new bytes(0));
return createFlow(token, receiver, flowRate, new bytes(0));
} else if (prevFlowRate != flowRate) {
return updateFlow(token, receiver, flowRate, new bytes(0));
} // else no change, do nothing
return true;
} else if (flowrate == 0) {
} else if (flowRate == 0) {
if (prevFlowRate > 0) {
return deleteFlow(token, sender, receiver, new bytes(0));
} // else no change, do nothing
return true;
} else {
revert INVALID_FLOW_RATE();
revert IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE();
}
}

/**
* @notice Like `setFlowrate`, but can be invoked by an account with flowOperator permissions
* @notice Like `setCFAFlowRate`, but can be invoked by an account with flowOperator permissions
* on behalf of the sender account.
* @param token Super token address
* @param sender The sender of the flow
* @param receiver The receiver of the flow
* @param flowrate The wanted flowrate in wad/second. Only positive values are valid here.
* @param flowRate The wanted flowRate in wad/second. Only positive values are valid here.
* @return bool
*/
function setFlowrateFrom(
function setCFAFlowRateFrom(
ISuperToken token,
address sender,
address receiver,
int96 flowrate
int96 flowRate
) internal returns (bool) {
int96 prevFlowRate = getCFAFlowRate(token, sender, receiver);

if (flowrate > 0) {
if (flowRate > 0) {
if (prevFlowRate == 0) {
return createFlowFrom(token, sender, receiver, flowrate, new bytes(0));
} else if (prevFlowRate != flowrate) {
return updateFlowFrom(token, sender, receiver, flowrate, new bytes(0));
return createFlowFrom(token, sender, receiver, flowRate, new bytes(0));
} else if (prevFlowRate != flowRate) {
return updateFlowFrom(token, sender, receiver, flowRate, new bytes(0));
} // else no change, do nothing
return true;
} else if (flowrate == 0) {
} else if (flowRate == 0) {
if (prevFlowRate > 0) {
return deleteFlowFrom(token, sender, receiver, new bytes(0));
} // else no change, do nothing
return true;
} else {
revert INVALID_FLOW_RATE();
revert IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE();
}
}

Expand All @@ -1503,10 +1501,10 @@ library SuperTokenV1Library {
* If the receiver is an account, it uses the CFA, if it's a pool it uses the GDA.
* @param token Super token address
* @param receiverOrPool The receiver (account) or pool
* @param flowRate the flowrate to be set.
* @param flowRate the flowRate to be set.
* @return A boolean value indicating whether the operation was successful.
* Note that all the specifics of the underlying agreement used still apply.
* E.g. if the GDA is used, the effective flowrate may differ from the selected one.
* E.g. if the GDA is used, the effective flowRate may differ from the selected one.
*/
function flowX(
ISuperToken token,
Expand All @@ -1524,7 +1522,7 @@ library SuperTokenV1Library {
flowRate
);
} else {
return setFlowrate(token, receiverOrPool, flowRate);
return setCFAFlowRate(token, receiverOrPool, flowRate);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: AGPLv3
pragma solidity ^0.8.23;

import { IConstantFlowAgreementV1 } from "../../../contracts/interfaces/agreements/IConstantFlowAgreementV1.sol";
import { FoundrySuperfluidTester, ISuperToken, SuperTokenV1Library, ISuperfluidPool }
from "../FoundrySuperfluidTester.sol";

Expand All @@ -12,6 +13,9 @@ import { FoundrySuperfluidTester, ISuperToken, SuperTokenV1Library, ISuperfluidP
contract SuperTokenV1LibraryTest is FoundrySuperfluidTester {
using SuperTokenV1Library for ISuperToken;

int96 internal constant DEFAULT_FLOWRATE = 1e12;
uint256 internal constant DEFAULT_AMOUNT = 1e18;

constructor() FoundrySuperfluidTester(3) {
}

Expand All @@ -34,25 +38,25 @@ contract SuperTokenV1LibraryTest is FoundrySuperfluidTester {
return sf.gda.getFlowRate(superToken, sender, pool);
}

function testSetFlowrate() external {
int96 fr1 = 1e9;

function testSetCFAFlowRate() external {
// initial createFlow
superToken.setFlowrate(bob, fr1);
assertEq(_getCFAFlowRate(address(this), bob), fr1, "createFlow unexpected result");
superToken.setCFAFlowRate(bob, DEFAULT_FLOWRATE);
assertEq(_getCFAFlowRate(address(this), bob), DEFAULT_FLOWRATE, "createFlow unexpected result");

// double it -> updateFlow
superToken.setFlowrate(bob, fr1 * 2);
assertEq(_getCFAFlowRate(address(this), bob), fr1 * 2, "updateFlow unexpected result");
superToken.setCFAFlowRate(bob, DEFAULT_FLOWRATE * 2);
assertEq(_getCFAFlowRate(address(this), bob), DEFAULT_FLOWRATE * 2, "updateFlow unexpected result");

// set to 0 -> deleteFlow
superToken.setFlowrate(bob, 0);
assertEq(_getCFAFlowRate(address(this), bob), 0, "deleteFlow unexpected result");
}
superToken.setCFAFlowRate(bob, 0);
assertEq(_getCFAFlowRate(address(this), bob), 0, "deleteFlow unexpected result");

function testSetFlowrateFrom() external {
int96 fr1 = 1e9;
// invalid flowrate
vm.expectRevert(IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE.selector);
this.__externalSetCFAFlowRate(address(this), bob, -1);
}

function testSetCFAFlowRateFrom() external {
// alice allows this Test contract to operate CFA flows on her behalf
vm.startPrank(alice);
sf.host.callAgreement(
Expand All @@ -63,68 +67,77 @@ contract SuperTokenV1LibraryTest is FoundrySuperfluidTester {
vm.stopPrank();

// initial createFlow
superToken.setFlowrateFrom(alice, bob, fr1);
assertEq(_getCFAFlowRate(alice, bob), fr1, "createFlow unexpected result");
superToken.setCFAFlowRateFrom(alice, bob, DEFAULT_FLOWRATE);
assertEq(_getCFAFlowRate(alice, bob), DEFAULT_FLOWRATE, "createFlow unexpected result");

// double it -> updateFlow
superToken.setFlowrateFrom(alice, bob, fr1 * 2);
assertEq(_getCFAFlowRate(alice, bob), fr1 * 2, "updateFlow unexpected result");
superToken.setCFAFlowRateFrom(alice, bob, DEFAULT_FLOWRATE * 2);
assertEq(_getCFAFlowRate(alice, bob), DEFAULT_FLOWRATE * 2, "updateFlow unexpected result");

// set to 0 -> deleteFlow
superToken.setFlowrateFrom(alice, bob, 0);
assertEq(_getCFAFlowRate(alice, bob), 0, "deleteFlow unexpected result");
superToken.setCFAFlowRateFrom(alice, bob, 0);
assertEq(_getCFAFlowRate(alice, bob), 0, "deleteFlow unexpected result");

vm.expectRevert(IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE.selector);
this.__externalSetCFAFlowRateFrom(address(this), alice, bob, -1);
}

function testFlowXToAccount() external {
int96 fr1 = 1e9;

superToken.flowX(bob, fr1);
assertEq(_getCFAFlowRate(address(this), bob), fr1, "createFlow unexpected result");
superToken.flowX(bob, DEFAULT_FLOWRATE);
assertEq(_getCFAFlowRate(address(this), bob), DEFAULT_FLOWRATE, "createFlow unexpected result");

// double it -> updateFlow
superToken.flowX(bob, fr1 * 2);
assertEq(_getCFAFlowRate(address(this), bob), fr1 * 2, "updateFlow unexpected result");
superToken.flowX(bob, DEFAULT_FLOWRATE * 2);
assertEq(_getCFAFlowRate(address(this), bob), DEFAULT_FLOWRATE * 2, "updateFlow unexpected result");

// set to 0 -> deleteFlow
superToken.flowX(bob, 0);
assertEq(_getCFAFlowRate(address(this), bob), 0, "deleteFlow unexpected result");
superToken.flowX(bob, 0);
assertEq(_getCFAFlowRate(address(this), bob), 0, "deleteFlow unexpected result");
}

function testFlowXToPool() external {
int96 fr1 = 1e9;

ISuperfluidPool pool = superToken.createPool();
pool.updateMemberUnits(bob, 1);

superToken.flowX(address(pool), fr1);
assertEq(_getGDAFlowRate(address(this), pool), fr1, "distrbuteFlow (new) unexpected result");
superToken.flowX(address(pool), DEFAULT_FLOWRATE);
assertEq(_getGDAFlowRate(address(this), pool), DEFAULT_FLOWRATE, "distrbuteFlow (new) unexpected result");

// double it -> updateFlow
superToken.flowX(address(pool), fr1 * 2);
assertEq(_getGDAFlowRate(address(this), pool), fr1 * 2, "distrbuteFlow (update) unexpected result");
superToken.flowX(address(pool), DEFAULT_FLOWRATE * 2);
assertEq(_getGDAFlowRate(address(this), pool), DEFAULT_FLOWRATE * 2, "distrbuteFlow (update) unexpected result");

// set to 0 -> deleteFlow
superToken.flowX(address(pool), 0);
assertEq(_getGDAFlowRate(address(this), pool), 0, "distrbuteFlow (delete) unexpected result");
superToken.flowX(address(pool), 0);
assertEq(_getGDAFlowRate(address(this), pool), 0, "distrbuteFlow (delete) unexpected result");
}

function testTransferXToAccount() external {
uint256 amount = 1e18;

uint256 bobBalBefore = superToken.balanceOf(bob);
superToken.transferX(bob, amount);
assertEq(superToken.balanceOf(bob) - bobBalBefore, amount, "transfer unexpected result");
superToken.transferX(bob, DEFAULT_AMOUNT);
assertEq(superToken.balanceOf(bob) - bobBalBefore, DEFAULT_AMOUNT, "transfer unexpected result");
}

function testTransferXToPool() external {
uint256 amount = 1e18;

uint256 bobBalBefore = superToken.balanceOf(bob);
ISuperfluidPool pool = superToken.createPool();
pool.updateMemberUnits(bob, 1);

superToken.transferX(address(pool), amount);
superToken.transferX(address(pool), DEFAULT_AMOUNT);
pool.claimAll(bob);
assertEq(superToken.balanceOf(bob) - bobBalBefore, amount, "distribute unexpected result");
assertEq(superToken.balanceOf(bob) - bobBalBefore, DEFAULT_AMOUNT, "distribute unexpected result");
}

// helpers converting the lib call to an external call, for exception checking

function __externalSetCFAFlowRate(address msgSender, address receiver, int96 flowRate) external {
vm.startPrank(msgSender);
superToken.setCFAFlowRate(receiver, flowRate);
vm.stopPrank();
}

function __externalSetCFAFlowRateFrom(address msgSender, address sender, address receiver, int96 flowRate) external {
vm.startPrank(msgSender);
superToken.setCFAFlowRateFrom(sender, receiver, flowRate);
vm.stopPrank();
}
}

0 comments on commit 4c08c7d

Please sign in to comment.