diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4194f4e95a..b8100a9d72 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,8 +10,8 @@ Before interacting with the Superfluid community, please read and understand our At minimum, you will need to have these available in your development environment: -- yarn, sufficiently recent version, the actual yarn version is locked in `.yarnrc`. -- nodejs 18.x. +- Yarn, sufficiently recent version, the actual yarn version is locked in `.yarnrc`. +- Node.js 18.x. Additionally recommended: - jq diff --git a/packages/automation-contracts/autowrap/package.json b/packages/automation-contracts/autowrap/package.json index e47f6782c8..016376ddd6 100644 --- a/packages/automation-contracts/autowrap/package.json +++ b/packages/automation-contracts/autowrap/package.json @@ -4,7 +4,7 @@ "version": "0.3.0", "devDependencies": { "@openzeppelin/contracts": "^4.9.6", - "@superfluid-finance/ethereum-contracts": "^1.11.1", + "@superfluid-finance/ethereum-contracts": "^1.12.0", "@superfluid-finance/metadata": "^1.5.2" }, "license": "MIT", diff --git a/packages/automation-contracts/scheduler/README.md b/packages/automation-contracts/scheduler/README.md index 3c5a895cef..4faa72f24a 100644 --- a/packages/automation-contracts/scheduler/README.md +++ b/packages/automation-contracts/scheduler/README.md @@ -49,16 +49,10 @@ Deployment script will deploy all contracts and verify them on Etherscan. npx hardhat deploy --network ``` - #### Deployed Contracts -#### Testnets -| | FlowScheduler | VestingScheduler | -|----------|--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------| -| OP Sepolia | [0x73B1Ce21d03ad389C2A291B1d1dc4DAFE7B5Dc68](https://sepolia-optimism.etherscan.io/address/0x73B1Ce21d03ad389C2A291B1d1dc4DAFE7B5Dc68) | [0x27444c0235a4D921F3106475faeba0B5e7ABDD7a](https://sepolia-optimism.etherscan.io/address/0x27444c0235a4D921F3106475faeba0B5e7ABDD7a) | +Contract addresses can be found in https://explorer.superfluid.finance/protocol, with the data source being `networks.json` in the metadata package. +All current production deployments are based on the codebase found in version 1.2.0 of the scheduler package. -#### Mainnets -| | FlowScheduler | VestingScheduler | -|---------|-------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------| -| Polygon | [0x47D34512492D95A3531A628e5B85e32fAFaC1b42](https://polygonscan.com/address/0x47D34512492D95A3531A628e5B85e32fAFaC1b42#code) | [0xF9B3b4c23d08ebcBb8A70F5C7471E3Edd3ddF210](https://polygonscan.com/address/0xF9B3b4c23d08ebcBb8A70F5C7471E3Edd3ddF210#code) | -| BSC | [0x1D65c6d3AD39d454Ea8F682c49aE7744706eA96d](https://bscscan.com/address/0x1D65c6d3AD39d454Ea8F682c49aE7744706eA96d#code) | [0x4f268bfB109439D7c23A903c237cdBEbd7E987a1](https://bscscan.com/address/0x4f268bfB109439D7c23A903c237cdBEbd7E987a1#code) | +In package version 1.3.0 VestingScheduler (v1) was removed, as it's not gonna be used for new production deployments. +VestingSchedulerV2 and FlowScheduler were modified to use the SuperTokenV1Library instead of the deprecated CFAv1Library. diff --git a/packages/automation-contracts/scheduler/contracts/FlowScheduler.sol b/packages/automation-contracts/scheduler/contracts/FlowScheduler.sol index 263a967ccd..8d0bc242f0 100644 --- a/packages/automation-contracts/scheduler/contracts/FlowScheduler.sol +++ b/packages/automation-contracts/scheduler/contracts/FlowScheduler.sol @@ -2,10 +2,10 @@ // solhint-disable not-rely-on-time pragma solidity ^0.8.0; import { - ISuperfluid, ISuperToken, SuperAppDefinitions, IConstantFlowAgreementV1 + ISuperfluid, ISuperToken, SuperAppDefinitions } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol"; import { SuperAppBase } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperAppBase.sol"; -import { CFAv1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/CFAv1Library.sol"; +import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; import { IFlowScheduler } from "./interface/IFlowScheduler.sol"; /** @@ -14,24 +14,12 @@ import { IFlowScheduler } from "./interface/IFlowScheduler.sol"; */ contract FlowScheduler is IFlowScheduler, SuperAppBase { - mapping(bytes32 => FlowSchedule) public flowSchedules; // id = keccak(supertoken, sender, receiver) + using SuperTokenV1Library for ISuperToken; - using CFAv1Library for CFAv1Library.InitData; - CFAv1Library.InitData public cfaV1; //initialize cfaV1 variable + ISuperfluid public immutable HOST; + mapping(bytes32 => FlowSchedule) public flowSchedules; // id = keccak(supertoken, sender, receiver) constructor(ISuperfluid host) { - // Initialize CFA Library - cfaV1 = CFAv1Library.InitData( - host, - IConstantFlowAgreementV1( - address( - host.getAgreementClass( - keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1") - ) - ) - ) - ); - // super app registration. This is a dumb superApp, only for frontend batching calls. uint256 configWord = SuperAppDefinitions.APP_LEVEL_FINAL | SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP | @@ -41,6 +29,7 @@ contract FlowScheduler is IFlowScheduler, SuperAppBase { SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP | SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP; host.registerApp(configWord); + HOST = host; } /// @dev IFlowScheduler.createFlowSchedule implementation. @@ -158,7 +147,7 @@ contract FlowScheduler is IFlowScheduler, SuperAppBase { } // Create a flow accordingly as per the flow schedule data. - cfaV1.createFlowByOperator(sender, receiver, superToken, schedule.flowRate, userData); + superToken.createFlowFrom(sender, receiver, schedule.flowRate, userData); emit CreateFlowExecuted( superToken, @@ -188,7 +177,7 @@ contract FlowScheduler is IFlowScheduler, SuperAppBase { // revert if userData was set by user, but caller didn't provide it if ((userData.length != 0 ? keccak256(userData) : bytes32(0x0)) != schedule.userData) revert UserDataInvalid(); - cfaV1.deleteFlowByOperator(sender, receiver, superToken, userData); + superToken.deleteFlowFrom(sender, receiver, userData); emit DeleteFlowExecuted( superToken, @@ -209,8 +198,8 @@ contract FlowScheduler is IFlowScheduler, SuperAppBase { function _getSender(bytes memory ctx) internal view returns(address sender) { if(ctx.length != 0) { - if(msg.sender != address(cfaV1.host)) revert HostInvalid(); - sender = cfaV1.host.decodeCtx(ctx).msgSender; + if(msg.sender != address(HOST)) revert HostInvalid(); + sender = HOST.decodeCtx(ctx).msgSender; } else { sender = msg.sender; } diff --git a/packages/automation-contracts/scheduler/contracts/FlowSchedulerResolver.sol b/packages/automation-contracts/scheduler/contracts/FlowSchedulerResolver.sol index 1c0a113043..7bdac6412d 100644 --- a/packages/automation-contracts/scheduler/contracts/FlowSchedulerResolver.sol +++ b/packages/automation-contracts/scheduler/contracts/FlowSchedulerResolver.sol @@ -4,11 +4,14 @@ pragma solidity ^0.8.0; import { FlowScheduler } from "./FlowScheduler.sol"; import { - ISuperToken, IConstantFlowAgreementV1 + ISuperToken } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol"; +import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; contract FlowSchedulerResolver { + using SuperTokenV1Library for ISuperToken; + FlowScheduler public flowScheduler; constructor(address _flowScheduler) { @@ -17,7 +20,7 @@ contract FlowSchedulerResolver { /** * @dev Gelato resolver that checks whether Flow Scheduler action can be taken - * @notice Make sure ACL permissions and ERC20 approvals are set for `flowScheduler` + * @notice Make sure ACL permissions and ERC20 approvals are set for `flowScheduler` * before using Gelato automation with this resolver * @return bool whether there is a valid Flow Scheduler action to be taken or not * @return bytes the function payload to be executed (empty if none) @@ -29,30 +32,24 @@ contract FlowSchedulerResolver { ) external view returns( bool , bytes memory ) { FlowScheduler.FlowSchedule memory flowSchedule = flowScheduler.getFlowSchedule( - superToken, - sender, + superToken, + sender, receiver ); - (, IConstantFlowAgreementV1 cfa) = flowScheduler.cfaV1(); - (,uint8 permissions, int96 flowRateAllowance) = cfa.getFlowOperatorData( - ISuperToken(superToken), - sender, - address(flowScheduler) - ); - (,int96 currentFlowRate,,) = cfa.getFlow(ISuperToken(superToken), sender, receiver); + (bool allowCreate, , bool allowDelete, int96 flowRateAllowance) = + ISuperToken(superToken).getFlowPermissions(sender, address(flowScheduler)); - // 1. permissions must not be create/update/delete (7) or create/delete (5) - // 2. scheduled flowRate must not be greater than allowance - if ( ( permissions != 5 && permissions != 7 ) - || flowSchedule.flowRate > flowRateAllowance ) { + int96 currentFlowRate = ISuperToken(superToken).getFlowRate(sender, receiver); + // 1. needs create and delete permission + // 2. scheduled flowRate must not be greater than allowance + if ( !allowCreate || !allowDelete || flowSchedule.flowRate > flowRateAllowance ) { // return canExec as false and non-executable payload return ( false, "0x" ); - } // 1. end date must be set (flow schedule exists) @@ -76,16 +73,16 @@ contract FlowSchedulerResolver { ); } - + // 1. start date must be set (flow schedule exists) // 2. start date must have been past // 3. max delay must have not been exceeded // 4. enough erc20 allowance to transfer the optional start amount else if ( flowSchedule.startDate != 0 && - block.timestamp >= flowSchedule.startDate && + block.timestamp >= flowSchedule.startDate && block.timestamp <= flowSchedule.startDate + flowSchedule.startMaxDelay && ISuperToken(superToken).allowance(sender, address(flowScheduler)) >= flowSchedule.startAmount ) { - + // return canExec as true and executeCreateFlow payload return ( true, diff --git a/packages/automation-contracts/scheduler/contracts/VestingScheduler.sol b/packages/automation-contracts/scheduler/contracts/VestingScheduler.sol deleted file mode 100644 index 9f6fd0c6c9..0000000000 --- a/packages/automation-contracts/scheduler/contracts/VestingScheduler.sol +++ /dev/null @@ -1,257 +0,0 @@ -// SPDX-License-Identifier: AGPLv3 -// solhint-disable not-rely-on-time -pragma solidity ^0.8.0; -import { - ISuperfluid, ISuperToken, SuperAppDefinitions, IConstantFlowAgreementV1 -} from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol"; -import { SuperAppBase } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperAppBase.sol"; -import { CFAv1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/CFAv1Library.sol"; -import { IVestingScheduler } from "./interface/IVestingScheduler.sol"; - -contract VestingScheduler is IVestingScheduler, SuperAppBase { - - using CFAv1Library for CFAv1Library.InitData; - CFAv1Library.InitData public cfaV1; - mapping(bytes32 => VestingSchedule) public vestingSchedules; // id = keccak(supertoken, sender, receiver) - - uint32 public constant MIN_VESTING_DURATION = 7 days; - uint32 public constant START_DATE_VALID_AFTER = 3 days; - uint32 public constant END_DATE_VALID_BEFORE = 1 days; - - constructor(ISuperfluid host) { - cfaV1 = CFAv1Library.InitData( - host, - IConstantFlowAgreementV1( - address( - host.getAgreementClass( - keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1") - ) - ) - ) - ); - // Superfluid SuperApp registration. This is a dumb SuperApp, only for front-end tx batch calls. - uint256 configWord = SuperAppDefinitions.APP_LEVEL_FINAL | - SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP | - SuperAppDefinitions.AFTER_AGREEMENT_CREATED_NOOP | - SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP | - SuperAppDefinitions.AFTER_AGREEMENT_UPDATED_NOOP | - SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP | - SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP; - host.registerApp(configWord); - } - - /// @dev IVestingScheduler.createVestingSchedule implementation. - function createVestingSchedule( - ISuperToken superToken, - address receiver, - uint32 startDate, - uint32 cliffDate, - int96 flowRate, - uint256 cliffAmount, - uint32 endDate, - bytes memory ctx - ) external returns (bytes memory newCtx) { - newCtx = ctx; - address sender = _getSender(ctx); - - if (receiver == address(0) || receiver == sender) revert AccountInvalid(); - if (address(superToken) == address(0)) revert ZeroAddress(); - if (flowRate <= 0) revert FlowRateInvalid(); - if (cliffDate != 0 && startDate > cliffDate) revert TimeWindowInvalid(); - if (cliffDate == 0 && cliffAmount != 0) revert CliffInvalid(); - - uint32 cliffAndFlowDate = cliffDate == 0 ? startDate : cliffDate; - if (cliffAndFlowDate <= block.timestamp || - cliffAndFlowDate >= endDate || - cliffAndFlowDate + START_DATE_VALID_AFTER >= endDate - END_DATE_VALID_BEFORE || - endDate - cliffAndFlowDate < MIN_VESTING_DURATION - ) revert TimeWindowInvalid(); - - bytes32 hashConfig = keccak256(abi.encodePacked(superToken, sender, receiver)); - if (vestingSchedules[hashConfig].endDate != 0) revert ScheduleAlreadyExists(); - vestingSchedules[hashConfig] = VestingSchedule( - cliffAndFlowDate, - endDate, - flowRate, - cliffAmount - ); - - emit VestingScheduleCreated( - superToken, - sender, - receiver, - startDate, - cliffDate, - flowRate, - endDate, - cliffAmount - ); - } - - function updateVestingSchedule( - ISuperToken superToken, - address receiver, - uint32 endDate, - bytes memory ctx - ) external returns (bytes memory newCtx) { - newCtx = ctx; - address sender = _getSender(ctx); - - bytes32 configHash = keccak256(abi.encodePacked(superToken, sender, receiver)); - VestingSchedule memory schedule = vestingSchedules[configHash]; - - if (endDate <= block.timestamp) revert TimeWindowInvalid(); - - // Only allow an update if 1. vesting exists 2. executeCliffAndFlow() has been called - if (schedule.cliffAndFlowDate != 0 || schedule.endDate == 0) revert ScheduleNotFlowing(); - vestingSchedules[configHash].endDate = endDate; - emit VestingScheduleUpdated( - superToken, - sender, - receiver, - schedule.endDate, - endDate - ); - } - - /// @dev IVestingScheduler.deleteVestingSchedule implementation. - function deleteVestingSchedule( - ISuperToken superToken, - address receiver, - bytes memory ctx - ) external returns (bytes memory newCtx) { - newCtx = ctx; - address sender = _getSender(ctx); - bytes32 configHash = keccak256(abi.encodePacked(superToken, sender, receiver)); - - if (vestingSchedules[configHash].endDate != 0) { - delete vestingSchedules[configHash]; - emit VestingScheduleDeleted(superToken, sender, receiver); - } else { - revert ScheduleDoesNotExist(); - } - } - - /// @dev IVestingScheduler.executeCliffAndFlow implementation. - function executeCliffAndFlow( - ISuperToken superToken, - address sender, - address receiver - ) external returns (bool success) { - bytes32 configHash = keccak256(abi.encodePacked(superToken, sender, receiver)); - VestingSchedule memory schedule = vestingSchedules[configHash]; - - if (schedule.cliffAndFlowDate > block.timestamp || - schedule.cliffAndFlowDate + START_DATE_VALID_AFTER < block.timestamp - ) revert TimeWindowInvalid(); - - // Invalidate configuration straight away -- avoid any chance of re-execution or re-entry. - delete vestingSchedules[configHash].cliffAndFlowDate; - delete vestingSchedules[configHash].cliffAmount; - - // Compensate for the fact that flow will almost always be executed slightly later than scheduled. - uint256 flowDelayCompensation = (block.timestamp - schedule.cliffAndFlowDate) * uint96(schedule.flowRate); - - // If there's cliff or compensation then transfer that amount. - if (schedule.cliffAmount != 0 || flowDelayCompensation != 0) { - superToken.transferFrom( - sender, - receiver, - schedule.cliffAmount + flowDelayCompensation - ); - } - - // Create a flow according to the vesting schedule configuration. - cfaV1.createFlowByOperator(sender, receiver, superToken, schedule.flowRate); - - emit VestingCliffAndFlowExecuted( - superToken, - sender, - receiver, - schedule.cliffAndFlowDate, - schedule.flowRate, - schedule.cliffAmount, - flowDelayCompensation - ); - - return true; - } - - /// @dev IVestingScheduler.executeEndVesting implementation. - function executeEndVesting( - ISuperToken superToken, - address sender, - address receiver - ) external returns (bool success){ - bytes32 configHash = keccak256(abi.encodePacked(superToken, sender, receiver)); - VestingSchedule memory schedule = vestingSchedules[configHash]; - - if (schedule.endDate - END_DATE_VALID_BEFORE > block.timestamp) revert TimeWindowInvalid(); - - // Invalidate configuration straight away -- avoid any chance of re-execution or re-entry. - delete vestingSchedules[configHash]; - // If vesting is not running, we can't do anything, just emit failing event. - if(_isFlowOngoing(superToken, sender, receiver)) { - // delete first the stream and unlock deposit amount. - cfaV1.deleteFlowByOperator(sender, receiver, superToken); - - uint256 earlyEndCompensation = schedule.endDate > block.timestamp ? - (schedule.endDate - block.timestamp) * uint96(schedule.flowRate) : 0; - bool didCompensationFail; - if (earlyEndCompensation != 0) { - // try-catch this because if the account does not have tokens for earlyEndCompensation - // we should delete the flow anyway. - try superToken.transferFrom(sender, receiver, earlyEndCompensation) - // solhint-disable-next-line no-empty-blocks - {} catch { - didCompensationFail = true; - } - } - - emit VestingEndExecuted( - superToken, - sender, - receiver, - schedule.endDate, - earlyEndCompensation, - didCompensationFail - ); - } else { - emit VestingEndFailed( - superToken, - sender, - receiver, - schedule.endDate - ); - } - - return true; - } - - /// @dev IVestingScheduler.getVestingSchedule implementation. - function getVestingSchedule( - address supertoken, - address sender, - address receiver - ) external view returns (VestingSchedule memory) { - return vestingSchedules[keccak256(abi.encodePacked(supertoken, sender, receiver))]; - } - - /// @dev get sender of transaction from Superfluid Context or transaction itself. - function _getSender(bytes memory ctx) internal view returns (address sender) { - if (ctx.length != 0) { - if (msg.sender != address(cfaV1.host)) revert HostInvalid(); - sender = cfaV1.host.decodeCtx(ctx).msgSender; - } else { - sender = msg.sender; - } - // This is an invariant and should never happen. - assert(sender != address(0)); - } - - /// @dev get flowRate of stream - function _isFlowOngoing(ISuperToken superToken, address sender, address receiver) internal view returns (bool) { - (,int96 flowRate,,) = cfaV1.cfa.getFlow(superToken, sender, receiver); - return flowRate != 0; - } -} \ No newline at end of file diff --git a/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol b/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol index 8163826012..dce469187b 100644 --- a/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol +++ b/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol @@ -2,17 +2,19 @@ // solhint-disable not-rely-on-time pragma solidity ^0.8.0; import { - ISuperfluid, ISuperToken, SuperAppDefinitions, IConstantFlowAgreementV1 + ISuperfluid, ISuperToken, SuperAppDefinitions } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol"; import { SuperAppBase } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperAppBase.sol"; -import { CFAv1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/CFAv1Library.sol"; +import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; import { IVestingSchedulerV2 } from "./interface/IVestingSchedulerV2.sol"; import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; contract VestingSchedulerV2 is IVestingSchedulerV2, SuperAppBase { - using CFAv1Library for CFAv1Library.InitData; - CFAv1Library.InitData public cfaV1; + + using SuperTokenV1Library for ISuperToken; + + ISuperfluid public immutable HOST; mapping(bytes32 => VestingSchedule) public vestingSchedules; // id = keccak(supertoken, sender, receiver) uint32 public constant MIN_VESTING_DURATION = 7 days; @@ -28,16 +30,6 @@ contract VestingSchedulerV2 is IVestingSchedulerV2, SuperAppBase { } constructor(ISuperfluid host) { - cfaV1 = CFAv1Library.InitData( - host, - IConstantFlowAgreementV1( - address( - host.getAgreementClass( - keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1") - ) - ) - ) - ); // Superfluid SuperApp registration. This is a dumb SuperApp, only for front-end tx batch calls. uint256 configWord = SuperAppDefinitions.APP_LEVEL_FINAL | SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP | @@ -47,6 +39,7 @@ contract VestingSchedulerV2 is IVestingSchedulerV2, SuperAppBase { SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP | SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP; host.registerApp(configWord); + HOST = host; } /// @dev IVestingScheduler.createVestingSchedule implementation. @@ -505,7 +498,7 @@ contract VestingSchedulerV2 is IVestingSchedulerV2, SuperAppBase { agg.sender, agg.receiver, schedule.cliffAmount + flowDelayCompensation)); } // Create a flow according to the vesting schedule configuration. - cfaV1.createFlowByOperator(agg.sender, agg.receiver, agg.superToken, schedule.flowRate); + agg.superToken.createFlowFrom(agg.sender, agg.receiver, schedule.flowRate); emit VestingCliffAndFlowExecuted( agg.superToken, agg.sender, @@ -579,7 +572,7 @@ contract VestingSchedulerV2 is IVestingSchedulerV2, SuperAppBase { // If vesting is not running, we can't do anything, just emit failing event. if (_isFlowOngoing(superToken, sender, receiver)) { // delete first the stream and unlock deposit amount. - cfaV1.deleteFlowByOperator(sender, receiver, superToken); + superToken.deleteFlowFrom(sender, receiver); uint256 earlyEndCompensation = schedule.endDate >= block.timestamp ? (schedule.endDate - block.timestamp) * uint96(schedule.flowRate) + schedule.remainderAmount @@ -695,8 +688,8 @@ contract VestingSchedulerV2 is IVestingSchedulerV2, SuperAppBase { /// @dev get sender of transaction from Superfluid Context or transaction itself. function _getSender(bytes memory ctx) private view returns (address sender) { if (ctx.length != 0) { - if (msg.sender != address(cfaV1.host)) revert HostInvalid(); - sender = cfaV1.host.decodeCtx(ctx).msgSender; + if (msg.sender != address(HOST)) revert HostInvalid(); + sender = HOST.decodeCtx(ctx).msgSender; } else { sender = msg.sender; } @@ -706,8 +699,7 @@ contract VestingSchedulerV2 is IVestingSchedulerV2, SuperAppBase { /// @dev get flowRate of stream function _isFlowOngoing(ISuperToken superToken, address sender, address receiver) private view returns (bool) { - (,int96 flowRate,,) = cfaV1.cfa.getFlow(superToken, sender, receiver); - return flowRate != 0; + return superToken.getFlowRate(sender, receiver) != 0; } function _getId( diff --git a/packages/automation-contracts/scheduler/package.json b/packages/automation-contracts/scheduler/package.json index cc5f866e52..697d4da9bb 100644 --- a/packages/automation-contracts/scheduler/package.json +++ b/packages/automation-contracts/scheduler/package.json @@ -1,10 +1,10 @@ { "name": "scheduler", "description": "Open contracts that allow scheduling streams and vestings onchain", - "version": "1.2.0", + "version": "1.3.0", "devDependencies": { "@openzeppelin/contracts": "^4.9.6", - "@superfluid-finance/ethereum-contracts": "^1.11.1", + "@superfluid-finance/ethereum-contracts": "^1.12.0", "@superfluid-finance/metadata": "^1.5.2" }, "license": "MIT", diff --git a/packages/automation-contracts/scheduler/test/VestingScheduler.t.sol b/packages/automation-contracts/scheduler/test/VestingScheduler.t.sol deleted file mode 100644 index fa93d90178..0000000000 --- a/packages/automation-contracts/scheduler/test/VestingScheduler.t.sol +++ /dev/null @@ -1,564 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import { ISuperToken } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol"; -import { FlowOperatorDefinitions } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol"; -import { IVestingScheduler } from "./../contracts/interface/IVestingScheduler.sol"; -import { VestingScheduler } from "./../contracts/VestingScheduler.sol"; -import { FoundrySuperfluidTester } from "@superfluid-finance/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol"; -import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; - -/// @title VestingSchedulerTests -/// @notice Look at me , I am the captain now - Elvijs -contract VestingSchedulerTests is FoundrySuperfluidTester { - using SuperTokenV1Library for ISuperToken; - - event VestingScheduleCreated( - ISuperToken indexed superToken, - address indexed sender, - address indexed receiver, - uint32 startDate, - uint32 cliffDate, - int96 flowRate, - uint32 endDate, - uint256 cliffAmount - ); - - event VestingScheduleUpdated( - ISuperToken indexed superToken, - address indexed sender, - address indexed receiver, - uint32 oldEndDate, - uint32 endDate - ); - - event VestingScheduleDeleted( - ISuperToken indexed superToken, - address indexed sender, - address indexed receiver - ); - - event VestingCliffAndFlowExecuted( - ISuperToken indexed superToken, - address indexed sender, - address indexed receiver, - uint32 cliffAndFlowDate, - int96 flowRate, - uint256 cliffAmount, - uint256 flowDelayCompensation - ); - - event VestingEndExecuted( - ISuperToken indexed superToken, - address indexed sender, - address indexed receiver, - uint32 endDate, - uint256 earlyEndCompensation, - bool didCompensationFail - ); - - event VestingEndFailed( - ISuperToken indexed superToken, - address indexed sender, - address indexed receiver, - uint32 endDate - ); - - event Transfer(address indexed from, address indexed to, uint256 value); - - /// @dev This is required by solidity for using the SuperTokenV1Library in the tester - VestingScheduler internal vestingScheduler; - - /// @dev Constants for Testing - uint32 immutable START_DATE = uint32(block.timestamp + 1); - uint32 immutable CLIFF_DATE = uint32(block.timestamp + 10 days); - int96 constant FLOW_RATE = 1000000000; - uint256 constant CLIFF_TRANSFER_AMOUNT = 1 ether; - uint32 immutable END_DATE = uint32(block.timestamp + 20 days); - bytes constant EMPTY_CTX = ""; - uint256 internal _expectedTotalSupply = 0; - - constructor() FoundrySuperfluidTester(3) { - vestingScheduler = new VestingScheduler(sf.host); - } - - /// SETUP AND HELPERS - function setUp() override public virtual { - super.setUp(); - } - - function _setACL_AUTHORIZE_FULL_CONTROL(address user, int96 flowRate) private { - vm.startPrank(user); - sf.host.callAgreement( - sf.cfa, - abi.encodeCall( - sf.cfa.updateFlowOperatorPermissions, - ( - superToken, - address(vestingScheduler), - FlowOperatorDefinitions.AUTHORIZE_FULL_CONTROL, - flowRate, - new bytes(0) - ) - ), - new bytes(0) - ); - vm.stopPrank(); - } - - function _createVestingScheduleWithDefaultData(address sender, address receiver) private { - vm.startPrank(sender); - vestingScheduler.createVestingSchedule( - superToken, - receiver, - START_DATE, - CLIFF_DATE, - FLOW_RATE, - CLIFF_TRANSFER_AMOUNT, - END_DATE, - EMPTY_CTX - ); - vm.stopPrank(); - } - - /// TESTS - - function testCreateVestingSchedule() public { - vm.expectEmit(true, true, true, true); - emit VestingScheduleCreated( - superToken, alice, bob, START_DATE, CLIFF_DATE, FLOW_RATE, END_DATE, CLIFF_TRANSFER_AMOUNT - ); - _createVestingScheduleWithDefaultData(alice, bob); - vm.startPrank(alice); - //assert storage data - VestingScheduler.VestingSchedule memory schedule = vestingScheduler.getVestingSchedule(address(superToken), alice, bob); - assertTrue(schedule.cliffAndFlowDate == CLIFF_DATE , "schedule.cliffAndFlowDate"); - assertTrue(schedule.endDate == END_DATE , "schedule.endDate"); - assertTrue(schedule.flowRate == FLOW_RATE , "schedule.flowRate"); - assertTrue(schedule.cliffAmount == CLIFF_TRANSFER_AMOUNT , "schedule.cliffAmount"); - } - - function testCannotCreateVestingScheduleWithWrongData() public { - vm.startPrank(alice); - // revert with superToken = 0 - vm.expectRevert(IVestingScheduler.ZeroAddress.selector); - vestingScheduler.createVestingSchedule( - ISuperToken(address(0)), - bob, - START_DATE, - CLIFF_DATE, - FLOW_RATE, - CLIFF_TRANSFER_AMOUNT, - END_DATE, - EMPTY_CTX - ); - - // revert with receivers = sender - vm.expectRevert(IVestingScheduler.AccountInvalid.selector); - vestingScheduler.createVestingSchedule( - superToken, - alice, - START_DATE, - CLIFF_DATE, - FLOW_RATE, - CLIFF_TRANSFER_AMOUNT, - END_DATE, - EMPTY_CTX - ); - - // revert with receivers = address(0) - vm.expectRevert(IVestingScheduler.AccountInvalid.selector); - vestingScheduler.createVestingSchedule( - superToken, - address(0), - START_DATE, - CLIFF_DATE, - FLOW_RATE, - CLIFF_TRANSFER_AMOUNT, - END_DATE, - EMPTY_CTX - ); - - // revert with flowRate = 0 - vm.expectRevert(IVestingScheduler.FlowRateInvalid.selector); - vestingScheduler.createVestingSchedule( - superToken, - bob, - START_DATE, - CLIFF_DATE, - 0, - CLIFF_TRANSFER_AMOUNT, - END_DATE, - EMPTY_CTX - ); - - // revert with startDate && cliffDate = 0 - vm.expectRevert(IVestingScheduler.CliffInvalid.selector); - vestingScheduler.createVestingSchedule( - superToken, - bob, - 0, - 0, - FLOW_RATE, - CLIFF_TRANSFER_AMOUNT, - END_DATE, - EMPTY_CTX - ); - - // revert with startDate && cliffDate = 0 - vm.expectRevert(IVestingScheduler.TimeWindowInvalid.selector); - vestingScheduler.createVestingSchedule( - superToken, - bob, - 0, - 0, - FLOW_RATE, - 0, - END_DATE, - EMPTY_CTX - ); - - // revert with endDate = 0 - vm.expectRevert(IVestingScheduler.TimeWindowInvalid.selector); - vestingScheduler.createVestingSchedule( - superToken, - bob, - START_DATE, - CLIFF_DATE, - FLOW_RATE, - CLIFF_TRANSFER_AMOUNT, - 0, - EMPTY_CTX - ); - - // revert with cliffAndFlowDate < block.timestamp - vm.expectRevert(IVestingScheduler.TimeWindowInvalid.selector); - vestingScheduler.createVestingSchedule( - superToken, - bob, - uint32(block.timestamp) - 1, - 0, - FLOW_RATE, - 0, - END_DATE, - EMPTY_CTX - ); - - // revert with cliffAndFlowDate >= endDate - vm.expectRevert(IVestingScheduler.TimeWindowInvalid.selector); - vestingScheduler.createVestingSchedule( - superToken, - bob, - START_DATE, - CLIFF_DATE, - FLOW_RATE, - CLIFF_TRANSFER_AMOUNT, - CLIFF_DATE, - EMPTY_CTX - ); - - // revert with cliffAndFlowDate + startDateValidFor >= endDate - endDateValidBefore - vm.expectRevert(IVestingScheduler.TimeWindowInvalid.selector); - vestingScheduler.createVestingSchedule( - superToken, - bob, - START_DATE, - CLIFF_DATE, - FLOW_RATE, - CLIFF_TRANSFER_AMOUNT, - CLIFF_DATE, - EMPTY_CTX - ); - - // revert with startDate > cliffDate - vm.expectRevert(IVestingScheduler.TimeWindowInvalid.selector); - vestingScheduler.createVestingSchedule( - superToken, - bob, - CLIFF_DATE + 1, - CLIFF_DATE, - FLOW_RATE, - CLIFF_TRANSFER_AMOUNT, - END_DATE, - EMPTY_CTX - ); - - - // revert with vesting duration < 7 days - vm.expectRevert(IVestingScheduler.TimeWindowInvalid.selector); - vestingScheduler.createVestingSchedule( - superToken, - bob, - START_DATE, - CLIFF_DATE, - FLOW_RATE, - CLIFF_TRANSFER_AMOUNT, - CLIFF_DATE + 2 days, - EMPTY_CTX - ); - } - - function testCannotCreateVestingScheduleIfDataExist() public { - _createVestingScheduleWithDefaultData(alice, bob); - vm.expectRevert(IVestingScheduler.ScheduleAlreadyExists.selector); - _createVestingScheduleWithDefaultData(alice, bob); - } - - function testUpdateVestingSchedule() public { - _setACL_AUTHORIZE_FULL_CONTROL(alice, FLOW_RATE); - vm.expectEmit(true, true, true, true); - emit VestingScheduleCreated( - superToken, alice, bob, START_DATE, CLIFF_DATE, FLOW_RATE, END_DATE, CLIFF_TRANSFER_AMOUNT - ); - _createVestingScheduleWithDefaultData(alice, bob); - vm.prank(alice); - superToken.increaseAllowance(address(vestingScheduler), type(uint256).max); - vm.startPrank(admin); - uint256 initialTimestamp = block.timestamp + 10 days + 1800; - vm.warp(initialTimestamp); - vestingScheduler.executeCliffAndFlow(superToken, alice, bob); - vm.stopPrank(); - vm.startPrank(alice); - vestingScheduler.updateVestingSchedule(superToken, bob, END_DATE + 1000, EMPTY_CTX); - //assert storage data - VestingScheduler.VestingSchedule memory schedule = vestingScheduler.getVestingSchedule(address(superToken), alice, bob); - assertTrue(schedule.cliffAndFlowDate == 0 , "schedule.cliffAndFlowDate"); - assertTrue(schedule.endDate == END_DATE + 1000 , "schedule.endDate"); - } - - function testCannotUpdateVestingScheduleIfNotRunning() public { - _createVestingScheduleWithDefaultData(alice, bob); - vm.startPrank(alice); - vm.expectRevert(IVestingScheduler.ScheduleNotFlowing.selector); - vestingScheduler.updateVestingSchedule(superToken, bob, END_DATE, EMPTY_CTX); - } - - function testCannotUpdateVestingScheduleIfDataDontExist() public { - vm.startPrank(alice); - vm.expectRevert(IVestingScheduler.ScheduleNotFlowing.selector); - vestingScheduler.updateVestingSchedule(superToken, bob, END_DATE, EMPTY_CTX); - } - - function testDeleteVestingSchedule() public { - _createVestingScheduleWithDefaultData(alice, bob); - vm.startPrank(alice); - vm.expectEmit(true, true, true, true); - emit VestingScheduleDeleted(superToken, alice, bob); - vestingScheduler.deleteVestingSchedule(superToken, bob, EMPTY_CTX); - } - - function testCannotDeleteVestingScheduleIfDataDontExist() public { - vm.startPrank(alice); - vm.expectRevert(IVestingScheduler.ScheduleDoesNotExist.selector); - vestingScheduler.deleteVestingSchedule( - superToken, - bob, - EMPTY_CTX - ); - } - - function testExecuteCliffAndFlowWithCliffAmount() public { - uint256 aliceInitialBalance = superToken.balanceOf(alice); - uint256 bobInitialBalance = superToken.balanceOf(bob); - _setACL_AUTHORIZE_FULL_CONTROL(alice, FLOW_RATE); - _createVestingScheduleWithDefaultData(alice, bob); - vm.prank(alice); - superToken.increaseAllowance(address(vestingScheduler), type(uint256).max); - vm.startPrank(admin); - uint256 initialTimestamp = block.timestamp + 10 days + 1800; - vm.warp(initialTimestamp); - uint256 flowDelayCompensation = (block.timestamp - CLIFF_DATE) * uint96(FLOW_RATE); - vm.expectEmit(true, true, true, true); - emit Transfer(alice, bob, CLIFF_TRANSFER_AMOUNT + flowDelayCompensation); - vm.expectEmit(true, true, true, true); - emit VestingCliffAndFlowExecuted( - superToken, alice, bob, CLIFF_DATE, FLOW_RATE, CLIFF_TRANSFER_AMOUNT, flowDelayCompensation - ); - bool success = vestingScheduler.executeCliffAndFlow(superToken, alice, bob); - assertTrue(success, "executeVesting should return true"); - uint256 finalTimestamp = block.timestamp + 10 days - 3600; - vm.warp(finalTimestamp); - vm.expectEmit(true, true, true, true); - uint256 timeDiffToEndDate = END_DATE > block.timestamp ? END_DATE - block.timestamp : 0; - uint256 adjustedAmountClosing = timeDiffToEndDate * uint96(FLOW_RATE); - emit Transfer(alice, bob, adjustedAmountClosing); - vm.expectEmit(true, true, true, true); - emit VestingEndExecuted( - superToken, alice, bob, END_DATE, adjustedAmountClosing, false - ); - success = vestingScheduler.executeEndVesting(superToken, alice, bob); - assertTrue(success, "executeCloseVesting should return true"); - uint256 aliceFinalBalance = superToken.balanceOf(alice); - uint256 bobFinalBalance = superToken.balanceOf(bob); - uint256 aliceShouldStream = (END_DATE-CLIFF_DATE) * uint96(FLOW_RATE) + CLIFF_TRANSFER_AMOUNT ; - assertEq(aliceInitialBalance - aliceFinalBalance, aliceShouldStream, "(sender) wrong final balance"); - assertEq(bobFinalBalance, bobInitialBalance + aliceShouldStream, "(receiver) wrong final balance"); - } - - function testExecuteCliffAndFlowWithoutCliffAmountOrAdjustment() public { - uint256 aliceInitialBalance = superToken.balanceOf(alice); - uint256 bobInitialBalance = superToken.balanceOf(bob); - _setACL_AUTHORIZE_FULL_CONTROL(alice, FLOW_RATE); - vm.startPrank(alice); - vestingScheduler.createVestingSchedule( - superToken, - bob, - START_DATE, - CLIFF_DATE, - FLOW_RATE, - 0, - END_DATE, - EMPTY_CTX - ); - superToken.increaseAllowance(address(vestingScheduler), type(uint256).max); - vm.stopPrank(); - vm.startPrank(admin); - vm.warp(CLIFF_DATE); - vm.expectEmit(true, true, true, true); - emit VestingCliffAndFlowExecuted( - superToken, alice, bob, CLIFF_DATE, FLOW_RATE, 0, 0 - ); - bool success = vestingScheduler.executeCliffAndFlow(superToken, alice, bob); - assertTrue(success, "executeVesting should return true"); - vm.warp(END_DATE); - vm.expectEmit(true, true, true, true); - emit VestingEndExecuted(superToken, alice, bob, END_DATE, 0, false); - success = vestingScheduler.executeEndVesting(superToken, alice, bob); - assertTrue(success, "executeCloseVesting should return true"); - uint256 aliceFinalBalance = superToken.balanceOf(alice); - uint256 bobFinalBalance = superToken.balanceOf(bob); - uint256 aliceShouldStream = (END_DATE-CLIFF_DATE) * uint96(FLOW_RATE); - assertEq(aliceInitialBalance - aliceFinalBalance, aliceShouldStream, "(sender) wrong final balance"); - assertEq(bobFinalBalance, bobInitialBalance + aliceShouldStream, "(receiver) wrong final balance"); - } - - function testExecuteCliffAndFlowWithUpdatedEndDate() public { - uint32 NEW_END_DATE = END_DATE - 1000; - uint256 aliceInitialBalance = superToken.balanceOf(alice); - uint256 bobInitialBalance = superToken.balanceOf(bob); - _setACL_AUTHORIZE_FULL_CONTROL(alice, FLOW_RATE); - _createVestingScheduleWithDefaultData(alice, bob); - vm.prank(alice); - superToken.increaseAllowance(address(vestingScheduler), type(uint256).max); - vm.startPrank(admin); - uint256 initialTimestamp = block.timestamp + 10 days + 1800; - vm.warp(initialTimestamp); - uint256 flowDelayCompensation = (block.timestamp - CLIFF_DATE) * uint96(FLOW_RATE); - vm.expectEmit(true, true, true, true); - emit Transfer(alice, bob, CLIFF_TRANSFER_AMOUNT + flowDelayCompensation); - vm.expectEmit(true, true, true, true); - emit VestingCliffAndFlowExecuted( - superToken, alice, bob, CLIFF_DATE, FLOW_RATE, CLIFF_TRANSFER_AMOUNT, flowDelayCompensation - ); - bool success = vestingScheduler.executeCliffAndFlow(superToken, alice, bob); - assertTrue(success, "executeVesting should return true"); - vm.stopPrank(); - vm.prank(alice); - vm.expectEmit(true, true, true, true); - emit VestingScheduleUpdated(superToken, alice, bob, END_DATE, NEW_END_DATE); - vestingScheduler.updateVestingSchedule(superToken, bob, NEW_END_DATE, EMPTY_CTX); - uint256 finalTimestamp = block.timestamp + 10 days - 3600; - vm.warp(finalTimestamp); - vm.expectEmit(true, true, true, true); - uint256 timeDiffToEndDate = NEW_END_DATE > block.timestamp ? NEW_END_DATE - block.timestamp : 0; - uint256 adjustedAmountClosing = timeDiffToEndDate * uint96(FLOW_RATE); - emit Transfer(alice, bob, adjustedAmountClosing); - vm.expectEmit(true, true, true, true); - emit VestingEndExecuted( - superToken, alice, bob, NEW_END_DATE, adjustedAmountClosing, false - ); - success = vestingScheduler.executeEndVesting(superToken, alice, bob); - assertTrue(success, "executeCloseVesting should return true"); - uint256 aliceFinalBalance = superToken.balanceOf(alice); - uint256 bobFinalBalance = superToken.balanceOf(bob); - uint256 aliceShouldStream = (NEW_END_DATE-CLIFF_DATE) * uint96(FLOW_RATE) + CLIFF_TRANSFER_AMOUNT ; - assertEq(aliceInitialBalance - aliceFinalBalance, aliceShouldStream, "(sender) wrong final balance"); - assertEq(bobFinalBalance, bobInitialBalance + aliceShouldStream, "(receiver) wrong final balance"); - } - - function testExecuteCliffAndFlowRevertClosingTransfer() public { - _setACL_AUTHORIZE_FULL_CONTROL(alice, FLOW_RATE); - _createVestingScheduleWithDefaultData(alice, bob); - vm.prank(alice); - superToken.increaseAllowance(address(vestingScheduler), type(uint256).max); - vm.startPrank(admin); - uint256 initialTimestamp = block.timestamp + 10 days + 1800; - vm.warp(initialTimestamp); - uint256 flowDelayCompensation = (block.timestamp - CLIFF_DATE) * uint96(FLOW_RATE); - vm.expectEmit(true, true, true, true); - emit Transfer(alice, bob, CLIFF_TRANSFER_AMOUNT + flowDelayCompensation); - vm.expectEmit(true, true, true, true); - emit VestingCliffAndFlowExecuted( - superToken, alice, bob, CLIFF_DATE, FLOW_RATE, CLIFF_TRANSFER_AMOUNT, flowDelayCompensation - ); - bool success = vestingScheduler.executeCliffAndFlow(superToken, alice, bob); - assertTrue(success, "executeVesting should return true"); - vm.stopPrank(); - vm.startPrank(alice); - superToken.transferAll(eve); - vm.stopPrank(); - vm.startPrank(admin); - uint256 finalTimestamp = block.timestamp + 10 days - 3600; - vm.warp(finalTimestamp); - uint256 timeDiffToEndDate = END_DATE > block.timestamp ? END_DATE - block.timestamp : 0; - uint256 adjustedAmountClosing = timeDiffToEndDate * uint96(FLOW_RATE); - vm.expectEmit(true, true, true, true); - emit VestingEndExecuted( - superToken, alice, bob, END_DATE, adjustedAmountClosing, true - ); - success = vestingScheduler.executeEndVesting(superToken, alice, bob); - assertTrue(success, "executeCloseVesting should return true"); - } - - function testCannotExecuteEndVestingBeforeTime() public { - _setACL_AUTHORIZE_FULL_CONTROL(alice, FLOW_RATE); - _createVestingScheduleWithDefaultData(alice, bob); - vm.prank(alice); - superToken.increaseAllowance(address(vestingScheduler), type(uint256).max); - vm.startPrank(admin); - vm.expectRevert(IVestingScheduler.TimeWindowInvalid.selector); - vestingScheduler.executeEndVesting(superToken, alice, bob); - } - - function testCannotExecuteCliffAndFlowBeforeTime() public { - _setACL_AUTHORIZE_FULL_CONTROL(alice, FLOW_RATE); - _createVestingScheduleWithDefaultData(alice, bob); - vm.prank(alice); - superToken.increaseAllowance(address(vestingScheduler), type(uint256).max); - vm.startPrank(admin); - vm.expectRevert(IVestingScheduler.TimeWindowInvalid.selector); - vestingScheduler.executeCliffAndFlow(superToken, alice, bob); - } - - function testCannotExecuteEndWithoutStreamRunning() public { - _setACL_AUTHORIZE_FULL_CONTROL(alice, FLOW_RATE); - _createVestingScheduleWithDefaultData(alice, bob); - vm.prank(alice); - superToken.increaseAllowance(address(vestingScheduler), type(uint256).max); - vm.startPrank(admin); - uint256 initialTimestamp = block.timestamp + 10 days + 1800; - vm.warp(initialTimestamp); - uint256 flowDelayCompensation = (block.timestamp - CLIFF_DATE) * uint96(FLOW_RATE); - vm.expectEmit(true, true, true, true); - emit Transfer(alice, bob, CLIFF_TRANSFER_AMOUNT + flowDelayCompensation); - vm.expectEmit(true, true, true, true); - emit VestingCliffAndFlowExecuted( - superToken, alice, bob, CLIFF_DATE, FLOW_RATE, CLIFF_TRANSFER_AMOUNT, flowDelayCompensation - ); - bool success = vestingScheduler.executeCliffAndFlow(superToken, alice, bob); - assertTrue(success, "executeVesting should return true"); - vm.stopPrank(); - vm.startPrank(alice); - superToken.deleteFlow(alice, bob); - vm.stopPrank(); - vm.startPrank(admin); - uint256 finalTimestamp = block.timestamp + 10 days - 3600; - vm.warp(finalTimestamp); - vm.expectEmit(true, true, true, true); - emit VestingEndFailed( - superToken, alice, bob, END_DATE - ); - success = vestingScheduler.executeEndVesting(superToken, alice, bob); - assertTrue(success, "executeCloseVesting should return true"); - } -} \ No newline at end of file diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index 2120d03c89..12b657a635 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -3,30 +3,44 @@ All notable changes to the ethereum-contracts will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [v1.12.0] ### Added -* `ISuperfluid.getERC2771Forwarder()`: to be used by batch call targets who want to use ERC-2771 for msg sender preservation. -* Utility contracts for forwarding of calls in the context of batch operations: - * `SimpleForwarder`: for forwarding arbitrary calls to arbitrary targets - * `ERC2771Forwarder`: for forwarding arbitrary calls to arbitrary targets with msg sender authenticated according to ERC-2771. Requires the target contract to recognize it as trusted forwarder. +- `SuperTokenV1Library` + - added agreement specific variants for `getFlowRate`, `getFlowInfo`, `getNetFlowRate` and `getNetFlowInfo`, e.g. `getCFAFlowRate`, `getGDAFlowRate`, ... + - added `flow` for changing CFA flows, can be used instead of the CRUD methods + - added `flowFrom` for changing CFA flows using ACL permissions, can be used instead of the CRUD methods + - added `distribute` and `distributeFlow` variants (overloaded) without `from` argument + - added `transferX` and `flowX` for agreement abstracted instant or flow transfers/distributions + - added `getTotalAmountReceivedFromPool` (alias for `getTotalAmountReceivedByMember`) +- Host: added `ISuperfluid.getERC2771Forwarder`: to be used by batch call targets who want to use ERC-2771 for msg sender preservation +- Utility contracts for forwarding of calls in the context of batch operations: + - `SimpleForwarder`: for forwarding arbitrary calls to arbitrary targets + - `ERC2771Forwarder`: for forwarding arbitrary calls to arbitrary targets with msg sender authenticated according to ERC-2771. Requires the target contract to recognize it as trusted forwarder. ### Breaking -* Source file `SuperfluidFrameworkDeployer.sol` renamed to `SuperfluidFrameworkDeployer.t.sol` -* Source file `FoundrySuperfluidTester.sol` renamed to `FoundrySuperfluidTester.t.sol` +- Removed `CFAv1Library`, superseded by `SuperTokenV1Library`. +- The IDA is now declared as deprecated, shouldn't be used anymore. The GDA covers all its functionality. +- Removed `IDAv1Library`. +- `SuperTokenV1Library` + - removed IDA specific functionality. `distribute` now maps to the GDA. + - `getFlowRate` and `getFlowInfo` now work return the GDA flowrate/info if the receiver is a pool (previously it would return 0). In order to specifically query the CFA flowrate, use the newly added `getCFAFlowRate` and `getCFAFlowInfo`. + - removed `updateMemberUnits` (use ISuperfluidPool.updateMemberUnits instead) +- Source file `SuperfluidFrameworkDeployer.sol` renamed to `SuperfluidFrameworkDeployer.t.sol` +- Source file `FoundrySuperfluidTester.sol` renamed to `FoundrySuperfluidTester.t.sol` ## [v1.11.1] ### Changed -* `MacroForwarder` made payable. -* `IUserDefinedMacro`: added a method `postCheck()` which allows to verify state changes after running the macro. -* `SuperfluidFrameworkDeployer` now also deploys and `MacroForwarder` and enables it as trusted forwarder. -* `deploy-test-environment.js` now deploys fUSDC (the underlying) with 6 decimals (instead of 18) to better resemble the actual USDC. +- `MacroForwarder` made payable. +- `IUserDefinedMacro`: added a method `postCheck()` which allows to verify state changes after running the macro. +- `SuperfluidFrameworkDeployer` now also deploys and `MacroForwarder` and enables it as trusted forwarder. +- `deploy-test-environment.js` now deploys fUSDC (the underlying) with 6 decimals (instead of 18) to better resemble the actual USDC. ### Fixed -* GDA Pools are not multi-tokens ready, added a permission check (#2010). +- GDA Pools are not multi-tokens ready, added a permission check (#2010). ## [v1.11.0] diff --git a/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol index 6bd8a6740b..701852a2c4 100644 --- a/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol @@ -14,7 +14,8 @@ import { AgreementLibrary } from "./AgreementLibrary.sol"; /** - * @title InstantDistributionAgreementV1 contract + * @title [DEPRECATED] InstantDistributionAgreementV1 contract + * @custom:deprecated Use GeneralDistributionAgreementV1 instead * @author Superfluid * @dev Please read IInstantDistributionAgreementV1 for implementation notes. * @dev For more technical notes, please visit protocol-monorepo wiki area. diff --git a/packages/ethereum-contracts/contracts/apps/CFASuperAppBase.sol b/packages/ethereum-contracts/contracts/apps/CFASuperAppBase.sol index b5145f6ef8..ec91b3e5e4 100644 --- a/packages/ethereum-contracts/contracts/apps/CFASuperAppBase.sol +++ b/packages/ethereum-contracts/contracts/apps/CFASuperAppBase.sol @@ -194,7 +194,7 @@ abstract contract CFASuperAppBase is ISuperApp { if (!isAcceptedSuperToken(superToken)) revert NotAcceptedSuperToken(); (address sender, ) = abi.decode(agreementData, (address, address)); - (uint256 lastUpdated, int96 flowRate,,) = superToken.getFlowInfo(sender, address(this)); + (uint256 lastUpdated, int96 flowRate,,) = superToken.getCFAFlowInfo(sender, address(this)); return abi.encode( flowRate, @@ -245,7 +245,7 @@ abstract contract CFASuperAppBase is ISuperApp { } (address sender, address receiver) = abi.decode(agreementData, (address, address)); - (uint256 lastUpdated, int96 flowRate,,) = superToken.getFlowInfo(sender, receiver); + (uint256 lastUpdated, int96 flowRate,,) = superToken.getCFAFlowInfo(sender, receiver); return abi.encode( lastUpdated, diff --git a/packages/ethereum-contracts/contracts/apps/CFAv1Library.sol b/packages/ethereum-contracts/contracts/apps/CFAv1Library.sol deleted file mode 100644 index c3b2efb283..0000000000 --- a/packages/ethereum-contracts/contracts/apps/CFAv1Library.sol +++ /dev/null @@ -1,819 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >= 0.8.11; - -import { - ISuperfluid, - ISuperfluidToken, - IConstantFlowAgreementV1 -} from "../interfaces/superfluid/ISuperfluid.sol"; - -/** - * @title Constant flow agreement v1 library - * @author Superfluid - * @dev for working with the constant flow agreement within solidity - * @dev the first set of functions are each for callAgreement() - * @dev the second set of functions are each for use in callAgreementWithContext() - */ -library CFAv1Library { - - /** - * @dev Initialization data - * @param host Superfluid host for calling agreements - * @param cfa Constant Flow Agreement contract - */ - struct InitData { - ISuperfluid host; - IConstantFlowAgreementV1 cfa; - } - - /** - * @dev Create flow without userData - * @param cfaLibrary The cfaLibrary storage variable - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - */ - function createFlow( - InitData storage cfaLibrary, - address receiver, - ISuperfluidToken token, - int96 flowRate - ) internal { - createFlow(cfaLibrary, receiver, token, flowRate, new bytes(0)); - } - - /** - * @dev Create flow with userData - * @param cfaLibrary The cfaLibrary storage variable - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - * @param userData The user provided data - */ - function createFlow( - InitData storage cfaLibrary, - address receiver, - ISuperfluidToken token, - int96 flowRate, - bytes memory userData - ) internal { - cfaLibrary.host.callAgreement( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.createFlow, - ( - token, - receiver, - flowRate, - new bytes(0) // placeholder - ) - ), - userData - ); - } - - /** - * @dev Update flow without userData - * @param cfaLibrary The cfaLibrary storage variable - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - */ - function updateFlow( - InitData storage cfaLibrary, - address receiver, - ISuperfluidToken token, - int96 flowRate - ) internal { - updateFlow(cfaLibrary, receiver, token, flowRate, new bytes(0)); - } - - /** - * @dev Update flow with userData - * @param cfaLibrary The cfaLibrary storage variable - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - * @param userData The user provided data - */ - function updateFlow( - InitData storage cfaLibrary, - address receiver, - ISuperfluidToken token, - int96 flowRate, - bytes memory userData - ) internal { - cfaLibrary.host.callAgreement( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.updateFlow, - ( - token, - receiver, - flowRate, - new bytes(0) // placeholder - ) - ), - userData - ); - } - - /** - * @dev Delete flow without userData - * @param cfaLibrary The cfaLibrary storage variable - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - */ - function deleteFlow( - InitData storage cfaLibrary, - address sender, - address receiver, - ISuperfluidToken token - ) internal { - deleteFlow(cfaLibrary, sender, receiver, token, new bytes(0)); - } - - /** - * @dev Delete flow with userData - * @param cfaLibrary The cfaLibrary storage variable - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param userData The user provided data - */ - function deleteFlow( - InitData storage cfaLibrary, - address sender, - address receiver, - ISuperfluidToken token, - bytes memory userData - ) internal { - cfaLibrary.host.callAgreement( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.deleteFlow, - ( - token, - sender, - receiver, - new bytes(0) // placeholder - ) - ), - userData - ); - } - - /** - * @dev Create flow with context and userData - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - */ - function createFlowWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address receiver, - ISuperfluidToken token, - int96 flowRate - ) internal returns (bytes memory newCtx) { - return createFlowWithCtx(cfaLibrary, ctx, receiver, token, flowRate, new bytes(0)); - } - - /** - * @dev Create flow with context and userData - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - * @param userData The user provided data - */ - function createFlowWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address receiver, - ISuperfluidToken token, - int96 flowRate, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = cfaLibrary.host.callAgreementWithContext( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.createFlow, - ( - token, - receiver, - flowRate, - new bytes(0) // placeholder - ) - ), - userData, - ctx - ); - } - - /** - * @dev Update flow with context - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - */ - function updateFlowWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address receiver, - ISuperfluidToken token, - int96 flowRate - ) internal returns (bytes memory newCtx) { - return updateFlowWithCtx(cfaLibrary, ctx, receiver, token, flowRate, new bytes(0)); - } - - /** - * @dev Update flow with context and userData - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - * @param userData The user provided data - */ - function updateFlowWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address receiver, - ISuperfluidToken token, - int96 flowRate, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = cfaLibrary.host.callAgreementWithContext( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.updateFlow, - ( - token, - receiver, - flowRate, - new bytes(0) // placeholder - ) - ), - userData, - ctx - ); - } - - /** - * @dev Delete flow with context - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - */ - function deleteFlowWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address sender, - address receiver, - ISuperfluidToken token - ) internal returns (bytes memory newCtx) { - return deleteFlowWithCtx(cfaLibrary, ctx, sender, receiver, token, new bytes(0)); - } - - /** - * @dev Delete flow with context and userData - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param userData The user provided data - */ - function deleteFlowWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address sender, - address receiver, - ISuperfluidToken token, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = cfaLibrary.host.callAgreementWithContext( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.deleteFlow, - ( - token, - sender, - receiver, - new bytes(0) // placeholder - ) - ), - userData, - ctx - ); - } - - /** - * @dev Creates flow as an operator without userData - * @param cfaLibrary The cfaLibrary storage variable - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - */ - function createFlowByOperator( - InitData storage cfaLibrary, - address sender, - address receiver, - ISuperfluidToken token, - int96 flowRate - ) internal returns (bytes memory newCtx) { - return createFlowByOperator(cfaLibrary, sender, receiver, token, flowRate, new bytes(0)); - } - - /** - * @dev Creates flow as an operator with userData - * @param cfaLibrary The cfaLibrary storage variable - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - * @param userData The user provided data - */ - function createFlowByOperator( - InitData storage cfaLibrary, - address sender, - address receiver, - ISuperfluidToken token, - int96 flowRate, - bytes memory userData - ) internal returns (bytes memory newCtx) { - return cfaLibrary.host.callAgreement( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.createFlowByOperator, - ( - token, - sender, - receiver, - flowRate, - new bytes(0) // placeholder - ) - ), - userData - ); - } - - /** - * @dev Creates flow as an operator without userData with context - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - */ - function createFlowByOperatorWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address sender, - address receiver, - ISuperfluidToken token, - int96 flowRate - ) internal returns (bytes memory newCtx) { - return createFlowByOperatorWithCtx( - cfaLibrary, - ctx, - sender, - receiver, - token, - flowRate, - new bytes(0) - ); - } - - /** - * @dev Creates flow as an operator with userData and context - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - * @param userData The user provided data - */ - function createFlowByOperatorWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address sender, - address receiver, - ISuperfluidToken token, - int96 flowRate, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = cfaLibrary.host.callAgreementWithContext( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.createFlowByOperator, - ( - token, - sender, - receiver, - flowRate, - new bytes(0) // placeholder - ) - ), - userData, - ctx - ); - } - - /** - * @dev Updates a flow as an operator without userData - * @param cfaLibrary The cfaLibrary storage variable - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - */ - function updateFlowByOperator( - InitData storage cfaLibrary, - address sender, - address receiver, - ISuperfluidToken token, - int96 flowRate - ) internal returns (bytes memory newCtx) { - return updateFlowByOperator(cfaLibrary, sender, receiver, token, flowRate, new bytes(0)); - } - - /** - * @dev Updates flow as an operator with userData - * @param cfaLibrary The cfaLibrary storage variable - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - * @param userData The user provided data - */ - function updateFlowByOperator( - InitData storage cfaLibrary, - address sender, - address receiver, - ISuperfluidToken token, - int96 flowRate, - bytes memory userData - ) internal returns (bytes memory newCtx) { - return cfaLibrary.host.callAgreement( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.updateFlowByOperator, - ( - token, - sender, - receiver, - flowRate, - new bytes(0) - ) - ), - userData - ); - } - - /** - * @dev Updates a flow as an operator without userData with context - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - */ - function updateFlowByOperatorWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address sender, - address receiver, - ISuperfluidToken token, - int96 flowRate - ) internal returns (bytes memory newCtx) { - return updateFlowByOperatorWithCtx( - cfaLibrary, - ctx, - sender, - receiver, - token, - flowRate, - new bytes(0) - ); - } - - /** - * @dev Updates flow as an operator with userData and context - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param flowRate The desired flowRate - * @param userData The user provided data - */ - function updateFlowByOperatorWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address sender, - address receiver, - ISuperfluidToken token, - int96 flowRate, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = cfaLibrary.host.callAgreementWithContext( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.updateFlowByOperator, - ( - token, - sender, - receiver, - flowRate, - new bytes(0) - ) - ), - userData, - ctx - ); - } - - /** - * @dev Deletes a flow as an operator without userData - * @param cfaLibrary The cfaLibrary storage variable - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - */ - function deleteFlowByOperator( - InitData storage cfaLibrary, - address sender, - address receiver, - ISuperfluidToken token - ) internal returns (bytes memory newCtx) { - return deleteFlowByOperator(cfaLibrary, sender, receiver, token, new bytes(0)); - } - - /** - * @dev Deletes a flow as an operator with userData - * @param cfaLibrary The cfaLibrary storage variable - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param userData The user provided data - */ - function deleteFlowByOperator( - InitData storage cfaLibrary, - address sender, - address receiver, - ISuperfluidToken token, - bytes memory userData - ) internal returns (bytes memory newCtx) { - return cfaLibrary.host.callAgreement( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.deleteFlowByOperator, - ( - token, - sender, - receiver, - new bytes(0) - ) - ), - userData - ); - } - - /** - * @dev Deletes a flow as an operator without userData with context - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - */ - function deleteFlowByOperatorWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address sender, - address receiver, - ISuperfluidToken token - ) internal returns (bytes memory newCtx) { - return deleteFlowByOperatorWithCtx(cfaLibrary, ctx, sender, receiver, token, new bytes(0)); - } - - /** - * @dev Deletes a flow as an operator with userData and context - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param token The token to flow - * @param userData The user provided data - */ - function deleteFlowByOperatorWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address sender, - address receiver, - ISuperfluidToken token, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = cfaLibrary.host.callAgreementWithContext( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.deleteFlowByOperator, - ( - token, - sender, - receiver, - new bytes(0) - ) - ), - userData, - ctx - ); - } - - /** - * @dev Updates the permissions of a flow operator - * @param cfaLibrary The cfaLibrary storage variable - * @param flowOperator The operator that can create/update/delete flows - * @param token The token of flows handled by the operator - * @param permissions The number of the permissions: create = 1; update = 2; delete = 4; - * To give multiple permissions, sum the above. create_delete = 5; create_update_delete = 7; etc - * @param flowRateAllowance The allowance for flow creation. Decremented as flowRate increases - */ - function updateFlowOperatorPermissions( - InitData storage cfaLibrary, - address flowOperator, - ISuperfluidToken token, - uint8 permissions, - int96 flowRateAllowance - ) internal returns (bytes memory newCtx) { - return cfaLibrary.host.callAgreement( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.updateFlowOperatorPermissions, - ( - token, - flowOperator, - permissions, - flowRateAllowance, - new bytes(0) - ) - ), - new bytes(0) - ); - } - - /** - * @dev Updates the permissions of a flow operator with context - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param flowOperator The operator that can create/update/delete flows - * @param token The token of flows handled by the operator - * @param permissions The number of the permissions: create = 1; update = 2; delete = 4; - * To give multiple permissions, sum the above. create_delete = 5; create_update_delete = 7; etc - * @param flowRateAllowance The allowance for flow creation. Decremented as flowRate increases - */ - function updateFlowOperatorPermissionsWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address flowOperator, - ISuperfluidToken token, - uint8 permissions, - int96 flowRateAllowance - ) internal returns (bytes memory newCtx) { - (newCtx, ) = cfaLibrary.host.callAgreementWithContext( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.updateFlowOperatorPermissions, - ( - token, - flowOperator, - permissions, - flowRateAllowance, - new bytes(0) - ) - ), - new bytes(0), - ctx - ); - } - - /** - * @dev Grants full, unlimited permission to a flow operator - * @param cfaLibrary The cfaLibrary storage variable - * @param flowOperator The operator that can create/update/delete flows - * @param token The token of flows handled by the operator - */ - function authorizeFlowOperatorWithFullControl( - InitData storage cfaLibrary, - address flowOperator, - ISuperfluidToken token - ) internal returns (bytes memory newCtx) { - return cfaLibrary.host.callAgreement( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.authorizeFlowOperatorWithFullControl, - ( - token, - flowOperator, - new bytes(0) - ) - ), - new bytes(0) - ); - } - - /** - * @dev Grants full, unlimited permission to a flow operator with context - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param flowOperator The operator that can create/update/delete flows - * @param token The token of flows handled by the operator - */ - function authorizeFlowOperatorWithFullControlWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address flowOperator, - ISuperfluidToken token - ) internal returns (bytes memory newCtx) { - (newCtx, ) = cfaLibrary.host.callAgreementWithContext( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.authorizeFlowOperatorWithFullControl, - ( - token, - flowOperator, - new bytes(0) - ) - ), - new bytes(0), - ctx - ); - } - - /** - * @dev Revokes all permissions from a flow operator - * @param cfaLibrary The cfaLibrary storage variable - * @param flowOperator The operator that can create/update/delete flows - * @param token The token of flows handled by the operator - */ - function revokeFlowOperatorWithFullControl( - InitData storage cfaLibrary, - address flowOperator, - ISuperfluidToken token - ) internal returns (bytes memory newCtx) { - return cfaLibrary.host.callAgreement( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.revokeFlowOperatorWithFullControl, - ( - token, - flowOperator, - new bytes(0) - ) - ), - new bytes(0) - ); - } - - /** - * @dev Revokes all permissions from a flow operator - * @param cfaLibrary The cfaLibrary storage variable - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @param flowOperator The operator that can create/update/delete flows - * @param token The token of flows handled by the operator - */ - function revokeFlowOperatorWithFullControlWithCtx( - InitData storage cfaLibrary, - bytes memory ctx, - address flowOperator, - ISuperfluidToken token - ) internal returns (bytes memory newCtx) { - (newCtx, ) = cfaLibrary.host.callAgreementWithContext( - cfaLibrary.cfa, - abi.encodeCall( - cfaLibrary.cfa.revokeFlowOperatorWithFullControl, - ( - token, - flowOperator, - new bytes(0) - ) - ), - new bytes(0), - ctx - ); - } -} diff --git a/packages/ethereum-contracts/contracts/apps/IDAv1Library.sol b/packages/ethereum-contracts/contracts/apps/IDAv1Library.sol deleted file mode 100644 index 1b00ccdcd6..0000000000 --- a/packages/ethereum-contracts/contracts/apps/IDAv1Library.sol +++ /dev/null @@ -1,988 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >= 0.8.11; -pragma experimental ABIEncoderV2; - -import {ISuperfluid, ISuperfluidToken} from "../interfaces/superfluid/ISuperfluid.sol"; - -import { - IInstantDistributionAgreementV1 -} from "../interfaces/agreements/IInstantDistributionAgreementV1.sol"; - -/// @title Instant Distribution Agreement V1 helper library for solidity development. -/// @author Superfluid -/// @dev Set a variable of type `InitData` in the contract, then call this library's functions -/// directly `initData.functionName()`. -library IDAv1Library { - - /// @dev Initialization data. - /// @param host Superfluid host contract for calling agreements. - /// @param ida Instant Distribution Agreement contract. - struct InitData { - ISuperfluid host; - IInstantDistributionAgreementV1 ida; - } - - /************************************************************************** - * View Functions - *************************************************************************/ - - /// @dev Gets an index by its ID and publisher. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @return exist True if the index exists. - /// @return indexValue Total value of the index. - /// @return totalUnitsApproved Units of the index approved by subscribers. - /// @return totalUnitsPending Units of teh index not yet approved by subscribers. - function getIndex( - InitData storage idaLibrary, - ISuperfluidToken token, - address publisher, - uint32 indexId - ) - internal - view - returns ( - bool exist, - uint128 indexValue, - uint128 totalUnitsApproved, - uint128 totalUnitsPending - ) - { - return idaLibrary.ida.getIndex(token, publisher, indexId); - } - - /// @dev Calculates the distribution amount based on the amount of tokens desired to distribute. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param amount Amount of tokens desired to distribute. - /// @return actualAmount Amount to be distributed with correct rounding. - /// @return newIndexValue The index value after the distribution would be called. - function calculateDistribution( - InitData storage idaLibrary, - ISuperfluidToken token, - address publisher, - uint32 indexId, - uint256 amount - ) - internal - view - returns ( - uint256 actualAmount, - uint128 newIndexValue - ) - { - return idaLibrary.ida.calculateDistribution(token, publisher, indexId, amount); - } - - /// @dev List all subscriptions of an address - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super token used in the indexes listed. - /// @param subscriber Subscriber address. - /// @return publishers Publishers of the indices. - /// @return indexIds IDs of the indices. - /// @return unitsList Units owned of the indices. - function listSubscriptions( - InitData storage idaLibrary, - ISuperfluidToken token, - address subscriber - ) - internal - view - returns ( - address[] memory publishers, - uint32[] memory indexIds, - uint128[] memory unitsList - ) - { - return idaLibrary.ida.listSubscriptions(token, subscriber); - } - - /// @dev Gets subscription by publisher, index id, and subscriber. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber to the index. - /// @return exist True if the subscription exists. - /// @return approved True if the subscription has been approved by the subscriber. - /// @return units Units held by the subscriber - /// @return pendingDistribution If not approved, the amount to be claimed on approval. - function getSubscription( - InitData storage idaLibrary, - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber - ) - internal - view - returns ( - bool exist, - bool approved, - uint128 units, - uint256 pendingDistribution - ) - { - return idaLibrary.ida.getSubscription(token, publisher, indexId, subscriber); - } - - /// @dev Gets subscription by the agreement ID. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param agreementId Agreement ID, unique to the subscriber and index ID. - /// @return publisher Publisher of the index. - /// @return indexId ID of the index. - /// @return approved True if the subscription has been approved by the subscriber. - /// @return units Units held by the subscriber - /// @return pendingDistribution If not approved, the amount to be claimed on approval. - function getSubscriptionByID( - InitData storage idaLibrary, - ISuperfluidToken token, - bytes32 agreementId - ) - internal - view - returns ( - address publisher, - uint32 indexId, - bool approved, - uint128 units, - uint256 pendingDistribution - ) - { - return idaLibrary.ida.getSubscriptionByID(token, agreementId); - } - - /************************************************************************** - * Index Operations - *************************************************************************/ - - /// @dev Creates a new index. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - function createIndex( - InitData storage idaLibrary, - ISuperfluidToken token, - uint32 indexId - ) internal { - createIndex(idaLibrary, token, indexId, new bytes(0)); - } - - /// @dev Creates a new index. This takes arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param userData Arbitrary user data field. - function createIndex( - InitData storage idaLibrary, - ISuperfluidToken token, - uint32 indexId, - bytes memory userData - ) internal { - idaLibrary.host.callAgreement( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.createIndex, - ( - token, - indexId, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - } - - /// @dev Creates a new index in a super app callback. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - function createIndexWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - uint32 indexId - ) internal returns (bytes memory newCtx) { - return createIndexWithCtx(idaLibrary, ctx, token, indexId, new bytes(0)); - } - - /// @dev Creates a new index in a super app callback. This takes arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param userData Arbitrary user data field. - function createIndexWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - uint32 indexId, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = idaLibrary.host.callAgreementWithContext( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.createIndex, - ( - token, - indexId, - new bytes(0) // ctx placeholder - ) - ), - userData, - ctx - ); - } - - /// @dev Updates an index value. This distributes an amount of tokens equal to - /// `indexValue - lastIndexValue`. See `distribute` for another way to distribute. This takes - /// arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param indexValue New TOTAL index value, this will equal the total amount distributed. - function updateIndexValue( - InitData storage idaLibrary, - ISuperfluidToken token, - uint32 indexId, - uint128 indexValue - ) internal { - updateIndexValue(idaLibrary, token, indexId, indexValue, new bytes(0)); - } - - /// @dev Updates an index value. This distributes an amount of tokens equal to - /// `indexValue - lastIndexValue`. See `distribute` for another way to distribute. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param indexValue New TOTAL index value, this will equal the total amount distributed. - /// @param userData Arbitrary user data field. - function updateIndexValue( - InitData storage idaLibrary, - ISuperfluidToken token, - uint32 indexId, - uint128 indexValue, - bytes memory userData - ) internal { - idaLibrary.host.callAgreement( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.updateIndex, - ( - token, - indexId, - indexValue, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - } - - /// @dev Updates an index value in a super app callback. This distributes an amount of tokens - /// equal to `indexValue - lastIndexValue`. See `distribute` for another way to distribute. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param indexValue New TOTAL index value, this will equal the total amount distributed. - function updateIndexValueWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - uint32 indexId, - uint128 indexValue - ) internal returns (bytes memory newCtx) { - return updateIndexValueWithCtx( - idaLibrary, - ctx, - token, - indexId, - indexValue, - new bytes(0) - ); - } - - /// @dev Updates an index value in a super app callback. This distributes an amount of tokens - /// equal to `indexValue - lastIndexValue`. See `distribute` for another way to distribute. - /// This takes arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param indexValue New TOTAL index value, this will equal the total amount distributed. - function updateIndexValueWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - uint32 indexId, - uint128 indexValue, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = idaLibrary.host.callAgreementWithContext( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.updateIndex, - ( - token, - indexId, - indexValue, - new bytes(0) // ctx placeholder - ) - ), - userData, - ctx - ); - } - - /** - * @dev Distributes tokens in a more developer friendly way than `updateIndex`. Instead of - * passing the new total index value, you pass the amount of tokens desired to be distributed. - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param amount - total number of tokens desired to be distributed - * NOTE in many cases, there can be some precision loss - This may cause a slight difference in the amount param specified and the actual amount distributed. - See below for math: - //indexDelta = amount the index will be updated by during internal call to _updateIndex(). - It is calculated like so: - indexDelta = amount / totalUnits - (see distribute() implementatation in ./agreements/InstantDistributionAgreement.sol) - * NOTE Solidity does not support floating point numbers - // So the indexDelta will be rounded down to the nearest integer. - This will create a 'remainder' amount of tokens that will not be distributed - (we'll call this the 'distribution modulo') - distributionModulo = amount - indexDelta * totalUnits - * NOTE due to rounding, there may be a small amount of tokens left in the publisher's account - This amount is equal to the 'distributionModulo' value - // - */ - function distribute( - InitData storage idaLibrary, - ISuperfluidToken token, - uint32 indexId, - uint256 amount - ) internal { - distribute(idaLibrary, token, indexId, amount, new bytes(0)); - } - - /// @dev Distributes tokens in a more developer friendly way than `updateIndex`. Instead of - /// passing the new total index value, this function will increase the index value by `amount`. - /// This takes arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param amount Amount by which the index value should increase. - /// @param userData Arbitrary user data field. - function distribute( - InitData storage idaLibrary, - ISuperfluidToken token, - uint32 indexId, - uint256 amount, - bytes memory userData - ) internal { - idaLibrary.host.callAgreement( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.distribute, - ( - token, - indexId, - amount, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - } - - /// @dev Distributes tokens in a super app callback. Instead of passing the new total index - /// value, this function will increase the index value by `amount`. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param amount Amount by which the index value should increase. - function distributeWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - uint32 indexId, - uint256 amount - ) internal returns (bytes memory newCtx) { - return distributeWithCtx(idaLibrary, ctx, token, indexId, amount, new bytes(0)); - } - - /// @dev Distributes tokens in a super app callback. Instead of passing the new total index - /// value, this function will increase the index value by `amount`. This takes arbitrary user - /// data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param amount Amount by which the index value should increase. - /// @param userData Arbitrary user data field. - function distributeWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - uint32 indexId, - uint256 amount, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = idaLibrary.host.callAgreementWithContext( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.distribute, - ( - token, - indexId, - amount, - new bytes(0) // ctx placeholder - ) - ), - userData, - ctx - ); - } - - /************************************************************************** - * Subscription Operations - *************************************************************************/ - - /// @dev Approves a subscription to an index. The subscriber's real time balance will not update - /// until the subscription is approved, but once approved, the balance will be updated with - /// prior distributions. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - function approveSubscription( - InitData storage idaLibrary, - ISuperfluidToken token, - address publisher, - uint32 indexId - ) internal { - approveSubscription(idaLibrary, token, publisher, indexId, new bytes(0)); - } - - /// @dev Approves a subscription to an index. The subscriber's real time balance will not update - /// until the subscription is approved, but once approved, the balance will be updated with - /// prior distributions. - /// This takes arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param userData Arbitrary user data field. - function approveSubscription( - InitData storage idaLibrary, - ISuperfluidToken token, - address publisher, - uint32 indexId, - bytes memory userData - ) internal { - idaLibrary.host.callAgreement( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.approveSubscription, - ( - token, - publisher, - indexId, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - } - - /// @dev Approves a subscription to an index in a super app callback. The subscriber's real time - /// balance will not update until the subscription is approved, but once approved, the balance - /// will be updated with prior distributions. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - function approveSubscriptionWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - address publisher, - uint32 indexId - ) internal returns (bytes memory newCtx) { - return approveSubscriptionWithCtx( - idaLibrary, - ctx, - token, - publisher, - indexId, - new bytes(0) - ); - } - - /// @dev Approves a subscription to an index in a super app callback. The subscriber's real time - /// balance will not update until the subscription is approved, but once approved, the balance - /// will be updated with prior distributions. This takes arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param userData Arbitrary user data field. - function approveSubscriptionWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - address publisher, - uint32 indexId, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = idaLibrary.host.callAgreementWithContext( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.approveSubscription, - ( - token, - publisher, - indexId, - new bytes(0) // ctx placeholder - ) - ), - userData, - ctx - ); - } - - /// @dev Revokes a previously approved subscription. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - function revokeSubscription( - InitData storage idaLibrary, - ISuperfluidToken token, - address publisher, - uint32 indexId - ) internal { - revokeSubscription(idaLibrary, token, publisher, indexId, new bytes(0)); - } - - /// @dev Revokes a previously approved subscription. This takes arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param userData Arbitrary user data field. - function revokeSubscription( - InitData storage idaLibrary, - ISuperfluidToken token, - address publisher, - uint32 indexId, - bytes memory userData - ) internal { - idaLibrary.host.callAgreement( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.revokeSubscription, - ( - token, - publisher, - indexId, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - } - - /// @dev Revokes a previously approved subscription in a super app callback. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - function revokeSubscriptionWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - address publisher, - uint32 indexId - ) internal returns (bytes memory newCtx) { - return revokeSubscriptionWithCtx( - idaLibrary, - ctx, - token, - publisher, - indexId, - new bytes(0) - ); - } - - /// @dev Revokes a previously approved subscription in a super app callback. This takes - /// arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param userData Arbitrary user data field. - function revokeSubscriptionWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - address publisher, - uint32 indexId, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = idaLibrary.host.callAgreementWithContext( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.revokeSubscription, - ( - token, - publisher, - indexId, - new bytes(0) // ctx placeholder - ) - ), - userData, - ctx - ); - } - - /// @dev Updates the units of a subscription. This changes the number of shares the subscriber - /// holds. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address whose units are to be updated. - /// @param units New number of units the subscriber holds. - function updateSubscriptionUnits( - InitData storage idaLibrary, - ISuperfluidToken token, - uint32 indexId, - address subscriber, - uint128 units - ) internal { - updateSubscriptionUnits(idaLibrary, token, indexId, subscriber, units, new bytes(0)); - } - - /// @dev Updates the units of a subscription. This changes the number of shares the subscriber - /// holds. This takes arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address whose units are to be updated. - /// @param units New number of units the subscriber holds. - /// @param userData Arbitrary user data field. - function updateSubscriptionUnits( - InitData storage idaLibrary, - ISuperfluidToken token, - uint32 indexId, - address subscriber, - uint128 units, - bytes memory userData - ) internal { - idaLibrary.host.callAgreement( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.updateSubscription, - ( - token, - indexId, - subscriber, - units, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - } - - /// @dev Updates the units of a subscription in a super app callback. This changes the number of - /// shares the subscriber holds. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address whose units are to be updated. - /// @param units New number of units the subscriber holds. - function updateSubscriptionUnitsWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - uint32 indexId, - address subscriber, - uint128 units - ) internal returns (bytes memory newCtx) { - return updateSubscriptionUnitsWithCtx( - idaLibrary, - ctx, - token, - indexId, - subscriber, - units, - new bytes(0) - ); - } - - /// @dev Updates the units of a subscription in a super app callback. This changes the number of - /// shares the subscriber holds. This takes arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address whose units are to be updated. - /// @param units New number of units the subscriber holds. - /// @param userData Arbitrary user data field. - function updateSubscriptionUnitsWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - uint32 indexId, - address subscriber, - uint128 units, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = idaLibrary.host.callAgreementWithContext( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.updateSubscription, - ( - token, - indexId, - subscriber, - units, - new bytes(0) // ctx placeholder - ) - ), - userData, - ctx - ); - } - - /// @dev Deletes a subscription, setting a subcriber's units to zero. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address whose units are to be deleted. - function deleteSubscription( - InitData storage idaLibrary, - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber - ) internal { - deleteSubscription(idaLibrary, token, publisher, indexId, subscriber, new bytes(0)); - } - - /// @dev Deletes a subscription, setting a subcriber's units to zero. This takes arbitrary user - /// data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address whose units are to be deleted. - /// @param userData Arbitrary user data field. - function deleteSubscription( - InitData storage idaLibrary, - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory userData - ) internal { - idaLibrary.host.callAgreement( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.deleteSubscription, - ( - token, - publisher, - indexId, - subscriber, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - } - - /// @dev Deletes a subscription in a super app callback, setting a subcriber's units to zero. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address whose units are to be deleted. - function deleteSubscriptionWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber - ) internal returns (bytes memory newCtx) { - return deleteSubscriptionWithCtx( - idaLibrary, - ctx, - token, - publisher, - indexId, - subscriber, - new bytes(0) - ); - } - - /// @dev Deletes a subscription in a super app callback, setting a subcriber's units to zero. - /// This takes arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param ctx Context byte string used by the Superfluid host. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address whose units are to be deleted. - /// @param userData Arbitrary user data field. - function deleteSubscriptionWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = idaLibrary.host.callAgreementWithContext( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.deleteSubscription, - ( - token, - publisher, - indexId, - subscriber, - new bytes(0) // ctx placeholder - ) - ), - userData, - ctx - ); - } - - /// @dev Claims pending distribution. Subscription should not be approved. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address that receives the claim. - function claim( - InitData storage idaLibrary, - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber - ) internal { - claim(idaLibrary, token, publisher, indexId, subscriber, new bytes(0)); - } - - /// @dev Claims pending distribution. Subscription should not be approved. This takes arbitrary - /// user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address that receives the claim. - /// @param userData Arbitrary user data field. - function claim( - InitData storage idaLibrary, - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory userData - ) internal { - idaLibrary.host.callAgreement( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.claim, - ( - token, - publisher, - indexId, - subscriber, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - } - - /// @dev Claims pending distribution in a super app callback. Subscription should not be - /// approved. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address that receives the claim. - function claimWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber - ) internal returns (bytes memory newCtx) { - return claimWithCtx( - idaLibrary, - ctx, - token, - publisher, - indexId, - subscriber, - new bytes(0) - ); - } - - /// @dev Claims pending distribution in a super app callback. Subscription should not be - /// approved. This takes arbitrary user data. - /// @param idaLibrary Storage pointer to host and ida interfaces. - /// @param token Super Token used with the index. - /// @param publisher Publisher of the index. - /// @param indexId ID of the index. - /// @param subscriber Subscriber address that receives the claim. - /// @param userData Arbitrary user data field. - function claimWithCtx( - InitData storage idaLibrary, - bytes memory ctx, - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory userData - ) internal returns (bytes memory newCtx) { - (newCtx, ) = idaLibrary.host.callAgreementWithContext( - idaLibrary.ida, - abi.encodeCall( - idaLibrary.ida.claim, - ( - token, - publisher, - indexId, - subscriber, - new bytes(0) // ctx placeholder - ) - ), - userData, - ctx - ); - } -} diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index b389f658f6..b44fd86b2c 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -5,28 +5,268 @@ import { ISuperfluid, ISuperToken, IConstantFlowAgreementV1, - IInstantDistributionAgreementV1 -} from "../interfaces/superfluid/ISuperfluid.sol"; - -import { IGeneralDistributionAgreementV1, ISuperfluidPool, PoolConfig -} from "../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; +} from "../interfaces/superfluid/ISuperfluid.sol"; + /** * @title Library for Token Centric Interface * @author Superfluid * @dev Set `using for ISuperToken` in including file, and call any of these functions on an instance * of ISuperToken. - * Note that it is important to "warm up" the cache and cache the host, cfa, ida before calling, - * this is only applicable to Foundry tests where the vm.expectRevert() will not work as expected. - * You must use vm.startPrank(account) instead of vm.prank when executing functions if the cache - * isn't "warmed up" yet. vm.prank impersonates the account only for the first call, which will be - * used for caching. + * The architecture of the Superfluid framework and its initial API were heavily influenced by the + * gas economics on Ethereum at the time, leading to compromises in terms of API ergonomics. + * This library mitigates that by providing a more convenient Solidity API for SuperTokens. + * While most methods are just wrappers around equivalent methods in a Superfluid agreement, + * some implement higher level abstractions. + * Note using the library in foundry tests can lead to counter-intuitive behaviour. + * E.g. "prank" won't set the expected msg.sender for calls done using the library. + * Also, reverts caused by the library itself won't be recognized by foundry, because + * it expects them to happen in the context of an external call. + * This is not specific to this library, but a general limitation of foundry when using libraries in tests. */ library SuperTokenV1Library { - /** CFA BASE CRUD ************************************* */ + + /** AGREEMENT-ABSTRACTED FUNCTIONS ************************************* */ + + /** + * @dev creates a flow to an account or to pool members. + * 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. + * @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. + */ + function flowX( + ISuperToken token, + address receiverOrPool, + int96 flowRate + ) internal returns(bool) { + address sender = address(this); + + (, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token); + if (gda.isPool(token, receiverOrPool)) { + return distributeFlow( + token, + sender, + ISuperfluidPool(receiverOrPool), + flowRate + ); + } else { + return flow(token, receiverOrPool, flowRate); + } + } + + /** + * @dev transfers `amount` to an account or distributes it to pool members. + * @param token Super token address + * @param receiverOrPool The receiver (account) or pool + * @param amount the amount to be transferred/distributed + * @return A boolean value indicating whether the operation was successful. + * Note in case of distribution, the effective amount may be smaller than requested. + */ + function transferX( + ISuperToken token, + address receiverOrPool, + uint256 amount + ) internal returns(bool) { + address sender = address(this); + + (, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token); + if (gda.isPool(token, receiverOrPool)) { + return distribute( + token, + sender, + ISuperfluidPool(receiverOrPool), + amount + ); + } else { + return token.transfer(receiverOrPool, amount); + } + } + + /** AGREEMENT-ABSTRACTED VIEW FUNCTIONS ************************************* */ + + /** + * @dev get flow rate between two accounts for given token + * @param token The token used in flow + * @param sender The sender of the flow + * @param receiverOrPool The receiver or pool receiving or distributing the flow + * @return flowRate The flow rate + * Note: edge case: if a CFA stream is going to a pool, it will return 0. + */ + function getFlowRate(ISuperToken token, address sender, address receiverOrPool) + internal view returns(int96 flowRate) + { + if (_isPool(token, receiverOrPool)) { + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + (, flowRate,) = gda.getFlow(token, sender, ISuperfluidPool(receiverOrPool)); + } else { + (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); + (, flowRate, , ) = cfa.getFlow(token, sender, receiverOrPool); + } + } + + /** + * @dev get flow info between an account and another account or pool for given token + * @param token The token used in flow + * @param sender The sender of the flow + * @param receiverOrPool The receiver or pool receiving or distributing the flow + * @return lastUpdated Timestamp of flow creation or last flowrate change + * @return flowRate The flow rate + * @return deposit The amount of deposit the flow + * @return owedDeposit The amount of owed deposit of the flow + * Note: edge case: a CFA stream going to a pool will not be "seen". + */ + function getFlowInfo(ISuperToken token, address sender, address receiverOrPool) + internal view + returns(uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) + { + if (_isPool(token, receiverOrPool)) { + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + (lastUpdated, flowRate, deposit) = gda.getFlow(token, sender, ISuperfluidPool(receiverOrPool)); + } else { + (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); + (lastUpdated, flowRate, deposit, owedDeposit) = cfa.getFlow(token, sender, receiverOrPool); + } + } + + /** + * @dev get net flow rate for given account for given token (CFA + GDA) + * @param token Super token address + * @param account Account to query + * @return flowRate The net flow rate of the account + */ + function getNetFlowRate(ISuperToken token, address account) + internal view returns (int96 flowRate) + { + (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + int96 cfaNetFlow = cfa.getNetFlow(token, account); + int96 gdaNetFlow = gda.getNetFlow(token, account); + return cfaNetFlow + gdaNetFlow; + } + + /** + * @dev get the aggregated flow info of the account (CFA + GDA) + * @param token Super token address + * @param account Account to query + * @return lastUpdated Timestamp of the last change of the net flow + * @return flowRate The net flow rate of token for account + * @return deposit The sum of all deposits for account's flows + * @return owedDeposit The sum of all owed deposits for account's flows + */ + function getNetFlowInfo(ISuperToken token, address account) + internal + view + returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) + { + (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + + { + (uint256 lastUpdatedCFA, int96 cfaNetFlowRate, uint256 cfaDeposit, uint256 cfaOwedDeposit) = + cfa.getAccountFlowInfo(token, account); + + lastUpdated = lastUpdatedCFA; + flowRate += cfaNetFlowRate; + deposit += cfaDeposit; + owedDeposit += cfaOwedDeposit; + } + + { + (uint256 lastUpdatedGDA, int96 gdaNetFlowRate, uint256 gdaDeposit) = gda.getAccountFlowInfo(token, account); + + if (lastUpdatedGDA > lastUpdated) { + lastUpdated = lastUpdatedGDA; + } + flowRate += gdaNetFlowRate; + deposit += gdaDeposit; + } + } + + /** + * @dev calculate buffer needed for a CFA flow with the given flowrate (for GDA, see 2nd notice below) + * @notice the returned amount is exact only for the scenario where no flow exists before. + * In order to get the buffer delta for a delta flowrate, you need to get the buffer amount + * for the new total flowrate and subtract the previous buffer. + * That's because there's not always linear proportionality between flowrate and buffer. + * @notice for GDA flows, the required buffer is typically slightly lower. + * That's due to an implementation detail (round-up "clipping" to 64 bit in the CFA). + * The return value of this method is thus to be considered not a precise value, but a + * lower bound for GDA flows. + * @param token The token used in flow + * @param flowRate The flowrate to calculate the needed buffer for + * @return bufferAmount The buffer amount based on flowRate, liquidationPeriod and minimum deposit + */ + function getBufferAmountByFlowRate(ISuperToken token, int96 flowRate) internal view + returns (uint256 bufferAmount) + { + (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); + return cfa.getDepositRequiredForFlowRate(token, flowRate); + } + + /** CFA BASE FUNCTIONS ************************************* */ + + /** + * @dev 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. + * @return bool + */ + function flow( + ISuperToken token, + address receiver, + int96 flowRate + ) internal returns (bool) { + return flow(token, receiver, flowRate, new bytes(0)); + } + + /** + * @dev Set CFA flowrate with userData + * @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 userData The userdata passed along with call + * @return bool + */ + function flow( + ISuperToken token, + address receiver, + int96 flowRate, + bytes memory userData + ) 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 (prevFlowRate == 0) { + return createFlow(token, receiver, flowRate, userData); + } else if (prevFlowRate != flowRate) { + return updateFlow(token, receiver, flowRate, userData); + } // else no change, do nothing + return true; + } else if (flowRate == 0) { + if (prevFlowRate > 0) { + return deleteFlow(token, sender, receiver, userData); + } // else no change, do nothing + return true; + } else { + revert IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE(); + } + } /** * @dev Create flow without userData @@ -62,7 +302,6 @@ library SuperTokenV1Library { return true; } - /** * @dev Update flow without userData * @param token The token used in flow @@ -75,7 +314,6 @@ library SuperTokenV1Library { return updateFlow(token, receiver, flowRate, new bytes(0)); } - /** * @dev Update flow with userData * @param token The token used in flow @@ -134,6 +372,59 @@ library SuperTokenV1Library { /** CFA ACL ************************************* */ + /** + * @notice Like `flow`, 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. + * @return bool + */ + function flowFrom( + ISuperToken token, + address sender, + address receiver, + int96 flowRate + ) internal returns (bool) { + return flowFrom(token, sender, receiver, flowRate, new bytes(0)); + } + + /** + * @notice Like `flowFrom`, but takes userData + * @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 userData The userdata passed along with call + * @return bool + */ + function flowFrom( + ISuperToken token, + address sender, + address receiver, + int96 flowRate, + bytes memory userData + ) internal returns (bool) { + int96 prevFlowRate = getCFAFlowRate(token, sender, receiver); + + if (flowRate > 0) { + if (prevFlowRate == 0) { + return createFlowFrom(token, sender, receiver, flowRate, userData); + } else if (prevFlowRate != flowRate) { + return updateFlowFrom(token, sender, receiver, flowRate, userData); + } // else no change, do nothing + return true; + } else if (flowRate == 0) { + if (prevFlowRate > 0) { + return deleteFlowFrom(token, sender, receiver, userData); + } // else no change, do nothing + return true; + } else { + revert IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE(); + } + } + /** * @dev Update permissions for flow operator * @param token The token used in flow @@ -384,152 +675,56 @@ library SuperTokenV1Library { } /** - * @dev Update permissions for flow operator in callback - * @notice allowing userData to be a parameter here triggered stack too deep error - * @param token The token used in flow - * @param flowOperator The address given flow permissions - * @param allowCreate creation permissions - * @param allowCreate update permissions - * @param allowCreate deletion permissions - * @param flowRateAllowance The allowance provided to flowOperator - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function + * @dev Creates flow as an operator without userData + * @param token The token to flow + * @param sender The sender of the flow + * @param receiver The receiver of the flow + * @param flowRate The desired flowRate */ - function setFlowPermissionsWithCtx( + function createFlowFrom( ISuperToken token, - address flowOperator, - bool allowCreate, - bool allowUpdate, - bool allowDelete, - int96 flowRateAllowance, - bytes memory ctx - ) internal returns (bytes memory newCtx) { + address sender, + address receiver, + int96 flowRate + ) internal returns (bool) { + return createFlowFrom(token, sender, receiver, flowRate, new bytes(0)); + } + + /** + * @dev Creates flow as an operator with userData + * @param token The token to flow + * @param sender The sender of the flow + * @param receiver The receiver of the flow + * @param flowRate The desired flowRate + * @param userData The user provided data + */ + function createFlowFrom( + ISuperToken token, + address sender, + address receiver, + int96 flowRate, + bytes memory userData + ) internal returns (bool) { (ISuperfluid host, IConstantFlowAgreementV1 cfa) = _getAndCacheHostAndCFA(token); - uint8 permissionsBitmask = (allowCreate ? 1 : 0) - | (allowUpdate ? 1 : 0) << 1 - | (allowDelete ? 1 : 0) << 2; - (newCtx, ) = host.callAgreementWithContext( + host.callAgreement( cfa, abi.encodeCall( - cfa.updateFlowOperatorPermissions, - ( - token, - flowOperator, - permissionsBitmask, - flowRateAllowance, - new bytes(0) - ) + cfa.createFlowByOperator, + (token, sender, receiver, flowRate, new bytes(0)) ), - "0x", - ctx + userData ); + return true; } /** - * @dev Update permissions for flow operator - give operator max permissions - * @param token The token used in flow - * @param flowOperator The address given flow permissions - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function + * @dev Updates flow as an operator without userData + * @param token The token to flow + * @param sender The sender of the flow + * @param receiver The receiver of the flow + * @param flowRate The desired flowRate */ - function setMaxFlowPermissionsWithCtx( - ISuperToken token, - address flowOperator, - bytes memory ctx - ) internal returns (bytes memory newCtx) { - (ISuperfluid host, IConstantFlowAgreementV1 cfa) = _getAndCacheHostAndCFA(token); - (newCtx, ) = host.callAgreementWithContext( - cfa, - abi.encodeCall( - cfa.authorizeFlowOperatorWithFullControl, - ( - token, - flowOperator, - new bytes(0) - ) - ), - "0x", - ctx - ); - } - - /** - * @dev Update permissions for flow operator - revoke all permission - * @param token The token used in flow - * @param flowOperator The address given flow permissions - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function - */ - function revokeFlowPermissionsWithCtx( - ISuperToken token, - address flowOperator, - bytes memory ctx - ) internal returns (bytes memory newCtx) { - (ISuperfluid host, IConstantFlowAgreementV1 cfa) = _getAndCacheHostAndCFA(token); - (newCtx, ) = host.callAgreementWithContext( - cfa, - abi.encodeCall( - cfa.revokeFlowOperatorWithFullControl, - (token, flowOperator, new bytes(0)) - ), - "0x", - ctx - ); - } - - - /** - * @dev Creates flow as an operator without userData - * @param token The token to flow - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param flowRate The desired flowRate - */ - function createFlowFrom( - ISuperToken token, - address sender, - address receiver, - int96 flowRate - ) internal returns (bool) { - return createFlowFrom(token, sender, receiver, flowRate, new bytes(0)); - } - - /** - * @dev Creates flow as an operator with userData - * @param token The token to flow - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param flowRate The desired flowRate - * @param userData The user provided data - */ - function createFlowFrom( - ISuperToken token, - address sender, - address receiver, - int96 flowRate, - bytes memory userData - ) internal returns (bool) { - (ISuperfluid host, IConstantFlowAgreementV1 cfa) = _getAndCacheHostAndCFA(token); - host.callAgreement( - cfa, - abi.encodeCall( - cfa.createFlowByOperator, - (token, sender, receiver, flowRate, new bytes(0)) - ), - userData - ); - return true; - } - - - /** - * @dev Updates flow as an operator without userData - * @param token The token to flow - * @param sender The sender of the flow - * @param receiver The receiver of the flow - * @param flowRate The desired flowRate - */ - function updateFlowFrom( + function updateFlowFrom( ISuperToken token, address sender, address receiver, @@ -604,7 +799,6 @@ library SuperTokenV1Library { return true; } - /** CFA With CTX FUNCTIONS ************************************* */ /** @@ -799,16 +993,110 @@ library SuperTokenV1Library { ); } + /** + * @dev Update permissions for flow operator in callback + * @notice allowing userData to be a parameter here triggered stack too deep error + * @param token The token used in flow + * @param flowOperator The address given flow permissions + * @param allowCreate creation permissions + * @param allowCreate update permissions + * @param allowCreate deletion permissions + * @param flowRateAllowance The allowance provided to flowOperator + * @param ctx Context bytes (see ISuperfluid.sol for Context struct) + * @return newCtx The updated context after the execution of the agreement function + */ + function setFlowPermissionsWithCtx( + ISuperToken token, + address flowOperator, + bool allowCreate, + bool allowUpdate, + bool allowDelete, + int96 flowRateAllowance, + bytes memory ctx + ) internal returns (bytes memory newCtx) { + (ISuperfluid host, IConstantFlowAgreementV1 cfa) = _getAndCacheHostAndCFA(token); + uint8 permissionsBitmask = (allowCreate ? 1 : 0) + | (allowUpdate ? 1 : 0) << 1 + | (allowDelete ? 1 : 0) << 2; + (newCtx, ) = host.callAgreementWithContext( + cfa, + abi.encodeCall( + cfa.updateFlowOperatorPermissions, + ( + token, + flowOperator, + permissionsBitmask, + flowRateAllowance, + new bytes(0) + ) + ), + "0x", + ctx + ); + } + + /** + * @dev Update permissions for flow operator - give operator max permissions + * @param token The token used in flow + * @param flowOperator The address given flow permissions + * @param ctx Context bytes (see ISuperfluid.sol for Context struct) + * @return newCtx The updated context after the execution of the agreement function + */ + function setMaxFlowPermissionsWithCtx( + ISuperToken token, + address flowOperator, + bytes memory ctx + ) internal returns (bytes memory newCtx) { + (ISuperfluid host, IConstantFlowAgreementV1 cfa) = _getAndCacheHostAndCFA(token); + (newCtx, ) = host.callAgreementWithContext( + cfa, + abi.encodeCall( + cfa.authorizeFlowOperatorWithFullControl, + ( + token, + flowOperator, + new bytes(0) + ) + ), + "0x", + ctx + ); + } + + /** + * @dev Update permissions for flow operator - revoke all permission + * @param token The token used in flow + * @param flowOperator The address given flow permissions + * @param ctx Context bytes (see ISuperfluid.sol for Context struct) + * @return newCtx The updated context after the execution of the agreement function + */ + function revokeFlowPermissionsWithCtx( + ISuperToken token, + address flowOperator, + bytes memory ctx + ) internal returns (bytes memory newCtx) { + (ISuperfluid host, IConstantFlowAgreementV1 cfa) = _getAndCacheHostAndCFA(token); + (newCtx, ) = host.callAgreementWithContext( + cfa, + abi.encodeCall( + cfa.revokeFlowOperatorWithFullControl, + (token, flowOperator, new bytes(0)) + ), + "0x", + ctx + ); + } + /** CFA VIEW FUNCTIONS ************************************* */ /** - * @dev get flow rate between two accounts for given token + * @dev get CFA flow rate between two accounts for given token * @param token The token used in flow * @param sender The sender of the flow * @param receiver The receiver of the flow * @return flowRate The flow rate */ - function getFlowRate(ISuperToken token, address sender, address receiver) + function getCFAFlowRate(ISuperToken token, address sender, address receiver) internal view returns(int96 flowRate) { (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); @@ -816,7 +1104,7 @@ library SuperTokenV1Library { } /** - * @dev get flow info between two accounts for given token + * @dev get CFA flow info between two accounts for given token * @param token The token used in flow * @param sender The sender of the flow * @param receiver The receiver of the flow @@ -825,7 +1113,7 @@ library SuperTokenV1Library { * @return deposit The amount of deposit the flow * @return owedDeposit The amount of owed deposit of the flow */ - function getFlowInfo(ISuperToken token, address sender, address receiver) + function getCFAFlowInfo(ISuperToken token, address sender, address receiver) internal view returns(uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) { @@ -833,43 +1121,6 @@ library SuperTokenV1Library { (lastUpdated, flowRate, deposit, owedDeposit) = cfa.getFlow(token, sender, receiver); } - /** - * @dev get flow info of a distributor to a pool for given token - * @param token The token used in flow - * @param distributor The sitributor of the flow - * @param pool The GDA pool - * @return lastUpdated Timestamp of flow creation or last flowrate change - * @return flowRate The flow rate - * @return deposit The amount of deposit the flow - */ - function getGDAFlowInfo(ISuperToken token, address distributor, ISuperfluidPool pool) - internal view - returns(uint256 lastUpdated, int96 flowRate, uint256 deposit) - { - (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); - return gda.getFlow(token, distributor, pool); - } - - /* function getGDAFlowInfo(ISuperToken token, address distributor, ISuperfluidPool pool) */ - /* { */ - /* } */ - - /** - * @dev get net flow rate for given account for given token (CFA + GDA) - * @param token Super token address - * @param account Account to query - * @return flowRate The net flow rate of the account - */ - function getNetFlowRate(ISuperToken token, address account) - internal view returns (int96 flowRate) - { - (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); - (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); - int96 cfaNetFlow = cfa.getNetFlow(token, account); - int96 gdaNetFlow = gda.getNetFlow(token, account); - return cfaNetFlow + gdaNetFlow; - } - /** * @dev get CFA net flow rate for given account for given token * @param token Super token address @@ -884,20 +1135,7 @@ library SuperTokenV1Library { } /** - * @dev get GDA net flow rate for given account for given token - * @param token Super token address - * @param account Account to query - * @return flowRate The net flow rate of the account - */ - function getGDANetFlowRate(ISuperToken token, address account) - internal view returns (int96 flowRate) - { - (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); - return gda.getNetFlow(token, account); - } - - /** - * @dev get the aggregated flow info of the account (CFA + GDA) + * @dev get the aggregated CFA flow info of the account * @param token Super token address * @param account Account to query * @return lastUpdated Timestamp of the last change of the net flow @@ -905,936 +1143,38 @@ library SuperTokenV1Library { * @return deposit The sum of all deposits for account's flows * @return owedDeposit The sum of all owed deposits for account's flows */ - function getNetFlowInfo(ISuperToken token, address account) + function getCFANetFlowInfo(ISuperToken token, address account) internal view returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) { (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); - (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); - - { - (uint256 lastUpdatedCFA, int96 cfaNetFlowRate, uint256 cfaDeposit, uint256 cfaOwedDeposit) = - cfa.getAccountFlowInfo(token, account); - - lastUpdated = lastUpdatedCFA; - flowRate += cfaNetFlowRate; - deposit += cfaDeposit; - owedDeposit += cfaOwedDeposit; - } - - { - (uint256 lastUpdatedGDA, int96 gdaNetFlowRate, uint256 gdaDeposit) = gda.getAccountFlowInfo(token, account); - - if (lastUpdatedGDA > lastUpdated) { - lastUpdated = lastUpdatedGDA; - } - flowRate += gdaNetFlowRate; - deposit += gdaDeposit; - } - } - - /** - * @dev get the aggregated CFA flow info of the account - * @param token Super token address - * @param account Account to query - * @return lastUpdated Timestamp of the last change of the net flow - * @return flowRate The net flow rate of token for account - * @return deposit The sum of all deposits for account's flows - * @return owedDeposit The sum of all owed deposits for account's flows - */ - function getCFANetFlowInfo(ISuperToken token, address account) - internal - view - returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) - { - (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); - return cfa.getAccountFlowInfo(token, account); - } - - /** - * @dev get the aggregated GDA flow info of the account - * @param token Super token address - * @param account Account to query - * @return lastUpdated Timestamp of the last change of the net flow - * @return flowRate The net flow rate of token for account - * @return deposit The sum of all deposits for account's flows - * @return owedDeposit The sum of all owed deposits for account's flows - */ - function getGDANetFlowInfo(ISuperToken token, address account) - internal - view - returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) - { - (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); - (lastUpdated, flowRate, deposit) = gda.getAccountFlowInfo(token, account); - owedDeposit = 0; // unused in GDA - } - - /** - * @dev get the adjustment flow rate for a pool - * @param token Super token address - * @param pool The pool to query - * @return poolAdjustmentFlowRate The adjustment flow rate of the pool - */ - function getPoolAdjustmentFlowRate(ISuperToken token, ISuperfluidPool pool) - internal - view - returns (int96 poolAdjustmentFlowRate) - { - (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); - return gda.getPoolAdjustmentFlowRate(address(pool)); - } - - /** - * @dev Get the total amount of tokens received by a member via instant and flowing distributions - * @param pool The pool to query - * @param memberAddr The member to query - * @return totalAmountReceived The total amount received by the member - */ - function getTotalAmountReceivedByMember(ISuperfluidPool pool, address memberAddr) - internal - view - returns (uint256 totalAmountReceived) - { - return pool.getTotalAmountReceivedByMember(memberAddr); - } - - /** - * @notice calculate buffer for a CFA/GDA flow rate - * @dev Even though we are using the CFA, the logic for calculating buffer is the same in the GDA - * and a change in the buffer logic in either means it is a BREAKING change - * @param token The token used in flow - * @param flowRate The flowrate to calculate the needed buffer for - * @return bufferAmount The buffer amount based on flowRate, liquidationPeriod and minimum deposit - */ - function getBufferAmountByFlowRate(ISuperToken token, int96 flowRate) internal view - returns (uint256 bufferAmount) - { - (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); - return cfa.getDepositRequiredForFlowRate(token, flowRate); - } - - /** - * @dev get existing flow permissions - * @param token The token used in flow - * @param sender sender of a flow - * @param flowOperator the address we are checking permissions of for sender & token - * @return allowCreate is true if the flowOperator can create flows - * @return allowUpdate is true if the flowOperator can update flows - * @return allowDelete is true if the flowOperator can delete flows - * @return flowRateAllowance The flow rate allowance the flowOperator is granted (only goes down) - */ - function getFlowPermissions(ISuperToken token, address sender, address flowOperator) - internal view - returns (bool allowCreate, bool allowUpdate, bool allowDelete, int96 flowRateAllowance) - { - (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); - uint8 permissionsBitmask; - (, permissionsBitmask, flowRateAllowance) = cfa.getFlowOperatorData(token, sender, flowOperator); - allowCreate = permissionsBitmask & 1 == 1; - allowUpdate = permissionsBitmask >> 1 & 1 == 1; - allowDelete = permissionsBitmask >> 2 & 1 == 1; - } - - - /** IDA VIEW FUNCTIONS ************************************* */ - - - /** - * @dev Gets an index by its ID and publisher. - * @param token Super token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @return exist True if the index exists. - * @return indexValue Total value of the index. - * @return totalUnitsApproved Units of the index approved by subscribers. - * @return totalUnitsPending Units of teh index not yet approved by subscribers. - */ - function getIndex(ISuperToken token, address publisher, uint32 indexId) - internal view - returns (bool exist, uint128 indexValue, uint128 totalUnitsApproved, uint128 totalUnitsPending) - { - (, IInstantDistributionAgreementV1 ida) = _getHostAndIDA(token); - return ida.getIndex(token, publisher, indexId); - } - - /** - * @dev Calculates the distribution amount based on the amount of tokens desired to distribute. - * @param token Super token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param amount Amount of tokens desired to distribute. - * @return actualAmount Amount to be distributed with correct rounding. - * @return newIndexValue The index value after the distribution would be called. - */ - function calculateDistribution(ISuperToken token, address publisher, uint32 indexId, uint256 amount) - internal view - returns (uint256 actualAmount, uint128 newIndexValue) - { - (, IInstantDistributionAgreementV1 ida) = _getHostAndIDA(token); - return ida.calculateDistribution(token, publisher, indexId, amount); - } - - /** - * @dev List all subscriptions of an address - * @param token Super token used in the indexes listed. - * @param subscriber Subscriber address. - * @return publishers Publishers of the indices. - * @return indexIds IDs of the indices. - * @return unitsList Units owned of the indices. - */ - function listSubscriptions( - ISuperToken token, - address subscriber - ) - internal view - returns ( - address[] memory publishers, - uint32[] memory indexIds, - uint128[] memory unitsList - ) - { - (, IInstantDistributionAgreementV1 ida) = _getHostAndIDA(token); - return ida.listSubscriptions(token, subscriber); - } - - /** - * @dev Gets subscription by publisher, index id, and subscriber. - * @param token Super token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param subscriber Subscriber to the index. - * @return exist True if the subscription exists. - * @return approved True if the subscription has been approved by the subscriber. - * @return units Units held by the subscriber - * @return pendingDistribution If not approved, the amount to be claimed on approval. - */ - function getSubscription(ISuperToken token, address publisher, uint32 indexId, address subscriber) - internal view - returns (bool exist, bool approved, uint128 units, uint256 pendingDistribution) - { - (, IInstantDistributionAgreementV1 ida) = _getHostAndIDA(token); - return ida.getSubscription(token, publisher, indexId, subscriber); - } - - /* - * @dev Gets subscription by the agreement ID. - * @param token Super Token used with the index. - * @param agreementId Agreement ID, unique to the subscriber and index ID. - * @return publisher Publisher of the index. - * @return indexId ID of the index. - * @return approved True if the subscription has been approved by the subscriber. - * @return units Units held by the subscriber - * @return pendingDistribution If not approved, the amount to be claimed on approval. - */ - function getSubscriptionByID(ISuperToken token, bytes32 agreementId) - internal view - returns ( - address publisher, - uint32 indexId, - bool approved, - uint128 units, - uint256 pendingDistribution - ) - { - (, IInstantDistributionAgreementV1 ida) = _getHostAndIDA(token); - return ida.getSubscriptionByID(token, agreementId); - } - - /** GDA VIEW FUNCTIONS ************************************* */ - function getFlowDistributionFlowRate(ISuperToken token, address from, ISuperfluidPool to) - internal - view - returns (int96) - { - (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); - return gda.getFlowRate(token, from, to); - } - - function estimateFlowDistributionActualFlowRate( - ISuperToken token, - address from, - ISuperfluidPool to, - int96 requestedFlowRate - ) internal view returns (int96 actualFlowRate, int96 totalDistributionFlowRate) { - (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); - return gda.estimateFlowDistributionActualFlowRate(token, from, to, requestedFlowRate); - } - - function estimateDistributionActualAmount( - ISuperToken token, - address from, - ISuperfluidPool to, - uint256 requestedAmount - ) internal view returns (uint256 actualAmount) { - (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); - return gda.estimateDistributionActualAmount(token, from, to, requestedAmount); - } - - function isMemberConnected(ISuperToken token, address pool, address member) internal view returns (bool) { - (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); - return gda.isMemberConnected(ISuperfluidPool(pool), member); - } - - - /** IDA BASE FUNCTIONS ************************************* */ - - - /** - * @dev Creates a new index. - * @param token Super Token used with the index. - * @param indexId ID of the index. - */ - function createIndex( - ISuperToken token, - uint32 indexId - ) internal returns (bool) { - return createIndex(token, indexId, new bytes(0)); - } - - /** - * @dev Creates a new index with userData. - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param userData Arbitrary user data field. - */ - function createIndex( - ISuperToken token, - uint32 indexId, - bytes memory userData - ) internal returns (bool) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - host.callAgreement( - ida, - abi.encodeCall( - ida.createIndex, - ( - token, - indexId, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - return true; - } - - /** - * @dev Updates an index value. This distributes an amount of tokens equal to - * `indexValue - lastIndexValue`. See `distribute` for another way to distribute. - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param indexValue New TOTAL index value, this will equal the total amount distributed. - */ - function updateIndexValue( - ISuperToken token, - uint32 indexId, - uint128 indexValue - ) internal returns (bool) { - return updateIndexValue(token, indexId, indexValue, new bytes(0)); - } - - /** - * @dev Updates an index value with userData. This distributes an amount of tokens equal to - * `indexValue - lastIndexValue`. See `distribute` for another way to distribute. - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param indexValue New TOTAL index value, this will equal the total amount distributed. - * @param userData Arbitrary user data field. - */ - function updateIndexValue( - ISuperToken token, - uint32 indexId, - uint128 indexValue, - bytes memory userData - ) internal returns (bool) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - host.callAgreement( - ida, - abi.encodeCall( - ida.updateIndex, - ( - token, - indexId, - indexValue, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - return true; - } - - /** - * @dev Distributes tokens in a more developer friendly way than `updateIndex`. Instead of - * passing the new total index value, you pass the amount of tokens desired to be distributed. - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param amount - total number of tokens desired to be distributed - * NOTE in many cases, there can be some precision loss - This may cause a slight difference in the amount param specified and the actual amount distributed. - See below for math: - //indexDelta = amount the index will be updated by during an internal call to _updateIndex(). - It is calculated like so: - indexDelta = amount / totalUnits - (see the distribute() implementatation in ./agreements/InstantDistributionAgreement.sol) - * NOTE Solidity does not support floating point numbers - So the indexDelta will be rounded down to the nearest integer. - This will create a 'remainder' amount of tokens that will not be distributed - (we'll call this the 'distribution modulo') - distributionModulo = amount - indexDelta * totalUnits - * NOTE due to rounding, there may be a small amount of tokens left in the publisher's account - This amount is equal to the 'distributionModulo' value - // - */ - function distribute( - ISuperToken token, - uint32 indexId, - uint256 amount - ) internal returns (bool) { - return distribute(token, indexId, amount, new bytes(0)); - } - - /** - * @dev Distributes tokens in a more developer friendly way than `updateIndex` (w user data). Instead of - * passing the new total index value, this function will increase the index value by `amount`. - * This takes arbitrary user data. - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param amount Amount by which the index value should increase. - * @param userData Arbitrary user data field. - */ - function distribute( - ISuperToken token, - uint32 indexId, - uint256 amount, - bytes memory userData - ) internal returns (bool) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - host.callAgreement( - ida, - abi.encodeCall( - ida.distribute, - ( - token, - indexId, - amount, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - return true; - } - - /** - * @dev Approves a subscription to an index. The subscriber's real time balance will not update - * until the subscription is approved, but once approved, the balance will be updated with - * prior distributions. - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - */ - function approveSubscription( - ISuperToken token, - address publisher, - uint32 indexId - ) internal returns (bool) { - return approveSubscription(token, publisher, indexId, new bytes(0)); - } - - /** - * @dev Approves a subscription to an index with user data. The subscriber's real time balance will not update - * until the subscription is approved, but once approved, the balance will be updated with - * prior distributions. - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param userData Arbitrary user data field. - */ - function approveSubscription( - ISuperToken token, - address publisher, - uint32 indexId, - bytes memory userData - ) internal returns (bool) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - host.callAgreement( - ida, - abi.encodeCall( - ida.approveSubscription, - ( - token, - publisher, - indexId, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - return true; - } - - /** - * @dev Revokes a previously approved subscription. - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - */ - function revokeSubscription( - ISuperToken token, - address publisher, - uint32 indexId - ) internal returns (bool) { - return revokeSubscription(token, publisher, indexId, new bytes(0)); - } - - /** - * @dev Revokes a previously approved subscription. This takes arbitrary user data. - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param userData Arbitrary user data field. - */ - function revokeSubscription( - ISuperToken token, - address publisher, - uint32 indexId, - bytes memory userData - ) internal returns (bool) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - host.callAgreement( - ida, - abi.encodeCall( - ida.revokeSubscription, - ( - token, - publisher, - indexId, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - return true; - } - - /** - * @dev Updates the units of a subscription. This changes the number of shares the subscriber holds - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param subscriber Subscriber address whose units are to be updated. - * @param units New number of units the subscriber holds. - */ - function updateSubscriptionUnits( - ISuperToken token, - uint32 indexId, - address subscriber, - uint128 units - ) internal returns (bool) { - return updateSubscriptionUnits(token, indexId, subscriber, units, new bytes(0)); - } - - /** - * @dev Updates the units of a subscription. This changes the number of shares the subscriber - * holds. This takes arbitrary user data. - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param subscriber Subscriber address whose units are to be updated. - * @param units New number of units the subscriber holds. - * @param userData Arbitrary user data field. - */ - function updateSubscriptionUnits( - ISuperToken token, - uint32 indexId, - address subscriber, - uint128 units, - bytes memory userData - ) internal returns (bool) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - host.callAgreement( - ida, - abi.encodeCall( - ida.updateSubscription, - ( - token, - indexId, - subscriber, - units, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - return true; - } - - /** - * @dev Deletes a subscription, setting a subcriber's units to zero - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param subscriber Subscriber address whose units are to be deleted. - */ - function deleteSubscription( - ISuperToken token, - address publisher, - uint32 indexId, - address subscriber - ) internal returns (bool) { - return deleteSubscription(token, publisher, indexId, subscriber, new bytes(0)); - } - - /** - * @dev Deletes a subscription, setting a subcriber's units to zero. This takes arbitrary userdata. - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param subscriber Subscriber address whose units are to be deleted. - * @param userData Arbitrary user data field. - */ - function deleteSubscription( - ISuperToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory userData - ) internal returns (bool) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - host.callAgreement( - ida, - abi.encodeCall( - ida.deleteSubscription, - ( - token, - publisher, - indexId, - subscriber, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - return true; - } - - /** - * @dev Claims pending distribution. Subscription should not be approved - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param subscriber Subscriber address that receives the claim. - */ - function claim( - ISuperToken token, - address publisher, - uint32 indexId, - address subscriber - ) internal returns (bool) { - return claim(token, publisher, indexId, subscriber, new bytes(0)); - } - - /** - * @dev Claims pending distribution. Subscription should not be approved. This takes arbitrary user data. - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param subscriber Subscriber address that receives the claim. - * @param userData Arbitrary user data field. - */ - function claim( - ISuperToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory userData - ) internal returns (bool) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - host.callAgreement( - ida, - abi.encodeCall( - ida.claim, - ( - token, - publisher, - indexId, - subscriber, - new bytes(0) // ctx placeholder - ) - ), - userData - ); - return true; - } - - /** IDA WITH CTX FUNCTIONS ************************************* */ - - /** - * @dev Creates a new index with ctx. - * Meant for usage in super app callbacks - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function - */ - function createIndexWithCtx( - ISuperToken token, - uint32 indexId, - bytes memory ctx - ) internal returns (bytes memory newCtx) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - (newCtx, ) = host.callAgreementWithContext( - ida, - abi.encodeCall( - ida.createIndex, - ( - token, - indexId, - new bytes(0) // ctx placeholder - ) - ), - "0x", - ctx - ); - } - - /** - * @dev Updates an index value with ctx. This distributes an amount of tokens equal to - * `indexValue - lastIndexValue`. See `distribute` for another way to distribute. - * Meant for usage in super app callbakcs - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param indexValue New TOTAL index value, this will equal the total amount distributed. - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function - */ - function updateIndexValueWithCtx( - ISuperToken token, - uint32 indexId, - uint128 indexValue, - bytes memory ctx - ) internal returns (bytes memory newCtx) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - (newCtx, ) = host.callAgreementWithContext( - ida, - abi.encodeCall( - ida.updateIndex, - ( - token, - indexId, - indexValue, - new bytes(0) // ctx placeholder - ) - ), - "0x", - ctx - ); - } - - /** - * @dev Distributes tokens in a more developer friendly way than `updateIndex`.Instead of - * passing the new total index value, this function will increase the index value by `amount`. - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param amount Amount by which the index value should increase. - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function - */ - function distributeWithCtx( - ISuperToken token, - uint32 indexId, - uint256 amount, - bytes memory ctx - ) internal returns (bytes memory newCtx) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - (newCtx, ) = host.callAgreementWithContext( - ida, - abi.encodeCall( - ida.distribute, - ( - token, - indexId, - amount, - new bytes(0) // ctx placeholder - ) - ), - "0x", - ctx - ); - } - - /** - * @dev Approves a subscription to an index. The subscriber's real time balance will not update - * until the subscription is approved, but once approved, the balance will be updated with - * prior distributions. - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function - */ - function approveSubscriptionWithCtx( - ISuperToken token, - address publisher, - uint32 indexId, - bytes memory ctx - ) internal returns (bytes memory newCtx) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - (newCtx, ) = host.callAgreementWithContext( - ida, - abi.encodeCall( - ida.approveSubscription, - ( - token, - publisher, - indexId, - new bytes(0) // ctx placeholder - ) - ), - "0x", - ctx - ); - } - - /** - * @dev Revokes a previously approved subscription. Meant for usage in super apps - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function - */ - function revokeSubscriptionWithCtx( - ISuperToken token, - address publisher, - uint32 indexId, - bytes memory ctx - ) internal returns (bytes memory newCtx) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - (newCtx, ) = host.callAgreementWithContext( - ida, - abi.encodeCall( - ida.revokeSubscription, - ( - token, - publisher, - indexId, - new bytes(0) // ctx placeholder - ) - ), - "0x", - ctx - ); - } - - /** - * @dev Updates the units of a subscription. This changes the number of shares the subscriber - * holds. Meant for usage in super apps - * @param token Super Token used with the index. - * @param indexId ID of the index. - * @param subscriber Subscriber address whose units are to be updated. - * @param units New number of units the subscriber holds. - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function - */ - function updateSubscriptionUnitsWithCtx( - ISuperToken token, - uint32 indexId, - address subscriber, - uint128 units, - bytes memory ctx - ) internal returns (bytes memory newCtx) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - (newCtx, ) = host.callAgreementWithContext( - ida, - abi.encodeCall( - ida.updateSubscription, - ( - token, - indexId, - subscriber, - units, - new bytes(0) // ctx placeholder - ) - ), - "0x", - ctx - ); - } - - /** - * @dev Deletes a subscription, setting a subcriber's units to zero. - * Meant for usage in super apps - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param subscriber Subscriber address whose units are to be deleted. - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function - */ - function deleteSubscriptionWithCtx( - ISuperToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory ctx - ) internal returns (bytes memory newCtx) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - (newCtx, ) = host.callAgreementWithContext( - ida, - abi.encodeCall( - ida.deleteSubscription, - ( - token, - publisher, - indexId, - subscriber, - new bytes(0) // ctx placeholder - ) - ), - "0x", - ctx - ); - } - - /** - * @dev Claims pending distribution. Subscription should not be approved. - * Meant for usage in super app callbacks - * @param token Super Token used with the index. - * @param publisher Publisher of the index. - * @param indexId ID of the index. - * @param subscriber Subscriber address that receives the claim. - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function - */ - function claimWithCtx( - ISuperToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory ctx - ) internal returns (bytes memory newCtx) { - (ISuperfluid host, IInstantDistributionAgreementV1 ida) = _getAndCacheHostAndIDA(token); - (newCtx, ) = host.callAgreementWithContext( - ida, - abi.encodeCall( - ida.claim, - ( - token, - publisher, - indexId, - subscriber, - new bytes(0) // ctx placeholder - ) - ), - "0x", - ctx - ); + return cfa.getAccountFlowInfo(token, account); } - /** GDA BASE FUNCTIONS ************************************* */ + /** + * @dev get existing CFA flow permissions + * @param token The token used in flow + * @param sender sender of a flow + * @param flowOperator the address we are checking permissions of for sender & token + * @return allowCreate is true if the flowOperator can create flows + * @return allowUpdate is true if the flowOperator can update flows + * @return allowDelete is true if the flowOperator can delete flows + * @return flowRateAllowance The flow rate allowance the flowOperator is granted (only goes down) + */ + function getFlowPermissions(ISuperToken token, address sender, address flowOperator) + internal view + returns (bool allowCreate, bool allowUpdate, bool allowDelete, int96 flowRateAllowance) + { + (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token); + uint8 permissionsBitmask; + (, permissionsBitmask, flowRateAllowance) = cfa.getFlowOperatorData(token, sender, flowOperator); + allowCreate = permissionsBitmask & 1 == 1; + allowUpdate = permissionsBitmask >> 1 & 1 == 1; + allowDelete = permissionsBitmask >> 2 & 1 == 1; + } + /** GDA BASE FUNCTIONS ************************************* */ /** * @dev Creates a new Superfluid Pool. @@ -1852,42 +1192,33 @@ library SuperTokenV1Library { } /** - * @dev Updates the units of a pool member. + * @dev Creates a new Superfluid Pool with default PoolConfig: units not transferrable, allow multi-distributors * @param token The Super Token address. - * @param pool The Superfluid Pool to update. - * @param memberAddress The address of the member to update. - * @param newUnits The new units of the member. - * @return bool A boolean value indicating whether the pool was created successfully. + * @param admin The pool admin address. + * @return pool The address of the deployed Superfluid Pool */ - function updateMemberUnits(ISuperToken token, ISuperfluidPool pool, address memberAddress, uint128 newUnits) + function createPool(ISuperToken token, address admin) internal - returns (bool) + returns (ISuperfluidPool pool) { - return updateMemberUnits(token, pool, memberAddress, newUnits, new bytes(0)); + return createPool( + token, + admin, + PoolConfig({ + transferabilityForUnitsOwner: false, + distributionFromAnyAddress: true + }) + ); } /** - * @dev Updates the units of a pool member. - * @param token The Super Token address. - * @param pool The Superfluid Pool to update. - * @param memberAddress The address of the member to update. - * @param newUnits The new units of the member. - * @param userData User-specific data. - * @return A boolean value indicating whether the pool was created successfully. + * @dev Creates a new Superfluid Pool with default PoolConfig and the caller set as admin + * @return pool The address of the deployed Superfluid Pool */ - function updateMemberUnits( - ISuperToken token, - ISuperfluidPool pool, - address memberAddress, - uint128 newUnits, - bytes memory userData - ) internal returns (bool) { - (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token); - host.callAgreement( - gda, abi.encodeCall(gda.updateMemberUnits, (pool, memberAddress, newUnits, new bytes(0))), userData - ); - - return true; + function createPool(ISuperToken token) internal returns (ISuperfluidPool pool) { + // note: from the perspective of the lib, msg.sender is the contract using the lib. + // from the perspective of the GDA contract, that will be the msg.sender + return createPool(token, address(this)); } /** @@ -1970,12 +1301,28 @@ library SuperTokenV1Library { /** * @dev Tries to distribute `requestedAmount` amount of `token` from `from` to `pool`. * @param token The Super Token address. + * @param pool The Superfluid Pool address. + * @param requestedAmount The amount of tokens to distribute. + * @return A boolean value indicating whether the distribution was successful. + */ + function distribute(ISuperToken token, ISuperfluidPool pool, uint256 requestedAmount) + internal + returns (bool) + { + return distribute(token, address(this), pool, requestedAmount, new bytes(0)); + } + + /** + * @dev Tries to distribute `requestedAmount` amount of `token` from `from` to `pool`. + * NOTE: has an additional argument `from`, but can only be msg.sender because GDA + * currently doesn't have ACL support. Only included for API completeness. + * @param token The Super Token address. * @param from The address from which to distribute tokens. * @param pool The Superfluid Pool address. * @param requestedAmount The amount of tokens to distribute. * @return A boolean value indicating whether the distribution was successful. */ - function distributeToPool(ISuperToken token, address from, ISuperfluidPool pool, uint256 requestedAmount) + function distribute(ISuperToken token, address from, ISuperfluidPool pool, uint256 requestedAmount) internal returns (bool) { @@ -2008,6 +1355,22 @@ library SuperTokenV1Library { /** * @dev Tries to distribute flow at `requestedFlowRate` of `token` from `from` to `pool`. * @param token The Super Token address. + * @param pool The Superfluid Pool address. + * @param requestedFlowRate The flow rate of tokens to distribute. + * @return A boolean value indicating whether the distribution was successful. + */ + function distributeFlow(ISuperToken token, ISuperfluidPool pool, int96 requestedFlowRate) + internal + returns (bool) + { + return distributeFlow(token, address(this), pool, requestedFlowRate, new bytes(0)); + } + + /** + * @dev Tries to distribute flow at `requestedFlowRate` of `token` from `from` to `pool`. + * NOTE: The ability to set the `from` argument is needed only when liquidating a GDA flow. + * The GDA currently doesn't have ACL support. + * @param token The Super Token address. * @param from The address from which to distribute tokens. * @param pool The Superfluid Pool address. * @param requestedFlowRate The flow rate of tokens to distribute. @@ -2045,39 +1408,6 @@ library SuperTokenV1Library { /** GDA WITH CTX FUNCTIONS ************************************* */ - /** - * @dev Updates the units of a pool member. - * @param token The Super Token address. - * @param pool The Superfluid Pool to update. - * @param memberAddress The address of the member to update. - * @param newUnits The new units of the member. - * @param ctx Context bytes (see ISuperfluid.sol for Context struct) - * @return newCtx The updated context after the execution of the agreement function - */ - function updateMemberUnitsWithCtx( - ISuperToken token, - ISuperfluidPool pool, - address memberAddress, - uint128 newUnits, - bytes memory ctx - ) internal returns (bytes memory newCtx) { - (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token); - (newCtx,) = host.callAgreementWithContext( - gda, - abi.encodeCall( - gda.updateMemberUnits, - ( - pool, - memberAddress, - newUnits, - new bytes(0) // ctx placeholder - ) - ), - "0x", - ctx - ); - } - /** * @dev Claims all tokens from the pool. * @param token The Super Token address. @@ -2226,7 +1556,147 @@ library SuperTokenV1Library { ); } - // ************** private helpers ************** + /** GDA VIEW FUNCTIONS ************************************* */ + + /** + * @dev get flowrate between a distributor and pool for given token + * @param token The token used in flow + * @param distributor The ditributor of the flow + * @param pool The GDA pool + * @return flowRate The flow rate + */ + function getGDAFlowRate(ISuperToken token, address distributor, ISuperfluidPool pool) + internal view returns(int96 flowRate) + { + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + return gda.getFlowRate(token, distributor, pool); + } + + /// alias of getGDAFlowRate + function getFlowDistributionFlowRate(ISuperToken token, address from, ISuperfluidPool to) + internal + view + returns (int96) + { + return getGDAFlowRate(token, from, to); + } + + /** + * @dev get flow info of a distributor to a pool for given token + * @param token The token used in flow + * @param distributor The ditributor of the flow + * @param pool The GDA pool + * @return lastUpdated Timestamp of flow creation or last flowrate change + * @return flowRate The flow rate + * @return deposit The amount of deposit the flow + */ + function getGDAFlowInfo(ISuperToken token, address distributor, ISuperfluidPool pool) + internal view + returns(uint256 lastUpdated, int96 flowRate, uint256 deposit) + { + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + return gda.getFlow(token, distributor, pool); + } + + /** + * @dev get GDA net flow rate for given account for given token + * @param token Super token address + * @param account Account to query + * @return flowRate The net flow rate of the account + */ + function getGDANetFlowRate(ISuperToken token, address account) + internal view returns (int96 flowRate) + { + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + return gda.getNetFlow(token, account); + } + + /** + * @dev get the aggregated GDA flow info of the account + * @param token Super token address + * @param account Account to query + * @return lastUpdated Timestamp of the last change of the net flow + * @return flowRate The net flow rate of token for account + * @return deposit The sum of all deposits for account's flows + * @return owedDeposit The sum of all owed deposits for account's flows + */ + function getGDANetFlowInfo(ISuperToken token, address account) + internal + view + returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) + { + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + (lastUpdated, flowRate, deposit) = gda.getAccountFlowInfo(token, account); + owedDeposit = 0; // unused in GDA + } + + /** + * @dev get the adjustment flow rate for a pool + * @param token Super token address + * @param pool The pool to query + * @return poolAdjustmentFlowRate The adjustment flow rate of the pool + */ + function getPoolAdjustmentFlowRate(ISuperToken token, ISuperfluidPool pool) + internal + view + returns (int96 poolAdjustmentFlowRate) + { + if (token != pool.superToken()) revert("pool/token mismatch"); + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + return gda.getPoolAdjustmentFlowRate(address(pool)); + } + + /** + * @dev Get the total amount of tokens received by a member via instant and flowing distributions + * @param token Super token address + * @param pool The pool to query + * @param memberAddr The member to query + * @return totalAmountReceived The total amount received by the member + */ + function getTotalAmountReceivedByMember(ISuperToken token, ISuperfluidPool pool, address memberAddr) + internal + view + returns (uint256 totalAmountReceived) + { + if (token != pool.superToken()) revert("pool/token mismatch"); + return pool.getTotalAmountReceivedByMember(memberAddr); + } + + /// alias for `getTotalAmountReceivedByMember` + function getTotalAmountReceivedFromPool(ISuperToken token, ISuperfluidPool pool, address memberAddr) + internal + view + returns (uint256 totalAmountReceived) + { + return getTotalAmountReceivedByMember(token, pool, memberAddr); + } + + function estimateFlowDistributionActualFlowRate( + ISuperToken token, + address from, + ISuperfluidPool to, + int96 requestedFlowRate + ) internal view returns (int96 actualFlowRate, int96 totalDistributionFlowRate) { + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + return gda.estimateFlowDistributionActualFlowRate(token, from, to, requestedFlowRate); + } + + function estimateDistributionActualAmount( + ISuperToken token, + address from, + ISuperfluidPool to, + uint256 requestedAmount + ) internal view returns (uint256 actualAmount) { + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + return gda.estimateDistributionActualAmount(token, from, to, requestedAmount); + } + + function isMemberConnected(ISuperToken token, address pool, address member) internal view returns (bool) { + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + return gda.isMemberConnected(ISuperfluidPool(pool), member); + } + + /** PRIVATE HELPERS ************************************* */ // @note We must use hardcoded constants here because: // Only direct number constants and references to such constants are supported by inline assembly. @@ -2234,8 +1704,6 @@ library SuperTokenV1Library { bytes32 private constant _HOST_SLOT = 0x65599bf746e17a00ea62e3610586992d88101b78eec3cf380706621fb97ea837; // keccak256("org.superfluid-finance.apps.SuperTokenLibrary.v1.cfa") bytes32 private constant _CFA_SLOT = 0xb969d79d88acd02d04ed7ee7d43b949e7daf093d363abcfbbc43dfdfd1ce969a; - // keccak256("org.superfluid-finance.apps.SuperTokenLibrary.v1.ida"); - bytes32 private constant _IDA_SLOT = 0xa832ee1924ea960211af2df07d65d166232018f613ac6708043cd8f8773eddeb; // keccak256("org.superfluid-finance.apps.SuperTokenLibrary.v1.gda"); bytes32 private constant _GDA_SLOT = 0xc36f6c05164a669ecb6da53e218d77ae44d51cfc99f91e5a125a18de0949bee4; @@ -2270,36 +1738,6 @@ library SuperTokenV1Library { assert(address(cfa) != address(0)); } - // gets the host and ida addrs for the token and caches it in storage for gas efficiency - // to be used in state changing methods - function _getAndCacheHostAndIDA(ISuperToken token) - private - returns (ISuperfluid host, IInstantDistributionAgreementV1 ida) - { - // check if already in contract storage... - assembly { - // solium-disable-line - host := sload(_HOST_SLOT) - ida := sload(_IDA_SLOT) - } - if (address(ida) == address(0)) { - // framework contract addrs not yet cached, retrieving now... - if (address(host) == address(0)) { - host = ISuperfluid(token.getHost()); - } - ida = IInstantDistributionAgreementV1(address(ISuperfluid(host).getAgreementClass( - keccak256("org.superfluid-finance.agreements.InstantDistributionAgreement.v1")))); - // now that we got them and are in a transaction context, persist in storage - assembly { - // solium-disable-line - sstore(_HOST_SLOT, host) - sstore(_IDA_SLOT, ida) - } - } - assert(address(host) != address(0)); - assert(address(ida) != address(0)); - } - // gets the host and gda addrs for the token and caches it in storage for gas efficiency // to be used in state changing methods function _getAndCacheHostAndGDA(ISuperToken token) @@ -2356,31 +1794,6 @@ library SuperTokenV1Library { assert(address(cfa) != address(0)); } - // gets the host and ida addrs for the token - // to be used in non-state changing methods (view functions) - function _getHostAndIDA(ISuperToken token) - private - view - returns (ISuperfluid host, IInstantDistributionAgreementV1 ida) - { - // check if already in contract storage... - assembly { - // solium-disable-line - host := sload(_HOST_SLOT) - ida := sload(_IDA_SLOT) - } - if (address(ida) == address(0)) { - // framework contract addrs not yet cached in storage, retrieving now... - if (address(host) == address(0)) { - host = ISuperfluid(token.getHost()); - } - ida = IInstantDistributionAgreementV1(address(ISuperfluid(host).getAgreementClass( - keccak256("org.superfluid-finance.agreements.InstantDistributionAgreement.v1")))); - } - assert(address(host) != address(0)); - assert(address(ida) != address(0)); - } - // gets the host and gda addrs for the token // to be used in non-state changing methods (view functions) function _getHostAndGDA(ISuperToken token) @@ -2410,4 +1823,15 @@ library SuperTokenV1Library { assert(address(host) != address(0)); assert(address(gda) != address(0)); } + + function _isPool(ISuperToken token, address maybePool) + private view returns (bool) { + // first check if it's a contract (saves some gas if not) + if (maybePool.code.length > 0) { + // it's a contract, now check if it's a pool + (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); + return (gda.isPool(token, maybePool)); + } + return false; + } } diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/IInstantDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/IInstantDistributionAgreementV1.sol index 775859c9f8..9ec28407e4 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/IInstantDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/IInstantDistributionAgreementV1.sol @@ -5,10 +5,11 @@ import { ISuperAgreement } from "../superfluid/ISuperAgreement.sol"; import { ISuperfluidToken } from "../superfluid/ISuperfluidToken.sol"; /** - * @title Instant Distribution Agreement interface + * @title [DEPRECATED] Instant Distribution Agreement interface + * @custom:deprecated Use IGeneralDistributionAgreementV1 instead * @author Superfluid * - * @notice + * @notice * - A publisher can create as many as indices as possibly identifiable with `indexId`. * - `indexId` is deliberately limited to 32 bits, to avoid the chance for sha-3 collision. * Despite knowing sha-3 collision is only theoretical. @@ -62,7 +63,7 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { * @param indexId Id of the index * @param ctx Context bytes (see ISuperfluid.sol for Context struct) * - * @custom:callbacks + * @custom:callbacks * None */ function createIndex( @@ -135,7 +136,7 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { * @param indexValue Value of the index * @param ctx Context bytes (see ISuperfluid.sol for Context struct) * - * @custom:callbacks + * @custom:callbacks * None */ function updateIndex( @@ -174,14 +175,14 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { * @param amount The amount of tokens desired to be distributed * @param ctx Context bytes (see ISuperfluid.sol for Context struct) * - * @custom:note + * @custom:note * - This is a convenient version of updateIndex. It adds to the index * a delta that equals to `amount / totalUnits` * - The actual amount distributed could be obtained via * `calculateDistribution`. This is due to precision error with index * value and units data range * - * @custom:callbacks + * @custom:callbacks * None */ function distribute( @@ -205,7 +206,7 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { * @param indexId Id of the index * @param ctx Context bytes (see ISuperfluid.sol for Context struct) * - * @custom:callbacks + * @custom:callbacks * - if subscription exist * - AgreementCreated callback to the publisher: * - agreementId is for the subscription @@ -259,7 +260,7 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { * @param indexId Id of the index * @param ctx Context bytes (see ISuperfluid.sol for Context struct) * - * @custom:callbacks + * @custom:callbacks * - AgreementUpdated callback to the publisher: * - agreementId is for the subscription */ @@ -285,7 +286,7 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { uint32 indexed indexId, address subscriber, bytes userData); - + /** * @dev Subscription approved event * @param token Super token address @@ -309,7 +310,7 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { * @param units Number of units of the subscription * @param ctx Context bytes (see ISuperfluid.sol for Context struct) * - * @custom:callbacks + * @custom:callbacks * - if subscription exist * - AgreementCreated callback to the subscriber: * - agreementId is for the subscription @@ -343,7 +344,7 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { address subscriber, uint128 units, bytes userData); - + /** * @dev Subscription units updated event * @param token Super token address @@ -439,7 +440,7 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { * @param subscriber The subscriber's address * @param ctx Context bytes (see ISuperfluid.sol for Context struct) * - * @custom:callbacks + * @custom:callbacks * - if the subscriber called it * - AgreementTerminated callback to the publsiher: * - agreementId is for the subscription @@ -467,7 +468,7 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { * * @custom:note The subscription should not be approved yet * - * @custom:callbacks + * @custom:callbacks * - AgreementUpdated callback to the publisher: * - agreementId is for the subscription */ @@ -480,7 +481,7 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { external virtual returns(bytes memory newCtx); - + /** * @dev Index distribution claimed event * @param token Super token address @@ -495,7 +496,7 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { uint32 indexed indexId, address subscriber, uint256 amount); - + /** * @dev Subscription distribution claimed event * @param token Super token address diff --git a/packages/ethereum-contracts/contracts/mocks/CFALibraryMock.t.sol b/packages/ethereum-contracts/contracts/mocks/CFALibraryMock.t.sol deleted file mode 100644 index 3dfdf0b466..0000000000 --- a/packages/ethereum-contracts/contracts/mocks/CFALibraryMock.t.sol +++ /dev/null @@ -1,208 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import { - ISuperfluid, ISuperfluidToken, ISuperToken, IConstantFlowAgreementV1 -} from "../interfaces/superfluid/ISuperfluid.sol"; -import { SuperAppDefinitions } from "../interfaces/superfluid/ISuperfluid.sol"; -import { SuperAppBase } from "../apps/SuperAppBase.sol"; -import { CFAv1Library } from "../apps/CFAv1Library.sol"; - -contract CFALibraryMock { - using CFAv1Library for CFAv1Library.InitData; - - //initialize cfaV1 variable - CFAv1Library.InitData public cfaV1; - - constructor(ISuperfluid host) { - //initialize InitData struct, and set equal to cfaV1 - cfaV1 = CFAv1Library.InitData( - host, - IConstantFlowAgreementV1( - address( - host.getAgreementClass( - keccak256( - "org.superfluid-finance.agreements.ConstantFlowAgreement.v1" - ) - ) - ) - ) - ); - } - - function createFlowTest( - ISuperfluidToken token, - address receiver, - int96 flowRate - ) public { - cfaV1.createFlow(receiver, token, flowRate); - } - - function updateFlowTest( - ISuperfluidToken token, - address receiver, - int96 flowRate - ) public { - cfaV1.updateFlow(receiver, token, flowRate); - } - - function deleteFlowTest(ISuperfluidToken token, address receiver) public { - cfaV1.deleteFlow(address(this), receiver, token); - } - - function createFlowByOperatorTest( - address sender, - address receiver, - ISuperfluidToken token, - int96 flowRate - ) public { - cfaV1.createFlowByOperator(sender, receiver, token, flowRate); - } - - function updateFlowByOperatorTest( - address sender, - address receiver, - ISuperfluidToken token, - int96 flowRate - ) public { - cfaV1.updateFlowByOperator(sender, receiver, token, flowRate); - } - - function deleteFlowByOperator( - address sender, - address receiver, - ISuperfluidToken token - ) public { - cfaV1.deleteFlowByOperator(sender, receiver, token); - } - - function updateFlowOperatorPermissionsTest( - address flowOperator, - ISuperfluidToken token, - uint8 permissions, - int96 flowRateAllowance - ) public { - cfaV1.updateFlowOperatorPermissions(flowOperator, token, permissions, flowRateAllowance); - } - - function authorizeFlowOperatorWithFullControlTest( - address flowOperator, - ISuperfluidToken token - ) public { - cfaV1.authorizeFlowOperatorWithFullControl(flowOperator, token); - } - - function revokeFlowOperatorWithFullControlTest( - address flowOperator, - ISuperfluidToken token - ) public { - cfaV1.revokeFlowOperatorWithFullControl(flowOperator, token); - } -} - -contract CFALibrarySuperAppMock is SuperAppBase { - - using CFAv1Library for CFAv1Library.InitData; - CFAv1Library.InitData internal cfaV1; - - // default values for smoke tests - uint8 internal constant PERMISSIONS = 7; - int96 internal constant FLOW_RATE = 1000000000000; - int96 internal constant UPDATED_FLOW_RATE = 2000000000000; - address internal immutable sender; - address internal immutable receiver; - address internal immutable flowOperator; - - // for selectively testing functions in the same callback - enum FunctionIndex { - CREATE_FLOW, - UPDATE_FLOW, - DELETE_FLOW, - CREATE_FLOW_BY_OPERATOR, - UPDATE_FLOW_BY_OPERATOR, - DELETE_FLOW_BY_OPERATOR, - UPDATE_FLOW_OPERATOR_PERMISSIONS, - AUTHORIZE_FLOW_OPERATOR_WITH_FULL_CONTROL, - REVOKE_FLOW_OPERATOR_WITH_FULL_CONTROL - } - - constructor( - ISuperfluid host, - address defaultSender, - address defaultReceiver, - address defaultFlowOperator - ) { - cfaV1 = CFAv1Library.InitData( - host, - IConstantFlowAgreementV1( - address( - host.getAgreementClass( - keccak256( - "org.superfluid-finance.agreements.ConstantFlowAgreement.v1" - ) - ) - ) - ) - ); - sender = defaultSender; - receiver = defaultReceiver; - flowOperator = defaultFlowOperator; - - uint256 configWord = SuperAppDefinitions.APP_LEVEL_FINAL | - SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP | - // SuperAppDefinitions.AFTER_AGREEMENT_CREATED_NOOP | - SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP | - SuperAppDefinitions.AFTER_AGREEMENT_UPDATED_NOOP | - SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP | - SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP; - - host.registerAppWithKey(configWord, ""); - } - - function createFlow(ISuperToken token) external { - cfaV1.createFlow(receiver, token, FLOW_RATE); - } - - // literally ONLY for the revokeFlowOperatorWithFullControlWithCtx test. - function authorizeFlowOperatorWithFullControl(ISuperToken token) external { - cfaV1.authorizeFlowOperatorWithFullControl(flowOperator, token); - } - - function afterAgreementCreated( - ISuperToken token, - address, - bytes32, - bytes calldata, - bytes calldata, - bytes calldata ctx - ) external override returns (bytes memory) { - bytes memory userData = cfaV1.host.decodeCtx(ctx).userData; - (uint8 functionIndex) = abi.decode(userData, (uint8)); - - if (functionIndex == uint8(FunctionIndex.CREATE_FLOW)) - return cfaV1.createFlowWithCtx(ctx, receiver, token, FLOW_RATE); - else if (functionIndex == uint8(FunctionIndex.UPDATE_FLOW)) - return cfaV1.updateFlowWithCtx(ctx, receiver, token, UPDATED_FLOW_RATE); - else if (functionIndex == uint8(FunctionIndex.DELETE_FLOW)) - return cfaV1.deleteFlowWithCtx(ctx, address(this), receiver, token); - else if (functionIndex == uint8(FunctionIndex.CREATE_FLOW_BY_OPERATOR)) - return cfaV1.createFlowByOperatorWithCtx(ctx, sender, receiver, token, FLOW_RATE); - else if (functionIndex == uint8(FunctionIndex.UPDATE_FLOW_BY_OPERATOR)) - return cfaV1.updateFlowByOperatorWithCtx(ctx, sender, receiver, token, UPDATED_FLOW_RATE); - else if (functionIndex == uint8(FunctionIndex.DELETE_FLOW_BY_OPERATOR)) - return cfaV1.deleteFlowByOperatorWithCtx(ctx, sender, receiver, token); - else if (functionIndex == uint8(FunctionIndex.UPDATE_FLOW_OPERATOR_PERMISSIONS)) - return cfaV1.updateFlowOperatorPermissionsWithCtx( - ctx, - flowOperator, - token, - PERMISSIONS, - FLOW_RATE - ); - else if (functionIndex == uint8(FunctionIndex.AUTHORIZE_FLOW_OPERATOR_WITH_FULL_CONTROL)) - return cfaV1.authorizeFlowOperatorWithFullControlWithCtx(ctx, flowOperator, token); - else if (functionIndex == uint8(FunctionIndex.REVOKE_FLOW_OPERATOR_WITH_FULL_CONTROL)) - return cfaV1.revokeFlowOperatorWithFullControlWithCtx(ctx, flowOperator, token); - else revert("invalid function index"); - } -} diff --git a/packages/ethereum-contracts/contracts/mocks/IDAv1LibraryMock.t.sol b/packages/ethereum-contracts/contracts/mocks/IDAv1LibraryMock.t.sol deleted file mode 100644 index 938d2fb327..0000000000 --- a/packages/ethereum-contracts/contracts/mocks/IDAv1LibraryMock.t.sol +++ /dev/null @@ -1,400 +0,0 @@ -// SPDX-License-Identifier: AGPLv3 -pragma solidity ^0.8.23; -pragma experimental ABIEncoderV2; - -import {ISuperfluid, ISuperfluidToken, ISuperToken} from "../interfaces/superfluid/ISuperfluid.sol"; - -import {SuperAppBase, SuperAppDefinitions} from "../apps/SuperAppBase.sol"; - -import { - IInstantDistributionAgreementV1 -} from "../interfaces/agreements/IInstantDistributionAgreementV1.sol"; - -import {IDAv1Library} from "../apps/IDAv1Library.sol"; - -contract IDAv1LibraryMock { - using IDAv1Library for IDAv1Library.InitData; - - IDAv1Library.InitData internal _idaLib; - - bytes32 internal constant _IDAV1_HASH = keccak256( - "org.superfluid-finance.agreements.InstantDistributionAgreement.v1" - ); - - constructor(ISuperfluid host) { - _idaLib = IDAv1Library.InitData( - host, - IInstantDistributionAgreementV1( - address(host.getAgreementClass(_IDAV1_HASH)) - ) - ); - } - - /************************************************************************** - * View Functions - *************************************************************************/ - - function getIndexTest(ISuperfluidToken token, address publisher, uint32 indexId) - external - view - returns ( - bool exist, - uint128 indexValue, - uint128 totalUnitsApproved, - uint128 totalUnitsPending - ) - { - return _idaLib.getIndex(token, publisher, indexId); - } - - function calculateDistributionTest( - ISuperfluidToken token, - address publisher, - uint32 indexId, - uint256 amount - ) - external - view - returns ( - uint256 actualAmount, - uint128 newIndexValue - ) - { - return _idaLib.calculateDistribution(token, publisher, indexId, amount); - } - - function listSubscriptionsTest(ISuperfluidToken token, address subscriber) - external - view - returns ( - address[] memory publishers, - uint32[] memory indexIds, - uint128[] memory unitsList - ) - { - return _idaLib.listSubscriptions(token, subscriber); - } - - function getSubscriptionTest( - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber - ) - external - view - returns ( - bool exist, - bool approved, - uint128 units, - uint256 pendingDistribution - ) - { - return _idaLib.getSubscription(token, publisher, indexId, subscriber); - } - - /// @dev agreementId == keccak256(abi.encodePacked("subscription", subscriber, indexId)); - function getSubscriptionByIDTest(ISuperfluidToken token, bytes32 agreementId) - external - view - returns ( - address publisher, - uint32 indexId, - bool approved, - uint128 units, - uint256 pendingDistribution - ) - { - return _idaLib.getSubscriptionByID(token, agreementId); - } - - /************************************************************************** - * Index Operations - *************************************************************************/ - - function createIndexTest(ISuperfluidToken token, uint32 indexId) external { - _idaLib.createIndex(token, indexId); - } - - function createIndexWithUserDataTest( - ISuperfluidToken token, - uint32 indexId, - bytes memory userData - ) external { - _idaLib.createIndex(token, indexId, userData); - } - - function updateIndexValueTest( - ISuperfluidToken token, - uint32 indexId, - uint128 indexValue - ) external { - _idaLib.updateIndexValue(token, indexId, indexValue); - } - - function updateIndexValueWithUserDataTest( - ISuperfluidToken token, - uint32 indexId, - uint128 indexValue, - bytes memory userData - ) external { - _idaLib.updateIndexValue(token, indexId, indexValue, userData); - } - - function distributeTest(ISuperfluidToken token, uint32 indexId, uint256 amount) external { - _idaLib.distribute(token, indexId, amount); - } - - function distributeWithUserDataTest( - ISuperfluidToken token, - uint32 indexId, - uint256 amount, - bytes memory userData - ) external { - _idaLib.distribute(token, indexId, amount, userData); - } - - /************************************************************************** - * Subscription Operations - *************************************************************************/ - - function approveSubscriptionTest( - ISuperfluidToken token, - address publisher, - uint32 indexId - ) external { - _idaLib.approveSubscription(token, publisher, indexId); - } - - function approveSubscriptionWithUserDataTest( - ISuperfluidToken token, - address publisher, - uint32 indexId, - bytes memory userData - ) external { - _idaLib.approveSubscription(token, publisher, indexId, userData); - } - - function revokeSubscriptionTest( - ISuperfluidToken token, - address publisher, - uint32 indexId - ) external { - _idaLib.revokeSubscription(token, publisher, indexId); - } - - function revokeSubscriptionWithUserDataTest( - ISuperfluidToken token, - address publisher, - uint32 indexId, - bytes memory userData - ) external { - _idaLib.revokeSubscription(token, publisher, indexId, userData); - } - - function updateSubscriptionUnitsTest( - ISuperfluidToken token, - uint32 indexId, - address subscriber, - uint128 units - ) external { - _idaLib.updateSubscriptionUnits(token, indexId, subscriber, units); - } - - function updateSubscriptionUnitsWithUserDataTest( - ISuperfluidToken token, - uint32 indexId, - address subscriber, - uint128 units, - bytes memory userData - ) external { - _idaLib.updateSubscriptionUnits(token, indexId, subscriber, units, userData); - } - - function deleteSubscriptionTest( - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber - ) external { - _idaLib.deleteSubscription(token, publisher, indexId, subscriber); - } - - function deleteSubscriptionWithUserDataTest( - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory userData - ) external { - _idaLib.deleteSubscription(token, publisher, indexId, subscriber, userData); - } - - function claimTest( - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber - ) external { - _idaLib.claim(token, publisher, indexId, subscriber); - } - - function claimWithUserDataTest( - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory userData - ) external { - _idaLib.claim(token, publisher, indexId, subscriber, userData); - } -} - -// IDA LIBRARY SUPER APP CALLBACK MOCK -contract IDAv1LibrarySuperAppMock is IDAv1LibraryMock, SuperAppBase { - using IDAv1Library for IDAv1Library.InitData; - - bytes internal constant _MOCK_USER_DATA = abi.encode("oh hello"); - - constructor(ISuperfluid host) IDAv1LibraryMock(host) { - uint256 configWord = SuperAppDefinitions.APP_LEVEL_FINAL | - SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP | - // SuperAppDefinitions.AFTER_AGREEMENT_CREATED_NOOP | - SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP | - // SuperAppDefinitions.AFTER_AGREEMENT_UPDATED_NOOP | - SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP | - SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP; - - host.registerAppWithKey(configWord, ""); - } - - function afterAgreementCreated( - ISuperToken token, - address, - bytes32, - bytes calldata, - bytes calldata, - bytes calldata ctx - ) external override returns (bytes memory newCtx) { - return _callbackTest(token, ctx); - } - - function afterAgreementUpdated( - ISuperToken token, - address, - bytes32, - bytes calldata, - bytes calldata, - bytes calldata ctx - ) external override returns (bytes memory newCtx) { - return _callbackTest(token, ctx); - } - - enum FunctionIndex { - CREATE_INDEX, - CREATE_INDEX_USER_DATA, - UPDATE_INDEX, - UPDATE_INDEX_USER_DATA, - DISTRIBUTE, - DISTRIBUTE_USER_DATA, - APROVE_SUBSCRIPTION, - APROVE_SUBSCRIPTION_USER_DATA, - REVOKE_SUBSCRIPTION, - REVOKE_SUBSCRIPTION_USER_DATA, - UPDATE_SUBSCRIPTION, - UPDATE_SUBSCRIPTION_USER_DATA, - DELETE_SUBSCRIPTION, - DELETE_SUBSCRIPTION_USER_DATA, - CLAIM, - CLAIM_USER_DATA - } - - /// @dev extracts some user data to test out all callback library functions - /// @param token super token - /// @param ctx Context string - /// @return New Context - function _callbackTest( - ISuperToken token, - bytes memory ctx - ) internal returns (bytes memory) { - - // extract userData, then decode everything else - bytes memory userData = _idaLib.host.decodeCtx(ctx).userData; - ( - uint8 functionIndex, - uint32 indexId, - address publisher, - address subscriber, - uint128 units - ) = abi.decode(userData, (uint8, uint32, address, address, uint128)); - - if (functionIndex == uint8(FunctionIndex.CREATE_INDEX)) { - return _idaLib.createIndexWithCtx(ctx, token, indexId); - } else if (functionIndex == uint8(FunctionIndex.CREATE_INDEX_USER_DATA)) { - return _idaLib.createIndexWithCtx(ctx, token, indexId, _MOCK_USER_DATA); - } else if (functionIndex == uint8(FunctionIndex.UPDATE_INDEX)) { - return _idaLib.updateIndexValueWithCtx(ctx, token, indexId, units); - } else if (functionIndex == uint8(FunctionIndex.UPDATE_INDEX_USER_DATA)) { - return _idaLib.updateIndexValueWithCtx(ctx, token, indexId, units, _MOCK_USER_DATA); - } else if (functionIndex == uint8(FunctionIndex.DISTRIBUTE)) { - return _idaLib.distributeWithCtx(ctx, token, indexId, units); - } else if (functionIndex == uint8(FunctionIndex.DISTRIBUTE_USER_DATA)) { - return _idaLib.distributeWithCtx(ctx, token, indexId, units, _MOCK_USER_DATA); - } else if (functionIndex == uint8(FunctionIndex.APROVE_SUBSCRIPTION)) { - return _idaLib.approveSubscriptionWithCtx(ctx, token, publisher, indexId); - } else if (functionIndex == uint8(FunctionIndex.APROVE_SUBSCRIPTION_USER_DATA)) { - return _idaLib.approveSubscriptionWithCtx( - ctx, - token, - publisher, - indexId, - _MOCK_USER_DATA - ); - } else if (functionIndex == uint8(FunctionIndex.REVOKE_SUBSCRIPTION)) { - return _idaLib.revokeSubscriptionWithCtx(ctx, token, publisher, indexId); - } else if (functionIndex == uint8(FunctionIndex.REVOKE_SUBSCRIPTION_USER_DATA)) { - return _idaLib.revokeSubscriptionWithCtx( - ctx, - token, - publisher, - indexId, - _MOCK_USER_DATA - ); - } else if (functionIndex == uint8(FunctionIndex.UPDATE_SUBSCRIPTION)) { - return _idaLib.updateSubscriptionUnitsWithCtx(ctx, token, indexId, subscriber, units); - } else if (functionIndex == uint8(FunctionIndex.UPDATE_SUBSCRIPTION_USER_DATA)) { - return _idaLib.updateSubscriptionUnitsWithCtx( - ctx, - token, - indexId, - subscriber, - units, - _MOCK_USER_DATA - ); - } else if (functionIndex == uint8(FunctionIndex.DELETE_SUBSCRIPTION)) { - return _idaLib.deleteSubscriptionWithCtx(ctx, token, publisher, indexId, subscriber); - } else if (functionIndex == uint8(FunctionIndex.DELETE_SUBSCRIPTION_USER_DATA)) { - return _idaLib.deleteSubscriptionWithCtx( - ctx, - token, - publisher, - indexId, - subscriber, - _MOCK_USER_DATA - ); - } else if (functionIndex == uint8(FunctionIndex.CLAIM)) { - return _idaLib.claimWithCtx(ctx, token, publisher, indexId, subscriber); - } else if (functionIndex == uint8(FunctionIndex.CLAIM_USER_DATA)) { - return _idaLib.claimWithCtx( - ctx, - token, - publisher, - indexId, - subscriber, - _MOCK_USER_DATA - ); - } else { - revert("invalid function index"); - } - } -} diff --git a/packages/ethereum-contracts/contracts/mocks/StreamRedirector.t.sol b/packages/ethereum-contracts/contracts/mocks/StreamRedirector.t.sol index 12c3bcbad0..8f1bfd106e 100644 --- a/packages/ethereum-contracts/contracts/mocks/StreamRedirector.t.sol +++ b/packages/ethereum-contracts/contracts/mocks/StreamRedirector.t.sol @@ -2,13 +2,12 @@ pragma solidity ^0.8.23; import { - ISuperfluid, ISuperToken, SuperAppBase, ISuperApp, SuperAppDefinitions, IConstantFlowAgreementV1, ISuperAgreement + ISuperfluid, ISuperToken, SuperAppBase, ISuperApp, SuperAppDefinitions, ISuperAgreement } from "../apps/SuperAppBase.sol"; -import { CFAv1Library } from "../apps/CFAv1Library.sol"; +import { SuperTokenV1Library } from "../apps/SuperTokenV1Library.sol"; contract StreamRedirector is SuperAppBase { - using CFAv1Library for CFAv1Library.InitData; - CFAv1Library.InitData public cfaV1; + using SuperTokenV1Library for ISuperToken; ISuperfluid public host; bytes32 public constant CFA_ID = keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1"); @@ -27,11 +26,6 @@ contract StreamRedirector is SuperAppBase { host = _host; - cfaV1 = CFAv1Library.InitData( - host, - IConstantFlowAgreementV1(address(host.getAgreementClass(CFA_ID))) - ); - uint256 configWord = _appLevel | SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP | SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP; @@ -56,10 +50,9 @@ contract StreamRedirector is SuperAppBase { * @param _flowRate desired flow rate */ function startStreamToSelf(address _originAccount, int96 _flowRate) public { - cfaV1.createFlowByOperator( + token.createFlowFrom( _originAccount, address(this), - token, _flowRate ); } @@ -69,7 +62,7 @@ contract StreamRedirector is SuperAppBase { * @param _originAccount ACL permissions granter */ function stopStreamToSelf(address _originAccount) public { - cfaV1.deleteFlowByOperator(_originAccount, address(this), token); + token.deleteFlowFrom(_originAccount, address(this)); } /** @@ -78,7 +71,7 @@ contract StreamRedirector is SuperAppBase { * @param _flowRate desired flow rate */ function startStreamToSuperApp(address _superApp, int96 _flowRate) public { - cfaV1.createFlow(_superApp, token, _flowRate); + token.createFlow(_superApp,_flowRate); } /** @@ -87,7 +80,7 @@ contract StreamRedirector is SuperAppBase { * @param _receiver the receiver super app */ function stopStreamToSuperApp(address _sender, address _receiver) public { - cfaV1.deleteFlow(_sender, _receiver, token); + token.deleteFlow(_sender, _receiver); } /** @@ -109,12 +102,10 @@ contract StreamRedirector is SuperAppBase { returns (bytes memory newCtx) { newCtx = _ctx; - int96 netFlowRate = cfaV1.cfa.getNetFlow(_superToken, address(this)); - newCtx = cfaV1.createFlowWithCtx( - newCtx, + newCtx = _superToken.createFlowWithCtx( receiver, - _superToken, - netFlowRate + _superToken.getNetFlowRate(address(this)), + newCtx ); } @@ -131,18 +122,13 @@ contract StreamRedirector is SuperAppBase { } newCtx = _ctx; - (, int96 currentFlowRate, , ) = cfaV1.cfa.getFlow( - _superToken, - address(this), - receiver - ); + int96 currentFlowRate = _superToken.getFlowRate(address(this), receiver); if (currentFlowRate > 0) { - newCtx = cfaV1.deleteFlowWithCtx( - newCtx, + newCtx = _superToken.deleteFlowWithCtx( address(this), receiver, - _superToken + newCtx ); } } diff --git a/packages/ethereum-contracts/contracts/mocks/SuperTokenLibraryV1Mock.t.sol b/packages/ethereum-contracts/contracts/mocks/SuperTokenLibraryV1Mock.t.sol index 663034e984..b41452d371 100644 --- a/packages/ethereum-contracts/contracts/mocks/SuperTokenLibraryV1Mock.t.sol +++ b/packages/ethereum-contracts/contracts/mocks/SuperTokenLibraryV1Mock.t.sol @@ -274,225 +274,6 @@ contract SuperTokenLibraryCFAMock { } } -contract SuperTokenLibraryIDAMock { - - using SuperTokenV1Library for ISuperToken; - - /************************************************************************** - * View Functions - *************************************************************************/ - - function getIndexTest(ISuperToken token, address publisher, uint32 indexId) - external view - returns ( - bool exist, - uint128 indexValue, - uint128 totalUnitsApproved, - uint128 totalUnitsPending - ) - { - return token.getIndex(publisher, indexId); - } - - function calculateDistributionTest( - ISuperToken token, - address publisher, - uint32 indexId, - uint256 amount - ) - external view - returns ( - uint256 actualAmount, - uint128 newIndexValue - ) - { - return token.calculateDistribution(publisher, indexId, amount); - } - - function listSubscriptionsTest(ISuperToken token, address subscriber) - external view - returns ( - address[] memory publishers, - uint32[] memory indexIds, - uint128[] memory unitsList - ) - { - return token.listSubscriptions(subscriber); - } - - function getSubscriptionTest( - ISuperToken token, - address publisher, - uint32 indexId, - address subscriber - ) - external view - returns ( - bool exist, - bool approved, - uint128 units, - uint256 pendingDistribution - ) - { - return token.getSubscription(publisher, indexId, subscriber); - } - - /// @dev agreementId == keccak256(abi.encodePacked("subscription", subscriber, indexId)); - function getSubscriptionByIDTest(ISuperToken token, bytes32 agreementId) - external view - returns ( - address publisher, - uint32 indexId, - bool approved, - uint128 units, - uint256 pendingDistribution - ) - { - return token.getSubscriptionByID(agreementId); - } - - /************************************************************************** - * Index Operations - *************************************************************************/ - - function createIndexTest(ISuperToken token, uint32 indexId) external { - token.createIndex(indexId); - } - - function createIndexWithUserDataTest( - ISuperToken token, - uint32 indexId, - bytes memory userData - ) external { - token.createIndex(indexId, userData); - } - - function updateIndexValueTest( - ISuperToken token, - uint32 indexId, - uint128 indexValue - ) external { - token.updateIndexValue(indexId, indexValue); - } - - function updateIndexValueWithUserDataTest( - ISuperToken token, - uint32 indexId, - uint128 indexValue, - bytes memory userData - ) external { - token.updateIndexValue(indexId, indexValue, userData); - } - - function distributeTest(ISuperToken token, uint32 indexId, uint256 amount) external { - token.distribute(indexId, amount); - } - - function distributeWithUserDataTest( - ISuperToken token, - uint32 indexId, - uint256 amount, - bytes memory userData - ) external { - token.distribute(indexId, amount, userData); - } - - /************************************************************************** - * Subscription Operations - *************************************************************************/ - - function approveSubscriptionTest( - ISuperToken token, - address publisher, - uint32 indexId - ) external { - token.approveSubscription(publisher, indexId); - } - - function approveSubscriptionWithUserDataTest( - ISuperToken token, - address publisher, - uint32 indexId, - bytes memory userData - ) external { - token.approveSubscription(publisher, indexId, userData); - } - - function revokeSubscriptionTest( - ISuperToken token, - address publisher, - uint32 indexId - ) external { - token.revokeSubscription(publisher, indexId); - } - - function revokeSubscriptionWithUserDataTest( - ISuperToken token, - address publisher, - uint32 indexId, - bytes memory userData - ) external { - token.revokeSubscription(publisher, indexId, userData); - } - - function updateSubscriptionUnitsTest( - ISuperToken token, - uint32 indexId, - address subscriber, - uint128 units - ) external { - token.updateSubscriptionUnits(indexId, subscriber, units); - } - - function updateSubscriptionUnitsWithUserDataTest( - ISuperToken token, - uint32 indexId, - address subscriber, - uint128 units, - bytes memory userData - ) external { - token.updateSubscriptionUnits(indexId, subscriber, units, userData); - } - - function deleteSubscriptionTest( - ISuperToken token, - address publisher, - uint32 indexId, - address subscriber - ) external { - token.deleteSubscription(publisher, indexId, subscriber); - } - - function deleteSubscriptionWithUserDataTest( - ISuperToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory userData - ) external { - token.deleteSubscription(publisher, indexId, subscriber, userData); - } - - function claimTest( - ISuperToken token, - address publisher, - uint32 indexId, - address subscriber - ) external { - token.claim(publisher, indexId, subscriber); - } - - function claimWithUserDataTest( - ISuperToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory userData - ) external { - token.claim(publisher, indexId, subscriber, userData); - } -} - contract SuperTokenLibraryGDAMock { using SuperTokenV1Library for ISuperToken; //// View Functions //// @@ -538,10 +319,10 @@ contract SuperTokenLibraryGDAMock { token.createPool(admin, config); } - function distributeToPoolTest(ISuperToken token, address from, ISuperfluidPool pool, uint256 requestedAmount) + function distributeTest(ISuperToken token, address from, ISuperfluidPool pool, uint256 requestedAmount) external { - token.distributeToPool(from, pool, requestedAmount); + token.distribute(from, pool, requestedAmount); } function distributeFlowTest(ISuperToken token, address from, ISuperfluidPool pool, int96 requestedFlowRate) @@ -658,145 +439,6 @@ contract SuperTokenLibraryCFASuperAppMock is SuperAppBase { } } -// IDA LIBRARY SUPER APP CALLBACK MOCK -contract SuperTokenLibraryIDASuperAppMock is SuperTokenLibraryIDAMock, SuperAppBase { - - using SuperTokenV1Library for ISuperToken; - - ISuperfluid internal immutable host; - - constructor(ISuperfluid _host) SuperTokenLibraryIDAMock() { - host = _host; - uint256 configWord = SuperAppDefinitions.APP_LEVEL_FINAL | - SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP | - SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP | - SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP | - SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP; - - host.registerAppWithKey(configWord, ""); - } - - function afterAgreementCreated( - ISuperToken token, - address, - bytes32, - bytes calldata, - bytes calldata, - bytes calldata ctx - ) external override returns (bytes memory newCtx) { - return _callbackTest(token, ctx); - } - - function afterAgreementUpdated( - ISuperToken token, - address, - bytes32, - bytes calldata, - bytes calldata, - bytes calldata ctx - ) external override returns (bytes memory newCtx) { - return _callbackTest(token, ctx); - } - - enum FunctionIndex { - CREATE_INDEX, - CREATE_INDEX_USER_DATA, - UPDATE_INDEX, - UPDATE_INDEX_USER_DATA, - DISTRIBUTE, - DISTRIBUTE_USER_DATA, - APROVE_SUBSCRIPTION, - APROVE_SUBSCRIPTION_USER_DATA, - REVOKE_SUBSCRIPTION, - REVOKE_SUBSCRIPTION_USER_DATA, - UPDATE_SUBSCRIPTION, - UPDATE_SUBSCRIPTION_USER_DATA, - DELETE_SUBSCRIPTION, - DELETE_SUBSCRIPTION_USER_DATA, - CLAIM, - CLAIM_USER_DATA - } - - /// @dev extracts some user data to test out all callback library functions - /// @param token super token - /// @param ctx Context string - /// @return New Context - function _callbackTest( - ISuperToken token, - bytes memory ctx - ) internal returns (bytes memory) { - - // extract userData, then decode everything else - bytes memory userData = host.decodeCtx(ctx).userData; - ( - uint8 functionIndex, - uint32 indexId, - address publisher, - address subscriber, - uint128 units - ) = abi.decode(userData, (uint8, uint32, address, address, uint128)); - - if (functionIndex == uint8(FunctionIndex.CREATE_INDEX)) { - return token.createIndexWithCtx(indexId, ctx); - } else if (functionIndex == uint8(FunctionIndex.CREATE_INDEX_USER_DATA)) { - return token.createIndexWithCtx(indexId, ctx); - } else if (functionIndex == uint8(FunctionIndex.UPDATE_INDEX)) { - return token.updateIndexValueWithCtx(indexId, units, ctx); - } else if (functionIndex == uint8(FunctionIndex.UPDATE_INDEX_USER_DATA)) { - return token.updateIndexValueWithCtx(indexId, units, ctx); - } else if (functionIndex == uint8(FunctionIndex.DISTRIBUTE)) { - return token.distributeWithCtx(indexId, units, ctx); - } else if (functionIndex == uint8(FunctionIndex.DISTRIBUTE_USER_DATA)) { - return token.distributeWithCtx(indexId, units, ctx); - } else if (functionIndex == uint8(FunctionIndex.APROVE_SUBSCRIPTION)) { - return token.approveSubscriptionWithCtx(publisher, indexId, ctx); - } else if (functionIndex == uint8(FunctionIndex.APROVE_SUBSCRIPTION_USER_DATA)) { - return token.approveSubscriptionWithCtx( - publisher, - indexId, - ctx - ); - } else if (functionIndex == uint8(FunctionIndex.REVOKE_SUBSCRIPTION)) { - return token.revokeSubscriptionWithCtx(publisher, indexId, ctx); - } else if (functionIndex == uint8(FunctionIndex.REVOKE_SUBSCRIPTION_USER_DATA)) { - return token.revokeSubscriptionWithCtx( - publisher, - indexId, - ctx - ); - } else if (functionIndex == uint8(FunctionIndex.UPDATE_SUBSCRIPTION)) { - return token.updateSubscriptionUnitsWithCtx(indexId, subscriber, units, ctx); - } else if (functionIndex == uint8(FunctionIndex.UPDATE_SUBSCRIPTION_USER_DATA)) { - return token.updateSubscriptionUnitsWithCtx( - indexId, - subscriber, - units, - ctx - ); - } else if (functionIndex == uint8(FunctionIndex.DELETE_SUBSCRIPTION)) { - return token.deleteSubscriptionWithCtx(publisher, indexId, subscriber, ctx); - } else if (functionIndex == uint8(FunctionIndex.DELETE_SUBSCRIPTION_USER_DATA)) { - return token.deleteSubscriptionWithCtx( - publisher, - indexId, - subscriber, - ctx - ); - } else if (functionIndex == uint8(FunctionIndex.CLAIM)) { - return token.claimWithCtx(publisher, indexId, subscriber, ctx); - } else if (functionIndex == uint8(FunctionIndex.CLAIM_USER_DATA)) { - return token.claimWithCtx( - publisher, - indexId, - subscriber, - ctx - ); - } else { - revert("invalid function index"); - } - } -} - // GDA LIBRARY SUPER APP CALLBACK MOCK contract SuperTokenLibraryGDASuperAppMock is SuperTokenLibraryGDAMock, SuperAppBase { using SuperTokenV1Library for ISuperToken; @@ -861,7 +503,14 @@ contract SuperTokenLibraryGDASuperAppMock is SuperTokenLibraryGDAMock, SuperAppB ) = abi.decode(userData, (uint8, address, address, address, uint128, uint256, int96)); if (functionIndex == uint8(FunctionIndex.UPDATE_MEMBER_UNITS)) { + /* + This used to be return token.updateMemberUnitsWithCtx(ISuperfluidPool(pool), member, units, ctx); + But updateMemberUnits doesn't need ctx, so we can move it to the pool interface + where it belongs. + */ + ISuperfluidPool(pool).updateMemberUnits(member, units); + return ctx; } else if (functionIndex == uint8(FunctionIndex.CONNECT_POOL)) { return token.connectPoolWithCtx(ISuperfluidPool(pool), ctx); } else if (functionIndex == uint8(FunctionIndex.DISCONNECT_POOL)) { diff --git a/packages/ethereum-contracts/contracts/utils/IDAv1Forwarder.sol b/packages/ethereum-contracts/contracts/utils/IDAv1Forwarder.sol deleted file mode 100644 index adcc812400..0000000000 --- a/packages/ethereum-contracts/contracts/utils/IDAv1Forwarder.sol +++ /dev/null @@ -1,312 +0,0 @@ -// SPDX-License-Identifier: AGPLv3 -pragma solidity ^0.8.23; - -import { ISuperfluid, ISuperfluidToken } from "../interfaces/superfluid/ISuperfluid.sol"; -import { IInstantDistributionAgreementV1 } from "../interfaces/agreements/IInstantDistributionAgreementV1.sol"; -import { ForwarderBase } from "./ForwarderBase.sol"; - -/** - * @title IDAv1Forwarder - * @author Superfluid - * The IDAv1Forwarder contract provides an easy to use interface to - * InstantDistributionAgreementV1 specific functionality of Super Tokens. - * Instances of this contract can operate on the protocol only if configured as "trusted forwarder" - * by protocol governance. - */ -contract IDAv1Forwarder is ForwarderBase { - IInstantDistributionAgreementV1 internal immutable _ida; - - // is tied to a specific instance of host and agreement contracts at deploy time - constructor(ISuperfluid host) ForwarderBase(host) { - _ida = IInstantDistributionAgreementV1( - address( - _host.getAgreementClass(keccak256("org.superfluid-finance.agreements.InstantDistributionAgreement.v1")) - ) - ); - } - - /** - * @dev Creates a new index for the specified token. - * @param token The address of the super token. - * @param indexId The ID of the index. - * @param userData Additional user-defined data. - * @return bool A boolean indicating whether the operation was successful. - */ - function createIndex(ISuperfluidToken token, uint32 indexId, bytes memory userData) external returns (bool) { - bytes memory callData = abi.encodeCall( - _ida.createIndex, - ( - token, - indexId, - new bytes(0) // ctx placeholder - ) - ); - return _forwardBatchCall(address(_ida), callData, userData); - } - - /** - * @dev Updates the value of an existing index for the specified token. - * @param token The address of the super token. - * @param indexId The ID of the index. - * @param indexValue The new value of the index. - * @param userData Additional user-defined data. - * @return bool A boolean indicating whether the operation was successful. - */ - function updateIndex(ISuperfluidToken token, uint32 indexId, uint128 indexValue, bytes memory userData) - external - returns (bool) - { - bytes memory callData = abi.encodeCall( - _ida.updateIndex, - ( - token, - indexId, - indexValue, - new bytes(0) // ctx placeholder - ) - ); - return _forwardBatchCall(address(_ida), callData, userData); - } - - /** - * @dev Distributes a specified amount of tokens according to the specified index. - * @param token The address of the super token. - * @param indexId The ID of the index. - * @param amount The amount of tokens to distribute. - * @param userData Additional user-defined data. - * @return bool A boolean indicating whether the operation was successful. - */ - function distribute(ISuperfluidToken token, uint32 indexId, uint128 amount, bytes memory userData) - external - returns (bool) - { - bytes memory callData = abi.encodeCall( - _ida.distribute, - ( - token, - indexId, - amount, - new bytes(0) // ctx placeholder - ) - ); - return _forwardBatchCall(address(_ida), callData, userData); - } - - /** - * @dev Calculates the distribution of tokens for a given publisher, index, and amount. - * @param token The address of the super token. - * @param publisher The address of the publisher. - * @param indexId The ID of the index. - * @param amount The amount of tokens to distribute. - * @return actualAmount The actual amount of tokens which will be distributed. - * @return newIndexValue The new value of the index. - */ - function calculateDistribution(ISuperfluidToken token, address publisher, uint32 indexId, uint128 amount) - external - view - returns (uint256 actualAmount, uint128 newIndexValue) - { - return _ida.calculateDistribution(token, publisher, indexId, amount); - } - - /** - * @dev Retrieves information about the specified index for a given publisher. - * @param token The address of the super token. - * @param publisher The address of the publisher. - * @param indexId The ID of the index. - * @return exist A boolean indicating whether the index exists. - * @return indexValue The value of the index. - * @return totalUnitsApproved The total number of approved units. - * @return totalUnitsPending The total number of pending units. - */ - function getIndex(ISuperfluidToken token, address publisher, uint32 indexId) - external - view - returns (bool exist, uint128 indexValue, uint128 totalUnitsApproved, uint128 totalUnitsPending) - { - return _ida.getIndex(token, publisher, indexId); - } - - /** - * @dev Approves a subscription for the specified token, publisher, and index. - * @param token The address of the super token. - * @param publisher The address of the publisher. - * @param indexId The ID of the index. - * @param userData Additional user-defined data. - * @return bool A boolean indicating whether the operation was successful. - */ - function approveSubscription(ISuperfluidToken token, address publisher, uint32 indexId, bytes memory userData) - external - returns (bool) - { - bytes memory callData = abi.encodeCall( - _ida.approveSubscription, - ( - token, - publisher, - indexId, - new bytes(0) // ctx placeholder - ) - ); - return _forwardBatchCall(address(_ida), callData, userData); - } - - /** - * @dev Revokes a subscription for the specified token, publisher, and index. - * @param token The address of the super token. - * @param publisher The address of the publisher. - * @param indexId The ID of the index. - * @param userData Additional user-defined data. - * @return bool A boolean indicating whether the operation was successful. - */ - function revokeSubscription(ISuperfluidToken token, address publisher, uint32 indexId, bytes memory userData) - external - returns (bool) - { - bytes memory callData = abi.encodeCall( - _ida.revokeSubscription, - ( - token, - publisher, - indexId, - new bytes(0) // ctx placeholder - ) - ); - return _forwardBatchCall(address(_ida), callData, userData); - } - - /** - * @dev Deletes a subscription for the specified token, publisher, index, and subscriber. - * @param token The address of the super token. - * @param publisher The address of the publisher. - * @param indexId The ID of the index. - * @param subscriber The address of the subscriber. - * @param userData Additional user-defined data. - * @return bool A boolean indicating whether the operation was successful. - */ - function deleteSubscription( - ISuperfluidToken token, - address publisher, - uint32 indexId, - address subscriber, - bytes memory userData - ) external returns (bool) { - bytes memory callData = abi.encodeCall( - _ida.deleteSubscription, - ( - token, - publisher, - indexId, - subscriber, - new bytes(0) // ctx placeholder - ) - ); - return _forwardBatchCall(address(_ida), callData, userData); - } - - /** - * @dev Updates the subscription units for the specified token, index, subscriber, and units. - * @param token The address of the super token. - * @param indexId The ID of the index. - * @param subscriber The address of the subscriber. - * @param units The new units value. - * @param userData Additional user-defined data. - * @return bool A boolean indicating whether the operation was successful. - */ - function updateSubscriptionUnits( - ISuperfluidToken token, - uint32 indexId, - address subscriber, - uint128 units, - bytes memory userData - ) external returns (bool) { - bytes memory callData = abi.encodeCall( - _ida.updateSubscription, - ( - token, - indexId, - subscriber, - units, - new bytes(0) // ctx placeholder - ) - ); - return _forwardBatchCall(address(_ida), callData, userData); - } - - /** - * @dev Retrieves information about the specified subscription for a given token, publisher, index, and subscriber. - * @param token The address of the super token. - * @param publisher The address of the publisher. - * @param indexId The ID of the index. - * @param subscriber The address of the subscriber. - * @return exist The address of the publisher. - * @return approved Whether the subscription is approved. - * @return units The units value of the subscription. - * @return pendingDistribution The amount of tokens pending distribution for the subscription. - */ - function getSubscription(ISuperfluidToken token, address publisher, uint32 indexId, address subscriber) - external - view - returns (bool exist, bool approved, uint128 units, uint256 pendingDistribution) - { - return _ida.getSubscription(token, publisher, indexId, subscriber); - } - - /** - * @dev Computes the ID of a publisher based on the publisher's address and index ID. - * @param publisher The address of the publisher. - * @param indexId The ID of the index. - * @return publisherId The computed publisher ID. - */ - function getPublisherId(address publisher, uint32 indexId) external pure returns (bytes32 publisherId) { - return keccak256(abi.encodePacked("publisher", publisher, indexId)); - } - - /** - * @dev Computes the ID of a subscription based on the subscriber's address and publisher ID. - * @param subscriber The address of the subscriber. - * @param publisherId The ID of the publisher. - * @return subscriptionId The computed subscription ID. - */ - function getSubscriptionId(address subscriber, bytes32 publisherId) - external - pure - returns (bytes32 subscriptionId) - { - return keccak256(abi.encodePacked("subscription", subscriber, publisherId)); - } - - /** - * @dev Retrieves information about the specified subscription based on its ID. - * @param token The address of the super token. - * @param subscriptionId The ID of the subscription. - * @return publisher The address of the publisher. - * @return indexId The ID of the index. - * @return approved Whether the subscription is approved. - * @return units The units value of the subscription. - * @return pendingDistribution The amount of tokens pending distribution for the subscription. - */ - function getSubscriptionByID(ISuperfluidToken token, bytes32 subscriptionId) - external - view - returns (address publisher, uint32 indexId, bool approved, uint128 units, uint256 pendingDistribution) - { - return _ida.getSubscriptionByID(token, subscriptionId); - } - - /** - * @dev Lists all subscriptions of a subscriber for the specified token. - * @param token The address of the super token. - * @param subscriber The address of the subscriber. - * @return publishers The addresses of the publishers. - * @return indexIds The IDs of the indexes for the subscription. - * @return unitsList The unit amount for subscriptions to each index. - */ - function listSubscriptions(ISuperfluidToken token, address subscriber) - external - view - returns (address[] memory publishers, uint32[] memory indexIds, uint128[] memory unitsList) - { - return _ida.listSubscriptions(token, subscriber); - } -} diff --git a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol index f635e8cd46..3a2cfe3fc2 100644 --- a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol +++ b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol @@ -5,7 +5,6 @@ pragma solidity >=0.8.11; // solhint-disable max-states-count import { CFAv1Forwarder } from "./CFAv1Forwarder.sol"; -import { IDAv1Forwarder } from "./IDAv1Forwarder.sol"; import { GDAv1Forwarder } from "./GDAv1Forwarder.sol"; import { ISuperfluid, ISuperfluidToken, Superfluid } from "../superfluid/Superfluid.sol"; import { TestGovernance } from "./TestGovernance.sol"; @@ -29,8 +28,6 @@ import { SuperfluidUpgradeableBeacon } from "../upgradability/SuperfluidUpgradea import { UUPSProxy } from "../upgradability/UUPSProxy.sol"; import { BatchLiquidator } from "./BatchLiquidator.sol"; import { TOGA } from "./TOGA.sol"; -import { CFAv1Library } from "../apps/CFAv1Library.sol"; -import { IDAv1Library } from "../apps/IDAv1Library.sol"; import { IResolver } from "../interfaces/utils/IResolver.sol"; import { ERC2771Forwarder } from "../utils/ERC2771Forwarder.sol"; import { MacroForwarder } from "../utils/MacroForwarder.sol"; @@ -56,16 +53,13 @@ contract SuperfluidFrameworkDeploymentSteps { TestGovernance governance; Superfluid host; ConstantFlowAgreementV1 cfa; - CFAv1Library.InitData cfaLib; InstantDistributionAgreementV1 ida; GeneralDistributionAgreementV1 gda; - IDAv1Library.InitData idaLib; SuperTokenFactory superTokenFactory; ISuperToken superTokenLogic; TestResolver resolver; SuperfluidLoader superfluidLoader; CFAv1Forwarder cfaV1Forwarder; - IDAv1Forwarder idaV1Forwarder; GDAv1Forwarder gdaV1Forwarder; MacroForwarder macroForwarder; BatchLiquidator batchLiquidator; @@ -92,7 +86,6 @@ contract SuperfluidFrameworkDeploymentSteps { // Forwarders CFAv1Forwarder internal cfaV1Forwarder; - IDAv1Forwarder internal idaV1Forwarder; GDAv1Forwarder internal gdaV1Forwarder; MacroForwarder internal macroForwarder; @@ -113,16 +106,13 @@ contract SuperfluidFrameworkDeploymentSteps { governance: testGovernance, host: host, cfa: cfaV1, - cfaLib: CFAv1Library.InitData(host, cfaV1), ida: idaV1, - idaLib: IDAv1Library.InitData(host, idaV1), gda: gdaV1, superTokenFactory: superTokenFactory, superTokenLogic: superTokenLogic, resolver: testResolver, superfluidLoader: superfluidLoader, cfaV1Forwarder: cfaV1Forwarder, - idaV1Forwarder: idaV1Forwarder, gdaV1Forwarder: gdaV1Forwarder, macroForwarder: macroForwarder, batchLiquidator: batchLiquidator, @@ -221,10 +211,6 @@ contract SuperfluidFrameworkDeploymentSteps { cfaV1Forwarder = CFAv1ForwarderDeployerLibrary.deploy(host); testGovernance.enableTrustedForwarder(host, ISuperfluidToken(address(0)), address(cfaV1Forwarder)); - // Deploy IDAv1Forwarder - idaV1Forwarder = IDAv1ForwarderDeployerLibrary.deploy(host); - testGovernance.enableTrustedForwarder(host, ISuperfluidToken(address(0)), address(idaV1Forwarder)); - // Deploy GDAv1Forwarder gdaV1Forwarder = GDAv1ForwarderDeployerLibrary.deploy(host); testGovernance.enableTrustedForwarder(host, ISuperfluidToken(address(0)), address(gdaV1Forwarder)); @@ -281,9 +267,6 @@ contract SuperfluidFrameworkDeploymentSteps { // Register CFAv1Forwarder with Resolver testResolver.set("CFAv1Forwarder", address(cfaV1Forwarder)); - // Register IDAv1Forwarder with Resolver - testResolver.set("IDAv1Forwarder", address(idaV1Forwarder)); - // Register GDAv1Forwarder with Resolver testResolver.set("GDAv1Forwarder", address(gdaV1Forwarder)); @@ -380,12 +363,6 @@ library CFAv1ForwarderDeployerLibrary { } } -library IDAv1ForwarderDeployerLibrary { - function deploy(ISuperfluid _host) external returns (IDAv1Forwarder) { - return new IDAv1Forwarder(_host); - } -} - library GDAv1ForwarderDeployerLibrary { function deploy(ISuperfluid _host) external returns (GDAv1Forwarder) { return new GDAv1Forwarder(_host); diff --git a/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js b/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js index da7d6323f7..81e17e09ed 100644 --- a/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js +++ b/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js @@ -13,7 +13,6 @@ const SuperfluidPoolLogicDeployerLibraryArtifact = require("@superfluid-finance/ const SuperfluidPoolNFTLogicDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol/SuperfluidPoolNFTLogicDeployerLibrary.json"); const ProxyDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol/ProxyDeployerLibrary.json"); const CFAv1ForwarderDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol/CFAv1ForwarderDeployerLibrary.json"); -const IDAv1ForwarderDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol/IDAv1ForwarderDeployerLibrary.json"); const GDAv1ForwarderDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol/GDAv1ForwarderDeployerLibrary.json"); const SuperTokenFactoryDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol/SuperTokenFactoryDeployerLibrary.json"); const SuperfluidFrameworkDeployerArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeployer.t.sol/SuperfluidFrameworkDeployer.json"); @@ -209,12 +208,6 @@ const _deployTestFramework = async (provider, signer) => { CFAv1ForwarderDeployerLibraryArtifact, signer ); - const IDAv1ForwarderDeployerLibrary = - await _getFactoryAndReturnDeployedContract( - "IDAv1ForwarderDeployerLibrary", - IDAv1ForwarderDeployerLibraryArtifact, - signer - ); const GDAv1ForwarderDeployerLibrary = await _getFactoryAndReturnDeployedContract( "GDAv1ForwarderDeployerLibrary", @@ -276,9 +269,6 @@ const _deployTestFramework = async (provider, signer) => { CFAv1ForwarderDeployerLibrary: getContractAddress( CFAv1ForwarderDeployerLibrary ), - IDAv1ForwarderDeployerLibrary: getContractAddress( - IDAv1ForwarderDeployerLibrary - ), GDAv1ForwarderDeployerLibrary: getContractAddress( GDAv1ForwarderDeployerLibrary ), @@ -309,7 +299,6 @@ const printProtocolFrameworkAddresses = (framework) => { Resolver: framework.resolver, SuperfluidLoader: framework.superfluidLoader, CFAv1Forwarder: framework.cfaV1Forwarder, - IDAv1Forwarder: framework.idaV1Forwarder, }; console.log(JSON.stringify(output, null, 2)); diff --git a/packages/ethereum-contracts/ops-scripts/deploy-framework.js b/packages/ethereum-contracts/ops-scripts/deploy-framework.js index 32027a2bfb..2dd3d88799 100644 --- a/packages/ethereum-contracts/ops-scripts/deploy-framework.js +++ b/packages/ethereum-contracts/ops-scripts/deploy-framework.js @@ -207,7 +207,6 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( const contracts = [ "Ownable", "CFAv1Forwarder", - "IDAv1Forwarder", "GDAv1Forwarder", "IMultiSigWallet", "ISafe", @@ -245,7 +244,6 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( IMultiSigWallet, ISafe, CFAv1Forwarder, - IDAv1Forwarder, GDAv1Forwarder, SuperfluidGovernanceBase, Resolver, @@ -758,23 +756,6 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( } ); - // deploy IDAv1Forwarder for test deployments - // for other (permanent) deployments, it's not handled by this script - await deployAndRegisterContractIf( - IDAv1Forwarder, - "IDAv1Forwarder", - async (contractAddress) => contractAddress === ZERO_ADDRESS, - async () => { - const forwarder = await IDAv1Forwarder.new(superfluid.address); - output += `IDA_V1_FORWARDER=${forwarder.address}\n`; - await web3tx( - governance.enableTrustedForwarder, - `Governance set IDAv1Forwarder` - )(superfluid.address, ZERO_ADDRESS, forwarder.address); - return forwarder; - } - ); - // deploy GDAv1Forwarder for test deployments // for other (permanent) deployments, it's not handled by this script await deployAndRegisterContractIf( diff --git a/packages/ethereum-contracts/package.json b/packages/ethereum-contracts/package.json index 32a3fba32c..9627eefa72 100644 --- a/packages/ethereum-contracts/package.json +++ b/packages/ethereum-contracts/package.json @@ -1,7 +1,7 @@ { "name": "@superfluid-finance/ethereum-contracts", "description": " Ethereum contracts implementation for the Superfluid Protocol", - "version": "1.11.1", + "version": "1.12.0", "dependencies": { "@decentral.ee/web3-helpers": "0.5.3", "@nomiclabs/hardhat-ethers": "2.2.3", diff --git a/packages/ethereum-contracts/test/contracts/apps/CFAv1Library.test.ts b/packages/ethereum-contracts/test/contracts/apps/CFAv1Library.test.ts deleted file mode 100644 index e3f8123d6a..0000000000 --- a/packages/ethereum-contracts/test/contracts/apps/CFAv1Library.test.ts +++ /dev/null @@ -1,605 +0,0 @@ -import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; -import {assert, ethers, web3} from "hardhat"; - -import { - CFALibraryMock, - CFALibrarySuperAppMock, - ConstantFlowAgreementV1, - SuperfluidMock, - SuperTokenMock, -} from "../../../typechain-types"; -import TestEnvironment from "../../TestEnvironment"; - -import {deploySuperTokenAndNFTContractsAndInitialize} from "./SuperTokenV1Library.CFA.test"; - -const mintAmount = "1000000000000000000000000000"; // a small loan of a billion dollars -const flowRate = "1000000000000"; -const updatedFlowRate = "2000000000000"; - -// used to trigger different functions to test in the same callback -const callbackFunctionIndex = { - CREATE_FLOW: 0, - UPDATE_FLOW: 1, - DELETE_FLOW: 2, - CREATE_FLOW_BY_OPERATOR: 3, - UPDATE_FLOW_BY_OPERATOR: 4, - DELETE_FLOW_BY_OPERATOR: 5, - UPDATE_FLOW_OPERATOR_PERMISSIONS: 6, - AUTHORIZE_FLOW_OPERATOR_WITH_FULL_CONTROL: 7, - REVOKE_FLOW_OPERATOR_WITH_FULL_CONTROL: 8, -}; - -describe("CFAv1 Library testing", function () { - this.timeout(300e3); - const t = TestEnvironment.getSingleton(); - - let superToken: SuperTokenMock, - host: SuperfluidMock, - cfa: ConstantFlowAgreementV1; - let alice: string, bob: string; - let aliceSigner: SignerWithAddress; - let cfaLibraryMock: CFALibraryMock; - let cfaLibrarySuperAppMock: CFALibrarySuperAppMock; - - // the calldata used for a lot of the tests are - // repeated, it makes sense to collapse them here - let createFlowCalldata: string; - let authorizeFullControlCalldata: string; - - before(async () => { - await t.beforeTestSuite({ - isTruffle: true, - nAccounts: 3, - }); - - cfa = t.contracts.cfa; - host = t.contracts.superfluid; - - ({alice, bob} = t.aliases); - aliceSigner = await ethers.getSigner(alice); - }); - - beforeEach(async function () { - superToken = await deploySuperTokenAndNFTContractsAndInitialize(t); - await superToken.mintInternal(alice, mintAmount, "0x", "0x"); - await superToken.mintInternal(bob, mintAmount, "0x", "0x"); - - // deploy a contract we'll use for testing the library - const CFALibraryMockFactory = - await ethers.getContractFactory("CFALibraryMock"); - cfaLibraryMock = await CFALibraryMockFactory.deploy(host.address); - - const CFALibrarySuperAppMockFactory = await ethers.getContractFactory( - "CFALibrarySuperAppMock" - ); - cfaLibrarySuperAppMock = await CFALibrarySuperAppMockFactory.deploy( - host.address, - alice, // sender - bob, // receiver - alice // operator - ); - - await superToken.mintInternal(alice, mintAmount, "0x", "0x"); - await superToken - .connect(aliceSigner) - .transfer(cfaLibraryMock.address, mintAmount); - - await superToken.mintInternal(alice, mintAmount, "0x", "0x"); - await superToken - .connect(aliceSigner) - .transfer(cfaLibrarySuperAppMock.address, mintAmount); - - createFlowCalldata = t.agreementHelper.cfaInterface.encodeFunctionData( - "createFlow", - [superToken.address, cfaLibrarySuperAppMock.address, flowRate, "0x"] - ); - authorizeFullControlCalldata = - t.agreementHelper.cfaInterface.encodeFunctionData( - "authorizeFlowOperatorWithFullControl", - [superToken.address, cfaLibraryMock.address, "0x"] - ); - t.beforeEachTestCaseBenchmark(this); - }); - - afterEach(() => { - t.afterEachTestCaseBenchmark(); - }); - - describe("1 - Flow Ops", async function () { - it("1.1 - Create Flow", async () => { - await cfaLibraryMock.createFlowTest( - superToken.address, - bob, - flowRate - ); - - assert.equal( - ( - await cfa.getFlow( - superToken.address, - cfaLibraryMock.address, - bob - ) - ).flowRate.toString(), - flowRate - ); - }); - - it("1.2 - Update Flow", async () => { - await cfaLibraryMock.createFlowTest( - superToken.address, - bob, - flowRate - ); - - await cfaLibraryMock.updateFlowTest( - superToken.address, - bob, - updatedFlowRate - ); - - assert.equal( - ( - await cfa.getFlow( - superToken.address, - cfaLibraryMock.address, - bob - ) - ).flowRate.toString(), - updatedFlowRate - ); - }); - - it("1.3 - Delete Flow", async () => { - await cfaLibraryMock.createFlowTest( - superToken.address, - bob, - flowRate - ); - - await cfaLibraryMock.deleteFlowTest(superToken.address, bob); - - assert.equal( - ( - await cfa.getFlow( - superToken.address, - cfaLibraryMock.address, - bob - ) - ).flowRate.toString(), - "0" - ); - }); - - it("1.4 - Create Flow in Callback", async () => { - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - t.agreementHelper.cfaInterface.encodeFunctionData( - "createFlow", - [ - superToken.address, - cfaLibrarySuperAppMock.address, - flowRate, - "0x", - ] - ), - web3.eth.abi.encodeParameter( - "uint8", - callbackFunctionIndex.CREATE_FLOW - ) - ); - - assert.equal( - ( - await cfa.getFlow( - superToken.address, - cfaLibrarySuperAppMock.address, - bob - ) - ).flowRate.toString(), - flowRate - ); - }); - - it("1.5 - Update Flow in Callback", async () => { - await cfaLibrarySuperAppMock.createFlow(superToken.address); - - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - createFlowCalldata, - web3.eth.abi.encodeParameter( - "uint8", - callbackFunctionIndex.UPDATE_FLOW - ) - ); - - assert.equal( - ( - await cfa.getFlow( - superToken.address, - cfaLibrarySuperAppMock.address, - bob - ) - ).flowRate.toString(), - updatedFlowRate - ); - }); - - it("1.6 - Delete Flow in Callback", async () => { - await cfaLibrarySuperAppMock.createFlow(superToken.address); - - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - createFlowCalldata, - web3.eth.abi.encodeParameter( - "uint8", - callbackFunctionIndex.DELETE_FLOW - ) - ); - - assert.equal( - ( - await cfa.getFlow( - superToken.address, - cfaLibrarySuperAppMock.address, - bob - ) - ).flowRate.toString(), - "0" - ); - }); - }); - - describe("2 - Flow Operator Ops", async () => { - // testing permissions first before testing operator functions - it("2.1 - Can Update Flow Operator Permissions", async () => { - await cfaLibraryMock.updateFlowOperatorPermissionsTest( - alice, - superToken.address, - "7", - "1" - ); - - assert.equal( - ( - await cfa.getFlowOperatorData( - superToken.address, - cfaLibraryMock.address, - alice - ) - ).permissions.toString(), - "7" - ); - }); - - it("2.2 - Can Authorize Flow Operator With full Control", async () => { - await cfaLibraryMock.authorizeFlowOperatorWithFullControlTest( - alice, - superToken.address - ); - - assert.equal( - ( - await cfa.getFlowOperatorData( - superToken.address, - cfaLibraryMock.address, - alice - ) - ).permissions.toString(), - "7" - ); - }); - - it("2.3 - Can Revoke Flow Operator With Full Control", async () => { - await cfaLibraryMock.authorizeFlowOperatorWithFullControlTest( - alice, - superToken.address - ); - - await cfaLibraryMock.revokeFlowOperatorWithFullControlTest( - alice, - superToken.address - ); - - assert.equal( - ( - await cfa.getFlowOperatorData( - superToken.address, - cfaLibraryMock.address, - alice - ) - ).permissions.toString(), - "0" - ); - }); - - it("2.4 - Can Create Flow By Operator", async () => { - await host - .connect(aliceSigner) - .callAgreement(cfa.address, authorizeFullControlCalldata, "0x"); - - await cfaLibraryMock.createFlowByOperatorTest( - alice, - bob, - superToken.address, - flowRate - ); - - assert.equal( - ( - await cfa.getFlow(superToken.address, alice, bob) - ).flowRate.toString(), - flowRate - ); - }); - - it("2.5 - Can Update Flow By Operator", async () => { - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - t.agreementHelper.cfaInterface.encodeFunctionData( - "authorizeFlowOperatorWithFullControl", - [superToken.address, cfaLibraryMock.address, "0x"] - ), - "0x" - ); - - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - t.agreementHelper.cfaInterface.encodeFunctionData( - "createFlow", - [superToken.address, bob, flowRate, "0x"] - ), - "0x" - ); - - await cfaLibraryMock.updateFlowByOperatorTest( - alice, - bob, - superToken.address, - updatedFlowRate - ); - - assert.equal( - ( - await cfa.getFlow(superToken.address, alice, bob) - ).flowRate.toString(), - updatedFlowRate - ); - }); - - it("2.6 - Can Delete Flow By Operator", async () => { - await host - .connect(aliceSigner) - .callAgreement(cfa.address, authorizeFullControlCalldata, "0x"); - - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - t.agreementHelper.cfaInterface.encodeFunctionData( - "createFlow", - [superToken.address, bob, flowRate, "0x"] - ), - "0x" - ); - - await cfaLibraryMock.deleteFlowByOperator( - alice, - bob, - superToken.address - ); - - assert.equal( - ( - await cfa.getFlow(superToken.address, alice, bob) - ).flowRate.toString(), - "0" - ); - }); - - it("2.7 - Can Create Flow By Operator in Callback", async () => { - // alice approves super app as operator, alice creates a flow to super app which creates - // a flow from alice to bob on alice's behalf. - authorizeFullControlCalldata = - t.agreementHelper.cfaInterface.encodeFunctionData( - "authorizeFlowOperatorWithFullControl", - [superToken.address, cfaLibrarySuperAppMock.address, "0x"] - ); - await host - .connect(aliceSigner) - .callAgreement(cfa.address, authorizeFullControlCalldata, "0x"); - - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - createFlowCalldata, - web3.eth.abi.encodeParameter( - "uint8", - callbackFunctionIndex.CREATE_FLOW_BY_OPERATOR - ) - ); - - assert.equal( - ( - await cfa.getFlow(superToken.address, alice, bob) - ).flowRate.toString(), - flowRate - ); - }); - - it("2.8 - Can Update Flow By Operator in Callback", async () => { - // alice approves super app as operator, alice creates a flow to bob, alice creates a - // flow to super app which updates the flow from alice to bob on alice's behalf. - authorizeFullControlCalldata = - t.agreementHelper.cfaInterface.encodeFunctionData( - "authorizeFlowOperatorWithFullControl", - [superToken.address, cfaLibrarySuperAppMock.address, "0x"] - ); - await host - .connect(aliceSigner) - .callAgreement(cfa.address, authorizeFullControlCalldata, "0x"); - - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - t.agreementHelper.cfaInterface.encodeFunctionData( - "createFlow", - [superToken.address, bob, flowRate, "0x"] - ), - "0x" - ); - - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - createFlowCalldata, - web3.eth.abi.encodeParameter( - "uint8", - callbackFunctionIndex.UPDATE_FLOW_BY_OPERATOR - ) - ); - - assert.equal( - ( - await cfa.getFlow(superToken.address, alice, bob) - ).flowRate.toString(), - updatedFlowRate - ); - }); - - it("2.9 - Can Delete Flow By Operator in Callback", async () => { - // alice approves super app as operator, alice creates a flow to bob, alice creates a - // flow to super app which deletes the flow from alice to bob on alice's behalf. - authorizeFullControlCalldata = - t.agreementHelper.cfaInterface.encodeFunctionData( - "authorizeFlowOperatorWithFullControl", - [superToken.address, cfaLibrarySuperAppMock.address, "0x"] - ); - await host - .connect(aliceSigner) - .callAgreement(cfa.address, authorizeFullControlCalldata, "0x"); - - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - t.agreementHelper.cfaInterface.encodeFunctionData( - "createFlow", - [superToken.address, bob, flowRate, "0x"] - ), - "0x" - ); - - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - createFlowCalldata, - web3.eth.abi.encodeParameter( - "uint8", - callbackFunctionIndex.DELETE_FLOW_BY_OPERATOR - ) - ); - - assert.equal( - ( - await cfa.getFlow(superToken.address, alice, bob) - ).flowRate.toString(), - "0" - ); - }); - - it("2.10 - Can Update Flow Operator Permissions in Callback", async () => { - // alice creates a flow to the super app which sets alice as the operator for it - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - createFlowCalldata, - web3.eth.abi.encodeParameter( - "uint8", - callbackFunctionIndex.UPDATE_FLOW_OPERATOR_PERMISSIONS - ) - ); - - assert.equal( - ( - await cfa.getFlowOperatorData( - superToken.address, - cfaLibrarySuperAppMock.address, - alice - ) - ).permissions.toString(), - "7" - ); - }); - - it("2.11 - Can Authorize Flow Operator With Full Control in Callback", async () => { - // alice creates a flow to the super app which sets alice as the operator for it - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - createFlowCalldata, - web3.eth.abi.encodeParameter( - "uint8", - callbackFunctionIndex.AUTHORIZE_FLOW_OPERATOR_WITH_FULL_CONTROL - ) - ); - - assert.equal( - ( - await cfa.getFlowOperatorData( - superToken.address, - cfaLibrarySuperAppMock.address, - alice - ) - ).permissions.toString(), - "7" - ); - }); - - it("2.12 - Can Revoke Flow Operator With Full Control in Callback", async () => { - // alice sets theirself as the operator for the super app, alice creates a flow to - // the super app which revokes alice's operator permissions - await cfaLibrarySuperAppMock - .connect(aliceSigner) - .authorizeFlowOperatorWithFullControl(superToken.address); - - await host - .connect(aliceSigner) - .callAgreement( - cfa.address, - createFlowCalldata, - web3.eth.abi.encodeParameter( - "uint8", - callbackFunctionIndex.REVOKE_FLOW_OPERATOR_WITH_FULL_CONTROL - ) - ); - - assert.equal( - ( - await cfa.getFlowOperatorData( - superToken.address, - cfaLibrarySuperAppMock.address, - alice - ) - ).permissions.toString(), - "0" - ); - }); - }); -}); diff --git a/packages/ethereum-contracts/test/contracts/apps/IDAv1Library.test.ts b/packages/ethereum-contracts/test/contracts/apps/IDAv1Library.test.ts deleted file mode 100644 index c03ee17d46..0000000000 --- a/packages/ethereum-contracts/test/contracts/apps/IDAv1Library.test.ts +++ /dev/null @@ -1,1384 +0,0 @@ -import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; -import {assert, ethers, expect, web3} from "hardhat"; - -import { - IDAv1LibraryMock, - IDAv1LibrarySuperAppMock, - InstantDistributionAgreementV1, - SuperfluidMock, - SuperTokenMock, -} from "../../../typechain-types"; -import TestEnvironment from "../../TestEnvironment"; - -const ZERO_ADDRESS = ethers.constants.AddressZero; - -describe("IDAv1Library testing", function () { - this.timeout(300e3); - - // HELPERS - - // enum simulator. - // This is used in the IDAv1LibrarySuperAppMock for checking all functions. - const FunctionIndex = { - CREATE_INDEX: 0, - CREATE_INDEX_USER_DATA: 1, - UPDATE_INDEX: 2, - UPDATE_INDEX_USER_DATA: 3, - DISTRIBUTE: 4, - DISTRIBUTE_USER_DATA: 5, - APPROVE_SUBSCRIPTION: 6, - APPROVE_SUBSCRIPTION_USER_DATA: 7, - REVOKE_SUBSCRIPTION: 8, - REVOKE_SUBSCRIPTION_USER_DATA: 9, - UPDATE_SUBSCRIPTION: 10, - UPDATE_SUBSCRIPTION_USER_DATA: 11, - DELETE_SUBSCRIPTION: 12, - DELETE_SUBSCRIPTION_USER_DATA: 13, - CLAIM: 14, - CLAIM_USER_DATA: 15, - }; - - const toBytes = (text: string) => - web3.eth.abi.encodeParameter("string", text); - - const userData = ( - functionIndex: number, - indexId: number, - publisher = ZERO_ADDRESS, - subscriber = ZERO_ADDRESS, - units = 0 - ) => - web3.eth.abi.encodeParameters( - ["uint8", "uint32", "address", "address", "uint128"], - [functionIndex, indexId, publisher, subscriber, units] - ); - - // TEST SET UP - const t = TestEnvironment.getSingleton(); - - const INDEX_ID = 0; - - let superToken: SuperTokenMock, - host: SuperfluidMock, - ida: InstantDistributionAgreementV1, - alice: string, - bob: string, - idaV1LibMock: IDAv1LibraryMock, - idaV1LibSuperAppMock: IDAv1LibrarySuperAppMock, - aliceSigner: SignerWithAddress; - - before(async () => { - await t.beforeTestSuite({isTruffle: true, nAccounts: 4}); - - ida = t.contracts.ida; - host = t.contracts.superfluid; - ({alice, bob} = t.aliases); - superToken = await ethers.getContractAt( - "SuperTokenMock", - t.tokens.SuperToken.address - ); - - await superToken.mintInternal( - alice, - ethers.utils.parseUnits("100000", "ether"), - "0x", - "0x" - ); - aliceSigner = await ethers.getSigner(alice); - }); - - beforeEach(async function () { - const idaV1LibMockFactory = - await ethers.getContractFactory("IDAv1LibraryMock"); - idaV1LibMock = (await idaV1LibMockFactory.deploy(host.address)).connect( - aliceSigner - ); - const idaV1LibSuperAppMockFactory = await ethers.getContractFactory( - "IDAv1LibrarySuperAppMock" - ); - idaV1LibSuperAppMock = ( - await idaV1LibSuperAppMockFactory.deploy(host.address) - ).connect(aliceSigner); - await superToken - .connect(aliceSigner) - .transfer( - idaV1LibMock.address, - ethers.utils.parseUnits("10", "ether") - ); - await superToken - .connect(aliceSigner) - .transfer( - idaV1LibSuperAppMock.address, - ethers.utils.parseUnits("10", "ether") - ); - t.beforeEachTestCaseBenchmark(this); - }); - - afterEach(() => { - t.afterEachTestCaseBenchmark(); - }); - - describe("#1 - Non-Callback Index Operations", async function () { - it("#1.1 - create index", async () => { - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ) - ).exist, - true - ); - }); - - it("#1.2 - create index with user data", async () => { - console.log("Alice create index with user data"); - await idaV1LibMock.createIndexWithUserDataTest( - superToken.address, - INDEX_ID, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ) - ).exist, - true - ); - }); - - it("#1.3 - update index value", async () => { - const indexValue = 1; - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("Alice updates index value"); - await idaV1LibMock.updateIndexValueTest( - superToken.address, - INDEX_ID, - indexValue - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - indexValue - ); - }); - - it("#1.4 - update index value with user data", async () => { - const indexValue = 1; - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("Alice updates index value with user data"); - await idaV1LibMock.updateIndexValueWithUserDataTest( - superToken.address, - INDEX_ID, - indexValue, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - indexValue - ); - }); - - it("#1.5 - distribute", async () => { - const distribution = 1; - const units = 1; - - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("Alice issues units to Bob"); - await idaV1LibMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - console.log("Alice distributes"); - await idaV1LibMock.distributeTest( - superToken.address, - INDEX_ID, - distribution - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - distribution - ); - }); - - it("#1.6 - distribute with user data", async () => { - const distribution = 1; - const units = 1; - - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("Alice issues units to Bob"); - await idaV1LibMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - console.log("Alice distributes with user data"); - await idaV1LibMock.distributeWithUserDataTest( - superToken.address, - INDEX_ID, - distribution, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - distribution - ); - }); - }); - - describe("#2 - Non-Callback Subscription Operations", async function () { - it("#2.1 - approve subscription", async () => { - // must create externally to test against mock contract - console.log("Alice creates index"); - await host - .connect(aliceSigner) - .callAgreement( - ida.address, - t.agreementHelper.idaInterface.encodeFunctionData( - "createIndex", - [superToken.address, INDEX_ID, "0x"] - ), - "0x" - ); - - console.log("Bob approves subscription"); - await idaV1LibMock - .connect(await ethers.getSigner(bob)) - .approveSubscriptionTest(superToken.address, alice, INDEX_ID); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - idaV1LibMock.address - ) - ).approved, - true - ); - }); - - it("#2.2 - approve subscription with user data", async () => { - console.log("Bob approves subscription with user data"); - await idaV1LibMock - .connect(await ethers.getSigner(bob)) - .approveSubscriptionWithUserDataTest( - superToken.address, - alice, - INDEX_ID, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - idaV1LibMock.address - ) - ).approved, - true - ); - }); - - it("#2.3 - revoke subscription", async () => { - console.log("Bob approves subscription"); - await idaV1LibMock.approveSubscriptionTest( - superToken.address, - alice, - INDEX_ID - ); - - console.log("Bob revokes subscription"); - await idaV1LibMock.revokeSubscriptionTest( - superToken.address, - alice, - INDEX_ID - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - idaV1LibMock.address - ) - ).approved, - false - ); - }); - - it("#2.4 - revoke subscription with user data", async () => { - console.log("Bob approves subscription"); - await idaV1LibMock.approveSubscriptionTest( - superToken.address, - alice, - INDEX_ID - ); - - console.log("Bob revokes subscription with user data"); - await idaV1LibMock.revokeSubscriptionWithUserDataTest( - superToken.address, - alice, - INDEX_ID, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - idaV1LibMock.address - ) - ).approved, - false - ); - }); - - it("#2.5 - update subscription units", async () => { - const units = 1; - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("Alice updates Bob's subscription"); - await idaV1LibMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - units - ); - }); - - it("#2.6 - update subscription with user data", async () => { - const units = 1; - - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("Alice updates Bob's subscription with user data"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - bob, - units, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - units - ); - }); - - it("#2.7 - delete subscription", async () => { - const units = 1; - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("Alice updates Bob's subscription"); - await idaV1LibMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - console.log("Alice deletes Bob's subscription"); - await idaV1LibMock - .connect(await ethers.getSigner(bob)) - .deleteSubscriptionTest( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - bob - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - 0 - ); - }); - - it("#2.8 - delete subscription with user data", async () => { - const units = 1; - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("Alice updates Bob's subscription"); - await idaV1LibMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - console.log("Alice deletes Bob's subscription"); - await idaV1LibMock - .connect(await ethers.getSigner(bob)) - .deleteSubscriptionWithUserDataTest( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - bob, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - 0 - ); - }); - - it("#2.9 - claim", async () => { - const units = 1; - - console.log("Alice updates subscription units"); - await host - .connect(aliceSigner) - .callAgreement( - ida.address, - t.agreementHelper.idaInterface.encodeFunctionData( - "updateSubscription", - [ - superToken.address, - INDEX_ID, - idaV1LibMock.address, - units, - "0x", - ] - ), - "0x" - ); - - console.log("Bob claims pending units"); - await idaV1LibMock - .connect(await ethers.getSigner(bob)) - .claimTest( - superToken.address, - alice, - INDEX_ID, - idaV1LibMock.address - ); - - const subscription = await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - idaV1LibMock.address - ); - - assert.equal(subscription.units.toNumber(), units); - assert.equal(subscription.pendingDistribution.toNumber(), 0); - }); - - it("#2.10 - claim with user data", async () => { - const units = 1; - console.log("Alice updates subscription units"); - await host - .connect(aliceSigner) - .callAgreement( - ida.address, - t.agreementHelper.idaInterface.encodeFunctionData( - "updateSubscription", - [ - superToken.address, - INDEX_ID, - idaV1LibMock.address, - units, - "0x", - ] - ), - "0x" - ); - - console.log("Bob claims pending units"); - await idaV1LibMock - .connect(await ethers.getSigner(bob)) - .claimWithUserDataTest( - superToken.address, - alice, - INDEX_ID, - idaV1LibMock.address, - toBytes("oh hello") - ); - - const subscription = await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - idaV1LibMock.address - ); - - assert.equal(subscription.units.toNumber(), units); - assert.equal(subscription.pendingDistribution.toNumber(), 0); - }); - }); - - describe("#3 - View Operations", async function () { - it("#3.1 - get index", async () => { - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - const index = await ida.getIndex( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ); - - const libIndex = await idaV1LibMock.getIndexTest( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ); - - assert.equal(index.exist, libIndex.exist); - assert.equal( - index.indexValue.toString(), - libIndex.indexValue.toString() - ); - assert.equal( - index.totalUnitsApproved.toString(), - libIndex.totalUnitsApproved.toString() - ); - assert.equal( - index.totalUnitsPending.toString(), - libIndex.totalUnitsPending.toString() - ); - }); - - it("#3.2 - calculate distribution", async () => { - const amount = 1; - const units = 1; - - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("Alice updates subscription units"); - await idaV1LibMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - const distribution = await ida.calculateDistribution( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - amount - ); - const distributionLib = - await idaV1LibMock.calculateDistributionTest( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - amount - ); - - assert.equal( - distribution.actualAmount.toString(), - distributionLib.actualAmount.toString() - ); - assert.equal( - distribution.newIndexValue.toString(), - distributionLib.newIndexValue.toString() - ); - }); - - it("#3.3 - list subscriptions", async () => { - const units = 1; - - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("Alice updates subscription units"); - await idaV1LibMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - const subscriptions = await ida.listSubscriptions( - superToken.address, - bob - ); - const subscriptionsLib = await idaV1LibMock.listSubscriptionsTest( - superToken.address, - bob - ); - - expect(subscriptions.publishers).to.eql( - subscriptionsLib.publishers - ); - expect(subscriptions.indexIds).to.eql(subscriptionsLib.indexIds); - expect(subscriptions.unitsList).to.eql(subscriptionsLib.unitsList); - }); - - it("#3.4 - get subscription", async () => { - const units = 1; - - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("Alice updates subscription units"); - await idaV1LibMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - const subscription = await ida.getSubscription( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - bob - ); - const subscriptionLib = await idaV1LibMock.getSubscriptionTest( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - bob - ); - - assert.equal(subscription.exist, subscriptionLib.exist); - assert.equal(subscription.approved, subscriptionLib.approved); - assert.equal( - subscription.units.toString(), - subscriptionLib.units.toString() - ); - assert.equal( - subscription.pendingDistribution.toString(), - subscriptionLib.pendingDistribution.toString() - ); - }); - - it("#3.5 - get subscription by id", async () => { - const units = 1; - - const publisherId = ethers.utils.solidityKeccak256( - ["string", "address", "uint32"], - ["publisher", idaV1LibMock.address, INDEX_ID] - ); - const subscriptionId = ethers.utils.solidityKeccak256( - ["string", "address", "bytes32"], - ["subscription", bob, publisherId] - ); - - console.log("Alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("Alice updates subscription units"); - await idaV1LibMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - const subscription = await ida.getSubscriptionByID( - superToken.address, - subscriptionId - ); - const subscriptionLib = await idaV1LibMock.getSubscriptionByIDTest( - superToken.address, - subscriptionId - ); - - assert.equal(subscription.approved, subscriptionLib.approved); - assert.equal( - subscription.units.toString(), - subscriptionLib.units.toString() - ); - assert.equal( - subscription.pendingDistribution.toString(), - subscriptionLib.pendingDistribution.toString() - ); - }); - }); - - // CALLBACK TESTS - // These tests pass `userData` to be extracted and used inside a super app callback. - describe("#4 - Callback Index Operations", async function () { - it("#4.1 - create index in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData(FunctionIndex.CREATE_INDEX, INDEX_ID) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibSuperAppMock.address, - INDEX_ID - ) - ).exist, - true - ); - }); - - it("#4.2 - create index in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData(FunctionIndex.CREATE_INDEX_USER_DATA, INDEX_ID) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibSuperAppMock.address, - INDEX_ID - ) - ).exist, - true - ); - }); - - it("#4.3 - update index in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("super app creates index"); - await idaV1LibSuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app adds subscription to bob"); - await idaV1LibSuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.UPDATE_INDEX, - INDEX_ID, - undefined, - undefined, - units - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibSuperAppMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - units - ); - }); - - it("#4.4 - update index in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("super app creates index"); - await idaV1LibSuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app adds subscription to bob"); - await idaV1LibSuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.UPDATE_INDEX_USER_DATA, - INDEX_ID, - undefined, - undefined, - units - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibSuperAppMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - units - ); - }); - - it("#4.5 - distribute in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("super app creates index"); - await idaV1LibSuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app adds subscription to bob"); - await idaV1LibSuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.DISTRIBUTE, - INDEX_ID, - undefined, - undefined, - units - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibSuperAppMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - units - ); - }); - - it("#4.6 - distribute in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("super app creates index"); - await idaV1LibSuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app adds subscription to bob"); - await idaV1LibSuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.DISTRIBUTE_USER_DATA, - INDEX_ID, - undefined, - undefined, - units - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibSuperAppMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - units - ); - }); - - it("#4.7 - approve subscription in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.APPROVE_SUBSCRIPTION, - INDEX_ID, - idaV1LibMock.address - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ) - ).totalUnitsApproved.toNumber(), - units - ); - }); - - it("#4.8 - approve subscription in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.APPROVE_SUBSCRIPTION_USER_DATA, - INDEX_ID, - idaV1LibMock.address - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ) - ).totalUnitsApproved.toNumber(), - units - ); - }); - - it("#4.9 - revoke subscription in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("super app approves subscription"); - await idaV1LibSuperAppMock.approveSubscriptionTest( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.REVOKE_SUBSCRIPTION, - INDEX_ID, - idaV1LibMock.address - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - idaV1LibSuperAppMock.address - ) - ).approved, - false - ); - }); - - it("#4.10 - revoke subscription in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("super app approves subscription"); - await idaV1LibSuperAppMock.approveSubscriptionTest( - superToken.address, - idaV1LibMock.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.REVOKE_SUBSCRIPTION_USER_DATA, - INDEX_ID, - idaV1LibMock.address - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - idaV1LibSuperAppMock.address - ) - ).approved, - false - ); - }); - - it("#4.11 - update subscription units in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("super app creates index"); - await idaV1LibSuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.UPDATE_SUBSCRIPTION, - INDEX_ID, - undefined, - idaV1LibMock.address, - units - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibSuperAppMock.address, - INDEX_ID, - idaV1LibMock.address - ) - ).units.toNumber(), - units - ); - }); - - it("#4.12 - update subscription units in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("super app creates index"); - await idaV1LibSuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.UPDATE_SUBSCRIPTION_USER_DATA, - INDEX_ID, - undefined, - idaV1LibMock.address, - units - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibSuperAppMock.address, - INDEX_ID, - idaV1LibMock.address - ) - ).units.toNumber(), - units - ); - }); - - it("#4.13 - delete subscription in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("super app creates index"); - await idaV1LibSuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app issues units to bob"); - await idaV1LibSuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.DELETE_SUBSCRIPTION, - INDEX_ID, - idaV1LibSuperAppMock.address, - bob - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibSuperAppMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - 0 - ); - }); - - it("#4.14 - delete subscription in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - - console.log("super app creates index"); - await idaV1LibSuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("super app issues units to bob"); - await idaV1LibSuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.DELETE_SUBSCRIPTION, - INDEX_ID, - idaV1LibSuperAppMock.address, - bob - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibSuperAppMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - 0 - ); - }); - - it("#4.15 - claim in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.CLAIM, - INDEX_ID, - idaV1LibMock.address, - idaV1LibSuperAppMock.address - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - idaV1LibSuperAppMock.address - ) - ).pendingDistribution.toNumber(), - 0 - ); - }); - - it("#4.15 - claim in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await idaV1LibMock.createIndexTest(superToken.address, INDEX_ID); - console.log("alice triggers callback on super app"); - await idaV1LibMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - idaV1LibSuperAppMock.address, - units, - userData( - FunctionIndex.CLAIM_USER_DATA, - INDEX_ID, - idaV1LibMock.address, - idaV1LibSuperAppMock.address - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - idaV1LibMock.address, - INDEX_ID, - idaV1LibSuperAppMock.address - ) - ).pendingDistribution.toNumber(), - 0 - ); - }); - }); -}); diff --git a/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts b/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts index 122fcd5b89..29d64bfd47 100644 --- a/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts +++ b/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts @@ -170,7 +170,7 @@ describe("SuperTokenV1Library.GDA", function () { pool.address, requestedDistributionAmount ); - await superTokenLibraryGDAMock.distributeToPoolTest( + await superTokenLibraryGDAMock.distributeTest( superToken.address, superTokenLibraryGDAMock.address, pool.address, diff --git a/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.IDA.test.ts b/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.IDA.test.ts deleted file mode 100644 index eac33fee16..0000000000 --- a/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.IDA.test.ts +++ /dev/null @@ -1,1485 +0,0 @@ -import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; -import {assert, ethers, expect, web3} from "hardhat"; - -import { - InstantDistributionAgreementV1, - SuperfluidMock, - SuperTokenLibraryIDAMock, - SuperTokenLibraryIDASuperAppMock, - SuperTokenMock, -} from "../../../typechain-types"; -import TestEnvironment from "../../TestEnvironment"; - -const ZERO_ADDRESS = ethers.constants.AddressZero; - -describe("IDAv1Library testing", function () { - this.timeout(300e3); - - // HELPERS - - // enum simulator. - // This is used in the IDAv1LibrarySuperAppMock for checking all functions. - const FunctionIndex = { - CREATE_INDEX: 0, - CREATE_INDEX_USER_DATA: 1, - UPDATE_INDEX: 2, - UPDATE_INDEX_USER_DATA: 3, - DISTRIBUTE: 4, - DISTRIBUTE_USER_DATA: 5, - APPROVE_SUBSCRIPTION: 6, - APPROVE_SUBSCRIPTION_USER_DATA: 7, - REVOKE_SUBSCRIPTION: 8, - REVOKE_SUBSCRIPTION_USER_DATA: 9, - UPDATE_SUBSCRIPTION: 10, - UPDATE_SUBSCRIPTION_USER_DATA: 11, - DELETE_SUBSCRIPTION: 12, - DELETE_SUBSCRIPTION_USER_DATA: 13, - CLAIM: 14, - CLAIM_USER_DATA: 15, - }; - - const toBytes = (text: string) => - web3.eth.abi.encodeParameter("string", text); - - const userData = ( - functionIndex: number, - indexId: number, - publisher = ZERO_ADDRESS, - subscriber = ZERO_ADDRESS, - units = 0 - ) => - web3.eth.abi.encodeParameters( - ["uint8", "uint32", "address", "address", "uint128"], - [functionIndex, indexId, publisher, subscriber, units] - ); - - // TEST SET UP - const t = TestEnvironment.getSingleton(); - - const INDEX_ID = 0; - - let superToken: SuperTokenMock, - host: SuperfluidMock, - ida: InstantDistributionAgreementV1, - alice: string, - bob: string, - superTokenLibIDAMock: SuperTokenLibraryIDAMock, - superTokenLibIDASuperAppMock: SuperTokenLibraryIDASuperAppMock, - aliceSigner: SignerWithAddress; - - before(async () => { - await t.beforeTestSuite({isTruffle: true, nAccounts: 4}); - - ida = t.contracts.ida; - host = t.contracts.superfluid; - ({alice, bob} = t.aliases); - superToken = await ethers.getContractAt( - "SuperTokenMock", - t.tokens.SuperToken.address - ); - - await superToken.mintInternal( - alice, - ethers.utils.parseUnits("100000", "ether"), - "0x", - "0x" - ); - aliceSigner = await ethers.getSigner(alice); - }); - - beforeEach(async function () { - const superTokenLibIDAMockFactory = await ethers.getContractFactory( - "SuperTokenLibraryIDAMock" - ); - superTokenLibIDAMock = ( - await superTokenLibIDAMockFactory.deploy() - ).connect(aliceSigner); - const superTokenLibIDASuperAppMockFactory = - await ethers.getContractFactory("SuperTokenLibraryIDASuperAppMock"); - superTokenLibIDASuperAppMock = ( - await superTokenLibIDASuperAppMockFactory.deploy(host.address) - ).connect(aliceSigner); - await superToken - .connect(aliceSigner) - .transfer( - superTokenLibIDAMock.address, - ethers.utils.parseUnits("10", "ether") - ); - await superToken - .connect(aliceSigner) - .transfer( - superTokenLibIDASuperAppMock.address, - ethers.utils.parseUnits("10", "ether") - ); - - t.beforeEachTestCaseBenchmark(this); - }); - - afterEach(() => { - t.afterEachTestCaseBenchmark(); - }); - - describe("#1 - Non-Callback Index Operations", async function () { - it("#1.1 - create index", async () => { - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ) - ).exist, - true - ); - }); - - it("#1.2 - create index with user data", async () => { - console.log("Alice create index with user data"); - await superTokenLibIDAMock.createIndexWithUserDataTest( - superToken.address, - INDEX_ID, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ) - ).exist, - true - ); - }); - - it("#1.3 - update index value", async () => { - const indexValue = 1; - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("Alice updates index value"); - await superTokenLibIDAMock.updateIndexValueTest( - superToken.address, - INDEX_ID, - indexValue - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - indexValue - ); - }); - - it("#1.4 - update index value with user data", async () => { - const indexValue = 1; - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("Alice updates index value with user data"); - await superTokenLibIDAMock.updateIndexValueWithUserDataTest( - superToken.address, - INDEX_ID, - indexValue, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - indexValue - ); - }); - - it("#1.5 - distribute", async () => { - const distribution = 1; - const units = 1; - - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("Alice issues units to Bob"); - await superTokenLibIDAMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - console.log("Alice distributes"); - await superTokenLibIDAMock.distributeTest( - superToken.address, - INDEX_ID, - distribution - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - distribution - ); - }); - - it("#1.6 - distribute with user data", async () => { - const distribution = 1; - const units = 1; - - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("Alice issues units to Bob"); - await superTokenLibIDAMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - console.log("Alice distributes with user data"); - await superTokenLibIDAMock.distributeWithUserDataTest( - superToken.address, - INDEX_ID, - distribution, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - distribution - ); - }); - - it("#1.7 - _getHostAndIDA empty cache test", async () => { - await superTokenLibIDAMock.listSubscriptionsTest( - superToken.address, - bob - ); - }); - }); - - describe("#2 - Non-Callback Subscription Operations", async function () { - it("#2.1 - approve subscription", async () => { - // must create externally to test against mock contract - console.log("Alice creates index"); - await host - .connect(aliceSigner) - .callAgreement( - ida.address, - t.agreementHelper.idaInterface.encodeFunctionData( - "createIndex", - [superToken.address, INDEX_ID, "0x"] - ), - "0x" - ); - - console.log("Bob approves subscription"); - await superTokenLibIDAMock - .connect(await ethers.getSigner(bob)) - .approveSubscriptionTest(superToken.address, alice, INDEX_ID); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - superTokenLibIDAMock.address - ) - ).approved, - true - ); - }); - - it("#2.2 - approve subscription with user data", async () => { - console.log("Bob approves subscription with user data"); - await superTokenLibIDAMock - .connect(await ethers.getSigner(bob)) - .approveSubscriptionWithUserDataTest( - superToken.address, - alice, - INDEX_ID, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - superTokenLibIDAMock.address - ) - ).approved, - true - ); - }); - - it("#2.3 - revoke subscription", async () => { - console.log("Bob approves subscription"); - await superTokenLibIDAMock.approveSubscriptionTest( - superToken.address, - alice, - INDEX_ID - ); - - console.log("Bob revokes subscription"); - await superTokenLibIDAMock.revokeSubscriptionTest( - superToken.address, - alice, - INDEX_ID - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - superTokenLibIDAMock.address - ) - ).approved, - false - ); - }); - - it("#2.4 - revoke subscription with user data", async () => { - console.log("Bob approves subscription"); - await superTokenLibIDAMock.approveSubscriptionTest( - superToken.address, - alice, - INDEX_ID - ); - - console.log("Bob revokes subscription with user data"); - await superTokenLibIDAMock.revokeSubscriptionWithUserDataTest( - superToken.address, - alice, - INDEX_ID, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - superTokenLibIDAMock.address - ) - ).approved, - false - ); - }); - - it("#2.5 - update subscription units", async () => { - const units = 1; - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("Alice updates Bob's subscription"); - await superTokenLibIDAMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - units - ); - }); - - it("#2.6 - update subscription with user data", async () => { - const units = 1; - - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("Alice updates Bob's subscription with user data"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - bob, - units, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - units - ); - }); - - it("#2.7 - delete subscription", async () => { - const units = 1; - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("Alice updates Bob's subscription"); - await superTokenLibIDAMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - console.log("Alice deletes Bob's subscription"); - await superTokenLibIDAMock - .connect(await ethers.getSigner(bob)) - .deleteSubscriptionTest( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - bob - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - 0 - ); - }); - - it("#2.8 - delete subscription with user data", async () => { - const units = 1; - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("Alice updates Bob's subscription"); - await superTokenLibIDAMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - console.log("Alice deletes Bob's subscription"); - await superTokenLibIDAMock - .connect(await ethers.getSigner(bob)) - .deleteSubscriptionWithUserDataTest( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - bob, - toBytes("oh hello") - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - 0 - ); - }); - - it("#2.9 - claim", async () => { - const units = 1; - - console.log("Alice updates subscription units"); - await host - .connect(aliceSigner) - .callAgreement( - ida.address, - t.agreementHelper.idaInterface.encodeFunctionData( - "updateSubscription", - [ - superToken.address, - INDEX_ID, - superTokenLibIDAMock.address, - units, - "0x", - ] - ), - "0x" - ); - - console.log("Bob claims pending units"); - await superTokenLibIDAMock - .connect(await ethers.getSigner(bob)) - .claimTest( - superToken.address, - alice, - INDEX_ID, - superTokenLibIDAMock.address - ); - - const subscription = await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - superTokenLibIDAMock.address - ); - - assert.equal(subscription.units.toNumber(), units); - assert.equal(subscription.pendingDistribution.toNumber(), 0); - }); - - it("#2.10 - claim with user data", async () => { - const units = 1; - console.log("Alice updates subscription units"); - await host - .connect(aliceSigner) - .callAgreement( - ida.address, - t.agreementHelper.idaInterface.encodeFunctionData( - "updateSubscription", - [ - superToken.address, - INDEX_ID, - superTokenLibIDAMock.address, - units, - "0x", - ] - ), - "0x" - ); - - console.log("Bob claims pending units"); - await superTokenLibIDAMock - .connect(await ethers.getSigner(bob)) - .claimWithUserDataTest( - superToken.address, - alice, - INDEX_ID, - superTokenLibIDAMock.address, - toBytes("oh hello") - ); - - const subscription = await ida.getSubscription( - superToken.address, - alice, - INDEX_ID, - superTokenLibIDAMock.address - ); - - assert.equal(subscription.units.toNumber(), units); - assert.equal(subscription.pendingDistribution.toNumber(), 0); - }); - }); - - describe("#3 - View Operations", async function () { - it("#3.1 - get index", async () => { - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - const index = await ida.getIndex( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ); - - const libIndex = await superTokenLibIDAMock.getIndexTest( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ); - - assert.equal(index.exist, libIndex.exist); - assert.equal( - index.indexValue.toString(), - libIndex.indexValue.toString() - ); - assert.equal( - index.totalUnitsApproved.toString(), - libIndex.totalUnitsApproved.toString() - ); - assert.equal( - index.totalUnitsPending.toString(), - libIndex.totalUnitsPending.toString() - ); - }); - - it("#3.2 - calculate distribution", async () => { - const amount = 1; - const units = 1; - - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("Alice updates subscription units"); - await superTokenLibIDAMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - const distribution = await ida.calculateDistribution( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - amount - ); - const distributionLib = - await superTokenLibIDAMock.calculateDistributionTest( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - amount - ); - - assert.equal( - distribution.actualAmount.toString(), - distributionLib.actualAmount.toString() - ); - assert.equal( - distribution.newIndexValue.toString(), - distributionLib.newIndexValue.toString() - ); - }); - - it("#3.3 - list subscriptions", async () => { - const units = 1; - - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("Alice updates subscription units"); - await superTokenLibIDAMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - const subscriptions = await ida.listSubscriptions( - superToken.address, - bob - ); - const subscriptionsLib = - await superTokenLibIDAMock.listSubscriptionsTest( - superToken.address, - bob - ); - - expect(subscriptions.publishers).to.eql( - subscriptionsLib.publishers - ); - expect(subscriptions.indexIds).to.eql(subscriptionsLib.indexIds); - expect(subscriptions.unitsList).to.eql(subscriptionsLib.unitsList); - }); - - it("#3.4 - get subscription", async () => { - const units = 1; - - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("Alice updates subscription units"); - await superTokenLibIDAMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - const subscription = await ida.getSubscription( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - bob - ); - const subscriptionLib = - await superTokenLibIDAMock.getSubscriptionTest( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - bob - ); - - assert.equal(subscription.exist, subscriptionLib.exist); - assert.equal(subscription.approved, subscriptionLib.approved); - assert.equal( - subscription.units.toString(), - subscriptionLib.units.toString() - ); - assert.equal( - subscription.pendingDistribution.toString(), - subscriptionLib.pendingDistribution.toString() - ); - }); - - it("#3.5 - get subscription by id", async () => { - const units = 1; - - const publisherId = ethers.utils.solidityKeccak256( - ["string", "address", "uint32"], - ["publisher", superTokenLibIDAMock.address, INDEX_ID] - ); - const subscriptionId = ethers.utils.solidityKeccak256( - ["string", "address", "bytes32"], - ["subscription", bob, publisherId] - ); - - console.log("Alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("Alice updates subscription units"); - await superTokenLibIDAMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - const subscription = await ida.getSubscriptionByID( - superToken.address, - subscriptionId - ); - const subscriptionLib = - await superTokenLibIDAMock.getSubscriptionByIDTest( - superToken.address, - subscriptionId - ); - - assert.equal(subscription.approved, subscriptionLib.approved); - assert.equal( - subscription.units.toString(), - subscriptionLib.units.toString() - ); - assert.equal( - subscription.pendingDistribution.toString(), - subscriptionLib.pendingDistribution.toString() - ); - }); - }); - - // CALLBACK TESTS - // These tests pass `userData` to be extracted and used inside a super app callback. - describe("#4 - Callback Index Operations", async function () { - it("#4.1 - create index in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData(FunctionIndex.CREATE_INDEX, INDEX_ID) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDASuperAppMock.address, - INDEX_ID - ) - ).exist, - true - ); - }); - - it("#4.2 - create index in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData(FunctionIndex.CREATE_INDEX_USER_DATA, INDEX_ID) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDASuperAppMock.address, - INDEX_ID - ) - ).exist, - true - ); - }); - - it("#4.3 - update index in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("super app creates index"); - await superTokenLibIDASuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app adds subscription to bob"); - await superTokenLibIDASuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.UPDATE_INDEX, - INDEX_ID, - undefined, - undefined, - units - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDASuperAppMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - units - ); - }); - - it("#4.4 - update index in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("super app creates index"); - await superTokenLibIDASuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app adds subscription to bob"); - await superTokenLibIDASuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.UPDATE_INDEX_USER_DATA, - INDEX_ID, - undefined, - undefined, - units - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDASuperAppMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - units - ); - }); - - it("#4.5 - distribute in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("super app creates index"); - await superTokenLibIDASuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app adds subscription to bob"); - await superTokenLibIDASuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.DISTRIBUTE, - INDEX_ID, - undefined, - undefined, - units - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDASuperAppMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - units - ); - }); - - it("#4.6 - distribute in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("super app creates index"); - await superTokenLibIDASuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app adds subscription to bob"); - await superTokenLibIDASuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.DISTRIBUTE_USER_DATA, - INDEX_ID, - undefined, - undefined, - units - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDASuperAppMock.address, - INDEX_ID - ) - ).indexValue.toNumber(), - units - ); - }); - - it("#4.7 - approve subscription in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.APPROVE_SUBSCRIPTION, - INDEX_ID, - superTokenLibIDAMock.address - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ) - ).totalUnitsApproved.toNumber(), - units - ); - }); - - it("#4.8 - approve subscription in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.APPROVE_SUBSCRIPTION_USER_DATA, - INDEX_ID, - superTokenLibIDAMock.address - ) - ); - - assert.equal( - ( - await ida.getIndex( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ) - ).totalUnitsApproved.toNumber(), - units - ); - }); - - it("#4.9 - revoke subscription in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app approves subscription"); - await superTokenLibIDASuperAppMock.approveSubscriptionTest( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.REVOKE_SUBSCRIPTION, - INDEX_ID, - superTokenLibIDAMock.address - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address - ) - ).approved, - false - ); - }); - - it("#4.10 - revoke subscription in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app approves subscription"); - await superTokenLibIDASuperAppMock.approveSubscriptionTest( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.REVOKE_SUBSCRIPTION_USER_DATA, - INDEX_ID, - superTokenLibIDAMock.address - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address - ) - ).approved, - false - ); - }); - - it("#4.11 - update subscription units in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app creates index"); - await superTokenLibIDASuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.UPDATE_SUBSCRIPTION, - INDEX_ID, - undefined, - superTokenLibIDAMock.address, - units - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDASuperAppMock.address, - INDEX_ID, - superTokenLibIDAMock.address - ) - ).units.toNumber(), - units - ); - }); - - it("#4.12 - update subscription units in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app creates index"); - await superTokenLibIDASuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.UPDATE_SUBSCRIPTION_USER_DATA, - INDEX_ID, - undefined, - superTokenLibIDAMock.address, - units - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDASuperAppMock.address, - INDEX_ID, - superTokenLibIDAMock.address - ) - ).units.toNumber(), - units - ); - }); - - it("#4.13 - delete subscription in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app creates index"); - await superTokenLibIDASuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("super app issues units to bob"); - await superTokenLibIDASuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.DELETE_SUBSCRIPTION, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - bob - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDASuperAppMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - 0 - ); - }); - - it("#4.14 - delete subscription in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("super app creates index"); - await superTokenLibIDASuperAppMock.createIndexTest( - superToken.address, - INDEX_ID - ); - - console.log("super app issues units to bob"); - await superTokenLibIDASuperAppMock.updateSubscriptionUnitsTest( - superToken.address, - INDEX_ID, - bob, - units - ); - - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.DELETE_SUBSCRIPTION, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - bob - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDASuperAppMock.address, - INDEX_ID, - bob - ) - ).units.toNumber(), - 0 - ); - }); - - it("#4.15 - claim in callback", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.CLAIM, - INDEX_ID, - superTokenLibIDAMock.address, - superTokenLibIDASuperAppMock.address - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address - ) - ).pendingDistribution.toNumber(), - 0 - ); - }); - - it("#4.15 - claim in callback with user data", async () => { - const units = 1; - - console.log("alice creates index"); - await superTokenLibIDAMock.createIndexTest( - superToken.address, - INDEX_ID - ); - console.log("alice triggers callback on super app"); - await superTokenLibIDAMock.updateSubscriptionUnitsWithUserDataTest( - superToken.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address, - units, - userData( - FunctionIndex.CLAIM_USER_DATA, - INDEX_ID, - superTokenLibIDAMock.address, - superTokenLibIDASuperAppMock.address - ) - ); - - assert.equal( - ( - await ida.getSubscription( - superToken.address, - superTokenLibIDAMock.address, - INDEX_ID, - superTokenLibIDASuperAppMock.address - ) - ).pendingDistribution.toNumber(), - 0 - ); - }); - }); -}); diff --git a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol index 8ea5fe4b23..6abe6eb76a 100644 --- a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol +++ b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol @@ -383,6 +383,35 @@ contract FoundrySuperfluidTester is Test { assertTrue(_defintionAumGtEqSuperTokenTotalSupplyInvariant(), "Invariant: AUM > SuperToken Total Supply"); } + /// @notice Asserts that the real time balances for all active test accounts are expected + /// @dev We also take a balance snapshot after each assertion + /// @param superToken_ The SuperToken to check + function _assertRealTimeBalances(ISuperToken superToken_) internal { + address[] memory accounts = _listAccounts(); + for (uint i; i < accounts.length; ++i) { + address account = accounts[i]; + RealtimeBalance memory balanceSnapshot = _balanceSnapshots[superToken_][account]; + (int256 avb, uint256 deposit, uint256 owedDeposit, uint256 currentTime) = + superToken_.realtimeBalanceOfNow(account); + int96 cfaNetFlowRate = superToken_.getCFANetFlowRate(account); + + // GDA Net Flow Rate is 0 for pools because this is not accounted for in the pools' RTB + // however it is the disconnected flow rate for that pool + int96 gdaNetFlowRate = + sf.gda.isPool(superToken_, account) ? int96(0) : superToken_.getGDANetFlowRate(account); + int96 netFlowRate = cfaNetFlowRate + gdaNetFlowRate; + int256 amountFlowedSinceSnapshot = (currentTime - balanceSnapshot.timestamp).toInt256() * netFlowRate; + int256 expectedAvb = balanceSnapshot.availableBalance + amountFlowedSinceSnapshot; + + assertEq(balanceSnapshot.deposit, deposit, "Real Time Balances: deposit"); + assertEq(balanceSnapshot.owedDeposit, owedDeposit, "Real Time Balances: owed deposit"); + assertEq(avb, expectedAvb, "Real Time Balances: available balance"); + + _helperTakeBalanceSnapshot(superToken_, account); + } + } + + /// @notice Warps forwards 1 day and asserts balances of all testers and global invariants function _warpAndAssertAll(ISuperToken superToken_) internal virtual { vm.warp(block.timestamp + DEFAULT_WARP_TIME); @@ -1041,457 +1070,6 @@ contract FoundrySuperfluidTester is Test { _assertGlobalInvariants(); } - // Write Helpers - InstantDistributionAgreementV1 - - /// @notice Creates an index as a publisher with index id - /// @dev We assert: - /// - the index was created and is empty - /// We also add the publisher id to the list of index id's belonging to the publisher - /// @param superToken_ The SuperToken to create the index for - /// @param publisher The publisher of the index - /// @param indexId The index id to create - function _helperCreateIndex(ISuperToken superToken_, address publisher, uint32 indexId) internal { - vm.startPrank(publisher); - superToken_.createIndex(indexId); - vm.stopPrank(); - - _helperAssertCreateIndex(superToken_, publisher, indexId); - - _indexIDs[superToken_][publisher].add(_generatePublisherId(publisher, indexId)); - - // Assert Global Invariants - _assertGlobalInvariants(); - } - - function _helperAssertCreateIndex(ISuperToken superToken_, address publisher, uint32 indexId) internal view { - _assertIndexData(superToken_, publisher, indexId, true, 0, 0, 0); - } - - /// @notice Updates the index value of an index which distributes tokens to subscribers - /// @dev We assert: - /// - The index data has been updated as expected - /// - the publisher's balance and deposit has been updated as expected - /// @param superToken_ The SuperToken to update the index value for - /// @param publisher The publisher of the index - /// @param indexId The indexId to update - /// @param newIndexValue The new index value to update to - function _helperUpdateIndexValue(ISuperToken superToken_, address publisher, uint32 indexId, uint128 newIndexValue) - internal - { - // Get Index Data and Publisher Balance Before - (, uint128 indexValueBefore, uint128 totalUnitsApprovedBefore, uint128 totalUnitsPendingBefore) = - superToken_.getIndex(publisher, indexId); - - (int256 publisherAvbBefore, uint256 publisherDepositBefore,,) = superToken_.realtimeBalanceOfNow(publisher); - - // Execute Update Index Value - vm.startPrank(publisher); - superToken_.updateIndexValue(indexId, newIndexValue); - vm.stopPrank(); - - // Update Test State - _helperTakeBalanceSnapshot(superToken, publisher); - - // Assert Publisher AVB and Deposit - { - uint128 indexValueDelta = newIndexValue - indexValueBefore; - int256 distributionAmount = - uint256(indexValueDelta * (totalUnitsApprovedBefore + totalUnitsPendingBefore)).toInt256(); - uint256 depositDelta = indexValueDelta * totalUnitsPendingBefore; - _assertIndexData( - superToken_, publisher, indexId, true, newIndexValue, totalUnitsApprovedBefore, totalUnitsPendingBefore - ); - (int256 publisherAvbAfter, uint256 publisherDepositAfter,,) = superToken_.realtimeBalanceOfNow(publisher); - assertEq(publisherAvbAfter, publisherAvbBefore - distributionAmount, "Update Index: Publisher AVB"); - assertEq(publisherDepositAfter, publisherDepositBefore + depositDelta, "Update Index: Publisher Deposit"); - } - // TODO we could actually save all the subscribers of an index and loop over them down the line - // Assert that balance for subscriber has been updated (dependent on approval status) - - // Assert Global Invariants - _assertGlobalInvariants(); - } - - /// @notice Executes an IDA distribution of tokens to subscribers - /// @dev We assert: - /// - The index data has been updated as expected - /// - the publisher's balance and deposit has been updated as expected - /// @param superToken_ The SuperToken to update the index value for - /// @param publisher The publisher of the index - /// @param indexId The indexId to update - /// @param amount The new index value to update to - function _helperDistributeViaIDA(ISuperToken superToken_, address publisher, uint32 indexId, uint256 amount) - internal - { - // Get Index Data and Publisher Balance Before - (, uint128 indexValueBefore, uint128 totalUnitsApprovedBefore, uint128 totalUnitsPendingBefore) = - superToken_.getIndex(publisher, indexId); - - (int256 publisherAvbBefore, uint256 publisherDepositBefore,,) = superToken_.realtimeBalanceOfNow(publisher); - - // Get Calculated Distribution and assert is expected - (uint256 actualAmount, uint128 newIndexValue) = superToken_.calculateDistribution(publisher, indexId, amount); - - uint128 indexValueDelta = newIndexValue - indexValueBefore; - int256 distributionAmount = - uint256(indexValueDelta * (totalUnitsApprovedBefore + totalUnitsPendingBefore)).toInt256(); - - assertEq(actualAmount, distributionAmount.toUint256(), "Distribute: Distribution Amount"); - uint256 depositDelta = indexValueDelta * totalUnitsPendingBefore; - - // Execute Distribute - vm.startPrank(publisher); - superToken_.distribute(indexId, amount); - vm.stopPrank(); - - // Update Test State - _helperTakeBalanceSnapshot(superToken, publisher); - - // Assert Index Data, Publisher AVB and Deposit - _assertIndexData( - superToken_, publisher, indexId, true, newIndexValue, totalUnitsApprovedBefore, totalUnitsPendingBefore - ); - (int256 publisherAvbAfter, uint256 publisherDepositAfter,,) = superToken_.realtimeBalanceOfNow(publisher); - assertEq(publisherAvbAfter, publisherAvbBefore - distributionAmount, "Distribute: Publisher AVB"); - assertEq(publisherDepositAfter, publisherDepositBefore + depositDelta, "Distribute: Publisher Deposit"); - - // TODO we could actually save all the subscribers of an index and loop over them down the line - // Assert that balance for subscriber has been updated (dependent on approval status) - } - - /// @notice Updates subscription units for a subscriber - /// @dev We assert: - /// - The index data has been updated as expected - /// - The subscription data has been updated as expected - /// @param params The params for IDA subscription function - /// @param units The desired units - function _helperUpdateSubscriptionUnits(IDASubscriptionParams memory params, uint128 units) internal { - // Get Subscription Data Before - bytes32 subId = - _generateSubscriptionId(params.subscriber, _generatePublisherId(params.publisher, params.indexId)); - (, uint128 indexValue, uint128 totalUnitsApprovedBefore, uint128 totalUnitsPendingBefore) = - params.superToken.getIndex(params.publisher, params.indexId); - - (bool approved,,) = _helperTryGetSubscription(params.superToken, subId); - - // Execute Update Subscription Units - vm.startPrank(params.publisher); - params.superToken.updateSubscriptionUnits(params.indexId, params.subscriber, units); - vm.stopPrank(); - - // Assert Index Data and Subscription Data - { - uint128 expectedTotalUnitsApproved = approved ? totalUnitsApprovedBefore + units : totalUnitsApprovedBefore; - uint128 expectedTotalUnitsPending = approved ? totalUnitsPendingBefore : totalUnitsPendingBefore + units; - - _assertIndexData( - params.superToken, - params.publisher, - params.indexId, - true, - indexValue, - expectedTotalUnitsApproved, - expectedTotalUnitsPending - ); - - // subIndexValue here is equivalent because we update the subscriber - // without updating the indexValue here. - uint256 subIndexValue = indexValue; - _lastUpdatedSubIndexValues[params.superToken][subId] = indexValue; - uint256 pending = approved ? 0 : indexValue - subIndexValue * units; - - _assertSubscriptionData(params.superToken, subId, approved, units, pending); - - // Assert Global Invariants - _assertGlobalInvariants(); - } - } - - /// @notice Approves a subscription - /// @dev We assert: - /// - The index data has been updated as expected - /// - The subscription data has been updated as expected - /// - The subscriber's balance has been updated as expected - /// - The publisher's balance has been updated as expected - /// @param params The params for IDA subscription function - function _helperApproveSubscription(IDASubscriptionParams memory params) internal { - bytes32 subId = - _generateSubscriptionId(params.subscriber, _generatePublisherId(params.publisher, params.indexId)); - - // Get Balance Data Before - (int256 publisherAvbBefore, uint256 publisherDepositBefore,,) = - params.superToken.realtimeBalanceOfNow(params.publisher); - (int256 subscriberAvbBefore,,,) = params.superToken.realtimeBalanceOfNow(params.subscriber); - - // Get Index/Subscription Data - (, uint128 indexValue, uint128 totalUnitsApprovedBefore, uint128 totalUnitsPendingBefore) = - params.superToken.getIndex(params.publisher, params.indexId); - (, uint128 unitsBefore,) = _helperTryGetSubscription(params.superToken, subId); - - uint128 subIndexValueDelta = indexValue - _lastUpdatedSubIndexValues[params.superToken][subId]; - int256 balanceDelta = uint256(subIndexValueDelta * unitsBefore).toInt256(); - - // Assert Subscription Data Before - _assertSubscriptionData(params.superToken, subId, false, unitsBefore, balanceDelta.toUint256()); - - // Execute Approve Subscription - { - vm.startPrank(params.subscriber); - params.superToken.approveSubscription(params.publisher, params.indexId); - vm.stopPrank(); - } - - // Take Balance Snapshot - { - _helperTakeBalanceSnapshot(superToken, params.publisher); - _helperTakeBalanceSnapshot(superToken, params.subscriber); - } - - // Assert Publisher Balance Data - { - (int256 publisherAvbAfter, uint256 publisherDepositAfter,,) = - params.superToken.realtimeBalanceOfNow(params.publisher); - assertEq(publisherAvbAfter, publisherAvbBefore, "Approve: Publisher AVB"); - assertEq( - publisherDepositAfter, - (publisherDepositBefore.toInt256() - balanceDelta).toUint256(), - "Approve: Publisher Deposit" - ); - } - - // Assert Subscription Balance Data - { - (int256 subscriberAvbAfter,,,) = params.superToken.realtimeBalanceOfNow(params.subscriber); - assertEq(subscriberAvbAfter, subscriberAvbBefore + balanceDelta, "Approve: Subscriber AVB"); - } - - // Assert Subscription and Index Data - { - _assertSubscriptionData(params.superToken, subId, true, unitsBefore, 0); - _assertIndexData( - params.superToken, - params.publisher, - params.indexId, - true, - indexValue, - totalUnitsApprovedBefore + unitsBefore, - totalUnitsPendingBefore - unitsBefore - ); - } - - _lastUpdatedSubIndexValues[params.superToken][subId] = indexValue; - - // Assert Global Invariants - _assertGlobalInvariants(); - } - - /// @notice Revokes a subscription - /// @dev We assert: - /// - The index data has been updated as expected - /// - The subscription data has been updated as expected - /// - The subscriber's balance has been updated as expected - /// - The publisher's balance has been updated as expected - /// @param params The params for IDA subscription function - function _helperRevokeSubscription(IDASubscriptionParams memory params) internal { - bytes32 subId = - _generateSubscriptionId(params.subscriber, _generatePublisherId(params.publisher, params.indexId)); - - // Get Balance Data Before - (int256 publisherAvbBefore,,,) = params.superToken.realtimeBalanceOfNow(params.publisher); - (int256 subscriberAvbBefore,,,) = params.superToken.realtimeBalanceOfNow(params.subscriber); - - // Get Index/Subscription Data - (, uint128 indexValue, uint128 totalUnitsApprovedBefore, uint128 totalUnitsPendingBefore) = - params.superToken.getIndex(params.publisher, params.indexId); - (, uint128 unitsBefore,) = _helperTryGetSubscription(params.superToken, subId); - - uint128 subIndexValueDelta = indexValue - _lastUpdatedSubIndexValues[params.superToken][subId]; - int256 balanceDelta = uint256(subIndexValueDelta * unitsBefore).toInt256(); - - // Assert Subscription Data Before - _assertSubscriptionData(params.superToken, subId, true, unitsBefore, 0); - - // Execute Revoke Subscription - { - vm.startPrank(params.subscriber); - params.superToken.revokeSubscription(params.publisher, params.indexId); - vm.stopPrank(); - } - - // Take Balance Snapshot - { - _helperTakeBalanceSnapshot(superToken, params.publisher); - _helperTakeBalanceSnapshot(superToken, params.subscriber); - } - - // Assert Publisher Balance Data - { - (int256 publisherAvbAfter,,,) = params.superToken.realtimeBalanceOfNow(params.publisher); - assertEq(publisherAvbAfter, publisherAvbBefore, "Revoke: Publisher AVB"); - } - - // Assert Subscription Balance Data - { - (int256 subscriberAvbAfter,,,) = params.superToken.realtimeBalanceOfNow(params.subscriber); - assertEq(subscriberAvbAfter, subscriberAvbBefore + balanceDelta, "Revoke: Subscriber AVB"); - } - - // Assert Subscription and Index Data - { - _assertSubscriptionData(params.superToken, subId, false, unitsBefore, 0); - _assertIndexData( - params.superToken, - params.publisher, - params.indexId, - true, - indexValue, - totalUnitsApprovedBefore - unitsBefore, - totalUnitsPendingBefore + unitsBefore - ); - } - - _lastUpdatedSubIndexValues[params.superToken][subId] = indexValue; - - // Assert Global Invariants - _assertGlobalInvariants(); - } - - /// @notice Deletes a subscription - /// @dev We assert: - /// - The index data has been updated as expected - /// - The subscription data has been updated as expected - /// - The subscriber's balance has been updated as expected - /// - The publisher's balance has been updated as expected - /// @param params The params for IDA subscription function - function _helperDeleteSubscription(IDASubscriptionParams memory params) internal { - bytes32 subId = - _generateSubscriptionId(params.subscriber, _generatePublisherId(params.publisher, params.indexId)); - - // Get Balance Data Before - (int256 publisherAvbBefore, uint256 publisherDepositBefore,,) = - params.superToken.realtimeBalanceOfNow(params.publisher); - (int256 subscriberAvbBefore,,,) = params.superToken.realtimeBalanceOfNow(params.subscriber); - - // Get Index/Subscription Data - (, uint128 indexValue, uint128 totalUnitsApprovedBefore, uint128 totalUnitsPendingBefore) = - params.superToken.getIndex(params.publisher, params.indexId); - (bool approvedBefore, uint128 unitsBefore,) = _helperTryGetSubscription(params.superToken, subId); - - uint128 subIndexValueDelta = indexValue - _lastUpdatedSubIndexValues[params.superToken][subId]; - int256 balanceDelta = uint256(subIndexValueDelta * unitsBefore).toInt256(); - - // Assert Subscription Data Before - _assertSubscriptionData( - params.superToken, subId, approvedBefore, unitsBefore, approvedBefore ? 0 : balanceDelta.toUint256() - ); - - // Execute Delete Subscription - { - vm.startPrank(params.publisher); - params.superToken.deleteSubscription(params.publisher, params.indexId, params.subscriber); - vm.stopPrank(); - } - - // Take Balance Snapshot - { - _helperTakeBalanceSnapshot(superToken, params.publisher); - _helperTakeBalanceSnapshot(superToken, params.subscriber); - } - - // Assert Publisher Balance Data - { - (int256 publisherAvbAfter, uint256 publisherDeposit,,) = - params.superToken.realtimeBalanceOfNow(params.publisher); - assertEq(publisherAvbAfter, publisherAvbBefore, "Delete: Publisher AVB"); - assertEq(publisherDeposit, publisherDepositBefore - balanceDelta.toUint256(), "Delete: Publisher Deposit"); - } - - // Assert Subscription Balance Data - { - (int256 subscriberAvbAfter,,,) = params.superToken.realtimeBalanceOfNow(params.subscriber); - assertEq(subscriberAvbAfter, subscriberAvbBefore + balanceDelta, "Delete: Subscriber AVB"); - } - - // Assert Subscription and Index Data - { - _assertSubscriptionData(params.superToken, subId, false, 0, 0); - _assertIndexData( - params.superToken, - params.publisher, - params.indexId, - true, - indexValue, - totalUnitsApprovedBefore - unitsBefore, - totalUnitsPendingBefore - unitsBefore - ); - } - - _lastUpdatedSubIndexValues[params.superToken][subId] = 0; - - // Assert Global Invariants - _assertGlobalInvariants(); - } - - /// @notice Executes a claim for a subscription - /// @dev We assert: - /// - The subscriber's balance has been updated as expected - /// - The publisher's balance has been updated as expected - /// @param superToken_ The SuperToken to claim - /// @param caller The caller of the claim function - /// @param publisher The publisher of the subscription - /// @param indexId The index ID of the index - /// @param subscriber The subscriber of the subscription - function _helperClaimViaIDA( - ISuperToken superToken_, - address caller, - address publisher, - uint32 indexId, - address subscriber - ) internal { - bytes32 subId = _generateSubscriptionId(subscriber, _generatePublisherId(publisher, indexId)); - - // Get Balance Data Before - (, uint256 publisherDepositBefore,,) = superToken_.realtimeBalanceOfNow(publisher); - (int256 subscriberAvbBefore,,,) = superToken_.realtimeBalanceOfNow(subscriber); - - // Get Index/Subscription Data - (, uint128 indexValue,,) = superToken_.getIndex(publisher, indexId); - (, uint128 unitsBefore,) = _helperTryGetSubscription(superToken_, subId); - - uint128 subIndexValueDelta = indexValue - _lastUpdatedSubIndexValues[superToken_][subId]; - int256 pendingDistribution = uint256(subIndexValueDelta * unitsBefore).toInt256(); - - // Execute Claim - vm.startPrank(caller); - superToken_.claim(publisher, indexId, subscriber); - vm.stopPrank(); - - // Take Balance Snapshot - { - _helperTakeBalanceSnapshot(superToken, publisher); - _helperTakeBalanceSnapshot(superToken, subscriber); - } - - // Assert Publisher Balance Data - { - (, uint256 publisherDeposit,,) = superToken_.realtimeBalanceOfNow(publisher); - assertEq( - publisherDeposit, publisherDepositBefore - pendingDistribution.toUint256(), "Claim: Publisher Deposit" - ); - } - - // Assert Subscription Balance Data - { - (int256 subscriberAvbAfter,,,) = superToken_.realtimeBalanceOfNow(subscriber); - assertEq(subscriberAvbAfter, subscriberAvbBefore + pendingDistribution, "Claim: Subscriber AVB"); - } - - _lastUpdatedSubIndexValues[superToken_][subId] = indexValue; - - // Assert Global Invariants - _assertGlobalInvariants(); - } - // Write Helpers - GeneralDistributionAgreementV1/SuperfluidPool function _helperCreatePool( @@ -1565,7 +1143,7 @@ contract FoundrySuperfluidTester is Test { function _updateMemberUnits( ISuperfluidPool pool_, - ISuperToken poolSuperToken, + ISuperToken /*poolSuperToken*/, address caller_, address member_, uint128 newUnits_, @@ -1576,7 +1154,7 @@ contract FoundrySuperfluidTester is Test { if (useBools_.useForwarder) { sf.gdaV1Forwarder.updateMemberUnits(pool_, member_, newUnits_, new bytes(0)); } else { - poolSuperToken.updateMemberUnits(pool_, member_, newUnits_); + pool_.updateMemberUnits(member_, newUnits_); } } else { pool_.updateMemberUnits(member_, newUnits_); @@ -1817,7 +1395,7 @@ contract FoundrySuperfluidTester is Test { if (useForwarder) { sf.gdaV1Forwarder.distribute(superToken_, from_, pool_, requestedAmount, new bytes(0)); } else { - superToken_.distributeToPool(from_, pool_, requestedAmount); + superToken_.distribute(from_, pool_, requestedAmount); } vm.stopPrank(); } @@ -2180,81 +1758,6 @@ contract FoundrySuperfluidTester is Test { assertEq(owedDeposit, 0, "AccountFlowInfo: owed deposit"); } - // InstantDistributionAgreement Assertions - - /// @dev Asserts that the index data has been updated as expected - /// @param superToken_ The SuperToken to check - /// @param publisher The publisher of the index - /// @param indexId The index ID of the index - /// @param expectedExist Whether the index should exist - /// @param expectedIndexValue The expected index value - /// @param expectedTotalUnitsApproved The expected total units approved - /// @param expectedTotalUnitsPending The expected total units pending - function _assertIndexData( - ISuperToken superToken_, - address publisher, - uint32 indexId, - bool expectedExist, - uint128 expectedIndexValue, - uint128 expectedTotalUnitsApproved, - uint128 expectedTotalUnitsPending - ) internal view { - (bool exist, uint128 indexValue, uint128 totalUnitsApproved, uint128 totalUnitsPending) = - superToken_.getIndex(publisher, indexId); - - assertEq(exist, expectedExist, "IndexData: exist"); - assertEq(indexValue, expectedIndexValue, "IndexData: index value"); - assertEq(totalUnitsApproved, expectedTotalUnitsApproved, "IndexData: total units approved"); - assertEq(totalUnitsPending, expectedTotalUnitsPending, "IndexData: total units pending"); - } - - /// @dev Asserts that the subscription data has been updated as expected - /// @param superToken_ The SuperToken to check - /// @param subscriptionId The subscription ID of the subscription - /// @param expectedApproved Whether the subscription should be approved - /// @param expectedUnits The expected units - /// @param expectedPending The expected pending - function _assertSubscriptionData( - ISuperToken superToken_, - bytes32 subscriptionId, - bool expectedApproved, - uint128 expectedUnits, - uint256 expectedPending - ) internal view { - (,, bool approved, uint128 units, uint256 pending) = superToken_.getSubscriptionByID(subscriptionId); - assertEq(approved, expectedApproved, "SubscriptionData: approved"); - assertEq(units, expectedUnits, "SubscriptionData: units"); - assertEq(pending, expectedPending, "SubscriptionData: pending"); - } - - /// @notice Asserts that the real time balances for all active test accounts are expected - /// @dev We also take a balance snapshot after each assertion - /// @param superToken_ The SuperToken to check - function _assertRealTimeBalances(ISuperToken superToken_) internal { - address[] memory accounts = _listAccounts(); - for (uint i; i < accounts.length; ++i) { - address account = accounts[i]; - RealtimeBalance memory balanceSnapshot = _balanceSnapshots[superToken_][account]; - (int256 avb, uint256 deposit, uint256 owedDeposit, uint256 currentTime) = - superToken_.realtimeBalanceOfNow(account); - int96 cfaNetFlowRate = superToken_.getCFANetFlowRate(account); - - // GDA Net Flow Rate is 0 for pools because this is not accounted for in the pools' RTB - // however it is the disconnected flow rate for that pool - int96 gdaNetFlowRate = - sf.gda.isPool(superToken_, account) ? int96(0) : superToken_.getGDANetFlowRate(account); - int96 netFlowRate = cfaNetFlowRate + gdaNetFlowRate; - int256 amountFlowedSinceSnapshot = (currentTime - balanceSnapshot.timestamp).toInt256() * netFlowRate; - int256 expectedAvb = balanceSnapshot.availableBalance + amountFlowedSinceSnapshot; - - assertEq(balanceSnapshot.deposit, deposit, "Real Time Balances: deposit"); - assertEq(balanceSnapshot.owedDeposit, owedDeposit, "Real Time Balances: owed deposit"); - assertEq(avb, expectedAvb, "Real Time Balances: available balance"); - - _helperTakeBalanceSnapshot(superToken_, account); - } - } - // GeneralDistributionAgreement Assertions function _assertPoolAllowance(ISuperfluidPool _pool, address owner, address spender, uint256 expectedAllowance) diff --git a/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol b/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol index dbef4a798f..caf9a76654 100644 --- a/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol +++ b/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol @@ -19,7 +19,6 @@ contract SuperfluidFrameworkDeployerTest is FoundrySuperfluidTester { assertTrue(address(sf.resolver) != address(0), "SFDeployer: resolver not deployed"); assertTrue(address(sf.superfluidLoader) != address(0), "SFDeployer: superfluidLoader not deployed"); assertTrue(address(sf.cfaV1Forwarder) != address(0), "SFDeployer: cfaV1Forwarder not deployed"); - assertTrue(address(sf.idaV1Forwarder) != address(0), "SFDeployer: idaV1Forwarder not deployed"); assertTrue(address(sf.gdaV1Forwarder) != address(0), "SFDeployer: gdaV1Forwarder not deployed"); assertTrue(address(sf.macroForwarder) != address(0), "SFDeployer: macroForwarder not deployed"); assertTrue(address(sf.batchLiquidator) != address(0), "SFDeployer: batchLiquidator not deployed"); diff --git a/packages/ethereum-contracts/test/foundry/agreements/InstantDistributionAgreementV1.t.sol b/packages/ethereum-contracts/test/foundry/agreements/InstantDistributionAgreementV1.t.sol deleted file mode 100644 index 5dd754757a..0000000000 --- a/packages/ethereum-contracts/test/foundry/agreements/InstantDistributionAgreementV1.t.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: AGPLv3 -pragma solidity ^0.8.23; - -import "../FoundrySuperfluidTester.t.sol"; -import { ISuperToken } from "../../../contracts/superfluid/SuperToken.sol"; -import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol"; - -contract InstantDistributionAgreementV1IntegrationTest is FoundrySuperfluidTester { - using SuperTokenV1Library for ISuperToken; - - constructor() FoundrySuperfluidTester(3) { } - - function testAlice2Bob(uint32 indexId, uint32 units, uint32 newIndexValue) public { - vm.assume(units > 0); - - bool exist; - uint128 indexValue; - uint128 totalUnitsApproved; - uint128 totalUnitsPending; - - // alice creates index - _helperCreateIndex(superToken, alice, indexId); - - // alice updates subscription units for bob - IDASubscriptionParams memory params = - IDASubscriptionParams({ superToken: superToken, publisher: alice, subscriber: bob, indexId: indexId }); - - _helperUpdateSubscriptionUnits(params, units); - - // alice distributes - _helperUpdateIndexValue(superToken, alice, indexId, newIndexValue); - (exist, indexValue, totalUnitsApproved, totalUnitsPending) = superToken.getIndex(alice, indexId); - - // bob subscribes to alice's index - _helperApproveSubscription(params); - - _warpAndAssertAll(superToken); - } - - function testRevertMaxNumberOFSubscriptionsASubscriberCanHave() public { - uint32 maxNumSubs = sf.ida.MAX_NUM_SUBSCRIPTIONS(); - - for (uint256 i; i < maxNumSubs; ++i) { - vm.startPrank(alice); - superToken.createIndex(uint32(i)); - superToken.updateSubscriptionUnits(uint32(i), bob, 1); - vm.stopPrank(); - - vm.startPrank(bob); - superToken.approveSubscription(alice, uint32(i)); - vm.stopPrank(); - } - - (, uint32[] memory indexIds,) = superToken.listSubscriptions(bob); - assertEq(indexIds.length, maxNumSubs, "IDAv1.t: subscriptions length mismatch"); - - vm.startPrank(alice); - superToken.createIndex(uint32(maxNumSubs)); - superToken.updateSubscriptionUnits(uint32(maxNumSubs), bob, 1); - vm.stopPrank(); - - vm.startPrank(bob); - vm.expectRevert("SlotBitmap out of bound"); - superToken.approveSubscription(alice, uint32(maxNumSubs)); - vm.stopPrank(); - - _warpAndAssertAll(superToken); - } -} diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol index 5ddee61851..25335b7ba2 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -220,7 +220,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste vm.expectRevert(IGeneralDistributionAgreementV1.GDA_DISTRIBUTE_FROM_ANY_ADDRESS_NOT_ALLOWED.selector); vm.startPrank(bob); - superToken.distributeToPool(bob, pool, 1); + superToken.distribute(bob, pool, 1); vm.stopPrank(); } @@ -238,7 +238,11 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, useForwarder, config); vm.startPrank(bob); vm.expectRevert(IGeneralDistributionAgreementV1.GDA_NOT_POOL_ADMIN.selector); - superToken.updateMemberUnits(pool, bob, 69); + sf.host.callAgreement( + sf.gda, + abi.encodeCall(sf.gda.updateMemberUnits, (pool, bob, 69, new bytes(0))), + new bytes(0) + ); vm.stopPrank(); } @@ -264,7 +268,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste vm.startPrank(alice); vm.expectRevert(IGeneralDistributionAgreementV1.GDA_ONLY_SUPER_TOKEN_POOL.selector); - superToken.distributeToPool(alice, ISuperfluidPool(bob), requestedAmount); + superToken.distribute(alice, ISuperfluidPool(bob), requestedAmount); vm.stopPrank(); } @@ -273,7 +277,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste ISuperToken badToken = sfDeployer.deployNativeAssetSuperToken("Super Bad", "BADx"); vm.startPrank(alice); vm.expectRevert(IGeneralDistributionAgreementV1.GDA_ONLY_SUPER_TOKEN_POOL.selector); - badToken.distributeToPool(alice, ISuperfluidPool(bob), requestedAmount); + badToken.distribute(alice, ISuperfluidPool(bob), requestedAmount); vm.stopPrank(); } diff --git a/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol b/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol new file mode 100644 index 0000000000..c5bb7209df --- /dev/null +++ b/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: AGPLv3 +pragma solidity ^0.8.23; + +import { IConstantFlowAgreementV1 } from "../../../contracts/interfaces/agreements/IConstantFlowAgreementV1.sol"; +import { FoundrySuperfluidTester, ISuperToken, SuperTokenV1Library, ISuperfluidPool } + from "../FoundrySuperfluidTester.t.sol"; + +/* +* Note: since libs are used by contracts, not EOAs, do NOT try to use +* vm.prank() in tests. That will lead to unexpected outcomes. +* Instead, let the Test contract itself be the mock sender. +*/ +contract SuperTokenV1LibraryTest is FoundrySuperfluidTester { + using SuperTokenV1Library for ISuperToken; + + int96 internal constant DEFAULT_FLOWRATE = 1e12; + uint256 internal constant DEFAULT_AMOUNT = 1e18; + + constructor() FoundrySuperfluidTester(3) { + } + + function setUp() public override { + super.setUp(); + + // fund this Test contract with SuperTokens + vm.startPrank(alice); + superToken.transfer(address(this), 10e18); + vm.stopPrank(); + } + + // TESTS ======================================================================================== + + function testFlow() external { + // initial createFlow + superToken.flow(bob, DEFAULT_FLOWRATE); + assertEq(_getCFAFlowRate(address(this), bob), DEFAULT_FLOWRATE, "createFlow unexpected result"); + + // double it -> updateFlow + superToken.flow(bob, DEFAULT_FLOWRATE * 2); + assertEq(_getCFAFlowRate(address(this), bob), DEFAULT_FLOWRATE * 2, "updateFlow unexpected result"); + + // set to 0 -> deleteFlow + superToken.flow(bob, 0); + assertEq(_getCFAFlowRate(address(this), bob), 0, "deleteFlow unexpected result"); + + // invalid flowrate + vm.expectRevert(IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE.selector); + this.__externalflow(address(this), bob, -1); + } + + function testflowFrom() external { + // alice allows this Test contract to operate CFA flows on her behalf + vm.startPrank(alice); + sf.host.callAgreement( + sf.cfa, + abi.encodeCall(sf.cfa.authorizeFlowOperatorWithFullControl, (superToken, address(this), new bytes(0))), + new bytes(0) // userData + ); + vm.stopPrank(); + + // initial createFlow + superToken.flowFrom(alice, bob, DEFAULT_FLOWRATE); + assertEq(_getCFAFlowRate(alice, bob), DEFAULT_FLOWRATE, "createFlow unexpected result"); + + // double it -> updateFlow + superToken.flowFrom(alice, bob, DEFAULT_FLOWRATE * 2); + assertEq(_getCFAFlowRate(alice, bob), DEFAULT_FLOWRATE * 2, "updateFlow unexpected result"); + + // set to 0 -> deleteFlow + superToken.flowFrom(alice, bob, 0); + assertEq(_getCFAFlowRate(alice, bob), 0, "deleteFlow unexpected result"); + + vm.expectRevert(IConstantFlowAgreementV1.CFA_INVALID_FLOW_RATE.selector); + this.__externalflowFrom(address(this), alice, bob, -1); + } + + function testFlowXToAccount() external { + superToken.flowX(bob, DEFAULT_FLOWRATE); + assertEq(_getCFAFlowRate(address(this), bob), DEFAULT_FLOWRATE, "createFlow unexpected result"); + + // double it -> updateFlow + 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"); + } + + function testFlowXToPool() external { + ISuperfluidPool pool = superToken.createPool(); + pool.updateMemberUnits(bob, 1); + + superToken.flowX(address(pool), DEFAULT_FLOWRATE); + assertEq(_getGDAFlowRate(address(this), pool), DEFAULT_FLOWRATE, "distrbuteFlow (new) unexpected result"); + + // double it -> updateFlow + 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"); + } + + function testTransferXToAccount() external { + uint256 bobBalBefore = superToken.balanceOf(bob); + superToken.transferX(bob, DEFAULT_AMOUNT); + assertEq(superToken.balanceOf(bob) - bobBalBefore, DEFAULT_AMOUNT, "transfer unexpected result"); + } + + function testTransferXToPool() external { + uint256 bobBalBefore = superToken.balanceOf(bob); + ISuperfluidPool pool = superToken.createPool(); + pool.updateMemberUnits(bob, 1); + + superToken.transferX(address(pool), DEFAULT_AMOUNT); + pool.claimAll(bob); + assertEq(superToken.balanceOf(bob) - bobBalBefore, DEFAULT_AMOUNT, "distribute unexpected result"); + } + + function testCreatePool() external { + ISuperfluidPool pool = superToken.createPool(); + assertEq(pool.admin(), address(this)); + _assertDefaultPoolConfig(pool); + } + + function testCreatePoolWithAdmin() external { + ISuperfluidPool pool = superToken.createPool(alice); + assertEq(pool.admin(), alice); + _assertDefaultPoolConfig(pool); + } + + function testGetCFAFlowRate() external { + assertEq(superToken.getCFAFlowRate(address(this), bob), 0); + superToken.flow(bob, DEFAULT_FLOWRATE); + assertEq(superToken.getCFAFlowRate(address(this), bob), DEFAULT_FLOWRATE); + } + + function testGetCFAFlowInfo() external { + superToken.flow(bob, DEFAULT_FLOWRATE); + (uint256 refLastUpdated, int96 refFlowRate, uint256 refDeposit, uint256 refOwedDeposit) + = sf.cfa.getFlow(superToken, address(this), bob); + (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) = + superToken.getCFAFlowInfo(address(this), bob); + assertEq(refLastUpdated, lastUpdated); + assertEq(refFlowRate, flowRate); + assertEq(refDeposit, deposit); + assertEq(refOwedDeposit, owedDeposit); + } + + function testGetGDANetFlowRate() external { + ISuperfluidPool pool = superToken.createPool(); + pool.updateMemberUnits(bob, 1); + vm.startPrank(bob); + superToken.connectPool(pool); + vm.stopPrank(); + + assertEq(superToken.getGDANetFlowRate(address(this)), 0); + superToken.distributeFlow(pool, DEFAULT_FLOWRATE); + assertEq(superToken.getGDANetFlowRate(address(this)), -DEFAULT_FLOWRATE, "sender unexpected net flowrate"); + assertEq(superToken.getGDANetFlowRate(bob), DEFAULT_FLOWRATE, "receiver unexpected net flowrate"); + } + + function testGetGDANetFlowInfo() external { + ISuperfluidPool pool = superToken.createPool(); + pool.updateMemberUnits(bob, 1); + vm.startPrank(bob); + superToken.connectPool(pool); + vm.stopPrank(); + + (uint256 lastUpdated1, int96 flowRate1, uint256 deposit1, uint256 owedDeposit1) = + superToken.getGDANetFlowInfo(address(this)); + assertEq(flowRate1, 0); + assertEq(deposit1, 0); + assertEq(owedDeposit1, 0); + + skip(1); + superToken.distributeFlow(pool, DEFAULT_FLOWRATE); + + (uint256 lastUpdated2, int96 flowRate2, uint256 deposit2, uint256 owedDeposit2) = + superToken.getGDANetFlowInfo(address(this)); + assertEq(flowRate2, -DEFAULT_FLOWRATE, "sender unexpected net flowrate"); + assert(deposit2 > 0); + assertEq(owedDeposit2, 0); // GDA doesn't use owed deposits + assert(lastUpdated2 > lastUpdated1); + } + + function testGetTotalAmountReceivedFromPool() external { + ISuperfluidPool pool = superToken.createPool(); + pool.updateMemberUnits(bob, 1); + + assertEq(superToken.getTotalAmountReceivedFromPool(pool, bob), 0); + + // Test with instant distribution + superToken.transferX(address(pool), DEFAULT_AMOUNT); + pool.claimAll(bob); + assertEq(superToken.getTotalAmountReceivedFromPool(pool, bob), DEFAULT_AMOUNT); + + // Test with flow distribution + superToken.flowX(address(pool), DEFAULT_FLOWRATE); + // Wait a bit to accumulate some flow + vm.warp(block.timestamp + 1); + pool.claimAll(bob); + assert(superToken.getTotalAmountReceivedFromPool(pool, bob) > DEFAULT_AMOUNT); + + // check alias function + assertEq( + superToken.getTotalAmountReceivedFromPool(pool, bob), + superToken.getTotalAmountReceivedByMember(pool, bob) + ); + } + + function testGetGDAFlowRate() external { + ISuperfluidPool pool = superToken.createPool(); + pool.updateMemberUnits(bob, 1); + + assertEq(superToken.getGDAFlowRate(address(this), pool), 0); + superToken.flowX(address(pool), DEFAULT_FLOWRATE); + assertEq(superToken.getGDAFlowRate(address(this), pool), DEFAULT_FLOWRATE); + } + + function testGetGDAFlowInfo() external { + ISuperfluidPool pool = superToken.createPool(); + pool.updateMemberUnits(bob, 1); + + (uint256 lastUpdated1, int96 flowRate1, uint256 deposit1) = + superToken.getGDAFlowInfo(address(this), pool); + assertEq(flowRate1, 0); + assertEq(deposit1, 0); + + superToken.flowX(address(pool), DEFAULT_FLOWRATE); + + (uint256 lastUpdated2, int96 flowRate2, uint256 deposit2) = + superToken.getGDAFlowInfo(address(this), pool); + assertEq(flowRate2, DEFAULT_FLOWRATE); + assert(deposit2 > 0); + assert(lastUpdated2 > lastUpdated1); + } + + function testGetFlowRateWithCFA() external { + // Test CFA flow + assertEq(superToken.getFlowRate(address(this), bob), 0); + superToken.flow(bob, DEFAULT_FLOWRATE); + assertEq(superToken.getFlowRate(address(this), bob), DEFAULT_FLOWRATE); + } + + function testGetFlowRateWithGDA() external { + // Test GDA flow (flow distribution) + ISuperfluidPool pool = superToken.createPool(); + pool.updateMemberUnits(bob, 1); + + assertEq(superToken.getFlowRate(address(this), address(pool)), 0); + superToken.distributeFlow(pool, DEFAULT_FLOWRATE); + assertEq(superToken.getFlowRate(address(this), address(pool)), DEFAULT_FLOWRATE); + } + + function testGetFlowInfoWithCFA() external { + // Test CFA flow + (uint256 lastUpdated1, int96 flowRate1, uint256 deposit1, uint256 owedDeposit1) = + superToken.getFlowInfo(address(this), bob); + assertEq(flowRate1, 0); + assertEq(deposit1, 0); + assertEq(owedDeposit1, 0); + + superToken.flow(bob, DEFAULT_FLOWRATE); + + (uint256 lastUpdated2, int96 flowRate2, uint256 deposit2, uint256 owedDeposit2) = + superToken.getFlowInfo(address(this), bob); + assertEq(flowRate2, DEFAULT_FLOWRATE); + assert(deposit2 > 0); + assert(owedDeposit2 == 0); // No owed deposit in this case + assert(lastUpdated2 > lastUpdated1); + } + + function testGetFlowInfoWithGDA() external { + // Test GDA flow (flow distribution) + ISuperfluidPool pool = superToken.createPool(); + pool.updateMemberUnits(bob, 1); + + (uint256 lastUpdated1, int96 flowRate1, uint256 deposit1, uint256 owedDeposit1) = + superToken.getFlowInfo(address(this), address(pool)); + assertEq(flowRate1, 0); + assertEq(deposit1, 0); + assertEq(owedDeposit1, 0); + + superToken.distributeFlow(pool, DEFAULT_FLOWRATE); + + (uint256 lastUpdated2, int96 flowRate2, uint256 deposit2, uint256 owedDeposit2) = + superToken.getFlowInfo(address(this), address(pool)); + assertEq(flowRate2, DEFAULT_FLOWRATE); + assert(deposit2 > 0); + assertEq(owedDeposit2, 0); // GDA doesn't use owed deposits + assert(lastUpdated2 > lastUpdated1); + } + + // HELPER FUNCTIONS ======================================================================================== + + // direct use of the agreement for assertions + function _getCFAFlowRate(address sender, address receiver) public view returns (int96 flowRate) { + (,flowRate,,) = sf.cfa.getFlow(superToken, sender, receiver); + } + + // Note: this is without adjustmentFR + function _getGDAFlowRate(address sender, ISuperfluidPool pool) public view returns (int96 flowRate) { + return sf.gda.getFlowRate(superToken, sender, pool); + } + + function _assertDefaultPoolConfig(ISuperfluidPool pool) internal view { + assertEq(pool.transferabilityForUnitsOwner(), false); + assertEq(pool.distributionFromAnyAddress(), true); + } + + // helpers converting the lib call to an external call, for exception checking + + function __externalflow(address msgSender, address receiver, int96 flowRate) external { + vm.startPrank(msgSender); + superToken.flow(receiver, flowRate); + vm.stopPrank(); + } + + function __externalflowFrom(address msgSender, address sender, address receiver, int96 flowRate) external { + vm.startPrank(msgSender); + superToken.flowFrom(sender, receiver, flowRate); + vm.stopPrank(); + } +} \ No newline at end of file diff --git a/packages/ethereum-contracts/test/foundry/echidna/EchidnaTestCases.t.sol b/packages/ethereum-contracts/test/foundry/echidna/EchidnaTestCases.t.sol index 4a4140fac3..0199edaca0 100644 --- a/packages/ethereum-contracts/test/foundry/echidna/EchidnaTestCases.t.sol +++ b/packages/ethereum-contracts/test/foundry/echidna/EchidnaTestCases.t.sol @@ -25,6 +25,7 @@ contract EchidnaTestCases is FoundrySuperfluidTester { function testDistributeFlowToDisconnectedMember(address member, uint64 units, int32 flowRate, bool useForwarder) public { + vm.assume(member != address(0)); vm.assume(flowRate > 0); _helperUpdateMemberUnits(currentPool, alice, member, units); diff --git a/packages/ethereum-contracts/test/foundry/utils/IDAv1Forwarder.t.sol b/packages/ethereum-contracts/test/foundry/utils/IDAv1Forwarder.t.sol deleted file mode 100644 index 00cc942701..0000000000 --- a/packages/ethereum-contracts/test/foundry/utils/IDAv1Forwarder.t.sol +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-License-Identifier: AGPLv3 -pragma solidity ^0.8.23; - -import { FoundrySuperfluidTester, SuperTokenV1Library } from "../FoundrySuperfluidTester.t.sol"; -import { ISuperToken } from "../../../contracts/superfluid/SuperToken.sol"; -import { IDAv1Forwarder } from "../../../contracts/utils/IDAv1Forwarder.sol"; - -contract IDAv1ForwarderIntegrationTest is FoundrySuperfluidTester { - constructor() FoundrySuperfluidTester(3) { } - - function testIDAv1ForwarderCreateIndex(uint32 indexId, bytes memory userData) external { - vm.startPrank(alice); - sf.idaV1Forwarder.createIndex(superToken, indexId, userData); - _helperAssertCreateIndex(superToken, alice, indexId); - vm.stopPrank(); - } - - function testIDAv1ForwarderUpdateIndex(uint32 indexValue, bytes memory userData) external { - uint128 units = 10; - vm.assume(indexValue >= units); - - vm.startPrank(alice); - sf.idaV1Forwarder.createIndex(superToken, 0, userData); - _helperAssertCreateIndex(superToken, alice, 0); - - sf.idaV1Forwarder.updateSubscriptionUnits(superToken, 0, bob, units, userData); - - sf.idaV1Forwarder.updateIndex(superToken, 0, uint128(indexValue), userData); - vm.stopPrank(); - - (bool exist, uint128 indexValue_, uint128 totalUnitsApproved, uint128 totalUnitsPending) = - sf.idaV1Forwarder.getIndex(superToken, alice, 0); - assertTrue(exist, "testIDAv1ForwarderUpdateIndex: index should exist"); - assertEq(indexValue_, uint128(indexValue), "testIDAv1ForwarderUpdateIndex: index value mismatch"); - assertEq(totalUnitsApproved, 0, "testIDAv1ForwarderUpdateIndex: total approved units mismatch"); - assertEq(totalUnitsPending, units, "testIDAv1ForwarderUpdateIndex: total units pending mismatch"); - } - - function testIDAv1ForwarderDistribute(uint32 amount, bytes memory userData) external { - uint128 units = 10; - vm.assume(amount >= units); - - vm.startPrank(alice); - sf.idaV1Forwarder.createIndex(superToken, 0, userData); - _helperAssertCreateIndex(superToken, alice, 0); - - sf.idaV1Forwarder.updateSubscriptionUnits(superToken, 0, bob, units, userData); - (, uint128 newIndexValue) = sf.idaV1Forwarder.calculateDistribution(superToken, alice, 0, uint128(amount)); - - sf.idaV1Forwarder.distribute(superToken, 0, uint128(amount), userData); - vm.stopPrank(); - - (bool exist, uint128 indexValue_, uint128 totalUnitsApproved, uint128 totalUnitsPending) = - sf.idaV1Forwarder.getIndex(superToken, alice, 0); - assertTrue(exist, "testIDAv1ForwarderDistribute: index should exist"); - assertEq(indexValue_, newIndexValue, "testIDAv1ForwarderDistribute: index value mismatch"); - assertEq(totalUnitsApproved, 0, "testIDAv1ForwarderDistribute: total approved units mismatch"); - assertEq(totalUnitsPending, units, "testIDAv1ForwarderDistribute: total units pending mismatch"); - } - - function testIDAv1ForwarderApproveSubscription(address subscriber, uint128 units, bytes memory userData) external { - vm.assume(units > 0); - vm.assume(subscriber != address(0)); - vm.startPrank(alice); - sf.idaV1Forwarder.createIndex(superToken, 0, userData); - _helperAssertCreateIndex(superToken, alice, 0); - - sf.idaV1Forwarder.updateSubscriptionUnits(superToken, 0, subscriber, units, userData); - vm.stopPrank(); - vm.startPrank(subscriber); - sf.idaV1Forwarder.approveSubscription(superToken, alice, 0, userData); - vm.stopPrank(); - - (bool indexExists, uint128 indexValue, uint128 totalUnitsApproved, uint128 totalUnitsPending) = - sf.idaV1Forwarder.getIndex(superToken, alice, 0); - assertTrue(indexExists, "testIDAv1ForwarderApproveSubscription: index should exist"); - assertEq(indexValue, 0, "testIDAv1ForwarderApproveSubscription: index value mismatch"); - assertEq(totalUnitsApproved, units, "testIDAv1ForwarderApproveSubscription: total approved units mismatch"); - assertEq(totalUnitsPending, 0, "testIDAv1ForwarderApproveSubscription: total units pending mismatch"); - - (bool subExists, bool approved, uint128 units_, uint256 pendingDistribution) = - sf.idaV1Forwarder.getSubscription(superToken, alice, 0, subscriber); - assertTrue(subExists, "testIDAv1ForwarderApproveSubscription: subscription should exist"); - assertTrue(approved, "testIDAv1ForwarderApproveSubscription: subscription should be approved"); - assertEq(units_, units, "testIDAv1ForwarderApproveSubscription: subscription units mismatch"); - assertEq( - pendingDistribution, 0, "testIDAv1ForwarderApproveSubscription: subscription pending distribution mismatch" - ); - } - - function testIDAv1ForwarderRevokeSubscription(address subscriber, uint128 units, bytes memory userData) external { - vm.assume(units > 0); - vm.assume(subscriber != address(0)); - vm.startPrank(alice); - sf.idaV1Forwarder.createIndex(superToken, 0, userData); - _helperAssertCreateIndex(superToken, alice, 0); - - sf.idaV1Forwarder.updateSubscriptionUnits(superToken, 0, subscriber, units, userData); - vm.stopPrank(); - - vm.startPrank(subscriber); - sf.idaV1Forwarder.approveSubscription(superToken, alice, 0, userData); - sf.idaV1Forwarder.revokeSubscription(superToken, alice, 0, userData); - vm.stopPrank(); - - (bool indexExists, uint128 indexValue, uint128 totalUnitsApproved, uint128 totalUnitsPending) = - sf.idaV1Forwarder.getIndex(superToken, alice, 0); - assertTrue(indexExists, "testIDAv1ForwarderRevokeSubscription: index should exist"); - assertEq(indexValue, 0, "testIDAv1ForwarderRevokeSubscription: index value mismatch"); - assertEq(totalUnitsApproved, 0, "testIDAv1ForwarderRevokeSubscription: total approved units mismatch"); - assertEq(totalUnitsPending, units, "testIDAv1ForwarderRevokeSubscription: total units pending mismatch"); - - (bool subExists, bool approved, uint128 units_, uint256 pendingDistribution) = - sf.idaV1Forwarder.getSubscription(superToken, alice, 0, subscriber); - assertTrue(subExists, "testIDAv1ForwarderRevokeSubscription: subscription should exist"); - assertTrue(!approved, "testIDAv1ForwarderRevokeSubscription: subscription should not be approved"); - assertEq(units_, units, "testIDAv1ForwarderRevokeSubscription: subscription units mismatch"); - assertEq( - pendingDistribution, 0, "testIDAv1ForwarderRevokeSubscription: subscription pending distribution mismatch" - ); - } - - function testIDAv1ForwarderDeleteSubscription(address subscriber, uint128 units, bytes memory userData) external { - vm.assume(units > 0); - vm.assume(subscriber != address(0)); - vm.startPrank(alice); - sf.idaV1Forwarder.createIndex(superToken, 0, userData); - _helperAssertCreateIndex(superToken, alice, 0); - - sf.idaV1Forwarder.updateSubscriptionUnits(superToken, 0, subscriber, units, userData); - sf.idaV1Forwarder.deleteSubscription(superToken, alice, 0, subscriber, userData); - vm.stopPrank(); - - (bool indexExists, uint128 indexValue, uint128 totalUnitsApproved, uint128 totalUnitsPending) = - sf.idaV1Forwarder.getIndex(superToken, alice, 0); - assertTrue(indexExists, "testIDAv1ForwarderDeleteSubscription: index should exist"); - assertEq(indexValue, 0, "testIDAv1ForwarderDeleteSubscription: index value mismatch"); - assertEq(totalUnitsApproved, 0, "testIDAv1ForwarderDeleteSubscription: total approved units mismatch"); - assertEq(totalUnitsPending, 0, "testIDAv1ForwarderDeleteSubscription: total units pending mismatch"); - - (bool subExists, bool approved, uint128 units_, uint256 pendingDistribution) = - sf.idaV1Forwarder.getSubscription(superToken, alice, 0, subscriber); - assertTrue(!subExists, "testIDAv1ForwarderDeleteSubscription: subscription should not exist"); - assertTrue(!approved, "testIDAv1ForwarderDeleteSubscription: subscription should not be approved"); - assertEq(units_, 0, "testIDAv1ForwarderDeleteSubscription: subscription units mismatch"); - assertEq( - pendingDistribution, 0, "testIDAv1ForwarderDeleteSubscription: subscription pending distribution mismatch" - ); - } - - function testIDAv1ForwarderGetSubscriptionByID( - address publisher, - address subscriber, - uint128 units, - uint32 indexId, - bytes memory userData - ) external { - vm.assume(subscriber != address(0)); - vm.assume(publisher != address(0)); - vm.assume(units > 0); - - vm.startPrank(publisher); - sf.idaV1Forwarder.createIndex(superToken, indexId, userData); - _helperAssertCreateIndex(superToken, publisher, indexId); - sf.idaV1Forwarder.updateSubscriptionUnits(superToken, indexId, subscriber, units, userData); - vm.stopPrank(); - - bytes32 publisherId = sf.idaV1Forwarder.getPublisherId(publisher, indexId); - bytes32 subscriptionId = sf.idaV1Forwarder.getSubscriptionId(subscriber, publisherId); - (address publisher_, uint32 indexId_, bool approved, uint128 units_, uint256 pendingDistribution) = - sf.idaV1Forwarder.getSubscriptionByID(superToken, subscriptionId); - - assertEq(publisher, publisher_, "testIDAv1ForwarderGetSubscriptionByID: publisher mismatch"); - assertEq(indexId, indexId_, "testIDAv1ForwarderGetSubscriptionByID: index ID mismatch"); - assertFalse(approved, "testIDAv1ForwarderGetSubscriptionByID: subscription should not be approved"); - assertEq(units, units_, "testIDAv1ForwarderGetSubscriptionByID: units mismatch"); - assertEq(pendingDistribution, 0, "testIDAv1ForwarderGetSubscriptionByID: pending distribution mismatch"); - } - - function testIDAv1ForwarderEmptyListSubscriptions(address subscriber) external view { - vm.assume(subscriber != address(0)); - (address[] memory publishers, uint32[] memory indexIds, uint128[] memory unitsList) = - sf.idaV1Forwarder.listSubscriptions(superToken, subscriber); - assertEq(publishers.length, 0, "testIDAv1ForwarderEmptyListSubscriptions: publishers length mismatch"); - assertEq(indexIds.length, 0, "testIDAv1ForwarderEmptyListSubscriptions: index IDs length mismatch"); - assertEq(unitsList.length, 0, "testIDAv1ForwarderEmptyListSubscriptions: units length mismatch"); - } -} diff --git a/packages/ethereum-contracts/testsuites/apps-contracts.ts b/packages/ethereum-contracts/testsuites/apps-contracts.ts index 20edbb21fc..7e393fce31 100644 --- a/packages/ethereum-contracts/testsuites/apps-contracts.ts +++ b/packages/ethereum-contracts/testsuites/apps-contracts.ts @@ -1,5 +1,3 @@ import "../test/contracts/apps/SuperTokenV1Library.CFA.test"; -import "../test/contracts/apps/SuperTokenV1Library.IDA.test"; import "../test/contracts/apps/SuperTokenV1Library.GDA.test"; -import "../test/contracts/apps/CFAv1Library.test"; -import "../test/contracts/apps/IDAv1Library.test"; \ No newline at end of file + diff --git a/packages/hot-fuzz/contracts/HotFuzzBase.sol b/packages/hot-fuzz/contracts/HotFuzzBase.sol index bdd313dfe2..96a1455fd5 100644 --- a/packages/hot-fuzz/contracts/HotFuzzBase.sol +++ b/packages/hot-fuzz/contracts/HotFuzzBase.sol @@ -4,19 +4,14 @@ pragma solidity >= 0.8.0; import { TestToken } from "@superfluid-finance/ethereum-contracts/contracts/utils/TestToken.sol"; -import { Superfluid } from "@superfluid-finance/ethereum-contracts/contracts/superfluid/Superfluid.sol"; +import { ISuperfluid, Superfluid } from "@superfluid-finance/ethereum-contracts/contracts/superfluid/Superfluid.sol"; import { SuperToken } from "@superfluid-finance/ethereum-contracts/contracts/superfluid/SuperToken.sol"; import { ConstantFlowAgreementV1 } from "@superfluid-finance/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol"; -import { - InstantDistributionAgreementV1 -} from "@superfluid-finance/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol"; import { SuperfluidFrameworkDeployer } from "@superfluid-finance/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.t.sol"; -import "@superfluid-finance/ethereum-contracts/contracts/apps/CFAv1Library.sol"; -import "@superfluid-finance/ethereum-contracts/contracts/apps/IDAv1Library.sol"; import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; @@ -25,7 +20,6 @@ import { IERC20, ISuperToken, IConstantFlowAgreementV1, - IInstantDistributionAgreementV1, SuperfluidTester } from "./SuperfluidTester.sol"; diff --git a/packages/hot-fuzz/contracts/SuperfluidTester.sol b/packages/hot-fuzz/contracts/SuperfluidTester.sol index e7c4e2b9a3..e84d94871f 100644 --- a/packages/hot-fuzz/contracts/SuperfluidTester.sol +++ b/packages/hot-fuzz/contracts/SuperfluidTester.sol @@ -8,9 +8,6 @@ import {ISuperfluidPool} from import {PoolConfig} from "@superfluid-finance/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; import "@superfluid-finance/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol"; -import "@superfluid-finance/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol"; -import "@superfluid-finance/ethereum-contracts/contracts/apps/CFAv1Library.sol"; -import "@superfluid-finance/ethereum-contracts/contracts/apps/IDAv1Library.sol"; import {SuperTokenV1Library} from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; import "@superfluid-finance/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.t.sol"; @@ -123,39 +120,6 @@ contract SuperfluidTester { ); } - // IDA functions - function createIndex(uint32 indexId) public { - superToken.createIndex(indexId); - } - - function updateSubscriptionUnits(uint32 indexId, address subscriber, uint128 units) public { - superToken.updateSubscriptionUnits(indexId, subscriber, units); - } - - function updateIndex(uint32 indexId, uint128 indexValue) public { - superToken.updateIndexValue(indexId, indexValue); - } - - function distribute(uint32 indexId, uint256 amount) public { - superToken.distribute(indexId, amount); - } - - function approveSubscription(address publisher, uint32 indexId) public { - superToken.approveSubscription(publisher, indexId); - } - - function revokeSubscription(address publisher, uint32 indexId) public { - superToken.revokeSubscription(publisher, indexId); - } - - function deleteSubscription(address publisher, uint32 indexId, address subscriber) public { - superToken.deleteSubscription(publisher, indexId, subscriber); - } - - function claim(address publisher, uint32 indexId, address subscriber) public { - superToken.claim(publisher, indexId, subscriber); - } - // GDA functions function createPool(address admin, PoolConfig memory config) public returns (ISuperfluidPool pool) { pool = superToken.createPool(admin, config); @@ -169,8 +133,8 @@ contract SuperfluidTester { superToken.disconnectPool(pool); } - function distributeToPool(address from, ISuperfluidPool pool, uint256 requestedAmount) public { - superToken.distributeToPool(from, pool, requestedAmount); + function distribute(address from, ISuperfluidPool pool, uint256 requestedAmount) public { + superToken.distribute(from, pool, requestedAmount); } function distributeFlow(address from, ISuperfluidPool pool, int96 flowRate) public { diff --git a/packages/hot-fuzz/contracts/superfluid-tests/GeneralDistributionAgreementV1.hott.sol b/packages/hot-fuzz/contracts/superfluid-tests/GeneralDistributionAgreementV1.hott.sol index b1ad780664..cb96999759 100644 --- a/packages/hot-fuzz/contracts/superfluid-tests/GeneralDistributionAgreementV1.hott.sol +++ b/packages/hot-fuzz/contracts/superfluid-tests/GeneralDistributionAgreementV1.hott.sol @@ -37,11 +37,11 @@ abstract contract GDAHotFuzzMixin is HotFuzzBase { } } - function distributeToPool(uint8 a, uint8 b, uint128 requestedAmount) public { + function distribute(uint8 a, uint8 b, uint128 requestedAmount) public { (SuperfluidTester tester) = _getOneTester(a); ISuperfluidPool pool = getRandomPool(b); - tester.distributeToPool(address(tester), pool, requestedAmount); + tester.distribute(address(tester), pool, requestedAmount); } function distributeFlow(uint8 a, uint8 b, uint8 c, int96 flowRate) public { diff --git a/packages/hot-fuzz/contracts/superfluid-tests/IDAHotFuzz.yaml b/packages/hot-fuzz/contracts/superfluid-tests/IDAHotFuzz.yaml deleted file mode 100644 index f663f5dc81..0000000000 --- a/packages/hot-fuzz/contracts/superfluid-tests/IDAHotFuzz.yaml +++ /dev/null @@ -1 +0,0 @@ -testMode: "property" diff --git a/packages/hot-fuzz/contracts/superfluid-tests/InstantDistributionAgreementV1.hott.sol b/packages/hot-fuzz/contracts/superfluid-tests/InstantDistributionAgreementV1.hott.sol deleted file mode 100644 index aadbb7080b..0000000000 --- a/packages/hot-fuzz/contracts/superfluid-tests/InstantDistributionAgreementV1.hott.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: AGPLv3 -// solhint-disable reason-string -pragma solidity >= 0.8.0; - -import "../HotFuzzBase.sol"; - - -abstract contract IDAHotFuzzMixin is HotFuzzBase { - uint32 private constant MAX_NUM_INDICES = 3; - - function setupIndex(uint8 a, uint8 b, uint32 indexId, uint128 units) public { - indexId = indexId % MAX_NUM_INDICES; - - (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b); - - (bool exists,,,) = sf.ida.getIndex(superToken, address(testerA), indexId); - if (!exists) { - testerA.createIndex(indexId); - } - testerA.updateSubscriptionUnits(indexId, address(testerB), units); - } - - function distributeIfIndexExists(uint8 a, uint32 indexId, uint256 amount) public { - indexId = indexId % MAX_NUM_INDICES; - (SuperfluidTester testerA) = _getOneTester(a); - - (bool exists,,,) = sf.ida.getIndex(superToken, address(testerA), indexId); - if (exists) { - (uint256 actualAmount, ) = sf.ida.calculateDistribution(superToken, address(testerA), indexId, amount); - int256 a1 = _superTokenBalanceOfNow(address(testerA)); - testerA.distribute(indexId, amount); - int256 a2 = _superTokenBalanceOfNow(address(testerA)); - assert(a1 - a2 == int256(actualAmount)); - } - } - - function updateIndexIfIndexExists(uint8 a, uint32 indexId, uint128 indexValue) public { - indexId = indexId % MAX_NUM_INDICES; - (SuperfluidTester testerA) = _getOneTester(a); - - (bool exists,,,) = sf.ida.getIndex(superToken, address(testerA), indexId); - if (exists) { - testerA.updateIndex(indexId, indexValue); - } - } - - function revokeSubscription(uint8 a, uint8 b, uint32 indexId) public { - indexId = indexId % MAX_NUM_INDICES; - - (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b); - testerB.revokeSubscription(address(testerA), indexId); - } - - function deleteSubscription(uint8 a, uint8 b, uint32 indexId) public { - indexId = indexId % MAX_NUM_INDICES; - - (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b); - testerB.deleteSubscription(address(testerB), indexId, address(testerA)); - } - - function claim(uint8 a, uint8 b, uint32 indexId) public { - (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b); - testerB.claim(address(testerB), indexId, address(testerA)); - } - - function updateSubscriptionUnits(uint8 a, uint8 b, uint32 indexId, uint128 units) public { - require(units > 0); - - indexId = indexId % MAX_NUM_INDICES; - (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b); - - testerA.updateSubscriptionUnits(indexId, address(testerB), units); - bool exist; - uint128 unitsActual; - (exist, , unitsActual, ) = sf.ida.getSubscription(superToken, address(testerA), indexId, address(testerB)); - assert(exist == true); - assert(unitsActual == units); - } - - function approveSubscriptionUnits(uint8 a, uint8 b, uint32 indexId) public { - indexId = indexId % MAX_NUM_INDICES; - - (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b); - bool exist; - bool approved; - sf.ida.getSubscription(superToken, address(testerA), indexId, address(testerB)); - testerB.approveSubscription(address(testerA), indexId); - (exist, approved, , ) = sf.ida.getSubscription(superToken, address(testerA), indexId, address(testerB)); - //assert(exist1 == exist2); - assert(exist == true); - assert(approved == true); - } -} - -contract IDAHotFuzz is IDAHotFuzzMixin { - constructor() HotFuzzBase(10) { - _initTesters(); - } -} diff --git a/packages/hot-fuzz/contracts/superfluid-tests/SuperHotFuzz.sol b/packages/hot-fuzz/contracts/superfluid-tests/SuperHotFuzz.sol index 300c89846e..bc19752a91 100644 --- a/packages/hot-fuzz/contracts/superfluid-tests/SuperHotFuzz.sol +++ b/packages/hot-fuzz/contracts/superfluid-tests/SuperHotFuzz.sol @@ -2,12 +2,11 @@ pragma solidity >= 0.8.0; import "./ConstantFlowAgreementV1.hott.sol"; -import "./InstantDistributionAgreementV1.hott.sol"; import "./GeneralDistributionAgreementV1.hott.sol"; import "./SuperToken.hott.sol"; // Combine all the hot fuzzes -contract SuperHotFuzz is HotFuzzBase(10), CFAHotFuzzMixin, IDAHotFuzzMixin, GDAHotFuzzMixin, SuperTokenHotFuzzMixin { +contract SuperHotFuzz is HotFuzzBase(10), CFAHotFuzzMixin, GDAHotFuzzMixin, SuperTokenHotFuzzMixin { constructor() { _initTesters(); } diff --git a/packages/hot-fuzz/hot-fuzz b/packages/hot-fuzz/hot-fuzz index 6a0599a8aa..1ea4908694 100755 --- a/packages/hot-fuzz/hot-fuzz +++ b/packages/hot-fuzz/hot-fuzz @@ -43,12 +43,11 @@ cryticArgs: [ "--compile-force-framework=${CRYTIC_COMPILE_FRAMEWORK}", "--foundry-out-directory=${FOUNDRY_ROOT}/${FOUNDRY_OUT:-out}", # "--export-dir=${PROJECT_DIR}/crytic-export", TODO unfortunately this doesn't work - "--compile-libraries=(CFAv1ForwarderDeployerLibrary,0xf01),(GDAv1ForwarderDeployerLibrary,0xf02),(IDAv1ForwarderDeployerLibrary,0xf03),(ProxyDeployerLibrary,0xf04),(SlotsBitmapLibrary,0xf05),(SuperfluidCFAv1DeployerLibrary,0xf06),(SuperfluidGDAv1DeployerLibrary,0xf08),(SuperfluidGovDeployerLibrary,0xf09),(SuperfluidHostDeployerLibrary,0xf0a),(SuperfluidIDAv1DeployerLibrary,0xf0b),(SuperfluidPeripheryDeployerLibrary,0xf0c),(SuperfluidPoolDeployerLibrary,0xf0d),(SuperfluidPoolLogicDeployerLibrary,0xf0e),(SuperfluidPoolNFTLogicDeployerLibrary,0xf0f),(SuperTokenDeployerLibrary,0xf10),(SuperTokenFactoryDeployerLibrary,0xf11),(TokenDeployerLibrary,0xf12),(SuperfluidERC2771ForwarderDeployerLibrary,0xf13)" + "--compile-libraries=(CFAv1ForwarderDeployerLibrary,0xf01),(GDAv1ForwarderDeployerLibrary,0xf02),(ProxyDeployerLibrary,0xf04),(SlotsBitmapLibrary,0xf05),(SuperfluidCFAv1DeployerLibrary,0xf06),(SuperfluidGDAv1DeployerLibrary,0xf08),(SuperfluidGovDeployerLibrary,0xf09),(SuperfluidHostDeployerLibrary,0xf0a),(SuperfluidIDAv1DeployerLibrary,0xf0b),(SuperfluidPeripheryDeployerLibrary,0xf0c),(SuperfluidPoolDeployerLibrary,0xf0d),(SuperfluidPoolLogicDeployerLibrary,0xf0e),(SuperfluidPoolNFTLogicDeployerLibrary,0xf0f),(SuperTokenDeployerLibrary,0xf10),(SuperTokenFactoryDeployerLibrary,0xf11),(TokenDeployerLibrary,0xf12),(SuperfluidERC2771ForwarderDeployerLibrary,0xf13)" ] deployContracts: [ ["0xf01", "CFAv1ForwarderDeployerLibrary"], ["0xf02", "GDAv1ForwarderDeployerLibrary"], -["0xf03", "IDAv1ForwarderDeployerLibrary"], ["0xf04", "ProxyDeployerLibrary"], ["0xf05", "SlotsBitmapLibrary"], ["0xf06", "SuperfluidCFAv1DeployerLibrary"], diff --git a/packages/hot-fuzz/package.json b/packages/hot-fuzz/package.json index 4a37adfc3f..38b0911087 100644 --- a/packages/hot-fuzz/package.json +++ b/packages/hot-fuzz/package.json @@ -10,13 +10,13 @@ "@openzeppelin/contracts": "4.9.6" }, "devDependencies": { - "@superfluid-finance/ethereum-contracts": "^1.11.1" + "@superfluid-finance/ethereum-contracts": "^1.12.0" }, "homepage": "https://github.com/superfluid-finance/protocol-monorepo#readme", "license": "AGPL-3.0", "main": "index.js", "peerDependencies": { - "@superfluid-finance/ethereum-contracts": "1.11.1" + "@superfluid-finance/ethereum-contracts": "1.12.0" }, "repository": { "type": "git", diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 0e135e8f14..fb229b4911 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -13,7 +13,7 @@ "node-fetch": "2.7.0" }, "devDependencies": { - "@superfluid-finance/ethereum-contracts": "^1.11.1", + "@superfluid-finance/ethereum-contracts": "^1.12.0", "chai-as-promised": "^8.0.0", "webpack": "^5.94.0", "webpack-bundle-analyzer": "^4.10.2", diff --git a/packages/sdk-core/package.json b/packages/sdk-core/package.json index 28fb1dee4c..0ddafbdba4 100644 --- a/packages/sdk-core/package.json +++ b/packages/sdk-core/package.json @@ -4,7 +4,7 @@ "version": "0.8.0", "bugs": "https://github.com/superfluid-finance/protocol-monorepo/issues", "dependencies": { - "@superfluid-finance/ethereum-contracts": "1.11.1", + "@superfluid-finance/ethereum-contracts": "1.12.0", "@superfluid-finance/metadata": "^1.5.2", "browserify": "17.0.0", "graphql-request": "6.1.0",