From ad75968a1961fe132093322744225089c0e11841 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Fri, 8 Mar 2024 12:02:00 +0200 Subject: [PATCH 01/13] WIP --- .../gdav1/GeneralDistributionAgreementV1.sol | 45 ++++++++++++++++++- .../agreements/gdav1/SuperfluidPool.sol | 17 +++++++ .../contracts/apps/SuperTokenV1Library.sol | 4 ++ .../gdav1/IGeneralDistributionAgreementV1.sol | 41 +++++------------ .../GeneralDistributionAgreementV1.prop.sol | 25 ++++------- 5 files changed, 85 insertions(+), 47 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 1f7f32f5ee..8e1eb1f1f5 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -32,6 +32,7 @@ import { SafeGasLibrary } from "../../libs/SafeGasLibrary.sol"; import { AgreementBase } from "../AgreementBase.sol"; import { AgreementLibrary } from "../AgreementLibrary.sol"; + /** * @title General Distribution Agreement * @author Superfluid @@ -80,6 +81,25 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi using SafeCast for int256; using SemanticMoney for BasicParticle; + struct UniversalIndexData { + int96 flowRate; + uint32 settledAt; + uint256 totalBuffer; + bool isPool; + int256 settledValue; + } + + struct PoolMemberData { + address pool; + uint32 poolID; // the slot id in the pool's subs bitmap + } + + struct FlowDistributionData { + uint32 lastUpdated; + int96 flowRate; + uint256 buffer; // stored as uint96 + } + address public constant SLOTS_BITMAP_LIBRARY_ADDRESS = address(SlotsBitmapLibrary); address public constant SUPERFLUID_POOL_DEPLOYER_ADDRESS = address(SuperfluidPoolDeployerLibrary); @@ -170,6 +190,18 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return data.flowRate; } + function getFlow(ISuperfluidToken token, address from, ISuperfluidPool to) + external + view + override + returns (uint256 lastUpdated, int96 flowRate, uint256 deposit) + { + (, FlowDistributionData memory data) = _getFlowDistributionData(token, _getFlowDistributionHash(from, to)); + lastUpdated = data.lastUpdated; + flowRate = data.flowRate; + deposit = data.buffer; + } + /// @inheritdoc IGeneralDistributionAgreementV1 function estimateFlowDistributionActualFlowRate( ISuperfluidToken token, @@ -428,6 +460,15 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi FlowRate oldFlowRate; } + struct _StackVars_Liquidation { + ISuperfluidToken token; + int256 availableBalance; + address sender; + bytes32 distributionFlowHash; + int256 signedTotalGDADeposit; + address liquidator; + } + /// @inheritdoc IGeneralDistributionAgreementV1 function distributeFlow( ISuperfluidToken token, @@ -482,7 +523,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi // liquidation case, requestedFlowRate == 0 (int256 availableBalance,,) = token.realtimeBalanceOf(from, flowVars.currentContext.timestamp); // StackVarsLiquidation used to handle good ol' stack too deep - StackVarsLiquidation memory liquidationData; + _StackVars_Liquidation memory liquidationData; { liquidationData.token = token; liquidationData.sender = from; @@ -612,7 +653,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } } - function _makeLiquidationPayouts(StackVarsLiquidation memory data) internal { + function _makeLiquidationPayouts(_StackVars_Liquidation memory data) internal { (, FlowDistributionData memory flowDistributionData) = _getFlowDistributionData(ISuperfluidToken(data.token), data.distributionFlowHash); int256 signedSingleDeposit = flowDistributionData.buffer.toInt256(); diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index 2be38c3b6e..3a82f9e85b 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -37,6 +37,23 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { using SafeCast for uint256; using SafeCast for int256; + // Structs + struct PoolIndexData { + uint128 totalUnits; + uint32 wrappedSettledAt; + int96 wrappedFlowRate; + int256 wrappedSettledValue; + } + + struct MemberData { + uint128 ownedUnits; + uint32 syncedSettledAt; + int96 syncedFlowRate; + int256 syncedSettledValue; + int256 settledValue; + int256 claimedValue; + } + GeneralDistributionAgreementV1 public immutable GDA; ISuperfluidToken public superToken; diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index ee6f1ee95b..5f41243117 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -833,6 +833,10 @@ library SuperTokenV1Library { (lastUpdated, flowRate, deposit, owedDeposit) = cfa.getFlow(token, sender, receiver); } + /* 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 diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol index 5b4e1fdb3b..8fde0bb732 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol @@ -19,35 +19,6 @@ struct PoolConfig { * @author Superfluid */ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement { - // Structs - struct UniversalIndexData { - int96 flowRate; - uint32 settledAt; - uint256 totalBuffer; - bool isPool; - int256 settledValue; - } - - struct FlowDistributionData { - uint32 lastUpdated; - int96 flowRate; - uint256 buffer; // stored as uint96 - } - - struct PoolMemberData { - address pool; - uint32 poolID; // the slot id in the pool's subs bitmap - } - - struct StackVarsLiquidation { - ISuperfluidToken token; - int256 availableBalance; - address sender; - bytes32 distributionFlowHash; - int256 signedTotalGDADeposit; - address liquidator; - } - // Custom Errors error GDA_DISTRIBUTE_FOR_OTHERS_NOT_ALLOWED(); // 0xf67d263e @@ -131,6 +102,18 @@ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement { virtual returns (int96); + function getFlow(ISuperfluidToken token, address from, ISuperfluidPool to) + external + view + virtual + returns (uint256 lastUpdated, int96 flowRate, uint256 deposit); + + function getAccountFlow(ISuperfluidPool token, address account) + external + view + virtual + returns (uint256 timestamp, int96 flowRate, uint256 deposit); + /// @notice Executes an optimistic estimation of what the actual flow distribution flow rate may be. /// The actual flow distribution flow rate is the flow rate that will be sent from `from`. /// NOTE: this is only precise in an atomic transaction. DO NOT rely on this if querying off-chain. diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.sol index f5bd9d89ad..a1901cbba0 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.sol @@ -12,9 +12,7 @@ import { SuperfluidUpgradeableBeacon } from "../../../../contracts/upgradability import { ISuperToken, SuperToken } from "../../../../contracts/superfluid/SuperToken.sol"; import { ISuperAgreement } from "../../../../contracts/interfaces/superfluid/ISuperAgreement.sol"; import { - GeneralDistributionAgreementV1, - ISuperfluid, - ISuperfluidPool + GeneralDistributionAgreementV1, ISuperfluid, ISuperfluidPool } from "../../../../contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol"; import { IGeneralDistributionAgreementV1, @@ -124,7 +122,7 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen vm.warp(1000); - (bool exist, IGeneralDistributionAgreementV1.FlowDistributionData memory setFlowDistributionData) = + (bool exist, FlowDistributionData memory setFlowDistributionData) = _getFlowDistributionData(superToken, flowHash); assertEq(true, exist, "flow distribution data does not exist"); @@ -156,14 +154,11 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen vm.startPrank(address(this)); superToken.updateAgreementData( poolMemberId, - _encodePoolMemberData( - IGeneralDistributionAgreementV1.PoolMemberData({ poolID: poolID, pool: address(_pool) }) - ) + _encodePoolMemberData(PoolMemberData({ poolID: poolID, pool: address(_pool) })) ); vm.stopPrank(); - (bool exist, IGeneralDistributionAgreementV1.PoolMemberData memory setPoolMemberData) = - _getPoolMemberData(superToken, poolMember, _pool); + (bool exist, PoolMemberData memory setPoolMemberData) = _getPoolMemberData(superToken, poolMember, _pool); assertEq(true, exist, "pool member data does not exist"); assertEq(poolID, setPoolMemberData.poolID, "poolID not equal"); @@ -333,11 +328,10 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen function testEncodeDecodeFlowDistributionData(int96 flowRate, uint96 buffer) public { vm.assume(flowRate >= 0); vm.assume(buffer >= 0); - IGeneralDistributionAgreementV1.FlowDistributionData memory original = IGeneralDistributionAgreementV1 - .FlowDistributionData({ flowRate: flowRate, lastUpdated: uint32(block.timestamp), buffer: buffer }); + FlowDistributionData memory original = + FlowDistributionData({ flowRate: flowRate, lastUpdated: uint32(block.timestamp), buffer: buffer }); bytes32[] memory encoded = _encodeFlowDistributionData(original); - (, IGeneralDistributionAgreementV1.FlowDistributionData memory decoded) = - _decodeFlowDistributionData(uint256(encoded[0])); + (, FlowDistributionData memory decoded) = _decodeFlowDistributionData(uint256(encoded[0])); assertEq(original.flowRate, decoded.flowRate, "flowRate not equal"); assertEq(original.buffer, decoded.buffer, "buffer not equal"); @@ -346,10 +340,9 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen function testEncodeDecodePoolMemberData(address pool, uint32 poolID) public { vm.assume(pool != address(0)); - IGeneralDistributionAgreementV1.PoolMemberData memory original = - IGeneralDistributionAgreementV1.PoolMemberData({ pool: pool, poolID: poolID }); + PoolMemberData memory original = PoolMemberData({ pool: pool, poolID: poolID }); bytes32[] memory encoded = _encodePoolMemberData(original); - (, IGeneralDistributionAgreementV1.PoolMemberData memory decoded) = _decodePoolMemberData(uint256(encoded[0])); + (, PoolMemberData memory decoded) = _decodePoolMemberData(uint256(encoded[0])); assertEq(original.pool, decoded.pool, "pool not equal"); assertEq(original.poolID, decoded.poolID, "poolID not equal"); From 413016a076634bf2d7d2430da3c22f0f3ab9b777 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Tue, 12 Mar 2024 15:07:33 +0200 Subject: [PATCH 02/13] lint fixes --- .../gdav1/GeneralDistributionAgreementV1.sol | 1 + .../gov/SuperfluidGovernanceII.test.ts | 18 ++++++++++++------ .../invalidateSpecificCacheTagsForEvents.ts | 5 ++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 8e1eb1f1f5..73bcecf420 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -460,6 +460,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi FlowRate oldFlowRate; } + // solhint-disable-next-line contract-name-camelcase struct _StackVars_Liquidation { ISuperfluidToken token; int256 availableBalance; diff --git a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts index 79839f200c..8e06678697 100644 --- a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts +++ b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts @@ -721,9 +721,12 @@ describe("Superfluid Ownable Governance Contract", function () { ); await governance .connect(aliceSigner) - [ - "setConfig(address,address,bytes32,address)" - ](superfluid.address, FAKE_TOKEN_ADDRESS1, SUPERFLUID_REWARD_ADDRESS_CONFIG_KEY, FAKE_ADDRESS2); + ["setConfig(address,address,bytes32,address)"]( + superfluid.address, + FAKE_TOKEN_ADDRESS1, + SUPERFLUID_REWARD_ADDRESS_CONFIG_KEY, + FAKE_ADDRESS2 + ); assert.equal( await governance.getRewardAddress( @@ -798,9 +801,12 @@ describe("Superfluid Ownable Governance Contract", function () { ); await governance .connect(aliceSigner) - [ - "setConfig(address,address,bytes32,uint256)" - ](superfluid.address, FAKE_TOKEN_ADDRESS1, SUPERTOKEN_MINIMUM_DEPOSIT_KEY, 123456); + ["setConfig(address,address,bytes32,uint256)"]( + superfluid.address, + FAKE_TOKEN_ADDRESS1, + SUPERTOKEN_MINIMUM_DEPOSIT_KEY, + 123456 + ); assert.equal( ( diff --git a/packages/sdk-redux/src/reduxSlices/rtkQuery/cacheTags/invalidateSpecificCacheTagsForEvents.ts b/packages/sdk-redux/src/reduxSlices/rtkQuery/cacheTags/invalidateSpecificCacheTagsForEvents.ts index b6bfcd6406..dabc837607 100644 --- a/packages/sdk-redux/src/reduxSlices/rtkQuery/cacheTags/invalidateSpecificCacheTagsForEvents.ts +++ b/packages/sdk-redux/src/reduxSlices/rtkQuery/cacheTags/invalidateSpecificCacheTagsForEvents.ts @@ -153,9 +153,8 @@ const getEventSpecificTags = (event: AllEvents, chainId: number) => { return []; default: console.warn( - `Unknown event [${ - (event as any)?.name - }] in "invalidateCacheTagsForEvents" for @superfluid-finance/sdk-redux. Cache might not be invalidated properly.` + `Unknown event [${(event as any) + ?.name}] in "invalidateCacheTagsForEvents" for @superfluid-finance/sdk-redux. Cache might not be invalidated properly.` ); return []; } From c217cafc16e59fa47a06915eeb6cac531d997569 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Tue, 12 Mar 2024 15:22:41 +0200 Subject: [PATCH 03/13] fix build errors --- .../gdav1/GeneralDistributionAgreementV1.sol | 16 +++++++++++++++- .../gdav1/IGeneralDistributionAgreementV1.sol | 15 ++++++++++++++- .../agreements/gdav1/ISuperfluidPool.sol | 17 ----------------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 73bcecf420..1773dcc274 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -190,6 +190,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return data.flowRate; } + /// @inheritdoc IGeneralDistributionAgreementV1 function getFlow(ISuperfluidToken token, address from, ISuperfluidPool to) external view @@ -202,6 +203,19 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi deposit = data.buffer; } + /// @inheritdoc IGeneralDistributionAgreementV1 + function getAccountFlowInfo(ISuperfluidToken token, address account) + external + view + override + returns (uint256 timestamp, int96 flowRate, uint256 deposit) + { + UniversalIndexData memory universalIndexData = _getUIndexData(abi.encode(token), account); + timestamp = universalIndexData.settledAt; + flowRate = universalIndexData.flowRate; + deposit = universalIndexData.totalBuffer; + } + /// @inheritdoc IGeneralDistributionAgreementV1 function estimateFlowDistributionActualFlowRate( ISuperfluidToken token, @@ -912,7 +926,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi bytes memory, // eff, address pool ) internal view override returns (PDPoolIndex memory) { - ISuperfluidPool.PoolIndexData memory data = SuperfluidPool(pool).getIndex(); + SuperfluidPool.PoolIndexData memory data = SuperfluidPool(pool).getIndex(); return SuperfluidPool(pool).poolIndexDataToPDPoolIndex(data); } diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol index 8fde0bb732..e8f9c63159 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol @@ -102,13 +102,26 @@ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement { virtual returns (int96); + /// @dev Gets the GDA flow data between `from` and `to` of `token` + /// @param token The token address + /// @param from The sender address + /// @param to The receiver address + /// @return lastUpdated The timestamp of when the flow was last updated + /// @return flowRate The flow rate + /// @return deposit The amount of deposit the flow function getFlow(ISuperfluidToken token, address from, ISuperfluidPool to) external view virtual returns (uint256 lastUpdated, int96 flowRate, uint256 deposit); - function getAccountFlow(ISuperfluidPool token, address account) + /// @dev Gets the aggregated GDA flow info of `account` for `token` + /// @param token The token address + /// @param account The account address + /// @return timestamp The timestamp of when the flow was last updated for account + /// @return flowRate The net flow rate of token for account + /// @return deposit The sum of all deposits for account's flows + function getAccountFlowInfo(ISuperfluidToken token, address account) external view virtual diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol index 4dee5ca426..3dd8d2098f 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol @@ -9,23 +9,6 @@ import { ISuperfluidToken } from "../../superfluid/ISuperfluidToken.sol"; */ interface ISuperfluidPool is IERC20 { - // Structs - struct PoolIndexData { - uint128 totalUnits; - uint32 wrappedSettledAt; - int96 wrappedFlowRate; - int256 wrappedSettledValue; - } - - struct MemberData { - uint128 ownedUnits; - uint32 syncedSettledAt; - int96 syncedFlowRate; - int256 syncedSettledValue; - int256 settledValue; - int256 claimedValue; - } - // Custom Errors error SUPERFLUID_POOL_INVALID_TIME(); // 0x83c35016 From ec93d5d06cb1c1c1617ff39e2f45dee8871f9d07 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Tue, 12 Mar 2024 15:52:14 +0200 Subject: [PATCH 04/13] new functions to lib --- .../contracts/apps/SuperTokenV1Library.sol | 84 ++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index 5f41243117..9fc0279798 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -833,6 +833,23 @@ 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) */ /* { */ /* } */ @@ -897,7 +914,72 @@ library SuperTokenV1Library { } /** - * @dev calculate buffer for a flow rate + * @dev get the aggregated flow info of the account (CFA + GDA) + * @param token Super token address + * @param account Account to query + * @return cfaLastUpdated Timestamp of the last change of the CFA net flow + * @return gdaLastUpdated Timestamp of the last change of the GDA 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 getAccountFlowInfo(ISuperToken token, address account) + internal + view + returns (uint256 cfaLastUpdated, uint256 gdaLastUpdated, 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); + (uint256 lastUpdatedGDA, int96 gdaNetFlowRate, uint256 gdaDeposit) = gda.getAccountFlowInfo(token, account); + int96 accountNetFlowRate = cfaNetFlowRate + gdaNetFlowRate; + uint256 accountDeposit = cfaDeposit + gdaDeposit; + + return (lastUpdatedCFA, lastUpdatedGDA, accountNetFlowRate, accountDeposit, cfaOwedDeposit); + } + + /** + * @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 getCFAAccountFlowInfo(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 getGDAAccountFlowInfo(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); + } + + /** + * @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 From 0ce50e18a331568d40aac5899d3619bf98e44fce Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Tue, 12 Mar 2024 16:08:47 +0200 Subject: [PATCH 05/13] fix formatting --- .../gov/SuperfluidGovernanceII.test.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts index 8e06678697..0df31e266d 100644 --- a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts +++ b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts @@ -721,12 +721,12 @@ describe("Superfluid Ownable Governance Contract", function () { ); await governance .connect(aliceSigner) - ["setConfig(address,address,bytes32,address)"]( - superfluid.address, - FAKE_TOKEN_ADDRESS1, - SUPERFLUID_REWARD_ADDRESS_CONFIG_KEY, - FAKE_ADDRESS2 - ); + ["setConfig(address,address,bytes32,address)"]( + superfluid.address, + FAKE_TOKEN_ADDRESS1, + SUPERFLUID_REWARD_ADDRESS_CONFIG_KEY, + FAKE_ADDRESS2 + ); assert.equal( await governance.getRewardAddress( @@ -801,12 +801,12 @@ describe("Superfluid Ownable Governance Contract", function () { ); await governance .connect(aliceSigner) - ["setConfig(address,address,bytes32,uint256)"]( - superfluid.address, - FAKE_TOKEN_ADDRESS1, - SUPERTOKEN_MINIMUM_DEPOSIT_KEY, - 123456 - ); + ["setConfig(address,address,bytes32,uint256)"]( + superfluid.address, + FAKE_TOKEN_ADDRESS1, + SUPERTOKEN_MINIMUM_DEPOSIT_KEY, + 123456 + ); assert.equal( ( From 6e64b5f46a4cbce16b08583d368a22b88768ef5e Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Tue, 12 Mar 2024 16:12:51 +0200 Subject: [PATCH 06/13] lint wip --- .../gov/SuperfluidGovernanceII.test.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts index 0df31e266d..8e06678697 100644 --- a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts +++ b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts @@ -721,12 +721,12 @@ describe("Superfluid Ownable Governance Contract", function () { ); await governance .connect(aliceSigner) - ["setConfig(address,address,bytes32,address)"]( - superfluid.address, - FAKE_TOKEN_ADDRESS1, - SUPERFLUID_REWARD_ADDRESS_CONFIG_KEY, - FAKE_ADDRESS2 - ); + ["setConfig(address,address,bytes32,address)"]( + superfluid.address, + FAKE_TOKEN_ADDRESS1, + SUPERFLUID_REWARD_ADDRESS_CONFIG_KEY, + FAKE_ADDRESS2 + ); assert.equal( await governance.getRewardAddress( @@ -801,12 +801,12 @@ describe("Superfluid Ownable Governance Contract", function () { ); await governance .connect(aliceSigner) - ["setConfig(address,address,bytes32,uint256)"]( - superfluid.address, - FAKE_TOKEN_ADDRESS1, - SUPERTOKEN_MINIMUM_DEPOSIT_KEY, - 123456 - ); + ["setConfig(address,address,bytes32,uint256)"]( + superfluid.address, + FAKE_TOKEN_ADDRESS1, + SUPERTOKEN_MINIMUM_DEPOSIT_KEY, + 123456 + ); assert.equal( ( From 34603ac95a42cff2ec81657c54bb8e283d8954c7 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Tue, 12 Mar 2024 16:24:00 +0200 Subject: [PATCH 07/13] yarn install lint fix --- .../gov/SuperfluidGovernanceII.test.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts index 8e06678697..79839f200c 100644 --- a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts +++ b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts @@ -721,12 +721,9 @@ describe("Superfluid Ownable Governance Contract", function () { ); await governance .connect(aliceSigner) - ["setConfig(address,address,bytes32,address)"]( - superfluid.address, - FAKE_TOKEN_ADDRESS1, - SUPERFLUID_REWARD_ADDRESS_CONFIG_KEY, - FAKE_ADDRESS2 - ); + [ + "setConfig(address,address,bytes32,address)" + ](superfluid.address, FAKE_TOKEN_ADDRESS1, SUPERFLUID_REWARD_ADDRESS_CONFIG_KEY, FAKE_ADDRESS2); assert.equal( await governance.getRewardAddress( @@ -801,12 +798,9 @@ describe("Superfluid Ownable Governance Contract", function () { ); await governance .connect(aliceSigner) - ["setConfig(address,address,bytes32,uint256)"]( - superfluid.address, - FAKE_TOKEN_ADDRESS1, - SUPERTOKEN_MINIMUM_DEPOSIT_KEY, - 123456 - ); + [ + "setConfig(address,address,bytes32,uint256)" + ](superfluid.address, FAKE_TOKEN_ADDRESS1, SUPERTOKEN_MINIMUM_DEPOSIT_KEY, 123456); assert.equal( ( From df994cffb5d854456faf4b5e6cf6ca9ab1810e7b Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Tue, 12 Mar 2024 16:50:43 +0200 Subject: [PATCH 08/13] i love it when lint errors are the issue --- .../cacheTags/invalidateSpecificCacheTagsForEvents.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/sdk-redux/src/reduxSlices/rtkQuery/cacheTags/invalidateSpecificCacheTagsForEvents.ts b/packages/sdk-redux/src/reduxSlices/rtkQuery/cacheTags/invalidateSpecificCacheTagsForEvents.ts index dabc837607..b6bfcd6406 100644 --- a/packages/sdk-redux/src/reduxSlices/rtkQuery/cacheTags/invalidateSpecificCacheTagsForEvents.ts +++ b/packages/sdk-redux/src/reduxSlices/rtkQuery/cacheTags/invalidateSpecificCacheTagsForEvents.ts @@ -153,8 +153,9 @@ const getEventSpecificTags = (event: AllEvents, chainId: number) => { return []; default: console.warn( - `Unknown event [${(event as any) - ?.name}] in "invalidateCacheTagsForEvents" for @superfluid-finance/sdk-redux. Cache might not be invalidated properly.` + `Unknown event [${ + (event as any)?.name + }] in "invalidateCacheTagsForEvents" for @superfluid-finance/sdk-redux. Cache might not be invalidated properly.` ); return []; } From 7ba71d03ca31f2bae7e3e3791f58b4bafb7b8386 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Thu, 14 Mar 2024 14:44:11 +0200 Subject: [PATCH 09/13] getTotalAmountReceivedByMember implemented --- .../agreements/gdav1/SuperfluidPool.sol | 37 +++++++++-------- .../agreements/gdav1/ISuperfluidPool.sol | 40 ++++++++++++++++--- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index 3a82f9e85b..b7feb708c6 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -37,23 +37,6 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { using SafeCast for uint256; using SafeCast for int256; - // Structs - struct PoolIndexData { - uint128 totalUnits; - uint32 wrappedSettledAt; - int96 wrappedFlowRate; - int256 wrappedSettledValue; - } - - struct MemberData { - uint128 ownedUnits; - uint32 syncedSettledAt; - int96 syncedFlowRate; - int256 syncedSettledValue; - int256 settledValue; - int256 claimedValue; - } - GeneralDistributionAgreementV1 public immutable GDA; ISuperfluidToken public superToken; @@ -231,6 +214,26 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { return Value.unwrap(PDPoolMemberMU(pdPoolIndex, pdPoolMember).rtb(Time.wrap(time))); } + /// @inheritdoc ISuperfluidPool + function getMemberData(address memberAddr) external view override returns (MemberData memory memberData) { + return _membersData[memberAddr]; + } + + /// @inheritdoc ISuperfluidPool + function getTotalAmountReceivedByMember(address memberAddr) external view override returns (uint256) { + MemberData memory memberData = _membersData[memberAddr]; + + // max timestamp is uint32.max + return uint256( + Value.unwrap( + // PDPoolMemberMU(poolIndex, memberData) + PDPoolMemberMU(poolIndexDataToPDPoolIndex(_index), _memberDataToPDPoolMember(memberData)).settle( + Time.wrap(uint32(block.timestamp)) + ).m._settled_value + ) + ); + } + /// @inheritdoc ISuperfluidPool function getMemberFlowRate(address memberAddr) external view override returns (int96) { uint128 units = _getUnits(memberAddr); diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol index 3dd8d2098f..c70083acdf 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol @@ -8,6 +8,22 @@ import { ISuperfluidToken } from "../../superfluid/ISuperfluidToken.sol"; * @dev The interface for any super token pool regardless of the distribution schemes. */ interface ISuperfluidPool is IERC20 { + // Structs + struct PoolIndexData { + uint128 totalUnits; + uint32 wrappedSettledAt; + int96 wrappedFlowRate; + int256 wrappedSettledValue; + } + + struct MemberData { + uint128 ownedUnits; + uint32 syncedSettledAt; + int96 syncedFlowRate; + int256 syncedSettledValue; + int256 settledValue; + int256 claimedValue; + } // Custom Errors @@ -49,9 +65,9 @@ interface ISuperfluidPool is IERC20 { /// @notice The total number of units of disconnected members function getTotalDisconnectedUnits() external view returns (uint128); - /// @notice The total number of units for `memberAddress` - /// @param memberAddress The address of the member - function getUnits(address memberAddress) external view returns (uint128); + /// @notice The total number of units for `memberAddr` + /// @param memberAddr The address of the member + function getUnits(address memberAddr) external view returns (uint128); /// @notice The total flow rate of the pool function getTotalFlowRate() external view returns (int96); @@ -66,9 +82,23 @@ interface ISuperfluidPool is IERC20 { /// @param time The time to query function getDisconnectedBalance(uint32 time) external view returns (int256 balance); + /// @notice Gets the index data of the pool + /// @return poolIndex The index data + function getIndex() external view returns (PoolIndexData memory poolIndex); + + /// @notice Gets the member data for `memberAddr` for the pool + /// @param memberAddr The address of the member + /// @return memberData The member data + function getMemberData(address memberAddr) external view returns (MemberData memory memberData); + + /// @notice The total amount received by `memberAddr` in the pool + /// @param memberAddr The address of the member + /// @return totalAmountReceived The total amount received by the member + function getTotalAmountReceivedByMember(address memberAddr) external view returns (uint256 totalAmountReceived); + /// @notice The flow rate a member is receiving from the pool - /// @param memberAddress The address of the member - function getMemberFlowRate(address memberAddress) external view returns (int96); + /// @param memberAddr The address of the member + function getMemberFlowRate(address memberAddr) external view returns (int96); /// @notice The claimable balance for `memberAddr` at `time` in the pool /// @param memberAddr The address of the member From 139971943b397a5d39d3932dbc77da73960c60b6 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Thu, 14 Mar 2024 15:27:06 +0200 Subject: [PATCH 10/13] free range functions --- .../gdav1/GeneralDistributionAgreementV1.sol | 6 +- .../agreements/gdav1/SuperfluidPool.sol | 65 +++++++++---------- .../superfluid/SuperfluidPool.prop.sol | 8 ++- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 1773dcc274..69feb6857f 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -14,7 +14,7 @@ import { FlowRate } from "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol"; import { TokenMonad } from "@superfluid-finance/solidity-semantic-money/src/TokenMonad.sol"; -import { SuperfluidPool } from "./SuperfluidPool.sol"; +import { poolIndexDataToPDPoolIndex, SuperfluidPool } from "./SuperfluidPool.sol"; import { SuperfluidPoolDeployerLibrary } from "./SuperfluidPoolDeployerLibrary.sol"; import { IGeneralDistributionAgreementV1, @@ -926,8 +926,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi bytes memory, // eff, address pool ) internal view override returns (PDPoolIndex memory) { - SuperfluidPool.PoolIndexData memory data = SuperfluidPool(pool).getIndex(); - return SuperfluidPool(pool).poolIndexDataToPDPoolIndex(data); + ISuperfluidPool.PoolIndexData memory data = SuperfluidPool(pool).getIndex(); + return poolIndexDataToPDPoolIndex(data); } function _setPDPIndex(bytes memory eff, address pool, PDPoolIndex memory p) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index b7feb708c6..f883206e5a 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -26,6 +26,36 @@ import { BeaconProxiable } from "../../upgradability/BeaconProxiable.sol"; import { IPoolMemberNFT } from "../../interfaces/agreements/gdav1/IPoolMemberNFT.sol"; import { SafeGasLibrary } from "../../libs/SafeGasLibrary.sol"; +using SafeCast for uint256; +using SafeCast for int256; + +function toSemanticMoneyUnit(uint128 units) pure returns (Unit) { + // @note safe upcasting from uint128 to uint256 + // and use of safecast library for downcasting from uint256 to int128 + return Unit.wrap(uint256(units).toInt256().toInt128()); +} + +function poolIndexDataToWrappedParticle(ISuperfluidPool.PoolIndexData memory data) + pure + returns (BasicParticle memory wrappedParticle) +{ + wrappedParticle = BasicParticle({ + _settled_at: Time.wrap(data.wrappedSettledAt), + _flow_rate: FlowRate.wrap(int128(data.wrappedFlowRate)), // upcast from int96 is safe + _settled_value: Value.wrap(data.wrappedSettledValue) + }); +} + +function poolIndexDataToPDPoolIndex(ISuperfluidPool.PoolIndexData memory data) + pure + returns (PDPoolIndex memory pdPoolIndex) +{ + pdPoolIndex = PDPoolIndex({ + total_units: toSemanticMoneyUnit(data.totalUnits), + _wrapped_particle: poolIndexDataToWrappedParticle(data) + }); +} + /** * @title SuperfluidPool * @author Superfluid @@ -34,8 +64,6 @@ import { SafeGasLibrary } from "../../libs/SafeGasLibrary.sol"; */ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { using SemanticMoney for BasicParticle; - using SafeCast for uint256; - using SafeCast for int256; GeneralDistributionAgreementV1 public immutable GDA; @@ -242,29 +270,6 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { else return (_index.wrappedFlowRate * uint256(units).toInt256()).toInt96(); } - function _poolIndexDataToWrappedParticle(PoolIndexData memory data) - internal - pure - returns (BasicParticle memory wrappedParticle) - { - wrappedParticle = BasicParticle({ - _settled_at: Time.wrap(data.wrappedSettledAt), - _flow_rate: FlowRate.wrap(int128(data.wrappedFlowRate)), // upcast from int96 is safe - _settled_value: Value.wrap(data.wrappedSettledValue) - }); - } - - function poolIndexDataToPDPoolIndex(PoolIndexData memory data) - public - pure - returns (PDPoolIndex memory pdPoolIndex) - { - pdPoolIndex = PDPoolIndex({ - total_units: _toSemanticMoneyUnit(data.totalUnits), - _wrapped_particle: _poolIndexDataToWrappedParticle(data) - }); - } - function _pdPoolIndexToPoolIndexData(PDPoolIndex memory pdPoolIndex) internal pure @@ -284,7 +289,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { returns (PDPoolMember memory pdPoolMember) { pdPoolMember = PDPoolMember({ - owned_units: _toSemanticMoneyUnit(memberData.ownedUnits), + owned_units: toSemanticMoneyUnit(memberData.ownedUnits), _synced_particle: BasicParticle({ _settled_at: Time.wrap(memberData.syncedSettledAt), _flow_rate: FlowRate.wrap(int128(memberData.syncedFlowRate)), // upcast from int96 is safe @@ -294,12 +299,6 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { }); } - function _toSemanticMoneyUnit(uint128 units) internal pure returns (Unit) { - // @note safe upcasting from uint128 to uint256 - // and use of safecast library for downcasting from uint256 to int128 - return Unit.wrap(uint256(units).toInt256().toInt128()); - } - function _pdPoolMemberToMemberData(PDPoolMember memory pdPoolMember, int256 claimedValue) internal pure @@ -411,7 +410,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { uint32 time = uint32(ISuperfluid(superToken.getHost()).getNow()); Time t = Time.wrap(time); - Unit wrappedUnits = _toSemanticMoneyUnit(newUnits); + Unit wrappedUnits = toSemanticMoneyUnit(newUnits); PDPoolIndex memory pdPoolIndex = poolIndexDataToPDPoolIndex(_index); MemberData memory memberData = _membersData[memberAddr]; diff --git a/packages/ethereum-contracts/test/foundry/superfluid/SuperfluidPool.prop.sol b/packages/ethereum-contracts/test/foundry/superfluid/SuperfluidPool.prop.sol index 0f697db6e9..943f4a782a 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/SuperfluidPool.prop.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/SuperfluidPool.prop.sol @@ -4,7 +4,11 @@ pragma solidity 0.8.23; import "forge-std/Test.sol"; import "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol"; import { GeneralDistributionAgreementV1 } from "../../../contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol"; -import { SuperfluidPool } from "../../../contracts/agreements/gdav1/SuperfluidPool.sol"; +import { + poolIndexDataToWrappedParticle, + poolIndexDataToPDPoolIndex, + SuperfluidPool +} from "../../../contracts/agreements/gdav1/SuperfluidPool.sol"; /// @title SuperfluidPool Property Tests /// @author Superfluid @@ -51,7 +55,7 @@ contract SuperfluidPoolProperties is SuperfluidPool, Test { } function testPoolIndexDataToWrappedParticle(PoolIndexData memory data) public { - BasicParticle memory wrappedParticle = _poolIndexDataToWrappedParticle(data); + BasicParticle memory wrappedParticle = poolIndexDataToWrappedParticle(data); _helperAssertWrappedParticle(data, wrappedParticle); } From 4a45496ec3a960524aacb91ebfb501a753cb30f3 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Fri, 15 Mar 2024 17:30:18 +0200 Subject: [PATCH 11/13] address review comments WIP --- .../gdav1/GeneralDistributionAgreementV1.sol | 2 +- .../agreements/gdav1/SuperfluidPool.sol | 30 +++++-- .../contracts/apps/SuperTokenV1Library.sol | 79 ++++++++++++------- .../agreements/gdav1/ISuperfluidPool.sol | 26 ------ 4 files changed, 74 insertions(+), 63 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 69feb6857f..cc395b8f3c 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -926,7 +926,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi bytes memory, // eff, address pool ) internal view override returns (PDPoolIndex memory) { - ISuperfluidPool.PoolIndexData memory data = SuperfluidPool(pool).getIndex(); + SuperfluidPool.PoolIndexData memory data = SuperfluidPool(pool).poolOperatorGetIndex(); return poolIndexDataToPDPoolIndex(data); } diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index 47449420cc..e40bc51aa9 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -35,7 +35,7 @@ function toSemanticMoneyUnit(uint128 units) pure returns (Unit) { return Unit.wrap(uint256(units).toInt256().toInt128()); } -function poolIndexDataToWrappedParticle(ISuperfluidPool.PoolIndexData memory data) +function poolIndexDataToWrappedParticle(SuperfluidPool.PoolIndexData memory data) pure returns (BasicParticle memory wrappedParticle) { @@ -46,7 +46,7 @@ function poolIndexDataToWrappedParticle(ISuperfluidPool.PoolIndexData memory dat }); } -function poolIndexDataToPDPoolIndex(ISuperfluidPool.PoolIndexData memory data) +function poolIndexDataToPDPoolIndex(SuperfluidPool.PoolIndexData memory data) pure returns (PDPoolIndex memory pdPoolIndex) { @@ -65,6 +65,24 @@ function poolIndexDataToPDPoolIndex(ISuperfluidPool.PoolIndexData memory data) contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { using SemanticMoney for BasicParticle; + // Structs + struct PoolIndexData { + uint128 totalUnits; + uint32 wrappedSettledAt; + int96 wrappedFlowRate; + int256 wrappedSettledValue; + } + + struct MemberData { + uint128 ownedUnits; + uint32 syncedSettledAt; + int96 syncedFlowRate; + int256 syncedSettledValue; + int256 settledValue; + int256 claimedValue; + } + + GeneralDistributionAgreementV1 public immutable GDA; ISuperfluidToken public superToken; @@ -104,7 +122,8 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { return keccak256("org.superfluid-finance.contracts.SuperfluidPool.implementation"); } - function getIndex() external view returns (PoolIndexData memory) { + /// @dev This function is only meant to be called by the GDAv1 contract + function poolOperatorGetIndex() external view returns (PoolIndexData memory) { return _index; } @@ -242,11 +261,6 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { return Value.unwrap(PDPoolMemberMU(pdPoolIndex, pdPoolMember).rtb(Time.wrap(time))); } - /// @inheritdoc ISuperfluidPool - function getMemberData(address memberAddr) external view override returns (MemberData memory memberData) { - return _membersData[memberAddr]; - } - /// @inheritdoc ISuperfluidPool function getTotalAmountReceivedByMember(address memberAddr) external view override returns (uint256) { MemberData memory memberData = _membersData[memberAddr]; diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index 9fc0279798..7c768e9585 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -897,7 +897,7 @@ library SuperTokenV1Library { } /** - * @dev get the aggregated flow info of the account + * @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 @@ -906,38 +906,32 @@ library SuperTokenV1Library { * @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); - return cfa.getAccountFlowInfo(token, account); - } - - /** - * @dev get the aggregated flow info of the account (CFA + GDA) - * @param token Super token address - * @param account Account to query - * @return cfaLastUpdated Timestamp of the last change of the CFA net flow - * @return gdaLastUpdated Timestamp of the last change of the GDA 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 getAccountFlowInfo(ISuperToken token, address account) internal view - returns (uint256 cfaLastUpdated, uint256 gdaLastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) + 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); - (uint256 lastUpdatedGDA, int96 gdaNetFlowRate, uint256 gdaDeposit) = gda.getAccountFlowInfo(token, account); - int96 accountNetFlowRate = cfaNetFlowRate + gdaNetFlowRate; - uint256 accountDeposit = cfaDeposit + gdaDeposit; + { + (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); - return (lastUpdatedCFA, lastUpdatedGDA, accountNetFlowRate, accountDeposit, cfaOwedDeposit); + if (lastUpdatedGDA > lastUpdated) { + lastUpdated = lastUpdatedGDA; + } + flowRate += gdaNetFlowRate; + deposit += gdaDeposit; + } } /** @@ -949,7 +943,7 @@ 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 getCFAAccountFlowInfo(ISuperToken token, address account) + function getCFANetFlowInfo(ISuperToken token, address account) internal view returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) @@ -967,7 +961,7 @@ 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 getGDAAccountFlowInfo(ISuperToken token, address account) + function getGDANetFlowInfo(ISuperToken token, address account) internal view returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) @@ -976,6 +970,35 @@ library SuperTokenV1Library { (lastUpdated, flowRate, deposit) = gda.getAccountFlowInfo(token, account); } + /** + * @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 diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol index c70083acdf..510736278e 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol @@ -8,23 +8,6 @@ import { ISuperfluidToken } from "../../superfluid/ISuperfluidToken.sol"; * @dev The interface for any super token pool regardless of the distribution schemes. */ interface ISuperfluidPool is IERC20 { - // Structs - struct PoolIndexData { - uint128 totalUnits; - uint32 wrappedSettledAt; - int96 wrappedFlowRate; - int256 wrappedSettledValue; - } - - struct MemberData { - uint128 ownedUnits; - uint32 syncedSettledAt; - int96 syncedFlowRate; - int256 syncedSettledValue; - int256 settledValue; - int256 claimedValue; - } - // Custom Errors error SUPERFLUID_POOL_INVALID_TIME(); // 0x83c35016 @@ -82,15 +65,6 @@ interface ISuperfluidPool is IERC20 { /// @param time The time to query function getDisconnectedBalance(uint32 time) external view returns (int256 balance); - /// @notice Gets the index data of the pool - /// @return poolIndex The index data - function getIndex() external view returns (PoolIndexData memory poolIndex); - - /// @notice Gets the member data for `memberAddr` for the pool - /// @param memberAddr The address of the member - /// @return memberData The member data - function getMemberData(address memberAddr) external view returns (MemberData memory memberData); - /// @notice The total amount received by `memberAddr` in the pool /// @param memberAddr The address of the member /// @return totalAmountReceived The total amount received by the member From 55317ea8741bb1e66280d43649db757280012483 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Mon, 18 Mar 2024 12:48:45 +0200 Subject: [PATCH 12/13] test getAmountReceived lightly --- .../gdav1/GeneralDistributionAgreement.t.sol | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 f7ad6d91f2..12e2ff9aee 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -552,7 +552,17 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste _helperUpdateMemberUnits(pool, alice, members[i], memberUnits[i], useBools_); } + uint256 actualAmount = sf.gda.estimateDistributionActualAmount(superToken, alice, pool, distributionAmount); _helperDistributeViaGDA(superToken, alice, alice, pool, distributionAmount, useBools_.useForwarder); + + uint128 perUnitDistributionAmount = uint128(actualAmount / pool.getTotalUnits()); + for (uint256 i = 0; i < members.length; ++i) { + if (sf.gda.isPool(superToken, members[i]) || members[i] == address(0)) continue; + + uint128 memberUnits = pool.getUnits(members[i]); + + assertEq(perUnitDistributionAmount * memberUnits, pool.getTotalAmountReceivedByMember(members[i])); + } } function testDistributeToConnectedMembers( @@ -577,7 +587,17 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste _helperUpdateMemberUnits(pool, alice, members[i], memberUnits[i], useBools_); _addAccount(members[i]); } + uint256 actualAmount = sf.gda.estimateDistributionActualAmount(superToken, alice, pool, distributionAmount); _helperDistributeViaGDA(superToken, alice, alice, pool, distributionAmount, useBools_.useForwarder); + + uint128 perUnitDistributionAmount = uint128(actualAmount / pool.getTotalUnits()); + for (uint256 i = 0; i < members.length; ++i) { + if (sf.gda.isPool(superToken, members[i]) || members[i] == address(0)) continue; + + uint128 memberUnits = pool.getUnits(members[i]); + + assertEq(perUnitDistributionAmount * memberUnits, pool.getTotalAmountReceivedByMember(members[i])); + } } function testDistributeFlowToConnectedMembers( From 8de3a127cb0a60a0c6b683ed72c05311803e3908 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Mon, 18 Mar 2024 18:04:41 +0200 Subject: [PATCH 13/13] readme --- packages/ethereum-contracts/CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index fdc1c8be97..5f11d6b420 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -12,14 +12,27 @@ Initialization is now split between constructor and a method `_initialize`, with made optional. This allows the contract to be used with a SuperApp factory pattern (disable self-registration on networks with permissioned SuperApps) and for logic contracts in the context of the proxy pattern. Note: this will NOT break any deployed contracts, only affects undeployed Super Apps in case the ethereum-contracts dependency is updated. +- `UniversalIndexData`, `PoolMemberData` and `FlowDistributionData` structs moved from `IGeneralDistributionAgreementV1.sol` to `GeneralDistributionAgreementV1.sol` +- `PoolIndexData`, `MemberData` structs moved from `ISuperfluidPool.sol` to `SuperfluidPool.sol` ### Added - New utility: MacroForwarder - a trusted forwarder extensible with permission-less macro contracts. +- New protocol contract view functions: + - `gdaV1.getFlow` + - `gdaV1.getAccountFlowInfo` + - `pool.poolOperatorGetIndex` + - `pool.getTotalAmountReceivedByMember` +- New SuperTokenV1Library functions: + - `getGDAFlowInfo` + - `getGDANetFlowInfo` + - `getPoolAdjustmentFlowRate` + - `getTotalAmountReceivedByMember` ### Changed - bump solc to 0.8.23 +- `superTokenV1Library.getNetFlowInfo` sums CFA and GDA net flow info ## [v1.9.0] - 2024-01-09