diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index ab3bc54deb..fb2240d093 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -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] diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index 8054f99fa9..96ceb6e3f5 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -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(); } } @@ -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, @@ -1524,7 +1522,7 @@ library SuperTokenV1Library { flowRate ); } else { - return setFlowrate(token, receiverOrPool, flowRate); + return setCFAFlowRate(token, receiverOrPool, flowRate); } } diff --git a/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol b/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol index 33cefd717a..bca99c7fef 100644 --- a/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol +++ b/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol @@ -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"; @@ -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) { } @@ -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( @@ -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(); } } \ No newline at end of file