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 diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 1f7f32f5ee..cc395b8f3c 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, @@ -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,32 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return data.flowRate; } + /// @inheritdoc IGeneralDistributionAgreementV1 + 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 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, @@ -428,6 +474,16 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi FlowRate oldFlowRate; } + // solhint-disable-next-line contract-name-camelcase + struct _StackVars_Liquidation { + ISuperfluidToken token; + int256 availableBalance; + address sender; + bytes32 distributionFlowHash; + int256 signedTotalGDADeposit; + address liquidator; + } + /// @inheritdoc IGeneralDistributionAgreementV1 function distributeFlow( ISuperfluidToken token, @@ -482,7 +538,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 +668,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(); @@ -870,8 +926,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi bytes memory, // eff, address pool ) internal view override returns (PDPoolIndex memory) { - ISuperfluidPool.PoolIndexData memory data = SuperfluidPool(pool).getIndex(); - return SuperfluidPool(pool).poolIndexDataToPDPoolIndex(data); + SuperfluidPool.PoolIndexData memory data = SuperfluidPool(pool).poolOperatorGetIndex(); + 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 11b06cb3f5..e40bc51aa9 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(SuperfluidPool.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(SuperfluidPool.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,24 @@ import { SafeGasLibrary } from "../../libs/SafeGasLibrary.sol"; */ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { using SemanticMoney for BasicParticle; - 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; @@ -76,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; } @@ -214,6 +261,21 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { return Value.unwrap(PDPoolMemberMU(pdPoolIndex, pdPoolMember).rtb(Time.wrap(time))); } + /// @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); @@ -222,29 +284,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 @@ -264,7 +303,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 @@ -274,12 +313,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 @@ -391,7 +424,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/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index ee6f1ee95b..7c768e9585 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -833,6 +833,27 @@ 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 @@ -876,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 @@ -885,7 +906,46 @@ library SuperTokenV1Library { * @return owedDeposit The sum of all owed deposits for account's flows */ function getNetFlowInfo(ISuperToken token, address account) - internal view + 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); @@ -893,7 +953,56 @@ library SuperTokenV1Library { } /** - * @dev calculate buffer for a flow rate + * @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); + } + + /** + * @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 diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol index 5b4e1fdb3b..e8f9c63159 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,31 @@ 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); + + /// @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 + 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/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol index 4dee5ca426..510736278e 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol @@ -8,24 +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 @@ -66,9 +48,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); @@ -83,9 +65,14 @@ interface ISuperfluidPool is IERC20 { /// @param time The time to query function getDisconnectedBalance(uint32 time) external view returns (int256 balance); + /// @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 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( 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"); 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); }