From d24a9ee11b1bd7fe49a756aad40f30a56e049629 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Thu, 3 Jul 2025 10:43:40 +0300 Subject: [PATCH 01/16] WIP; GDA storage layout refactoring --- .../agreements/gdav1/GDAv1StorageLayout.sol | 211 ++++++++++ .../gdav1/GeneralDistributionAgreementV1.sol | 366 ++++++------------ .../agreements/gdav1/SuperfluidPool.sol | 8 +- .../GeneralDistributionAgreementV1.prop.t.sol | 94 +++-- 4 files changed, 387 insertions(+), 292 deletions(-) create mode 100644 packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol new file mode 100644 index 0000000000..660066b35c --- /dev/null +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: AGPLv3 +pragma solidity ^0.8.23; +// open-zeppelin +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +// semantic-money +import { + BasicParticle, + Value, + Time, + FlowRate +} from "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol"; +// superfluid +import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol"; +import { + IGeneralDistributionAgreementV1 +} from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; +import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol"; + + +/* @title Storage layout reader for the GDAv1. + * @author Superfluid + * @notice + * + * Storage Layout Notes + * ## Agreement State + * + * Universal Index Data + * slotId = _universalIndexStateSlotId() or 0 + * msg.sender = address of GDAv1 + * account = context.msgSender + * Universal Index Data stores a Basic Particle for an account as well as the total buffer and + * whether the account is a pool or not. + * + * SlotsBitmap Data + * slotId = _POOL_SUBS_BITMAP_STATE_SLOT_ID or 1 + * msg.sender = address of GDAv1 + * account = context.msgSender + * Slots Bitmap Data Slot stores a bitmap of the slots that are "enabled" for a pool member. + * + * Pool Connections Data Slot Id Start + * slotId (start) = _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START or 1 << 128 or 340282366920938463463374607431768211456 + * msg.sender = address of GDAv1 + * account = context.msgSender + * Pool Connections Data Slot Id Start indicates the starting slot for where we begin to store the pools that a + * pool member is a part of. + * + * ## Agreement Data + * NOTE The Agreement Data slot is calculated with the following function: + * keccak256(abi.encode("AgreementData", agreementClass, agreementId)) + * agreementClass = address of GDAv1 + * agreementId = DistributionFlowId | PoolMemberId + * + * DistributionFlowId = + * keccak256(abi.encode(block.chainid, "distributionFlow", from, pool)) + * DistributionFlowId stores FlowDistributionData between a sender (from) and pool. + * + * PoolMemberId = + * keccak256(abi.encode(block.chainid, "poolMember", member, pool)) + * PoolMemberId stores PoolMemberData for a member at a pool. + */ +library GDAv1StorageReader { + uint256 internal constant ACCOUNT_DATA_STATE_SLOT_ID = 0; + + // # Account Data Operations + // + // Account data includes: + // - Semantic universal index (a basic particle) + // - buffer amount + // - isPool flag + // store buffer (96) and one bit to specify is pool in free + // --------+------------------+------------------+------------------+------------------+ + // WORD 1: | flowRate | settledAt | totalBuffer | isPool | + // --------+------------------+------------------+------------------+------------------+ + // | 96b | 32b | 96b | 32b | + // --------+------------------+------------------+------------------+------------------+ + // WORD 2: | settledValue | + // -------- ------------------+------------------+------------------+------------------+ + // | 256b | + // --------+------------------+------------------+------------------+------------------+ + + struct AccountData { + int96 flowRate; + uint32 settledAt; + uint256 totalBuffer; + bool isPool; + int256 settledValue; + } + + /// @dev Update the universal index of the account data. + function encodeUpdatedUniversalIndex(AccountData memory accountData, BasicParticle memory uIndex) + internal + pure + returns (bytes32[] memory data) + { + data = new bytes32[](2); + data[0] = bytes32( + (uint256(int256(SafeCast.toInt96(FlowRate.unwrap(uIndex.flow_rate())))) << 160) | + (uint256(SafeCast.toUint32(Time.unwrap(uIndex.settled_at()))) << 128) | + (uint256(SafeCast.toUint96(accountData.totalBuffer)) << 32) | + (accountData.isPool ? 1 : 0) + ); + data[1] = bytes32(uint256(Value.unwrap(uIndex._settled_value))); + } + + /// @dev Update the total buffer of the account data. + function encodeUpdatedTotalBuffer(AccountData memory accountData, uint256 totalBuffer) + internal + pure + returns (bytes32[] memory data) + { + data = new bytes32[](1); + data[0] = bytes32( + (uint256(int256(accountData.flowRate)) << 160) | + (uint256(accountData.settledAt) << 128) | + (uint256(SafeCast.toUint96(totalBuffer)) << 32) | + (accountData.isPool ? 1 : 0) + ); + } + + function decodeAccountData(bytes32[] memory data) + internal + pure + returns (AccountData memory accountData) + { + uint256 a = uint256(data[0]); + + if (a > 0) { + accountData.flowRate = int96(int256(a >> 160) & int256(uint256(type(uint96).max))); + accountData.settledAt = uint32(uint256(a >> 128) & uint256(type(uint32).max)); + accountData.totalBuffer = uint256(a >> 32) & uint256(type(uint96).max); + accountData.isPool = a & 1 == 1; + } + + // encodeUpdatedTotalBuffer only encodes the first word + if (data.length >= 2) { + uint256 b = uint256(data[1]); + accountData.settledValue = int256(b); + } + } + + function getAccountData(ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, address owner) + internal + view + returns (AccountData memory accountData) + { + return decodeAccountData(token.getAgreementStateSlot(address(gda), owner, ACCOUNT_DATA_STATE_SLOT_ID, 2)); + } + + /// @dev returns true if the account is a pool + function isPool(ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, address account) + internal + view + returns (bool) + { + uint256 a = uint256(token.getAgreementStateSlot(address(gda), account, ACCOUNT_DATA_STATE_SLOT_ID, 1)[0]); + return a & 1 == 1; + } + + function getUniversalIndexFromAccountData(AccountData memory accountData) + internal + pure + returns (BasicParticle memory uIndex) + { + uIndex._flow_rate = FlowRate.wrap(accountData.flowRate); + uIndex._settled_at = Time.wrap(accountData.settledAt); + uIndex._settled_value = Value.wrap(accountData.settledValue); + } +} + + +/* @title Storage layout writer for the GDAv1. + * @author Superfluid + * @dev Due to how agreement framework works, `address(this)` must be the GeneralDistributionAgreementV1 itself. + */ +library GDAv1StorageWriter { + function setUniversalIndex(ISuperfluidToken token, + address owner, + BasicParticle memory uIndex) internal + { + GDAv1StorageReader.AccountData memory accountData = + GDAv1StorageReader.getAccountData(token, IGeneralDistributionAgreementV1(address(this)), owner); + + token.updateAgreementStateSlot( + owner, + GDAv1StorageReader.ACCOUNT_DATA_STATE_SLOT_ID, + GDAv1StorageReader.encodeUpdatedUniversalIndex(accountData, uIndex) + ); + } + + function setPool(ISuperfluidToken token, ISuperfluidPool pool) + internal + { + bytes32[] memory data = new bytes32[](1); + data[0] = bytes32(uint256(1)); + token.updateAgreementStateSlot(address(pool), GDAv1StorageReader.ACCOUNT_DATA_STATE_SLOT_ID, data); + } + + function setTotalBuffer(ISuperfluidToken token, + address owner, + uint256 totalBuffer) internal + { + GDAv1StorageReader.AccountData memory accountData = + GDAv1StorageReader.getAccountData(token, IGeneralDistributionAgreementV1(address(this)), owner); + + token.updateAgreementStateSlot( + owner, + GDAv1StorageReader.ACCOUNT_DATA_STATE_SLOT_ID, + GDAv1StorageReader.encodeUpdatedTotalBuffer(accountData, totalBuffer) + ); + } +} diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index c529ebc354..1e7789198a 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -30,84 +30,19 @@ import { SlotsBitmapLibrary } from "../../libs/SlotsBitmapLibrary.sol"; import { SolvencyHelperLibrary } from "../../libs/SolvencyHelperLibrary.sol"; import { AgreementBase } from "../AgreementBase.sol"; import { AgreementLibrary } from "../AgreementLibrary.sol"; +import { GDAv1StorageReader, GDAv1StorageWriter } from "./GDAv1StorageLayout.sol"; -/// @dev Universal Index state slot id for storing universal index data -function _universalIndexStateSlotId() pure returns (uint256) { - return 0; -} - -/// @dev returns true if the account is a pool -function _isPool( - IGeneralDistributionAgreementV1 gda, - ISuperfluidToken token, - address account -) view returns (bool exists) { - // solhint-disable var-name-mixedcase - // @note see createPool, we retrieve the isPool bit from - // UniversalIndex for this pool to determine whether the account - // is a pool - exists = ( - (uint256(token.getAgreementStateSlot(address(gda), account, _universalIndexStateSlotId(), 1)[0]) << 224) - >> 224 - ) & 1 == 1; -} - /** * @title General Distribution Agreement * @author Superfluid - * @notice - * - * Storage Layout Notes - * Agreement State - * - * Universal Index Data - * slotId = _universalIndexStateSlotId() or 0 - * msg.sender = address of GDAv1 - * account = context.msgSender - * Universal Index Data stores a Basic Particle for an account as well as the total buffer and - * whether the account is a pool or not. - * - * SlotsBitmap Data - * slotId = _POOL_SUBS_BITMAP_STATE_SLOT_ID or 1 - * msg.sender = address of GDAv1 - * account = context.msgSender - * Slots Bitmap Data Slot stores a bitmap of the slots that are "enabled" for a pool member. - * - * Pool Connections Data Slot Id Start - * slotId (start) = _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START or 1 << 128 or 340282366920938463463374607431768211456 - * msg.sender = address of GDAv1 - * account = context.msgSender - * Pool Connections Data Slot Id Start indicates the starting slot for where we begin to store the pools that a - * pool member is a part of. - * - * - * Agreement Data - * NOTE The Agreement Data slot is calculated with the following function: - * keccak256(abi.encode("AgreementData", agreementClass, agreementId)) - * agreementClass = address of GDAv1 - * agreementId = DistributionFlowId | PoolMemberId - * - * DistributionFlowId = - * keccak256(abi.encode(block.chainid, "distributionFlow", from, pool)) - * DistributionFlowId stores FlowDistributionData between a sender (from) and pool. - * - * PoolMemberId = - * keccak256(abi.encode(block.chainid, "poolMember", member, pool)) - * PoolMemberId stores PoolMemberData for a member at a pool. */ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDistributionAgreementV1 { using SafeCast for uint256; using SafeCast for int256; using SemanticMoney for BasicParticle; - - struct UniversalIndexData { - int96 flowRate; - uint32 settledAt; - uint256 totalBuffer; - bool isPool; - int256 settledValue; - } + using GDAv1StorageReader for ISuperfluidToken; + using GDAv1StorageWriter for ISuperfluidToken; struct PoolMemberData { address pool; @@ -144,12 +79,14 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (int256 rtb, uint256 buf, uint256 owedBuffer) { - UniversalIndexData memory universalIndexData = _getUIndexData(abi.encode(token), account); + GDAv1StorageReader.AccountData memory accountData = token.getAccountData(this, account); - if (_isPool(this, token, account)) { + if (token.isPool(this, account)) { rtb = ISuperfluidPool(account).getDisconnectedBalance(uint32(time)); } else { - rtb = Value.unwrap(_getBasicParticleFromUIndex(universalIndexData).rtb(Time.wrap(uint32(time)))); + rtb = Value.unwrap(GDAv1StorageReader + .getUniversalIndexFromAccountData(accountData) + .rtb(Time.wrap(uint32(time)))); } int256 fromPools; @@ -166,7 +103,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } rtb += fromPools; - buf = uint256(universalIndexData.totalBuffer.toInt256()); // upcasting to uint256 is safe + buf = uint256(accountData.totalBuffer.toInt256()); // upcasting to uint256 is safe owedBuffer = 0; } @@ -184,7 +121,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi function getNetFlow(ISuperfluidToken token, address account) external view override returns (int96 netFlowRate) { netFlowRate = int256(FlowRate.unwrap(_getUIndex(abi.encode(token), account).flow_rate())).toInt96(); - if (_isPool(this, token, account)) { + if (token.isPool(this, account)) { netFlowRate += ISuperfluidPool(account).getTotalDisconnectedFlowRate(); } @@ -228,10 +165,10 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi 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; + GDAv1StorageReader.AccountData memory accountData = token.getAccountData(this, account); + timestamp = accountData.settledAt; + flowRate = accountData.flowRate; + deposit = accountData.totalBuffer; } /// @inheritdoc IGeneralDistributionAgreementV1 @@ -293,7 +230,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ) internal returns (ISuperfluidPool pool) { // @note ensure if token and admin are the same that nothing funky happens with echidna if (admin == address(0)) revert GDA_NO_ZERO_ADDRESS_ADMIN(); - if (_isPool(this, token, admin)) revert GDA_ADMIN_CANNOT_BE_POOL(); + if (token.isPool(this, admin)) revert GDA_ADMIN_CANNOT_BE_POOL(); pool = ISuperfluidPool( address( @@ -303,11 +240,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ) ); - // @note We utilize the storage slot for Universal Index State - // to store whether an account is a pool or not - bytes32[] memory data = new bytes32[](1); - data[0] = bytes32(uint256(1)); - token.updateAgreementStateSlot(address(pool), _universalIndexStateSlotId(), data); + token.setPool(pool); IPoolAdminNFT poolAdminNFT = IPoolAdminNFT(_getPoolAdminNFTAddress(token)); @@ -428,7 +361,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) external returns (bool) { - if (_isPool(this, token, msg.sender) == false) { + if (token.isPool(this, msg.sender) == false) { revert GDA_ONLY_SUPER_TOKEN_POOL(); } bytes memory eff = abi.encode(token); @@ -441,7 +374,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi external returns (bool) { - if (_isPool(this, superToken, msg.sender) == false) { + if (superToken.isPool(this, msg.sender) == false) { revert GDA_ONLY_SUPER_TOKEN_POOL(); } @@ -462,7 +395,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi newCtx = ctx; - if (_isPool(this, token, address(pool)) == false || + if (token.isPool(this, address(pool)) == false || // Note: we do not support multi-tokens pools pool.superToken() != token) { revert GDA_ONLY_SUPER_TOKEN_POOL(); @@ -528,7 +461,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi int96 requestedFlowRate, bytes calldata ctx ) external override returns (bytes memory newCtx) { - if (_isPool(this, token, address(pool)) == false || + if (token.isPool(this, address(pool)) == false || // Note: we do not support multi-tokens pools pool.superToken() != token) { revert GDA_ONLY_SUPER_TOKEN_POOL(); @@ -582,8 +515,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi liquidationData.sender = from; liquidationData.liquidator = flowVars.currentContext.msgSender; liquidationData.distributionFlowHash = flowVars.distributionFlowHash; - liquidationData.signedTotalGDADeposit = - _getUIndexData(abi.encode(token), from).totalBuffer.toInt256(); + // TODO: GeneralDistributionAgreementV1Storage may offer an function that reads only 1 word. + liquidationData.signedTotalGDADeposit = token.getAccountData(this, from).totalBuffer.toInt256(); liquidationData.availableBalance = availableBalance; } // closing stream on behalf of someone else: liquidation case @@ -722,27 +655,24 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ISuperfluidToken(token).updateAgreementData(flowHash, data); } - UniversalIndexData memory universalIndexData = _getUIndexData(abi.encode(token), from); - universalIndexData.totalBuffer = + GDAv1StorageReader.AccountData memory accountData = token.getAccountData(this, from); // new buffer - (universalIndexData.totalBuffer.toInt256() + Value.unwrap(bufferDelta)).toUint256(); - ISuperfluidToken(token).updateAgreementStateSlot( - from, _universalIndexStateSlotId(), _encodeUniversalIndexData(universalIndexData) - ); + accountData.totalBuffer = (accountData.totalBuffer.toInt256() + Value.unwrap(bufferDelta)).toUint256(); + token.setTotalBuffer(from, accountData.totalBuffer); - { - emit BufferAdjusted( - ISuperfluidToken(token), - ISuperfluidPool(pool), - from, - Value.unwrap(bufferDelta), - Value.unwrap(newBufferAmount).toUint256(), - universalIndexData.totalBuffer - ); - } + emit BufferAdjusted( + ISuperfluidToken(token), + ISuperfluidPool(pool), + from, + Value.unwrap(bufferDelta), + Value.unwrap(newBufferAmount).toUint256(), + accountData.totalBuffer + ); } // Solvency Related Getters + + /// @inheritdoc IGeneralDistributionAgreementV1 function isPatricianPeriodNow(ISuperfluidToken token, address account) external view @@ -753,6 +683,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi isCurrentlyPatricianPeriod = isPatricianPeriod(token, account, timestamp); } + /// @inheritdoc IGeneralDistributionAgreementV1 function isPatricianPeriod(ISuperfluidToken token, address account, uint256 timestamp) public view @@ -769,158 +700,136 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return SolvencyHelperLibrary.isPatricianPeriod( availableBalance, - _getUIndexData(abi.encode(token), account).totalBuffer.toInt256(), + token.getAccountData(this, account).totalBuffer.toInt256(), liquidationPeriod, patricianPeriod ); } - // Hash Getters + // pool info and operators - function _getPoolMemberHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { - return keccak256(abi.encode(block.chainid, "poolMember", poolMember, address(pool))); + /// @inheritdoc IGeneralDistributionAgreementV1 + function getPoolAdjustmentFlowInfo(ISuperfluidPool pool) + external + view + override + returns (address recipient, bytes32 flowHash, int96 flowRate) + { + return _getPoolAdjustmentFlowInfo(abi.encode(pool.superToken()), address(pool)); } - function _getFlowDistributionHash(address from, ISuperfluidPool to) internal view returns (bytes32) { - return keccak256(abi.encode(block.chainid, "distributionFlow", from, to)); + /// @inheritdoc IGeneralDistributionAgreementV1 + function isPool(ISuperfluidToken token, address account) external view override returns (bool) { + return token.isPool(this, account); } - function _getPoolAdjustmentFlowHash(address from, address to) internal view returns (bytes32) { - // this will never be in conflict with other flow has types - return keccak256(abi.encode(block.chainid, "poolAdjustmentFlow", from, to)); + /// @inheritdoc IGeneralDistributionAgreementV1 + function getPoolAdjustmentFlowRate(address pool) external view override returns (int96) { + ISuperfluidToken token = ISuperfluidPool(pool).superToken(); + return int256(FlowRate.unwrap(_getPoolAdjustmentFlowRate(abi.encode(token), pool))).toInt96(); } - // # Universal Index operations - // - // Universal Index packing: - // store buffer (96) and one bit to specify is pool in free - // -------- ------------------ ------------------ ------------------ ------------------ - // WORD 1: | flowRate | settledAt | totalBuffer | isPool | - // -------- ------------------ ------------------ ------------------ ------------------ - // | 96b | 32b | 96b | 32b | - // -------- ------------------ ------------------ ------------------ ------------------ - // WORD 2: | settledValue | - // -------- ------------------ ------------------ ------------------ ------------------ - // | 256b | - // -------- ------------------ ------------------ ------------------ ------------------ - - function _encodeUniversalIndexData(BasicParticle memory p, uint256 buffer, bool isPool_) - internal - pure - returns (bytes32[] memory data) + function _getPoolAdjustmentFlowInfo(bytes memory eff, address pool) + internal view + returns (address adjustmentRecipient, bytes32 flowHash, int96 flowRate) { - data = new bytes32[](2); - data[0] = bytes32( - (uint256(int256(FlowRate.unwrap(p.flow_rate()))) << 160) | (uint256(Time.unwrap(p.settled_at())) << 128) - | (uint256(buffer.toUint96()) << 32) | (isPool_ ? 1 : 0) - ); - data[1] = bytes32(uint256(Value.unwrap(p._settled_value))); + // pool admin is always the adjustment recipient + adjustmentRecipient = ISuperfluidPool(pool).admin(); + flowHash = _getPoolAdjustmentFlowHash(pool, adjustmentRecipient); + return (adjustmentRecipient, flowHash, int256(FlowRate.unwrap(_getFlowRate(eff, flowHash))).toInt96()); } - function _encodeUniversalIndexData(UniversalIndexData memory uIndexData) + function _setPoolAdjustmentFlowRate(bytes memory eff, address pool, bool doShiftFlow, FlowRate flowRate, Time t) internal - pure - returns (bytes32[] memory data) + returns (bytes memory) { - data = new bytes32[](2); - data[0] = bytes32( - (uint256(int256(uIndexData.flowRate)) << 160) | (uint256(uIndexData.settledAt) << 128) - | (uint256(uIndexData.totalBuffer.toUint96()) << 32) | (uIndexData.isPool ? 1 : 0) - ); - data[1] = bytes32(uint256(uIndexData.settledValue)); - } + // @note should this also always be + address adjustmentRecipient = ISuperfluidPool(pool).admin(); + bytes32 adjustmentFlowHash = _getPoolAdjustmentFlowHash(pool, adjustmentRecipient); - function _decodeUniversalIndexData(bytes32[] memory data) - internal - pure - returns (bool exists, UniversalIndexData memory universalIndexData) - { - uint256 a = uint256(data[0]); - uint256 b = uint256(data[1]); + if (doShiftFlow) { + flowRate = flowRate + _getFlowRate(eff, adjustmentFlowHash); + } + eff = _doFlow(eff, pool, adjustmentRecipient, adjustmentFlowHash, flowRate, t); + return eff; + } - exists = a > 0 || b > 0; + // Hash Getters - if (exists) { - universalIndexData.flowRate = int96(int256(a >> 160) & int256(uint256(type(uint96).max))); - universalIndexData.settledAt = uint32(uint256(a >> 128) & uint256(type(uint32).max)); - universalIndexData.totalBuffer = uint256(a >> 32) & uint256(type(uint96).max); - universalIndexData.isPool = ((a << 224) >> 224) & 1 == 1; - universalIndexData.settledValue = int256(b); - } + function _getPoolMemberHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { + return keccak256(abi.encode(block.chainid, "poolMember", poolMember, address(pool))); } - function _getUIndexData(bytes memory eff, address owner) - internal - view - returns (UniversalIndexData memory universalIndexData) - { - (, universalIndexData) = _decodeUniversalIndexData( - ISuperfluidToken(abi.decode(eff, (address))).getAgreementStateSlot( - address(this), owner, _universalIndexStateSlotId(), 2 - ) - ); + function _getFlowDistributionHash(address from, ISuperfluidPool to) internal view returns (bytes32) { + return keccak256(abi.encode(block.chainid, "distributionFlow", from, to)); } - function _getBasicParticleFromUIndex(UniversalIndexData memory universalIndexData) - internal - pure - returns (BasicParticle memory particle) - { - particle._flow_rate = FlowRate.wrap(universalIndexData.flowRate); - particle._settled_at = Time.wrap(universalIndexData.settledAt); - particle._settled_value = Value.wrap(universalIndexData.settledValue); + function _getPoolAdjustmentFlowHash(address from, address to) internal view returns (bytes32) { + // this will never be in conflict with other flow has types + return keccak256(abi.encode(block.chainid, "poolAdjustmentFlow", from, to)); } + // // TokenMonad virtual functions - function _getUIndex(bytes memory eff, address owner) internal view override returns (BasicParticle memory uIndex) { - (, UniversalIndexData memory universalIndexData) = _decodeUniversalIndexData( - ISuperfluidToken(abi.decode(eff, (address))).getAgreementStateSlot( - address(this), owner, _universalIndexStateSlotId(), 2 - ) - ); - uIndex = _getBasicParticleFromUIndex(universalIndexData); + // + + /// @inheritdoc TokenMonad + function _getUIndex(bytes memory eff, address owner) + internal view + override + returns (BasicParticle memory uIndex) + { + ISuperfluidToken token = ISuperfluidToken(abi.decode(eff, (address))); + GDAv1StorageReader.AccountData memory accountData = token.getAccountData(this, owner); + uIndex = GDAv1StorageReader.getUniversalIndexFromAccountData(accountData); } - function _setUIndex(bytes memory eff, address owner, BasicParticle memory p) + /// @inheritdoc TokenMonad + function _setUIndex(bytes memory eff, address owner, BasicParticle memory uIndex) internal override returns (bytes memory) { - UniversalIndexData memory universalIndexData = _getUIndexData(eff, owner); - - ISuperfluidToken(abi.decode(eff, (address))).updateAgreementStateSlot( - owner, - _universalIndexStateSlotId(), - _encodeUniversalIndexData(p, universalIndexData.totalBuffer, universalIndexData.isPool) - ); - + ISuperfluidToken token = ISuperfluidToken(abi.decode(eff, (address))); + token.setUniversalIndex(owner, uIndex); return eff; } + /// @inheritdoc TokenMonad function _getPDPIndex( bytes memory, // eff, address pool - ) internal view override returns (PDPoolIndex memory) { + ) + internal view + override + returns (PDPoolIndex memory) + { SuperfluidPool.PoolIndexData memory data = SuperfluidPool(pool).poolOperatorGetIndex(); return poolIndexDataToPDPoolIndex(data); } + /// @inheritdoc TokenMonad function _setPDPIndex(bytes memory eff, address pool, PDPoolIndex memory p) internal override returns (bytes memory) { assert(SuperfluidPool(pool).operatorSetIndex(p)); - return eff; } - function _getFlowRate(bytes memory eff, bytes32 distributionFlowHash) internal view override returns (FlowRate) { + /// @inheritdoc TokenMonad + function _getFlowRate(bytes memory eff, bytes32 distributionFlowHash) + internal view + override + returns (FlowRate) + { (, FlowDistributionData memory data) = _getFlowDistributionData(ISuperfluidToken(abi.decode(eff, (address))), distributionFlowHash); return FlowRate.wrap(data.flowRate); } + /// @inheritdoc TokenMonad function _setFlowInfo( bytes memory eff, bytes32 flowHash, @@ -928,7 +837,11 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi address, // to, FlowRate newFlowRate, FlowRate // flowRateDelta - ) internal override returns (bytes memory) { + ) + internal + override + returns (bytes memory) + { address token = abi.decode(eff, (address)); (, FlowDistributionData memory flowDistributionData) = _getFlowDistributionData(ISuperfluidToken(token), flowHash); @@ -947,30 +860,9 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return eff; } - /// @inheritdoc IGeneralDistributionAgreementV1 - function getPoolAdjustmentFlowInfo(ISuperfluidPool pool) - external - view - override - returns (address recipient, bytes32 flowHash, int96 flowRate) - { - return _getPoolAdjustmentFlowInfo(abi.encode(pool.superToken()), address(pool)); - } - - function _getPoolAdjustmentFlowInfo(bytes memory eff, address pool) - internal - view - returns (address adjustmentRecipient, bytes32 flowHash, int96 flowRate) - { - // pool admin is always the adjustment recipient - adjustmentRecipient = ISuperfluidPool(pool).admin(); - flowHash = _getPoolAdjustmentFlowHash(pool, adjustmentRecipient); - return (adjustmentRecipient, flowHash, int256(FlowRate.unwrap(_getFlowRate(eff, flowHash))).toInt96()); - } - + /// @inheritdoc TokenMonad function _getPoolAdjustmentFlowRate(bytes memory eff, address pool) - internal - view + internal view override returns (FlowRate flowRate) { @@ -978,11 +870,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi flowRate = FlowRate.wrap(int128(rawFlowRate)); // upcasting to int128 is safe } - function getPoolAdjustmentFlowRate(address pool) external view override returns (int96) { - ISuperfluidToken token = ISuperfluidPool(pool).superToken(); - return int256(FlowRate.unwrap(_getPoolAdjustmentFlowRate(abi.encode(token), pool))).toInt96(); - } - + /// @inheritdoc TokenMonad function _setPoolAdjustmentFlowRate(bytes memory eff, address pool, FlowRate flowRate, Time t) internal override @@ -991,26 +879,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return _setPoolAdjustmentFlowRate(eff, pool, false, /* doShift? */ flowRate, t); } - function _setPoolAdjustmentFlowRate(bytes memory eff, address pool, bool doShiftFlow, FlowRate flowRate, Time t) - internal - returns (bytes memory) - { - // @note should this also always be - address adjustmentRecipient = ISuperfluidPool(pool).admin(); - bytes32 adjustmentFlowHash = _getPoolAdjustmentFlowHash(pool, adjustmentRecipient); - - if (doShiftFlow) { - flowRate = flowRate + _getFlowRate(eff, adjustmentFlowHash); - } - eff = _doFlow(eff, pool, adjustmentRecipient, adjustmentFlowHash, flowRate, t); - return eff; - } - - /// @inheritdoc IGeneralDistributionAgreementV1 - function isPool(ISuperfluidToken token, address account) external view override returns (bool) { - return _isPool(this, token, account); - } - // FlowDistributionData data packing: // -------- ---------- ------------- ---------- -------- // WORD A: | reserved | lastUpdated | flowRate | buffer | diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index c2df801724..06b3a7031c 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -20,10 +20,11 @@ import { import { ISuperfluid } from "../../interfaces/superfluid/ISuperfluid.sol"; import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol"; import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol"; -import { - GeneralDistributionAgreementV1, _isPool } from "../../agreements/gdav1/GeneralDistributionAgreementV1.sol"; +import { GeneralDistributionAgreementV1 } from "../../agreements/gdav1/GeneralDistributionAgreementV1.sol"; +import { GDAv1StorageReader } from "../../agreements/gdav1/GDAv1StorageLayout.sol"; import { BeaconProxiable } from "../../upgradability/BeaconProxiable.sol"; + using SafeCast for uint256; using SafeCast for int256; @@ -62,6 +63,7 @@ function poolIndexDataToPDPoolIndex(SuperfluidPool.PoolIndexData memory data) */ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { using SemanticMoney for BasicParticle; + using GDAv1StorageReader for ISuperfluidToken; // Structs struct PoolIndexData { @@ -425,7 +427,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { function _updateMemberUnits(address memberAddr, uint128 newUnits) internal returns (uint128 oldUnits) { // @note normally we keep the sanitization in the external functions, but here // this is used in both updateMemberUnits and transfer - if (_isPool(GDA, superToken, memberAddr)) revert SUPERFLUID_POOL_NO_POOL_MEMBERS(); + if (superToken.isPool(GDA, memberAddr)) revert SUPERFLUID_POOL_NO_POOL_MEMBERS(); if (memberAddr == address(0)) revert SUPERFLUID_POOL_NO_ZERO_ADDRESS(); uint32 time = uint32(ISuperfluid(superToken.getHost()).getNow()); diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol index 323d265830..c5bffd2a74 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol @@ -12,15 +12,15 @@ 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, + PoolConfig, + ISuperfluid, ISuperfluidPool, ISuperToken } from "../../../../contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol"; -import { - IGeneralDistributionAgreementV1, - PoolConfig -} from "../../../../contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; +import { GDAv1StorageReader } from "../../../../contracts/agreements/gdav1/GDAv1StorageLayout.sol"; import { ISuperfluidPool, SuperfluidPool } from "../../../../contracts/agreements/gdav1/SuperfluidPool.sol"; import { SuperTokenV1Library } from "../../../../contracts/apps/SuperTokenV1Library.sol"; + /// @title GeneralDistributionAgreementV1 Property Tests /// @author Superfluid /// @notice This is a contract that runs property tests for the GDAv1 @@ -28,6 +28,7 @@ import { SuperTokenV1Library } from "../../../../contracts/apps/SuperTokenV1Libr /// the expected output for a range of inputs. contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreementV1, Test { using SuperTokenV1Library for ISuperToken; + using GDAv1StorageReader for ISuperToken; SuperfluidFrameworkDeployer internal immutable sfDeployer; SuperfluidFrameworkDeployer.Framework internal sf; @@ -91,13 +92,13 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen _settled_value: Value.wrap(settledValue) }); _setUIndex(eff, owner, p); - GeneralDistributionAgreementV1.UniversalIndexData memory setUIndexData = _getUIndexData(eff, owner); + GDAv1StorageReader.AccountData memory accountData = superToken.getAccountData(this, owner); - assertEq(settledAt, setUIndexData.settledAt, "settledAt not equal"); - assertEq(flowRate, setUIndexData.flowRate, "flowRate not equal"); - assertEq(settledValue, setUIndexData.settledValue, "settledValue not equal"); - assertEq(0, setUIndexData.totalBuffer, "totalBuffer not equal"); - assertEq(false, setUIndexData.isPool, "isPool not equal"); + assertEq(settledAt, accountData.settledAt, "settledAt not equal"); + assertEq(flowRate, accountData.flowRate, "flowRate not equal"); + assertEq(settledValue, accountData.settledValue, "settledValue not equal"); + assertEq(0, accountData.totalBuffer, "totalBuffer not equal"); + assertEq(false, accountData.isPool, "isPool not equal"); } // Flow Distribution Data Setters/Getters @@ -271,55 +272,68 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen // ); // } - function testEncodeDecodeParticleInputUniversalIndexData( + function testEncodeUpdatedUniversalIndex( int96 flowRate, uint32 settledAt, int256 settledValue, uint96 totalBuffer, - bool isPool_ - ) public pure { - BasicParticle memory particle = BasicParticle({ - _flow_rate: FlowRate.wrap(flowRate), - _settled_at: Time.wrap(settledAt), - _settled_value: Value.wrap(settledValue) - }); - bytes32[] memory encoded = _encodeUniversalIndexData(particle, totalBuffer, isPool_); - (, UniversalIndexData memory decoded) = _decodeUniversalIndexData(encoded); + bool isPool + ) pure public + { + GDAv1StorageReader.AccountData memory accountData = + GDAv1StorageReader.AccountData({ + flowRate: 0, + settledAt: 0, + settledValue: 0, + totalBuffer: totalBuffer, + isPool: isPool + }); + BasicParticle memory uIndex = + BasicParticle({ + _flow_rate: FlowRate.wrap(flowRate), + _settled_at: Time.wrap(settledAt), + _settled_value: Value.wrap(settledValue) + }); + + bytes32[] memory encoded = GDAv1StorageReader.encodeUpdatedUniversalIndex(accountData, uIndex); + GDAv1StorageReader.AccountData memory decoded = GDAv1StorageReader.decodeAccountData(encoded); assertEq(flowRate, decoded.flowRate, "flowRate not equal"); assertEq(settledAt, decoded.settledAt, "settledAt not equal"); assertEq(settledValue, decoded.settledValue, "settledValue not equal"); assertEq(totalBuffer, decoded.totalBuffer, "totalBuffer not equal"); - assertEq(isPool_, decoded.isPool, "isPool not equal"); + assertEq(isPool, decoded.isPool, "isPool not equal"); } - function testEncodeDecodeUIDataInputeUniversalIndexData( + function testEncodeUpdatedTotalBuffer( int96 flowRate, uint32 settledAt, - int256 settledValue, uint96 totalBuffer, - bool isPool_ - ) public pure { - UniversalIndexData memory data = UniversalIndexData({ - flowRate: flowRate, - settledAt: settledAt, - settledValue: settledValue, - totalBuffer: totalBuffer, - isPool: isPool_ - }); - - bytes32[] memory encoded = _encodeUniversalIndexData(data); - (, UniversalIndexData memory decoded) = _decodeUniversalIndexData(encoded); + bool isPool + ) public pure + { + GDAv1StorageReader.AccountData memory accountData = + GDAv1StorageReader.AccountData({ + flowRate: flowRate, + settledAt: settledAt, + settledValue: 0, + totalBuffer: 0, + isPool: isPool + }); + bytes32[] memory encoded = GDAv1StorageReader.encodeUpdatedTotalBuffer(accountData, totalBuffer); + GDAv1StorageReader.AccountData memory decoded = GDAv1StorageReader.decodeAccountData(encoded); assertEq(flowRate, decoded.flowRate, "flowRate not equal"); assertEq(settledAt, decoded.settledAt, "settledAt not equal"); - assertEq(settledValue, decoded.settledValue, "settledValue not equal"); assertEq(totalBuffer, decoded.totalBuffer, "totalBuffer not equal"); - assertEq(isPool_, decoded.isPool, "isPool not equal"); + assertEq(isPool, decoded.isPool, "isPool not equal"); } - function testGetBasicParticleFromUIndex(UniversalIndexData memory data) public pure { - BasicParticle memory particle = _getBasicParticleFromUIndex(data); + function testDecodeAccountData(GDAv1StorageReader.AccountData memory data) + pure + public + { + BasicParticle memory particle = GDAv1StorageReader.getUniversalIndexFromAccountData(data); assertEq(data.flowRate, int96(FlowRate.unwrap(particle._flow_rate)), "flowRate not equal"); assertEq(data.settledAt, Time.unwrap(particle._settled_at), "settledAt not equal"); assertEq(data.settledValue, Value.unwrap(particle._settled_value), "settledValue not equal"); From 3647c07a09cf1cefde9b1b1ba702403abd809313 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Thu, 3 Jul 2025 11:49:14 +0300 Subject: [PATCH 02/16] WIP: refactor FlowInfo reader/writer --- .../agreements/gdav1/GDAv1StorageLayout.sol | 71 +++++++++++++- .../gdav1/GeneralDistributionAgreementV1.sol | 96 +++++-------------- .../GeneralDistributionAgreementV1.prop.t.sol | 22 +++-- 3 files changed, 103 insertions(+), 86 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol index 660066b35c..0cd60a416e 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -59,7 +59,6 @@ import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPo * PoolMemberId stores PoolMemberData for a member at a pool. */ library GDAv1StorageReader { - uint256 internal constant ACCOUNT_DATA_STATE_SLOT_ID = 0; // # Account Data Operations // @@ -78,10 +77,12 @@ library GDAv1StorageReader { // | 256b | // --------+------------------+------------------+------------------+------------------+ + uint256 internal constant ACCOUNT_DATA_STATE_SLOT_ID = 0; + struct AccountData { int96 flowRate; uint32 settledAt; - uint256 totalBuffer; + uint256 totalBuffer; // stored as uint96 bool isPool; int256 settledValue; } @@ -165,6 +166,70 @@ library GDAv1StorageReader { uIndex._settled_at = Time.wrap(accountData.settledAt); uIndex._settled_value = Value.wrap(accountData.settledValue); } + + // FlowDistributionData data packing: + // --------+----------+-------------+----------+--------+ + // WORD A: | reserved | lastUpdated | flowRate | buffer | + // --------+----------+-------------+----------+--------+ + // | 32 | 32 | 96 | 96 | + // --------+----------+-------------+----------+--------+ + + struct FlowDistributionData { + uint32 lastUpdated; + int96 flowRate; + uint256 buffer; // stored as uint96 + } + + function getFlowDistributionHash(address from, ISuperfluidPool to) internal view returns (bytes32) { + return keccak256(abi.encode(block.chainid, "distributionFlow", from, to)); + } + + function encodeFlowDistributionData(FlowDistributionData memory flowDistributionData) + internal pure + returns (bytes32[] memory data) + { + data = new bytes32[](1); + data[0] = bytes32( + (uint256(uint32(flowDistributionData.lastUpdated)) << 192) | + (uint256(uint96(flowDistributionData.flowRate)) << 96) | + uint256(flowDistributionData.buffer) + ); + } + + function decodeFlowDistributionData(uint256 data) + internal pure + returns (FlowDistributionData memory flowDistributionData) + { + if (data > 0) { + flowDistributionData.lastUpdated = uint32((data >> 192) & uint256(type(uint32).max)); + flowDistributionData.flowRate = int96(int256(data >> 96)); + flowDistributionData.buffer = uint96(data & uint256(type(uint96).max)); + } + } + + function getFlowDistributionDataWithKnownHash( + ISuperfluidToken token, + IGeneralDistributionAgreementV1 gda, + bytes32 flowDistributionHash + ) + internal view + returns (FlowDistributionData memory flowDistributionData) + { + uint256 data = uint256(token.getAgreementData(address(gda), flowDistributionHash, 1)[0]); + return decodeFlowDistributionData(data); + } + + function getFlowDistributionData( + ISuperfluidToken token, + IGeneralDistributionAgreementV1 gda, + address from, + ISuperfluidPool to + ) + internal view + returns (FlowDistributionData memory flowDistributionData) + { + return getFlowDistributionDataWithKnownHash(token, gda, getFlowDistributionHash(from, to)); + } } @@ -187,7 +252,7 @@ library GDAv1StorageWriter { ); } - function setPool(ISuperfluidToken token, ISuperfluidPool pool) + function setIsPoolFlag(ISuperfluidToken token, ISuperfluidPool pool) internal { bytes32[] memory data = new bytes32[](1); diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 1e7789198a..35f1e1fac9 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -49,12 +49,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi 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); @@ -141,7 +135,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (int96) { - (, FlowDistributionData memory data) = _getFlowDistributionData(token, _getFlowDistributionHash(from, to)); + GDAv1StorageReader.FlowDistributionData memory data = token.getFlowDistributionData(this, from, to); return data.flowRate; } @@ -152,7 +146,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (uint256 lastUpdated, int96 flowRate, uint256 deposit) { - (, FlowDistributionData memory data) = _getFlowDistributionData(token, _getFlowDistributionHash(from, to)); + GDAv1StorageReader.FlowDistributionData memory data = token.getFlowDistributionData(this, from, to); lastUpdated = data.lastUpdated; flowRate = data.flowRate; deposit = data.buffer; @@ -179,7 +173,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi int96 requestedFlowRate ) external view override returns (int96 actualFlowRate, int96 totalDistributionFlowRate) { bytes memory eff = abi.encode(token); - bytes32 distributionFlowHash = _getFlowDistributionHash(from, to); + bytes32 distributionFlowHash = GDAv1StorageReader.getFlowDistributionHash(from, to); BasicParticle memory fromUIndexData = _getUIndex(eff, from); @@ -240,7 +234,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ) ); - token.setPool(pool); + token.setIsPoolFlag(pool); IPoolAdminNFT poolAdminNFT = IPoolAdminNFT(_getPoolAdminNFTAddress(token)); @@ -473,7 +467,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi _StackVars_DistributeFlow memory flowVars; { flowVars.currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); - flowVars.distributionFlowHash = _getFlowDistributionHash(from, pool); + flowVars.distributionFlowHash = GDAv1StorageReader.getFlowDistributionHash(from, pool); flowVars.oldFlowRate = _getFlowRate(abi.encode(token), flowVars.distributionFlowHash); } @@ -577,8 +571,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } function _makeLiquidationPayouts(_StackVars_Liquidation memory data) internal { - (, FlowDistributionData memory flowDistributionData) = - _getFlowDistributionData(ISuperfluidToken(data.token), data.distributionFlowHash); + GDAv1StorageReader.FlowDistributionData memory flowDistributionData = + data.token.getFlowDistributionDataWithKnownHash(this, data.distributionFlowHash); int256 signedSingleDeposit = flowDistributionData.buffer.toInt256(); bool isCurrentlyPatricianPeriod; @@ -631,8 +625,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi (uint256 liquidationPeriod,) = SolvencyHelperLibrary.decode3PsData(ISuperfluid(_host), ISuperfluidToken(token)); - (, FlowDistributionData memory flowDistributionData) = - _getFlowDistributionData(ISuperfluidToken(token), flowHash); + GDAv1StorageReader.FlowDistributionData memory flowDistributionData = + token.getFlowDistributionDataWithKnownHash(this, flowHash); // @note downcasting from uint256 -> uint32 for liquidation period Value newBufferAmount = newFlowRate.mul(Time.wrap(uint32(liquidationPeriod))); @@ -644,8 +638,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi Value bufferDelta = newBufferAmount - Value.wrap(uint256(flowDistributionData.buffer).toInt256()); { - bytes32[] memory data = _encodeFlowDistributionData( - FlowDistributionData({ + bytes32[] memory data = GDAv1StorageReader.encodeFlowDistributionData( + GDAv1StorageReader.FlowDistributionData({ lastUpdated: uint32(block.timestamp), flowRate: int256(FlowRate.unwrap(newFlowRate)).toInt96(), buffer: uint256(Value.unwrap(newBufferAmount)) // upcast to uint256 is safe @@ -756,19 +750,15 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi // Hash Getters - function _getPoolMemberHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { - return keccak256(abi.encode(block.chainid, "poolMember", poolMember, address(pool))); - } - - function _getFlowDistributionHash(address from, ISuperfluidPool to) internal view returns (bytes32) { - return keccak256(abi.encode(block.chainid, "distributionFlow", from, to)); - } - function _getPoolAdjustmentFlowHash(address from, address to) internal view returns (bytes32) { // this will never be in conflict with other flow has types return keccak256(abi.encode(block.chainid, "poolAdjustmentFlow", from, to)); } + function _getPoolMemberHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { + return keccak256(abi.encode(block.chainid, "poolMember", poolMember, address(pool))); + } + // // TokenMonad virtual functions // @@ -824,8 +814,9 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (FlowRate) { - (, FlowDistributionData memory data) = - _getFlowDistributionData(ISuperfluidToken(abi.decode(eff, (address))), distributionFlowHash); + ISuperfluidToken token = ISuperfluidToken(abi.decode(eff, (address))); + GDAv1StorageReader.FlowDistributionData memory data = + token.getFlowDistributionDataWithKnownHash(this, distributionFlowHash); return FlowRate.wrap(data.flowRate); } @@ -842,14 +833,14 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (bytes memory) { - address token = abi.decode(eff, (address)); - (, FlowDistributionData memory flowDistributionData) = - _getFlowDistributionData(ISuperfluidToken(token), flowHash); + ISuperfluidToken token = ISuperfluidToken(abi.decode(eff, (address))); + GDAv1StorageReader.FlowDistributionData memory flowDistributionData = + token.getFlowDistributionDataWithKnownHash(this, flowHash); ISuperfluidToken(token).updateAgreementData( flowHash, - _encodeFlowDistributionData( - FlowDistributionData({ + GDAv1StorageReader.encodeFlowDistributionData( + GDAv1StorageReader.FlowDistributionData({ lastUpdated: uint32(block.timestamp), flowRate: int256(FlowRate.unwrap(newFlowRate)).toInt96(), buffer: flowDistributionData.buffer @@ -879,47 +870,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return _setPoolAdjustmentFlowRate(eff, pool, false, /* doShift? */ flowRate, t); } - // FlowDistributionData data packing: - // -------- ---------- ------------- ---------- -------- - // WORD A: | reserved | lastUpdated | flowRate | buffer | - // -------- ---------- ------------- ---------- -------- - // | 32 | 32 | 96 | 96 | - // -------- ---------- ------------- ---------- -------- - - function _encodeFlowDistributionData(FlowDistributionData memory flowDistributionData) - internal - pure - returns (bytes32[] memory data) - { - data = new bytes32[](1); - data[0] = bytes32( - (uint256(uint32(flowDistributionData.lastUpdated)) << 192) - | (uint256(uint96(flowDistributionData.flowRate)) << 96) | uint256(flowDistributionData.buffer) - ); - } - - function _decodeFlowDistributionData(uint256 data) - internal - pure - returns (bool exist, FlowDistributionData memory flowDistributionData) - { - exist = data > 0; - if (exist) { - flowDistributionData.lastUpdated = uint32((data >> 192) & uint256(type(uint32).max)); - flowDistributionData.flowRate = int96(int256(data >> 96)); - flowDistributionData.buffer = uint96(data & uint256(type(uint96).max)); - } - } - - function _getFlowDistributionData(ISuperfluidToken token, bytes32 distributionFlowHash) - internal - view - returns (bool exist, FlowDistributionData memory flowDistributionData) - { - (exist, flowDistributionData) = - _decodeFlowDistributionData(uint256(token.getAgreementData(address(this), distributionFlowHash, 1)[0])); - } - // PoolMemberData data packing: // -------- ---------- -------- ------------- // WORD A: | reserved | poolID | poolAddress | diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol index c5bffd2a74..c7cd396bb0 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol @@ -110,7 +110,7 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen ) public { uint256 lastUpdated = block.timestamp; - bytes32 flowHash = _getFlowDistributionHash(from, to); + bytes32 flowHash = GDAv1StorageReader.getFlowDistributionHash(from, to); _setFlowInfo( abi.encode(superToken), @@ -122,11 +122,8 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen ); vm.warp(1000); - - (bool exist, FlowDistributionData memory setFlowDistributionData) = - _getFlowDistributionData(superToken, flowHash); - - assertEq(true, exist, "flow distribution data does not exist"); + GDAv1StorageReader.FlowDistributionData memory setFlowDistributionData = + superToken.getFlowDistributionDataWithKnownHash(this, flowHash); assertEq(int96(uint96(newFlowRate)), setFlowDistributionData.flowRate, "flowRate not equal"); @@ -342,10 +339,15 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen function testEncodeDecodeFlowDistributionData(int96 flowRate, uint96 buffer) public view { vm.assume(flowRate >= 0); vm.assume(buffer >= 0); - FlowDistributionData memory original = - FlowDistributionData({ flowRate: flowRate, lastUpdated: uint32(block.timestamp), buffer: buffer }); - bytes32[] memory encoded = _encodeFlowDistributionData(original); - (, FlowDistributionData memory decoded) = _decodeFlowDistributionData(uint256(encoded[0])); + GDAv1StorageReader.FlowDistributionData memory original = + GDAv1StorageReader.FlowDistributionData({ + flowRate: flowRate, + lastUpdated: uint32(block.timestamp), + buffer: buffer + }); + bytes32[] memory encoded = GDAv1StorageReader.encodeFlowDistributionData(original); + GDAv1StorageReader.FlowDistributionData memory decoded = + GDAv1StorageReader.decodeFlowDistributionData(uint256(encoded[0])); assertEq(original.flowRate, decoded.flowRate, "flowRate not equal"); assertEq(original.buffer, decoded.buffer, "buffer not equal"); From 1f85b8cb98dbe9769c731395282af2f95133c310 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Fri, 4 Jul 2025 11:17:13 +0300 Subject: [PATCH 03/16] WIP: GDAv1StorageLib & GDAv1StorageLib.FlowInfo --- .../agreements/gdav1/GDAv1StorageLayout.sol | 117 +++++++++++------- .../gdav1/GeneralDistributionAgreementV1.sol | 84 +++++-------- .../GeneralDistributionAgreementV1.prop.t.sol | 56 ++++----- 3 files changed, 135 insertions(+), 122 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol index 0cd60a416e..0658d25825 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -17,7 +17,7 @@ import { import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol"; -/* @title Storage layout reader for the GDAv1. +/* @title Storage layout library for the GDAv1. * @author Superfluid * @notice * @@ -52,15 +52,15 @@ import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPo * * DistributionFlowId = * keccak256(abi.encode(block.chainid, "distributionFlow", from, pool)) - * DistributionFlowId stores FlowDistributionData between a sender (from) and pool. + * DistributionFlowId stores FlowInfo between a sender (from) and pool. * * PoolMemberId = * keccak256(abi.encode(block.chainid, "poolMember", member, pool)) * PoolMemberId stores PoolMemberData for a member at a pool. */ -library GDAv1StorageReader { +library GDAv1StorageLib { - // # Account Data Operations + // # Account Data // // Account data includes: // - Semantic universal index (a basic particle) @@ -139,24 +139,6 @@ library GDAv1StorageReader { } } - function getAccountData(ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, address owner) - internal - view - returns (AccountData memory accountData) - { - return decodeAccountData(token.getAgreementStateSlot(address(gda), owner, ACCOUNT_DATA_STATE_SLOT_ID, 2)); - } - - /// @dev returns true if the account is a pool - function isPool(ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, address account) - internal - view - returns (bool) - { - uint256 a = uint256(token.getAgreementStateSlot(address(gda), account, ACCOUNT_DATA_STATE_SLOT_ID, 1)[0]); - return a & 1 == 1; - } - function getUniversalIndexFromAccountData(AccountData memory accountData) internal pure @@ -167,14 +149,17 @@ library GDAv1StorageReader { uIndex._settled_value = Value.wrap(accountData.settledValue); } - // FlowDistributionData data packing: + // # FlowInfo + // + // ## Data Packing + // // --------+----------+-------------+----------+--------+ // WORD A: | reserved | lastUpdated | flowRate | buffer | // --------+----------+-------------+----------+--------+ // | 32 | 32 | 96 | 96 | // --------+----------+-------------+----------+--------+ - struct FlowDistributionData { + struct FlowInfo { uint32 lastUpdated; int96 flowRate; uint256 buffer; // stored as uint96 @@ -184,7 +169,11 @@ library GDAv1StorageReader { return keccak256(abi.encode(block.chainid, "distributionFlow", from, to)); } - function encodeFlowDistributionData(FlowDistributionData memory flowDistributionData) + function getPoolAdjustmentFlowHash(address from, address to) internal view returns (bytes32) { + return keccak256(abi.encode(block.chainid, "poolAdjustmentFlow", from, to)); + } + + function encodeFlowInfo(FlowInfo memory flowDistributionData) internal pure returns (bytes32[] memory data) { @@ -196,9 +185,9 @@ library GDAv1StorageReader { ); } - function decodeFlowDistributionData(uint256 data) + function decodeFlowInfo(uint256 data) internal pure - returns (FlowDistributionData memory flowDistributionData) + returns (FlowInfo memory flowDistributionData) { if (data > 0) { flowDistributionData.lastUpdated = uint32((data >> 192) & uint256(type(uint32).max)); @@ -206,29 +195,58 @@ library GDAv1StorageReader { flowDistributionData.buffer = uint96(data & uint256(type(uint96).max)); } } +} + +/* @title Storage layout writer for the GDAv1. + * @author Superfluid + */ +library GDAv1StorageReader { + // AccountData + + function getAccountData(ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, address owner) + internal + view + returns (GDAv1StorageLib.AccountData memory accountData) + { + return GDAv1StorageLib.decodeAccountData(token.getAgreementStateSlot + (address(gda), owner, GDAv1StorageLib.ACCOUNT_DATA_STATE_SLOT_ID, 2)); + } - function getFlowDistributionDataWithKnownHash( + /// @dev returns true if the account is a pool + function isPool(ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, address account) + internal + view + returns (bool) + { + uint256 a = uint256(token.getAgreementStateSlot + (address(gda), account, GDAv1StorageLib.ACCOUNT_DATA_STATE_SLOT_ID, 1)[0]); + return a & 1 == 1; + } + + // FlowInfo + + function getFlowInfoByFlowHash( ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, - bytes32 flowDistributionHash + bytes32 flowHash ) internal view - returns (FlowDistributionData memory flowDistributionData) + returns (GDAv1StorageLib.FlowInfo memory flowDistributionData) { - uint256 data = uint256(token.getAgreementData(address(gda), flowDistributionHash, 1)[0]); - return decodeFlowDistributionData(data); + uint256 data = uint256(token.getAgreementData(address(gda), flowHash, 1)[0]); + return GDAv1StorageLib.decodeFlowInfo(data); } - function getFlowDistributionData( + function getDistributionFlowInfo( ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, address from, ISuperfluidPool to ) internal view - returns (FlowDistributionData memory flowDistributionData) + returns (GDAv1StorageLib.FlowInfo memory flowDistributionData) { - return getFlowDistributionDataWithKnownHash(token, gda, getFlowDistributionHash(from, to)); + return getFlowInfoByFlowHash(token, gda, GDAv1StorageLib.getFlowDistributionHash(from, to)); } } @@ -238,17 +256,19 @@ library GDAv1StorageReader { * @dev Due to how agreement framework works, `address(this)` must be the GeneralDistributionAgreementV1 itself. */ library GDAv1StorageWriter { + // AccountData + function setUniversalIndex(ISuperfluidToken token, address owner, BasicParticle memory uIndex) internal { - GDAv1StorageReader.AccountData memory accountData = + GDAv1StorageLib.AccountData memory accountData = GDAv1StorageReader.getAccountData(token, IGeneralDistributionAgreementV1(address(this)), owner); token.updateAgreementStateSlot( owner, - GDAv1StorageReader.ACCOUNT_DATA_STATE_SLOT_ID, - GDAv1StorageReader.encodeUpdatedUniversalIndex(accountData, uIndex) + GDAv1StorageLib.ACCOUNT_DATA_STATE_SLOT_ID, + GDAv1StorageLib.encodeUpdatedUniversalIndex(accountData, uIndex) ); } @@ -257,20 +277,31 @@ library GDAv1StorageWriter { { bytes32[] memory data = new bytes32[](1); data[0] = bytes32(uint256(1)); - token.updateAgreementStateSlot(address(pool), GDAv1StorageReader.ACCOUNT_DATA_STATE_SLOT_ID, data); + token.updateAgreementStateSlot(address(pool), GDAv1StorageLib.ACCOUNT_DATA_STATE_SLOT_ID, data); } function setTotalBuffer(ISuperfluidToken token, address owner, - uint256 totalBuffer) internal + uint256 totalBuffer) + internal { - GDAv1StorageReader.AccountData memory accountData = + GDAv1StorageLib.AccountData memory accountData = GDAv1StorageReader.getAccountData(token, IGeneralDistributionAgreementV1(address(this)), owner); token.updateAgreementStateSlot( owner, - GDAv1StorageReader.ACCOUNT_DATA_STATE_SLOT_ID, - GDAv1StorageReader.encodeUpdatedTotalBuffer(accountData, totalBuffer) + GDAv1StorageLib.ACCOUNT_DATA_STATE_SLOT_ID, + GDAv1StorageLib.encodeUpdatedTotalBuffer(accountData, totalBuffer) ); } + + // FlowInfo + + function setFlowInfoByFlowHash(ISuperfluidToken token, + bytes32 flowHash, + GDAv1StorageLib.FlowInfo memory flowInfo) + internal + { + token.updateAgreementData(flowHash, GDAv1StorageLib.encodeFlowInfo(flowInfo)); + } } diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 35f1e1fac9..8d91ee0d80 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -30,7 +30,7 @@ import { SlotsBitmapLibrary } from "../../libs/SlotsBitmapLibrary.sol"; import { SolvencyHelperLibrary } from "../../libs/SolvencyHelperLibrary.sol"; import { AgreementBase } from "../AgreementBase.sol"; import { AgreementLibrary } from "../AgreementLibrary.sol"; -import { GDAv1StorageReader, GDAv1StorageWriter } from "./GDAv1StorageLayout.sol"; +import { GDAv1StorageLib, GDAv1StorageReader, GDAv1StorageWriter } from "./GDAv1StorageLayout.sol"; /** @@ -73,12 +73,12 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (int256 rtb, uint256 buf, uint256 owedBuffer) { - GDAv1StorageReader.AccountData memory accountData = token.getAccountData(this, account); + GDAv1StorageLib.AccountData memory accountData = token.getAccountData(this, account); if (token.isPool(this, account)) { rtb = ISuperfluidPool(account).getDisconnectedBalance(uint32(time)); } else { - rtb = Value.unwrap(GDAv1StorageReader + rtb = Value.unwrap(GDAv1StorageLib .getUniversalIndexFromAccountData(accountData) .rtb(Time.wrap(uint32(time)))); } @@ -135,7 +135,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (int96) { - GDAv1StorageReader.FlowDistributionData memory data = token.getFlowDistributionData(this, from, to); + GDAv1StorageLib.FlowInfo memory data = token.getDistributionFlowInfo(this, from, to); return data.flowRate; } @@ -146,7 +146,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (uint256 lastUpdated, int96 flowRate, uint256 deposit) { - GDAv1StorageReader.FlowDistributionData memory data = token.getFlowDistributionData(this, from, to); + GDAv1StorageLib.FlowInfo memory data = token.getDistributionFlowInfo(this, from, to); lastUpdated = data.lastUpdated; flowRate = data.flowRate; deposit = data.buffer; @@ -159,7 +159,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (uint256 timestamp, int96 flowRate, uint256 deposit) { - GDAv1StorageReader.AccountData memory accountData = token.getAccountData(this, account); + GDAv1StorageLib.AccountData memory accountData = token.getAccountData(this, account); timestamp = accountData.settledAt; flowRate = accountData.flowRate; deposit = accountData.totalBuffer; @@ -173,7 +173,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi int96 requestedFlowRate ) external view override returns (int96 actualFlowRate, int96 totalDistributionFlowRate) { bytes memory eff = abi.encode(token); - bytes32 distributionFlowHash = GDAv1StorageReader.getFlowDistributionHash(from, to); + bytes32 distributionFlowHash = GDAv1StorageLib.getFlowDistributionHash(from, to); BasicParticle memory fromUIndexData = _getUIndex(eff, from); @@ -467,7 +467,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi _StackVars_DistributeFlow memory flowVars; { flowVars.currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); - flowVars.distributionFlowHash = GDAv1StorageReader.getFlowDistributionHash(from, pool); + flowVars.distributionFlowHash = GDAv1StorageLib.getFlowDistributionHash(from, pool); flowVars.oldFlowRate = _getFlowRate(abi.encode(token), flowVars.distributionFlowHash); } @@ -571,8 +571,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } function _makeLiquidationPayouts(_StackVars_Liquidation memory data) internal { - GDAv1StorageReader.FlowDistributionData memory flowDistributionData = - data.token.getFlowDistributionDataWithKnownHash(this, data.distributionFlowHash); + GDAv1StorageLib.FlowInfo memory flowDistributionData = + data.token.getFlowInfoByFlowHash(this, data.distributionFlowHash); int256 signedSingleDeposit = flowDistributionData.buffer.toInt256(); bool isCurrentlyPatricianPeriod; @@ -625,8 +625,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi (uint256 liquidationPeriod,) = SolvencyHelperLibrary.decode3PsData(ISuperfluid(_host), ISuperfluidToken(token)); - GDAv1StorageReader.FlowDistributionData memory flowDistributionData = - token.getFlowDistributionDataWithKnownHash(this, flowHash); + GDAv1StorageLib.FlowInfo memory flowDistributionData = token.getFlowInfoByFlowHash(this, flowHash); // @note downcasting from uint256 -> uint32 for liquidation period Value newBufferAmount = newFlowRate.mul(Time.wrap(uint32(liquidationPeriod))); @@ -637,19 +636,15 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi Value bufferDelta = newBufferAmount - Value.wrap(uint256(flowDistributionData.buffer).toInt256()); - { - bytes32[] memory data = GDAv1StorageReader.encodeFlowDistributionData( - GDAv1StorageReader.FlowDistributionData({ - lastUpdated: uint32(block.timestamp), - flowRate: int256(FlowRate.unwrap(newFlowRate)).toInt96(), - buffer: uint256(Value.unwrap(newBufferAmount)) // upcast to uint256 is safe - }) - ); + token.setFlowInfoByFlowHash(flowHash, + GDAv1StorageLib.FlowInfo({ + lastUpdated: uint32(block.timestamp), + flowRate: int256(FlowRate.unwrap(newFlowRate)).toInt96(), + buffer: uint256(Value.unwrap(newBufferAmount)) // upcast to uint256 is safe + }) + ); - ISuperfluidToken(token).updateAgreementData(flowHash, data); - } - - GDAv1StorageReader.AccountData memory accountData = token.getAccountData(this, from); + GDAv1StorageLib.AccountData memory accountData = token.getAccountData(this, from); // new buffer accountData.totalBuffer = (accountData.totalBuffer.toInt256() + Value.unwrap(bufferDelta)).toUint256(); token.setTotalBuffer(from, accountData.totalBuffer); @@ -729,7 +724,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi { // pool admin is always the adjustment recipient adjustmentRecipient = ISuperfluidPool(pool).admin(); - flowHash = _getPoolAdjustmentFlowHash(pool, adjustmentRecipient); + flowHash = GDAv1StorageLib.getPoolAdjustmentFlowHash(pool, adjustmentRecipient); return (adjustmentRecipient, flowHash, int256(FlowRate.unwrap(_getFlowRate(eff, flowHash))).toInt96()); } @@ -739,22 +734,16 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi { // @note should this also always be address adjustmentRecipient = ISuperfluidPool(pool).admin(); - bytes32 adjustmentFlowHash = _getPoolAdjustmentFlowHash(pool, adjustmentRecipient); + bytes32 adjustmentFlowHash = GDAv1StorageLib.getPoolAdjustmentFlowHash(pool, adjustmentRecipient); if (doShiftFlow) { flowRate = flowRate + _getFlowRate(eff, adjustmentFlowHash); } - eff = _doFlow(eff, pool, adjustmentRecipient, adjustmentFlowHash, flowRate, t); - return eff; + return _doFlow(eff, pool, adjustmentRecipient, adjustmentFlowHash, flowRate, t); } // Hash Getters - function _getPoolAdjustmentFlowHash(address from, address to) internal view returns (bytes32) { - // this will never be in conflict with other flow has types - return keccak256(abi.encode(block.chainid, "poolAdjustmentFlow", from, to)); - } - function _getPoolMemberHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { return keccak256(abi.encode(block.chainid, "poolMember", poolMember, address(pool))); } @@ -770,8 +759,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi returns (BasicParticle memory uIndex) { ISuperfluidToken token = ISuperfluidToken(abi.decode(eff, (address))); - GDAv1StorageReader.AccountData memory accountData = token.getAccountData(this, owner); - uIndex = GDAv1StorageReader.getUniversalIndexFromAccountData(accountData); + GDAv1StorageLib.AccountData memory accountData = token.getAccountData(this, owner); + uIndex = GDAv1StorageLib.getUniversalIndexFromAccountData(accountData); } /// @inheritdoc TokenMonad @@ -815,8 +804,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi returns (FlowRate) { ISuperfluidToken token = ISuperfluidToken(abi.decode(eff, (address))); - GDAv1StorageReader.FlowDistributionData memory data = - token.getFlowDistributionDataWithKnownHash(this, distributionFlowHash); + GDAv1StorageLib.FlowInfo memory data = token.getFlowInfoByFlowHash(this, distributionFlowHash); return FlowRate.wrap(data.flowRate); } @@ -834,19 +822,15 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi returns (bytes memory) { ISuperfluidToken token = ISuperfluidToken(abi.decode(eff, (address))); - GDAv1StorageReader.FlowDistributionData memory flowDistributionData = - token.getFlowDistributionDataWithKnownHash(this, flowHash); - - ISuperfluidToken(token).updateAgreementData( - flowHash, - GDAv1StorageReader.encodeFlowDistributionData( - GDAv1StorageReader.FlowDistributionData({ - lastUpdated: uint32(block.timestamp), - flowRate: int256(FlowRate.unwrap(newFlowRate)).toInt96(), - buffer: flowDistributionData.buffer - }) - ) - ); + GDAv1StorageLib.FlowInfo memory flowDistributionData = token.getFlowInfoByFlowHash(this, flowHash); + + token.setFlowInfoByFlowHash(flowHash, + GDAv1StorageLib.FlowInfo({ + lastUpdated: uint32(block.timestamp), + flowRate: int256(FlowRate.unwrap(newFlowRate)).toInt96(), + buffer: flowDistributionData.buffer + }) + ); return eff; } diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol index c7cd396bb0..f4ed1ead31 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol @@ -16,7 +16,7 @@ import { PoolConfig, ISuperfluid, ISuperfluidPool, ISuperToken } from "../../../../contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol"; -import { GDAv1StorageReader } from "../../../../contracts/agreements/gdav1/GDAv1StorageLayout.sol"; +import { GDAv1StorageLib, GDAv1StorageReader } from "../../../../contracts/agreements/gdav1/GDAv1StorageLayout.sol"; import { ISuperfluidPool, SuperfluidPool } from "../../../../contracts/agreements/gdav1/SuperfluidPool.sol"; import { SuperTokenV1Library } from "../../../../contracts/apps/SuperTokenV1Library.sol"; @@ -92,7 +92,7 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen _settled_value: Value.wrap(settledValue) }); _setUIndex(eff, owner, p); - GDAv1StorageReader.AccountData memory accountData = superToken.getAccountData(this, owner); + GDAv1StorageLib.AccountData memory accountData = superToken.getAccountData(this, owner); assertEq(settledAt, accountData.settledAt, "settledAt not equal"); assertEq(flowRate, accountData.flowRate, "flowRate not equal"); @@ -102,7 +102,7 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen } // Flow Distribution Data Setters/Getters - function testSetGetFlowDistributionData( + function testSetGetFlowInfo( address from, ISuperfluidPool to, uint32 newFlowRate, @@ -110,7 +110,7 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen ) public { uint256 lastUpdated = block.timestamp; - bytes32 flowHash = GDAv1StorageReader.getFlowDistributionHash(from, to); + bytes32 flowHash = GDAv1StorageLib.getFlowDistributionHash(from, to); _setFlowInfo( abi.encode(superToken), @@ -122,14 +122,13 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen ); vm.warp(1000); - GDAv1StorageReader.FlowDistributionData memory setFlowDistributionData = - superToken.getFlowDistributionDataWithKnownHash(this, flowHash); + GDAv1StorageLib.FlowInfo memory setFlowInfo = superToken.getFlowInfoByFlowHash(this, flowHash); - assertEq(int96(uint96(newFlowRate)), setFlowDistributionData.flowRate, "flowRate not equal"); + assertEq(int96(uint96(newFlowRate)), setFlowInfo.flowRate, "flowRate not equal"); - assertEq(lastUpdated, setFlowDistributionData.lastUpdated, "lastUpdated not equal"); + assertEq(lastUpdated, setFlowInfo.lastUpdated, "lastUpdated not equal"); - assertEq(0, setFlowDistributionData.buffer, "buffer not equal"); + assertEq(0, setFlowInfo.buffer, "buffer not equal"); assertEq( int96(FlowRate.unwrap(_getFlowRate(abi.encode(superToken), flowHash))), int96(uint96(newFlowRate)), @@ -208,8 +207,8 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen ); } - // // Adjust Buffer => FlowDistributionData modified - // function testAdjustBufferUpdatesFlowDistributionData(address from, int32 oldFlowRate, int32 newFlowRate) public { + // // Adjust Buffer => FlowInfo modified + // function testAdjustBufferUpdatesFlowInfo(address from, int32 oldFlowRate, int32 newFlowRate) public { // vm.assume(newFlowRate >= 0); // bytes32 flowHash = _getFlowDistributionHash(from, currentPool); @@ -224,8 +223,8 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen // FlowRate.wrap(int128(newFlowRate)) // ); - // (bool exist, IGeneralDistributionAgreementV1.FlowDistributionData memory flowDistributionData) = - // _getFlowDistributionData(superToken, flowHash); + // (bool exist, IGeneralDistributionAgreementV1.FlowInfo memory flowDistributionData) = + // _getFlowInfo(superToken, flowHash); // assertEq(exist, true, "flow distribution data does not exist"); // assertEq(flowDistributionData.buffer, expectedBuffer, "buffer not equal"); // assertEq(flowDistributionData.flowRate, int96(newFlowRate), "buffer not equal"); @@ -277,8 +276,8 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen bool isPool ) pure public { - GDAv1StorageReader.AccountData memory accountData = - GDAv1StorageReader.AccountData({ + GDAv1StorageLib.AccountData memory accountData = + GDAv1StorageLib.AccountData({ flowRate: 0, settledAt: 0, settledValue: 0, @@ -292,8 +291,8 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen _settled_value: Value.wrap(settledValue) }); - bytes32[] memory encoded = GDAv1StorageReader.encodeUpdatedUniversalIndex(accountData, uIndex); - GDAv1StorageReader.AccountData memory decoded = GDAv1StorageReader.decodeAccountData(encoded); + bytes32[] memory encoded = GDAv1StorageLib.encodeUpdatedUniversalIndex(accountData, uIndex); + GDAv1StorageLib.AccountData memory decoded = GDAv1StorageLib.decodeAccountData(encoded); assertEq(flowRate, decoded.flowRate, "flowRate not equal"); assertEq(settledAt, decoded.settledAt, "settledAt not equal"); @@ -309,16 +308,16 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen bool isPool ) public pure { - GDAv1StorageReader.AccountData memory accountData = - GDAv1StorageReader.AccountData({ + GDAv1StorageLib.AccountData memory accountData = + GDAv1StorageLib.AccountData({ flowRate: flowRate, settledAt: settledAt, settledValue: 0, totalBuffer: 0, isPool: isPool }); - bytes32[] memory encoded = GDAv1StorageReader.encodeUpdatedTotalBuffer(accountData, totalBuffer); - GDAv1StorageReader.AccountData memory decoded = GDAv1StorageReader.decodeAccountData(encoded); + bytes32[] memory encoded = GDAv1StorageLib.encodeUpdatedTotalBuffer(accountData, totalBuffer); + GDAv1StorageLib.AccountData memory decoded = GDAv1StorageLib.decodeAccountData(encoded); assertEq(flowRate, decoded.flowRate, "flowRate not equal"); assertEq(settledAt, decoded.settledAt, "settledAt not equal"); @@ -326,28 +325,27 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen assertEq(isPool, decoded.isPool, "isPool not equal"); } - function testDecodeAccountData(GDAv1StorageReader.AccountData memory data) + function testDecodeAccountData(GDAv1StorageLib.AccountData memory data) pure public { - BasicParticle memory particle = GDAv1StorageReader.getUniversalIndexFromAccountData(data); + BasicParticle memory particle = GDAv1StorageLib.getUniversalIndexFromAccountData(data); assertEq(data.flowRate, int96(FlowRate.unwrap(particle._flow_rate)), "flowRate not equal"); assertEq(data.settledAt, Time.unwrap(particle._settled_at), "settledAt not equal"); assertEq(data.settledValue, Value.unwrap(particle._settled_value), "settledValue not equal"); } - function testEncodeDecodeFlowDistributionData(int96 flowRate, uint96 buffer) public view { + function testEncodeDecodeFlowInfo(int96 flowRate, uint96 buffer) public view { vm.assume(flowRate >= 0); vm.assume(buffer >= 0); - GDAv1StorageReader.FlowDistributionData memory original = - GDAv1StorageReader.FlowDistributionData({ + GDAv1StorageLib.FlowInfo memory original = + GDAv1StorageLib.FlowInfo({ flowRate: flowRate, lastUpdated: uint32(block.timestamp), buffer: buffer }); - bytes32[] memory encoded = GDAv1StorageReader.encodeFlowDistributionData(original); - GDAv1StorageReader.FlowDistributionData memory decoded = - GDAv1StorageReader.decodeFlowDistributionData(uint256(encoded[0])); + bytes32[] memory encoded = GDAv1StorageLib.encodeFlowInfo(original); + GDAv1StorageLib.FlowInfo memory decoded = GDAv1StorageLib.decodeFlowInfo(uint256(encoded[0])); assertEq(original.flowRate, decoded.flowRate, "flowRate not equal"); assertEq(original.buffer, decoded.buffer, "buffer not equal"); From 5b2d9d872a49b602f4e0912b0683cc6433ba38dd Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Fri, 4 Jul 2025 12:01:29 +0300 Subject: [PATCH 04/16] PoolMemberData refactored out --- .../agreements/gdav1/GDAv1StorageLayout.sol | 138 +++++++++++++++--- .../gdav1/GeneralDistributionAgreementV1.sol | 85 +++-------- .../GeneralDistributionAgreementV1.prop.t.sol | 29 ++-- 3 files changed, 153 insertions(+), 99 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol index 0658d25825..51e32ce8cd 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -60,13 +60,15 @@ import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPo */ library GDAv1StorageLib { - // # Account Data + // # AccountData + // + // ## Data Packing // // Account data includes: // - Semantic universal index (a basic particle) - // - buffer amount + // - buffer amount (96b) // - isPool flag - // store buffer (96) and one bit to specify is pool in free + // // --------+------------------+------------------+------------------+------------------+ // WORD 1: | flowRate | settledAt | totalBuffer | isPool | // --------+------------------+------------------+------------------+------------------+ @@ -95,6 +97,7 @@ library GDAv1StorageLib { { data = new bytes32[](2); data[0] = bytes32( + // FIXME: this allows negative flow rate, is it a problem? (uint256(int256(SafeCast.toInt96(FlowRate.unwrap(uIndex.flow_rate())))) << 160) | (uint256(SafeCast.toUint32(Time.unwrap(uIndex.settled_at()))) << 128) | (uint256(SafeCast.toUint96(accountData.totalBuffer)) << 32) | @@ -195,6 +198,48 @@ library GDAv1StorageLib { flowDistributionData.buffer = uint96(data & uint256(type(uint96).max)); } } + + // # PoolMemberData + // + // ## Data Packing + // + // --------+----------+--------+-------------+ + // WORD A: | reserved | poolID | poolAddress | + // --------+----------+--------+-------------+ + // | 64 | 32 | 160 | + // --------+----------+--------+-------------+ + + struct PoolMemberData { + address pool; + uint32 poolID; // the slot id in the pool's subs bitmap + } + + function getPoolMemberHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { + return keccak256(abi.encode(block.chainid, "poolMember", poolMember, address(pool))); + } + + function encodePoolMemberData(PoolMemberData memory poolMemberData) + internal + pure + returns (bytes32[] memory data) + { + data = new bytes32[](1); + data[0] = bytes32( + (uint256(uint32(poolMemberData.poolID)) << 160) | + uint256(uint160(poolMemberData.pool))); + } + + function decodePoolMemberData(uint256 data) + internal + pure + returns (bool exist, PoolMemberData memory poolMemberData) + { + exist = data > 0; + if (exist) { + poolMemberData.pool = address(uint160(data & uint256(type(uint160).max))); + poolMemberData.poolID = uint32(data >> 160); + } + } } /* @title Storage layout writer for the GDAv1. @@ -225,11 +270,11 @@ library GDAv1StorageReader { // FlowInfo - function getFlowInfoByFlowHash( - ISuperfluidToken token, - IGeneralDistributionAgreementV1 gda, - bytes32 flowHash - ) + function getFlowInfoByFlowHash + (ISuperfluidToken token, + IGeneralDistributionAgreementV1 gda, + bytes32 flowHash + ) internal view returns (GDAv1StorageLib.FlowInfo memory flowDistributionData) { @@ -237,17 +282,33 @@ library GDAv1StorageReader { return GDAv1StorageLib.decodeFlowInfo(data); } - function getDistributionFlowInfo( - ISuperfluidToken token, - IGeneralDistributionAgreementV1 gda, - address from, - ISuperfluidPool to - ) + function getDistributionFlowInfo + (ISuperfluidToken token, + IGeneralDistributionAgreementV1 gda, + address from, + ISuperfluidPool to + ) internal view returns (GDAv1StorageLib.FlowInfo memory flowDistributionData) { return getFlowInfoByFlowHash(token, gda, GDAv1StorageLib.getFlowDistributionHash(from, to)); } + + // PoolMemberData + + function getPoolMemberData + (ISuperfluidToken token, + IGeneralDistributionAgreementV1 gda, + address poolMember, + ISuperfluidPool pool + ) + internal view + returns (bool exist, GDAv1StorageLib.PoolMemberData memory poolMemberData) + { + bytes32 dataId = GDAv1StorageLib.getPoolMemberHash(poolMember, pool); + uint256 data = uint256(token.getAgreementData(address(gda), dataId, 1)[0]); + return GDAv1StorageLib.decodePoolMemberData(data); + } } @@ -258,9 +319,12 @@ library GDAv1StorageReader { library GDAv1StorageWriter { // AccountData - function setUniversalIndex(ISuperfluidToken token, - address owner, - BasicParticle memory uIndex) internal + function setUniversalIndex + (ISuperfluidToken token, + address owner, + BasicParticle memory uIndex + ) + internal { GDAv1StorageLib.AccountData memory accountData = GDAv1StorageReader.getAccountData(token, IGeneralDistributionAgreementV1(address(this)), owner); @@ -280,9 +344,11 @@ library GDAv1StorageWriter { token.updateAgreementStateSlot(address(pool), GDAv1StorageLib.ACCOUNT_DATA_STATE_SLOT_ID, data); } - function setTotalBuffer(ISuperfluidToken token, - address owner, - uint256 totalBuffer) + function setTotalBuffer + (ISuperfluidToken token, + address owner, + uint256 totalBuffer + ) internal { GDAv1StorageLib.AccountData memory accountData = @@ -297,11 +363,37 @@ library GDAv1StorageWriter { // FlowInfo - function setFlowInfoByFlowHash(ISuperfluidToken token, - bytes32 flowHash, - GDAv1StorageLib.FlowInfo memory flowInfo) + function setFlowInfoByFlowHash + (ISuperfluidToken token, + bytes32 flowHash, + GDAv1StorageLib.FlowInfo memory flowInfo + ) internal { token.updateAgreementData(flowHash, GDAv1StorageLib.encodeFlowInfo(flowInfo)); } + + // PoolMemberData + + function createPoolMembership + (ISuperfluidToken token, + address poolMember, + ISuperfluidPool pool, + GDAv1StorageLib.PoolMemberData memory poolMemberData + ) + internal + { + token.createAgreement + (GDAv1StorageLib.getPoolMemberHash(poolMember, pool), + GDAv1StorageLib.encodePoolMemberData(poolMemberData)); + } + + function deletePoolMembership + (ISuperfluidToken token, + address poolMember, + ISuperfluidPool pool) + internal + { + token.terminateAgreement(GDAv1StorageLib.getPoolMemberHash(poolMember, pool), 1); + } } diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 8d91ee0d80..3d4f41f48f 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -44,11 +44,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi using GDAv1StorageReader for ISuperfluidToken; using GDAv1StorageWriter for ISuperfluidToken; - struct PoolMemberData { - address pool; - uint32 poolID; // the slot id in the pool's subs bitmap - } - address public constant SLOTS_BITMAP_LIBRARY_ADDRESS = address(SlotsBitmapLibrary); address public constant SUPERFLUID_POOL_DEPLOYER_ADDRESS = address(SuperfluidPoolDeployerLibrary); @@ -83,24 +78,29 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi .rtb(Time.wrap(uint32(time)))); } - int256 fromPools; + int256 totalClaimableFromPools; { (uint32[] memory slotIds, bytes32[] memory pidList) = _listPoolConnectionIds(token, account); for (uint256 i = 0; i < slotIds.length; ++i) { address pool = address(uint160(uint256(pidList[i]))); - (bool exist, PoolMemberData memory poolMemberData) = - _getPoolMemberData(token, account, ISuperfluidPool(pool)); - assert(exist); - assert(poolMemberData.pool == pool); - fromPools += ISuperfluidPool(pool).getClaimable(account, uint32(time)); + _assertPoolMembership(token, account, ISuperfluidPool(pool)); + totalClaimableFromPools += ISuperfluidPool(pool).getClaimable(account, uint32(time)); } } - rtb += fromPools; + rtb += totalClaimableFromPools; buf = uint256(accountData.totalBuffer.toInt256()); // upcasting to uint256 is safe owedBuffer = 0; } + function _assertPoolMembership(ISuperfluidToken token, address account, ISuperfluidPool pool) internal view + { + (bool exist, GDAv1StorageLib.PoolMemberData memory poolMemberData) = + token.getPoolMemberData(this, account, ISuperfluidPool(pool)); + assert(exist); + assert(poolMemberData.pool == address(pool)); + } + /// @dev ISuperAgreement.realtimeBalanceOf implementation function realtimeBalanceOfNow(ISuperfluidToken token, address account) external @@ -327,16 +327,13 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi uint32 poolSlotID = _findAndFillPoolConnectionsBitmap(token, msgSender, bytes32(uint256(uint160(address(pool))))); - // malicious token can reenter here - // external call to untrusted contract - // what sort of boundary can we trust - token.createAgreement( - _getPoolMemberHash(msgSender, pool), - _encodePoolMemberData(PoolMemberData({ poolID: poolSlotID, pool: address(pool) })) - ); + token.createPoolMembership + (msgSender, pool, + GDAv1StorageLib.PoolMemberData({ poolID: poolSlotID, pool: address(pool) })); } else { - (, PoolMemberData memory poolMemberData) = _getPoolMemberData(token, msgSender, pool); - token.terminateAgreement(_getPoolMemberHash(msgSender, pool), 1); + (, GDAv1StorageLib.PoolMemberData memory poolMemberData) = + token.getPoolMemberData(this, msgSender, pool); + token.deletePoolMembership(msgSender, pool); _clearPoolConnectionsBitmap(token, msgSender, poolMemberData.poolID); } @@ -346,7 +343,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } function _isMemberConnected(ISuperfluidToken token, address pool, address member) internal view returns (bool) { - (bool exist,) = _getPoolMemberData(token, member, ISuperfluidPool(pool)); + (bool exist,) = token.getPoolMemberData(this,member, ISuperfluidPool(pool)); return exist; } @@ -742,12 +739,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return _doFlow(eff, pool, adjustmentRecipient, adjustmentFlowHash, flowRate, t); } - // Hash Getters - - function _getPoolMemberHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { - return keccak256(abi.encode(block.chainid, "poolMember", poolMember, address(pool))); - } - // // TokenMonad virtual functions // @@ -854,44 +845,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return _setPoolAdjustmentFlowRate(eff, pool, false, /* doShift? */ flowRate, t); } - // PoolMemberData data packing: - // -------- ---------- -------- ------------- - // WORD A: | reserved | poolID | poolAddress | - // -------- ---------- -------- ------------- - // | 64 | 32 | 160 | - // -------- ---------- -------- ------------- - - function _encodePoolMemberData(PoolMemberData memory poolMemberData) - internal - pure - returns (bytes32[] memory data) - { - data = new bytes32[](1); - data[0] = bytes32((uint256(uint32(poolMemberData.poolID)) << 160) | uint256(uint160(poolMemberData.pool))); - } - - function _decodePoolMemberData(uint256 data) - internal - pure - returns (bool exist, PoolMemberData memory poolMemberData) - { - exist = data > 0; - if (exist) { - poolMemberData.pool = address(uint160(data & uint256(type(uint160).max))); - poolMemberData.poolID = uint32(data >> 160); - } - } - - function _getPoolMemberData(ISuperfluidToken token, address poolMember, ISuperfluidPool pool) - internal - view - returns (bool exist, PoolMemberData memory poolMemberData) - { - (exist, poolMemberData) = _decodePoolMemberData( - uint256(token.getAgreementData(address(this), _getPoolMemberHash(poolMember, pool), 1)[0]) - ); - } - // SlotsBitmap Pool Data: function _findAndFillPoolConnectionsBitmap(ISuperfluidToken token, address poolMember, bytes32 poolID) private diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol index f4ed1ead31..956652ce8e 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol @@ -16,7 +16,9 @@ import { PoolConfig, ISuperfluid, ISuperfluidPool, ISuperToken } from "../../../../contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol"; -import { GDAv1StorageLib, GDAv1StorageReader } from "../../../../contracts/agreements/gdav1/GDAv1StorageLayout.sol"; +import { + GDAv1StorageLib, GDAv1StorageReader, GDAv1StorageWriter +} from "../../../../contracts/agreements/gdav1/GDAv1StorageLayout.sol"; import { ISuperfluidPool, SuperfluidPool } from "../../../../contracts/agreements/gdav1/SuperfluidPool.sol"; import { SuperTokenV1Library } from "../../../../contracts/apps/SuperTokenV1Library.sol"; @@ -29,6 +31,7 @@ import { SuperTokenV1Library } from "../../../../contracts/apps/SuperTokenV1Libr contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreementV1, Test { using SuperTokenV1Library for ISuperToken; using GDAv1StorageReader for ISuperToken; + using GDAv1StorageWriter for ISuperToken; SuperfluidFrameworkDeployer internal immutable sfDeployer; SuperfluidFrameworkDeployer.Framework internal sf; @@ -146,16 +149,20 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen vm.assume(poolID > 0); vm.assume(address(_pool) != address(0)); vm.assume(address(poolMember) != address(0)); - bytes32 poolMemberId = _getPoolMemberHash(poolMember, _pool); vm.startPrank(address(this)); - superToken.updateAgreementData( - poolMemberId, - _encodePoolMemberData(PoolMemberData({ poolID: poolID, pool: address(_pool) })) - ); + superToken.createPoolMembership + (poolMember, + _pool, + GDAv1StorageLib.PoolMemberData ({ + poolID: poolID, + pool: address(_pool) + }) + ); vm.stopPrank(); - (bool exist, PoolMemberData memory setPoolMemberData) = _getPoolMemberData(superToken, poolMember, _pool); + (bool exist, GDAv1StorageLib.PoolMemberData memory setPoolMemberData) = + superToken.getPoolMemberData(this, poolMember, _pool); assertEq(true, exist, "pool member data does not exist"); assertEq(poolID, setPoolMemberData.poolID, "poolID not equal"); @@ -354,9 +361,11 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen function testEncodeDecodePoolMemberData(address pool, uint32 poolID) public pure { vm.assume(pool != address(0)); - PoolMemberData memory original = PoolMemberData({ pool: pool, poolID: poolID }); - bytes32[] memory encoded = _encodePoolMemberData(original); - (, PoolMemberData memory decoded) = _decodePoolMemberData(uint256(encoded[0])); + GDAv1StorageLib.PoolMemberData memory original = + GDAv1StorageLib.PoolMemberData({ pool: pool, poolID: poolID }); + bytes32[] memory encoded = GDAv1StorageLib.encodePoolMemberData(original); + (, GDAv1StorageLib.PoolMemberData memory decoded) = + GDAv1StorageLib.decodePoolMemberData(uint256(encoded[0])); assertEq(original.pool, decoded.pool, "pool not equal"); assertEq(original.poolID, decoded.poolID, "poolID not equal"); From 31bc222f6727c930f7330090acf0da374f3813c5 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Fri, 4 Jul 2025 12:18:40 +0300 Subject: [PATCH 05/16] calculate _isMemberConnected inside the pool directly --- .../gdav1/GeneralDistributionAgreementV1.sol | 73 ++++++++++--------- .../agreements/gdav1/SuperfluidPool.sol | 12 ++- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 3d4f41f48f..451b538fa2 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -306,6 +306,19 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return connectPool(pool, false, ctx); } + /// @inheritdoc IGeneralDistributionAgreementV1 + function isMemberConnected(ISuperfluidPool pool, address member) external view override returns (bool) { + return _isMemberConnected(pool.superToken(), pool, member); + } + + function _isMemberConnected(ISuperfluidToken token, ISuperfluidPool pool, address member) + internal view + returns (bool) + { + (bool exist,) = token.getPoolMemberData(this,member, pool); + return exist; + } + // @note setPoolConnection function naming function connectPool(ISuperfluidPool pool, bool doConnect, bytes calldata ctx) public @@ -315,7 +328,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); address msgSender = currentContext.msgSender; newCtx = ctx; - bool isConnected = _isMemberConnected(token, address(pool), msgSender); + bool isConnected = _isMemberConnected(token, pool, msgSender); if (doConnect != isConnected) { assert( SuperfluidPool(address(pool)).operatorConnectMember( @@ -342,38 +355,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } } - function _isMemberConnected(ISuperfluidToken token, address pool, address member) internal view returns (bool) { - (bool exist,) = token.getPoolMemberData(this,member, ISuperfluidPool(pool)); - return exist; - } - - function isMemberConnected(ISuperfluidPool pool, address member) external view override returns (bool) { - return _isMemberConnected(pool.superToken(), address(pool), member); - } - - function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) external returns (bool) { - if (token.isPool(this, msg.sender) == false) { - revert GDA_ONLY_SUPER_TOKEN_POOL(); - } - bytes memory eff = abi.encode(token); - _setUIndex(eff, msg.sender, _getUIndex(eff, msg.sender).mappend(p)); - _setPoolAdjustmentFlowRate(eff, msg.sender, true, /* doShift? */ p.flow_rate(), t); - return true; - } - - function poolSettleClaim(ISuperfluidToken superToken, address claimRecipient, int256 amount) - external - returns (bool) - { - if (superToken.isPool(this, msg.sender) == false) { - revert GDA_ONLY_SUPER_TOKEN_POOL(); - } - - // _poolSettleClaim() - _doShift(abi.encode(superToken), msg.sender, claimRecipient, Value.wrap(amount)); - return true; - } - /// @inheritdoc IGeneralDistributionAgreementV1 function distribute( ISuperfluidToken token, @@ -739,6 +720,32 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return _doFlow(eff, pool, adjustmentRecipient, adjustmentFlowHash, flowRate, t); } + // + // Pool operations + // + + function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) external returns (bool) { + if (token.isPool(this, msg.sender) == false) { + revert GDA_ONLY_SUPER_TOKEN_POOL(); + } + bytes memory eff = abi.encode(token); + _setUIndex(eff, msg.sender, _getUIndex(eff, msg.sender).mappend(p)); + _setPoolAdjustmentFlowRate(eff, msg.sender, true, /* doShift? */ p.flow_rate(), t); + return true; + } + + function poolSettleClaim(ISuperfluidToken superToken, address claimRecipient, int256 amount) + external + returns (bool) + { + if (superToken.isPool(this, msg.sender) == false) { + revert GDA_ONLY_SUPER_TOKEN_POOL(); + } + + _doShift(abi.encode(superToken), msg.sender, claimRecipient, Value.wrap(amount)); + return true; + } + // // TokenMonad virtual functions // diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index 06b3a7031c..b3bb3e87db 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -443,7 +443,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { PDPoolMemberMU memory mu = PDPoolMemberMU(pdPoolIndex, pdPoolMember); // update pool's disconnected units - if (!GDA.isMemberConnected(ISuperfluidPool(address(this)), memberAddr)) { + if (!_isMemberConnected(superToken, ISuperfluidPool(address(this)), memberAddr)) { _shiftDisconnectedUnits(wrappedUnits - mu.m.owned_units, Value.wrap(0), t); } @@ -474,7 +474,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { /// @inheritdoc ISuperfluidPool function claimAll(address memberAddr) public returns (bool) { - bool isConnected = GDA.isMemberConnected(ISuperfluidPool(address(this)), memberAddr); + bool isConnected = _isMemberConnected(superToken, this, memberAddr); uint32 time = uint32(ISuperfluid(superToken.getHost()).getNow()); int256 claimedAmount = _claimAll(memberAddr, time); if (!isConnected) { @@ -513,6 +513,14 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { _disconnectedMembers = _pdPoolMemberToMemberData(mu.m, 0); } + function _isMemberConnected(ISuperfluidToken token, ISuperfluidPool pool, address member) + internal view + returns (bool) + { + (bool exist,) = token.getPoolMemberData(GDA, member, pool); + return exist; + } + modifier onlyGDA() { if (msg.sender != address(GDA)) revert SUPERFLUID_POOL_NOT_GDA(); _; From 512528ed6bfdcf47039c78b47c6b940973fb523a Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Fri, 4 Jul 2025 12:27:06 +0300 Subject: [PATCH 06/16] extract isPoolMemberConnected out --- .../agreements/gdav1/GDAv1StorageLayout.sol | 13 +++++++++++++ .../gdav1/GeneralDistributionAgreementV1.sol | 12 ++---------- .../contracts/agreements/gdav1/SuperfluidPool.sol | 12 ++---------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol index 51e32ce8cd..208847a593 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -309,6 +309,19 @@ library GDAv1StorageReader { uint256 data = uint256(token.getAgreementData(address(gda), dataId, 1)[0]); return GDAv1StorageLib.decodePoolMemberData(data); } + + function isPoolMemberConnected + (ISuperfluidToken token, + IGeneralDistributionAgreementV1 gda, + ISuperfluidPool pool, + address member + ) + internal view + returns (bool) + { + (bool exist,) = getPoolMemberData(token, gda, member, pool); + return exist; + } } diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 451b538fa2..6949dee4cc 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -308,15 +308,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi /// @inheritdoc IGeneralDistributionAgreementV1 function isMemberConnected(ISuperfluidPool pool, address member) external view override returns (bool) { - return _isMemberConnected(pool.superToken(), pool, member); - } - - function _isMemberConnected(ISuperfluidToken token, ISuperfluidPool pool, address member) - internal view - returns (bool) - { - (bool exist,) = token.getPoolMemberData(this,member, pool); - return exist; + return pool.superToken().isPoolMemberConnected(this, pool, member); } // @note setPoolConnection function naming @@ -328,7 +320,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); address msgSender = currentContext.msgSender; newCtx = ctx; - bool isConnected = _isMemberConnected(token, pool, msgSender); + bool isConnected = token.isPoolMemberConnected(this, pool, msgSender); if (doConnect != isConnected) { assert( SuperfluidPool(address(pool)).operatorConnectMember( diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index b3bb3e87db..b4faa49280 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -443,7 +443,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { PDPoolMemberMU memory mu = PDPoolMemberMU(pdPoolIndex, pdPoolMember); // update pool's disconnected units - if (!_isMemberConnected(superToken, ISuperfluidPool(address(this)), memberAddr)) { + if (!superToken.isPoolMemberConnected(GDA, ISuperfluidPool(address(this)), memberAddr)) { _shiftDisconnectedUnits(wrappedUnits - mu.m.owned_units, Value.wrap(0), t); } @@ -474,7 +474,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { /// @inheritdoc ISuperfluidPool function claimAll(address memberAddr) public returns (bool) { - bool isConnected = _isMemberConnected(superToken, this, memberAddr); + bool isConnected = superToken.isPoolMemberConnected(GDA, this, memberAddr); uint32 time = uint32(ISuperfluid(superToken.getHost()).getNow()); int256 claimedAmount = _claimAll(memberAddr, time); if (!isConnected) { @@ -513,14 +513,6 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { _disconnectedMembers = _pdPoolMemberToMemberData(mu.m, 0); } - function _isMemberConnected(ISuperfluidToken token, ISuperfluidPool pool, address member) - internal view - returns (bool) - { - (bool exist,) = token.getPoolMemberData(GDA, member, pool); - return exist; - } - modifier onlyGDA() { if (msg.sender != address(GDA)) revert SUPERFLUID_POOL_NOT_GDA(); _; From 223a6e01b166c237e4fbaf68c64e691dd9635645 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 8 Jul 2025 11:27:51 +0300 Subject: [PATCH 07/16] code cleanups --- .../agreements/gdav1/GDAv1StorageLayout.sol | 71 ++++--- .../gdav1/GeneralDistributionAgreementV1.sol | 182 +++++++++++------- .../GeneralDistributionAgreementV1.prop.t.sol | 61 ------ 3 files changed, 155 insertions(+), 159 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol index 208847a593..57280293f6 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -19,32 +19,21 @@ import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPo /* @title Storage layout library for the GDAv1. * @author Superfluid - * @notice * - * Storage Layout Notes + * @notice Storage Layout Notes + * * ## Agreement State * - * Universal Index Data - * slotId = _universalIndexStateSlotId() or 0 + * ### Universal Index Data + * + * slotId = 0 (ACCOUNT_DATA_STATE_SLOT_ID) * msg.sender = address of GDAv1 * account = context.msgSender * Universal Index Data stores a Basic Particle for an account as well as the total buffer and * whether the account is a pool or not. * - * SlotsBitmap Data - * slotId = _POOL_SUBS_BITMAP_STATE_SLOT_ID or 1 - * msg.sender = address of GDAv1 - * account = context.msgSender - * Slots Bitmap Data Slot stores a bitmap of the slots that are "enabled" for a pool member. - * - * Pool Connections Data Slot Id Start - * slotId (start) = _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START or 1 << 128 or 340282366920938463463374607431768211456 - * msg.sender = address of GDAv1 - * account = context.msgSender - * Pool Connections Data Slot Id Start indicates the starting slot for where we begin to store the pools that a - * pool member is a part of. - * * ## Agreement Data + * * NOTE The Agreement Data slot is calculated with the following function: * keccak256(abi.encode("AgreementData", agreementClass, agreementId)) * agreementClass = address of GDAv1 @@ -60,7 +49,7 @@ import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPo */ library GDAv1StorageLib { - // # AccountData + // # Account Data // // ## Data Packing // @@ -79,8 +68,10 @@ library GDAv1StorageLib { // | 256b | // --------+------------------+------------------+------------------+------------------+ + /// @dev Agreement state slot id for account data. uint256 internal constant ACCOUNT_DATA_STATE_SLOT_ID = 0; + /// @dev Account data struct. struct AccountData { int96 flowRate; uint32 settledAt; @@ -121,6 +112,7 @@ library GDAv1StorageLib { ); } + /// @dev Decode account data. function decodeAccountData(bytes32[] memory data) internal pure @@ -142,6 +134,7 @@ library GDAv1StorageLib { } } + /// @dev Extract universal index from the decoded account data. function getUniversalIndexFromAccountData(AccountData memory accountData) internal pure @@ -152,7 +145,7 @@ library GDAv1StorageLib { uIndex._settled_value = Value.wrap(accountData.settledValue); } - // # FlowInfo + // # Flow Info // // ## Data Packing // @@ -162,32 +155,37 @@ library GDAv1StorageLib { // | 32 | 32 | 96 | 96 | // --------+----------+-------------+----------+--------+ + /// @dev Flow info struct, for both distribution flow and adjustment flow. struct FlowInfo { uint32 lastUpdated; int96 flowRate; uint256 buffer; // stored as uint96 } + /// @dev Calculate flow hash for distribution flow. function getFlowDistributionHash(address from, ISuperfluidPool to) internal view returns (bytes32) { return keccak256(abi.encode(block.chainid, "distributionFlow", from, to)); } + /// @dev Calculate flow hash for adjustment flow. function getPoolAdjustmentFlowHash(address from, address to) internal view returns (bytes32) { return keccak256(abi.encode(block.chainid, "poolAdjustmentFlow", from, to)); } - function encodeFlowInfo(FlowInfo memory flowDistributionData) + /// @dev Encode flow info. + function encodeFlowInfo(FlowInfo memory flowInfo) internal pure returns (bytes32[] memory data) { data = new bytes32[](1); data[0] = bytes32( - (uint256(uint32(flowDistributionData.lastUpdated)) << 192) | - (uint256(uint96(flowDistributionData.flowRate)) << 96) | - uint256(flowDistributionData.buffer) + (uint256(uint32(flowInfo.lastUpdated)) << 192) | + (uint256(uint96(flowInfo.flowRate)) << 96) | + uint256(flowInfo.buffer) ); } + /// @dev Decode flow info. function decodeFlowInfo(uint256 data) internal pure returns (FlowInfo memory flowDistributionData) @@ -199,7 +197,7 @@ library GDAv1StorageLib { } } - // # PoolMemberData + // # Pool Member Data // // ## Data Packing // @@ -209,15 +207,18 @@ library GDAv1StorageLib { // | 64 | 32 | 160 | // --------+----------+--------+-------------+ + /// @dev Pool member data struct. struct PoolMemberData { address pool; uint32 poolID; // the slot id in the pool's subs bitmap } + /// @dev Calculate pool member data hash for agreement data. function getPoolMemberHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { return keccak256(abi.encode(block.chainid, "poolMember", poolMember, address(pool))); } + /// @dev Encode pool member data. function encodePoolMemberData(PoolMemberData memory poolMemberData) internal pure @@ -229,6 +230,7 @@ library GDAv1StorageLib { uint256(uint160(poolMemberData.pool))); } + /// @dev Decode pool member data. function decodePoolMemberData(uint256 data) internal pure @@ -246,8 +248,9 @@ library GDAv1StorageLib { * @author Superfluid */ library GDAv1StorageReader { - // AccountData + // Account Data + /// @dev Get account data. function getAccountData(ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, address owner) internal view @@ -268,8 +271,9 @@ library GDAv1StorageReader { return a & 1 == 1; } - // FlowInfo + // Flow Info + /// @dev Get flow info by its flow hash. function getFlowInfoByFlowHash (ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, @@ -282,6 +286,7 @@ library GDAv1StorageReader { return GDAv1StorageLib.decodeFlowInfo(data); } + /// @dev Get flow info of a distribution flow. function getDistributionFlowInfo (ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, @@ -294,8 +299,9 @@ library GDAv1StorageReader { return getFlowInfoByFlowHash(token, gda, GDAv1StorageLib.getFlowDistributionHash(from, to)); } - // PoolMemberData + // Pool Member Data + /// @dev Get pool member data. function getPoolMemberData (ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, @@ -310,6 +316,7 @@ library GDAv1StorageReader { return GDAv1StorageLib.decodePoolMemberData(data); } + /// @dev Check whether a pool member is connected. function isPoolMemberConnected (ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, @@ -332,6 +339,7 @@ library GDAv1StorageReader { library GDAv1StorageWriter { // AccountData + /// @dev Set unviversal index of an account. function setUniversalIndex (ISuperfluidToken token, address owner, @@ -349,6 +357,7 @@ library GDAv1StorageWriter { ); } + /// @dev Set is pool flag for a pool. function setIsPoolFlag(ISuperfluidToken token, ISuperfluidPool pool) internal { @@ -357,6 +366,7 @@ library GDAv1StorageWriter { token.updateAgreementStateSlot(address(pool), GDAv1StorageLib.ACCOUNT_DATA_STATE_SLOT_ID, data); } + /// @dev Set total buffer field of an account. function setTotalBuffer (ISuperfluidToken token, address owner, @@ -374,8 +384,9 @@ library GDAv1StorageWriter { ); } - // FlowInfo + // Flow Info + /// @dev Set flow info. function setFlowInfoByFlowHash (ISuperfluidToken token, bytes32 flowHash, @@ -386,8 +397,9 @@ library GDAv1StorageWriter { token.updateAgreementData(flowHash, GDAv1StorageLib.encodeFlowInfo(flowInfo)); } - // PoolMemberData + // Pool Member Data + /// @dev Create a pool membership. function createPoolMembership (ISuperfluidToken token, address poolMember, @@ -401,6 +413,7 @@ library GDAv1StorageWriter { GDAv1StorageLib.encodePoolMemberData(poolMemberData)); } + /// @dev Delete a pool membership. function deletePoolMembership (ISuperfluidToken token, address poolMember, diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 6949dee4cc..92bbd3690b 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -28,7 +28,7 @@ import { IPoolAdminNFT } from "../../interfaces/agreements/gdav1/IPoolAdminNFT.s import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol"; import { SlotsBitmapLibrary } from "../../libs/SlotsBitmapLibrary.sol"; import { SolvencyHelperLibrary } from "../../libs/SolvencyHelperLibrary.sol"; -import { AgreementBase } from "../AgreementBase.sol"; +import { AgreementBase, ISuperAgreement } from "../AgreementBase.sol"; import { AgreementLibrary } from "../AgreementLibrary.sol"; import { GDAv1StorageLib, GDAv1StorageReader, GDAv1StorageWriter } from "./GDAv1StorageLayout.sol"; @@ -62,6 +62,11 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi superfluidPoolBeacon = superfluidPoolBeacon_; } + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // ISuperAgreement interface + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + /// @inheritdoc ISuperAgreement function realtimeBalanceOf(ISuperfluidToken token, address account, uint256 time) public view @@ -101,7 +106,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi assert(poolMemberData.pool == address(pool)); } - /// @dev ISuperAgreement.realtimeBalanceOf implementation + /// @dev Use block.timestamp for realtimeBalanceOf function realtimeBalanceOfNow(ISuperfluidToken token, address account) external view @@ -111,6 +116,10 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi timestamp = block.timestamp; } + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // IGeneralDistributionAgreementV1 interface + ////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @inheritdoc IGeneralDistributionAgreementV1 function getNetFlow(ISuperfluidToken token, address account) external view override returns (int96 netFlowRate) { netFlowRate = int256(FlowRate.unwrap(_getUIndex(abi.encode(token), account).flow_rate())).toInt96(); @@ -296,21 +305,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi pool.claimAll(memberAddress); } - /// @inheritdoc IGeneralDistributionAgreementV1 - function connectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { - return connectPool(pool, true, ctx); - } - - /// @inheritdoc IGeneralDistributionAgreementV1 - function disconnectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { - return connectPool(pool, false, ctx); - } - - /// @inheritdoc IGeneralDistributionAgreementV1 - function isMemberConnected(ISuperfluidPool pool, address member) external view override returns (bool) { - return pool.superToken().isPoolMemberConnected(this, pool, member); - } - // @note setPoolConnection function naming function connectPool(ISuperfluidPool pool, bool doConnect, bytes calldata ctx) public @@ -347,6 +341,21 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } } + /// @inheritdoc IGeneralDistributionAgreementV1 + function connectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { + return connectPool(pool, true, ctx); + } + + /// @inheritdoc IGeneralDistributionAgreementV1 + function disconnectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { + return connectPool(pool, false, ctx); + } + + /// @inheritdoc IGeneralDistributionAgreementV1 + function isMemberConnected(ISuperfluidPool pool, address member) external view override returns (bool) { + return pool.superToken().isPoolMemberConnected(this, pool, member); + } + /// @inheritdoc IGeneralDistributionAgreementV1 function distribute( ISuperfluidToken token, @@ -540,51 +549,13 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } } - function _makeLiquidationPayouts(_StackVars_Liquidation memory data) internal { - GDAv1StorageLib.FlowInfo memory flowDistributionData = - data.token.getFlowInfoByFlowHash(this, data.distributionFlowHash); - int256 signedSingleDeposit = flowDistributionData.buffer.toInt256(); - - bool isCurrentlyPatricianPeriod; - - { - (uint256 liquidationPeriod, uint256 patricianPeriod) = - SolvencyHelperLibrary.decode3PsData(ISuperfluid(_host), data.token); - isCurrentlyPatricianPeriod = SolvencyHelperLibrary.isPatricianPeriod( - data.availableBalance, data.signedTotalGDADeposit, liquidationPeriod, patricianPeriod - ); - } - - int256 totalRewardLeft = data.availableBalance + data.signedTotalGDADeposit; - - // critical case - if (totalRewardLeft >= 0) { - int256 rewardAmount = (signedSingleDeposit * totalRewardLeft) / data.signedTotalGDADeposit; - data.token.makeLiquidationPayoutsV2( - data.distributionFlowHash, - abi.encode(2, isCurrentlyPatricianPeriod ? 0 : 1), - data.liquidator, - isCurrentlyPatricianPeriod, - data.sender, - rewardAmount.toUint256(), - rewardAmount * -1 - ); - } else { - int256 rewardAmount = signedSingleDeposit; - // bailout case - data.token.makeLiquidationPayoutsV2( - data.distributionFlowHash, - abi.encode(2, 2), - data.liquidator, - false, - data.sender, - rewardAmount.toUint256(), - totalRewardLeft * -1 - ); - } - } - - function _adjustBuffer(ISuperfluidToken token, address pool, address from, bytes32 flowHash, FlowRate newFlowRate) + function _adjustBuffer + (ISuperfluidToken token, + address pool, + address from, + bytes32 flowHash, // cached result of: GDAv1StorageLib.getFlowDistributionHash(from, pool) + FlowRate newFlowRate + ) internal { // not using oldFlowRate in this model @@ -629,7 +600,9 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ); } - // Solvency Related Getters + // + // Solvency + // /// @inheritdoc IGeneralDistributionAgreementV1 function isPatricianPeriodNow(ISuperfluidToken token, address account) @@ -665,7 +638,53 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ); } + function _makeLiquidationPayouts(_StackVars_Liquidation memory data) internal { + GDAv1StorageLib.FlowInfo memory flowDistributionData = + data.token.getFlowInfoByFlowHash(this, data.distributionFlowHash); + int256 signedSingleDeposit = flowDistributionData.buffer.toInt256(); + + bool isCurrentlyPatricianPeriod; + + { + (uint256 liquidationPeriod, uint256 patricianPeriod) = + SolvencyHelperLibrary.decode3PsData(ISuperfluid(_host), data.token); + isCurrentlyPatricianPeriod = SolvencyHelperLibrary.isPatricianPeriod( + data.availableBalance, data.signedTotalGDADeposit, liquidationPeriod, patricianPeriod + ); + } + + int256 totalRewardLeft = data.availableBalance + data.signedTotalGDADeposit; + + // critical case + if (totalRewardLeft >= 0) { + int256 rewardAmount = (signedSingleDeposit * totalRewardLeft) / data.signedTotalGDADeposit; + data.token.makeLiquidationPayoutsV2( + data.distributionFlowHash, + abi.encode(2, isCurrentlyPatricianPeriod ? 0 : 1), + data.liquidator, + isCurrentlyPatricianPeriod, + data.sender, + rewardAmount.toUint256(), + rewardAmount * -1 + ); + } else { + int256 rewardAmount = signedSingleDeposit; + // bailout case + data.token.makeLiquidationPayoutsV2( + data.distributionFlowHash, + abi.encode(2, 2), + data.liquidator, + false, + data.sender, + rewardAmount.toUint256(), + totalRewardLeft * -1 + ); + } + } + + // // pool info and operators + // /// @inheritdoc IGeneralDistributionAgreementV1 function getPoolAdjustmentFlowInfo(ISuperfluidPool pool) @@ -713,10 +732,13 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } // - // Pool operations + // Pool-only operations // - function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) external returns (bool) { + function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) + external + returns (bool) + { if (token.isPool(this, msg.sender) == false) { revert GDA_ONLY_SUPER_TOKEN_POOL(); } @@ -738,9 +760,9 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return true; } - // - // TokenMonad virtual functions - // + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // TokenMonad interface + ////////////////////////////////////////////////////////////////////////////////////////////////////// /// @inheritdoc TokenMonad function _getUIndex(bytes memory eff, address owner) @@ -844,7 +866,29 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return _setPoolAdjustmentFlowRate(eff, pool, false, /* doShift? */ flowRate, t); } - // SlotsBitmap Pool Data: + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // Pool Subscription SlotsBitmap + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + /* + * + * ### SlotsBitmap Data + * + * slotId = _POOL_SUBS_BITMAP_STATE_SLOT_ID or 1 + * msg.sender = address of GDAv1 + * account = context.msgSender + * Slots Bitmap Data Slot stores a bitmap of the slots that are "enabled" for a pool member. + * + * ### Pool Connections Data Slot Id Start + * + * slotId (start) = _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START or 1 << 128 + * msg.sender = address of GDAv1 + * account = context.msgSender + * Pool Connections Data Slot Id Start indicates the starting slot for where we begin to store the pools that a + * pool member is a part of. + */ + function _findAndFillPoolConnectionsBitmap(ISuperfluidToken token, address poolMember, bytes32 poolID) private returns (uint32 slotId) diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol index 956652ce8e..b14331e7f1 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol @@ -214,67 +214,6 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen ); } - // // Adjust Buffer => FlowInfo modified - // function testAdjustBufferUpdatesFlowInfo(address from, int32 oldFlowRate, int32 newFlowRate) public { - // vm.assume(newFlowRate >= 0); - - // bytes32 flowHash = _getFlowDistributionHash(from, currentPool); - - // uint256 expectedBuffer = uint256(int256(newFlowRate)) * liquidationPeriod; - // _adjustBuffer( - // abi.encode(superToken), - // address(currentPool), - // from, - // flowHash, - // FlowRate.wrap(int128(oldFlowRate)), - // FlowRate.wrap(int128(newFlowRate)) - // ); - - // (bool exist, IGeneralDistributionAgreementV1.FlowInfo memory flowDistributionData) = - // _getFlowInfo(superToken, flowHash); - // assertEq(exist, true, "flow distribution data does not exist"); - // assertEq(flowDistributionData.buffer, expectedBuffer, "buffer not equal"); - // assertEq(flowDistributionData.flowRate, int96(newFlowRate), "buffer not equal"); - // assertEq( - // int96(FlowRate.unwrap(_getFlowRate(abi.encode(superToken), flowHash))), - // int96(newFlowRate), - // "_getFlowRate: flow rate not equal" - // ); - // assertEq( - // sf.gda.getFlowRate(superToken, from, ISuperfluidPool(currentPool)), - // int96(newFlowRate), - // "getFlowRate: flow rate not equal" - // ); - // } - - // // Adjust Buffer => UniversalIndexData modified - // function testAdjustBufferUpdatesUniversalIndexData(address from, int32 oldFlowRate, int32 newFlowRate) public { - // vm.assume(newFlowRate >= 0); - - // uint256 bufferDelta = uint256(int256(newFlowRate)) * liquidationPeriod; // expected buffer == buffer delta - // // because of fresh state - // bytes32 flowHash = _getFlowDistributionHash(from, currentPool); - // GeneralDistributionAgreementV1.UniversalIndexData memory fromUindexDataBefore = - // _getUIndexData(abi.encode(superToken), from); - // _adjustBuffer( - // abi.encode(superToken), - // address(currentPool), - // from, - // flowHash, - // FlowRate.wrap(int128(oldFlowRate)), - // FlowRate.wrap(int128(newFlowRate)) - // ); - - // GeneralDistributionAgreementV1.UniversalIndexData memory fromUindexDataAfter = - // _getUIndexData(abi.encode(superToken), from); - - // assertEq( - // fromUindexDataBefore.totalBuffer + bufferDelta, - // fromUindexDataAfter.totalBuffer, - // "from total buffer not equal" - // ); - // } - function testEncodeUpdatedUniversalIndex( int96 flowRate, uint32 settledAt, From db5661719c70c87024c8518f78216ebac4125492 Mon Sep 17 00:00:00 2001 From: Miao ZhiCheng Date: Wed, 9 Jul 2025 14:26:39 +0300 Subject: [PATCH 08/16] Delete .github/workflows/daily-slack-message.yml --- .github/workflows/daily-slack-message.yml | 36 ----------------------- 1 file changed, 36 deletions(-) delete mode 100644 .github/workflows/daily-slack-message.yml diff --git a/.github/workflows/daily-slack-message.yml b/.github/workflows/daily-slack-message.yml deleted file mode 100644 index e473995fcc..0000000000 --- a/.github/workflows/daily-slack-message.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Daily protocol-monorepo slack message - -on: - workflow_dispatch: - schedule: - - cron: 0 11 * * 1-5 - -jobs: - send-slack-message: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 24.x - uses: actions/setup-node@v4 - with: - node-version: 24.x - - - - name: Send slack message - working-directory: tasks - run: | - npm install ethers --force - node daily-slack-bot.js - env: - CI_SLACK_WEBHOOK: ${{ secrets.CI_SLACK_WEBHOOK }} - ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} - POLYGONSCAN_API_KEY: ${{ secrets.POLYGONSCAN_API_KEY }} - SNOWTRACE_API_KEY: ${{ secrets.SNOWTRACE_API_KEY }} - OPTIMISTIC_API_KEY: ${{ secrets.OPTIMISTIC_API_KEY }} - ARBISCAN_API_KEY: ${{ secrets.ARBISCAN_API_KEY }} - BSCSCAN_API_KEY: ${{ secrets.BSCSCAN_API_KEY }} - CELOSCAN_API_KEY: ${{ secrets.CELOSCAN_API_KEY }} - GNOSISSCAN_API_KEY: ${{ secrets.GNOSISSCAN_API_KEY}} - BASESCAN_API_KEY: ${{ secrets.BASESCAN_API_KEY}} - SCROLLSCAN_API_KEY: ${{ secrets.SCROLLSCAN_API_KEY}} From 9b43db353cae292d06c4d3cc17723f085fa3e5b0 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 15 Jul 2025 12:29:09 +0300 Subject: [PATCH 09/16] minor fixes --- .../agreements/gdav1/GDAv1StorageLayout.sol | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol index 57280293f6..58edbeca6a 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -58,15 +58,15 @@ library GDAv1StorageLib { // - buffer amount (96b) // - isPool flag // - // --------+------------------+------------------+------------------+------------------+ - // WORD 1: | flowRate | settledAt | totalBuffer | isPool | - // --------+------------------+------------------+------------------+------------------+ - // | 96b | 32b | 96b | 32b | - // --------+------------------+------------------+------------------+------------------+ - // WORD 2: | settledValue | - // -------- ------------------+------------------+------------------+------------------+ - // | 256b | - // --------+------------------+------------------+------------------+------------------+ + // --------+----------------+-----------------+------------------+----------------+--------------+ + // WORD 1: | flowRate | settledAt | totalBuffer | (reserved) | isPool | + // --------+----------------+-----------------+------------------+----------------+--------------+ + // | 96b | 32b | 96b | 31b | 1b | + // --------+----------------+-----------------+------------------+----------------+--------------+ + // WORD 2: | settledValue | + // -------- ----------------+------------------+------------------+------------------------------+ + // | 256b | + // --------+----------------+------------------+------------------+------------------------------+ /// @dev Agreement state slot id for account data. uint256 internal constant ACCOUNT_DATA_STATE_SLOT_ID = 0; @@ -244,7 +244,7 @@ library GDAv1StorageLib { } } -/* @title Storage layout writer for the GDAv1. +/* @title Storage layout reader for the GDAv1. * @author Superfluid */ library GDAv1StorageReader { From 58b60dae3bf9de677acaf26b00000212f9c1fa87 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 15 Jul 2025 12:34:20 +0300 Subject: [PATCH 10/16] typo: Id, instead of ID --- .../agreements/gdav1/GDAv1StorageLayout.sol | 8 ++++---- .../gdav1/GeneralDistributionAgreementV1.sol | 6 +++--- .../GeneralDistributionAgreementV1.prop.t.sol | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol index 58edbeca6a..ce53468b17 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -202,15 +202,15 @@ library GDAv1StorageLib { // ## Data Packing // // --------+----------+--------+-------------+ - // WORD A: | reserved | poolID | poolAddress | + // WORD A: | reserved | poolId | poolAddress | // --------+----------+--------+-------------+ // | 64 | 32 | 160 | // --------+----------+--------+-------------+ /// @dev Pool member data struct. struct PoolMemberData { + uint32 poolId; // the slot id in the pool's subs bitmap address pool; - uint32 poolID; // the slot id in the pool's subs bitmap } /// @dev Calculate pool member data hash for agreement data. @@ -226,7 +226,7 @@ library GDAv1StorageLib { { data = new bytes32[](1); data[0] = bytes32( - (uint256(uint32(poolMemberData.poolID)) << 160) | + (uint256(uint32(poolMemberData.poolId)) << 160) | uint256(uint160(poolMemberData.pool))); } @@ -238,8 +238,8 @@ library GDAv1StorageLib { { exist = data > 0; if (exist) { + poolMemberData.poolId = uint32(data >> 160); poolMemberData.pool = address(uint160(data & uint256(type(uint160).max))); - poolMemberData.poolID = uint32(data >> 160); } } } diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 92bbd3690b..817417b506 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -323,18 +323,18 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ); if (doConnect) { - uint32 poolSlotID = + uint32 poolSlotId = _findAndFillPoolConnectionsBitmap(token, msgSender, bytes32(uint256(uint160(address(pool))))); token.createPoolMembership (msgSender, pool, - GDAv1StorageLib.PoolMemberData({ poolID: poolSlotID, pool: address(pool) })); + GDAv1StorageLib.PoolMemberData({ poolId: poolSlotId, pool: address(pool) })); } else { (, GDAv1StorageLib.PoolMemberData memory poolMemberData) = token.getPoolMemberData(this, msgSender, pool); token.deletePoolMembership(msgSender, pool); - _clearPoolConnectionsBitmap(token, msgSender, poolMemberData.poolID); + _clearPoolConnectionsBitmap(token, msgSender, poolMemberData.poolId); } emit PoolConnectionUpdated(token, pool, msgSender, doConnect, currentContext.userData); diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol index b14331e7f1..4f4bdaabb3 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol @@ -145,8 +145,8 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen } // Pool Member Data Setters/Getters - function testSetGetPoolMemberData(address poolMember, ISuperfluidPool _pool, uint32 poolID) public { - vm.assume(poolID > 0); + function testSetGetPoolMemberData(address poolMember, ISuperfluidPool _pool, uint32 poolId) public { + vm.assume(poolId > 0); vm.assume(address(_pool) != address(0)); vm.assume(address(poolMember) != address(0)); @@ -155,7 +155,7 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen (poolMember, _pool, GDAv1StorageLib.PoolMemberData ({ - poolID: poolID, + poolId: poolId, pool: address(_pool) }) ); @@ -165,7 +165,7 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen superToken.getPoolMemberData(this, poolMember, _pool); assertEq(true, exist, "pool member data does not exist"); - assertEq(poolID, setPoolMemberData.poolID, "poolID not equal"); + assertEq(poolId, setPoolMemberData.poolId, "poolId not equal"); assertEq(address(_pool), setPoolMemberData.pool, "pool not equal"); } @@ -298,15 +298,15 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen assertEq(original.lastUpdated, decoded.lastUpdated, "lastUpdated not equal"); } - function testEncodeDecodePoolMemberData(address pool, uint32 poolID) public pure { + function testEncodeDecodePoolMemberData(address pool, uint32 poolId) public pure { vm.assume(pool != address(0)); GDAv1StorageLib.PoolMemberData memory original = - GDAv1StorageLib.PoolMemberData({ pool: pool, poolID: poolID }); + GDAv1StorageLib.PoolMemberData({ poolId: poolId, pool: pool }); bytes32[] memory encoded = GDAv1StorageLib.encodePoolMemberData(original); (, GDAv1StorageLib.PoolMemberData memory decoded) = GDAv1StorageLib.decodePoolMemberData(uint256(encoded[0])); + assertEq(original.poolId, decoded.poolId, "poolId not equal"); assertEq(original.pool, decoded.pool, "pool not equal"); - assertEq(original.poolID, decoded.poolID, "poolID not equal"); } } From 9a5dcb68b3741b6adedcd41553f9dbbc59e660c9 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 15 Jul 2025 13:13:35 +0300 Subject: [PATCH 11/16] rename PoolMembership to PoolConnectivity --- .../agreements/gdav1/GDAv1StorageLayout.sol | 70 +++++++++---------- .../gdav1/GeneralDistributionAgreementV1.sol | 17 +++-- .../GeneralDistributionAgreementV1.prop.t.sol | 39 +++++------ 3 files changed, 62 insertions(+), 64 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol index ce53468b17..6bf48fd23b 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -201,45 +201,46 @@ library GDAv1StorageLib { // // ## Data Packing // - // --------+----------+--------+-------------+ - // WORD A: | reserved | poolId | poolAddress | - // --------+----------+--------+-------------+ - // | 64 | 32 | 160 | - // --------+----------+--------+-------------+ - - /// @dev Pool member data struct. - struct PoolMemberData { - uint32 poolId; // the slot id in the pool's subs bitmap - address pool; + // --------+----------+--------+----------------+ + // WORD A: | reserved | slotId | pool (address) | + // --------+----------+--------+----------------+ + // | 64 | 32 | 160 | + // --------+----------+--------+----------------+ + + /// @dev Pool member connectivity data struct. + struct PoolConnectivity { + uint32 slotId; // the slot id in the member's subscription bitmap + ISuperfluidPool pool; } /// @dev Calculate pool member data hash for agreement data. - function getPoolMemberHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { - return keccak256(abi.encode(block.chainid, "poolMember", poolMember, address(pool))); + function getPoolConnectivityHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { + // NOTE: it is called "poolMember" for legacy reasons; !! DO NOT CHANGE THIS !! + return keccak256(abi.encode(block.chainid, "poolMember", poolMember, pool)); } /// @dev Encode pool member data. - function encodePoolMemberData(PoolMemberData memory poolMemberData) + function encodePoolConnectivity(PoolConnectivity memory poolConnectivity) internal pure returns (bytes32[] memory data) { data = new bytes32[](1); data[0] = bytes32( - (uint256(uint32(poolMemberData.poolId)) << 160) | - uint256(uint160(poolMemberData.pool))); + (uint256(uint32(poolConnectivity.slotId)) << 160) | + uint256(uint160(address(poolConnectivity.pool)))); } /// @dev Decode pool member data. - function decodePoolMemberData(uint256 data) + function decodePoolConnectivity(uint256 data) internal pure - returns (bool exist, PoolMemberData memory poolMemberData) + returns (bool exist, PoolConnectivity memory poolConnectivity) { exist = data > 0; if (exist) { - poolMemberData.poolId = uint32(data >> 160); - poolMemberData.pool = address(uint160(data & uint256(type(uint160).max))); + poolConnectivity.slotId = uint32(data >> 160); + poolConnectivity.pool = ISuperfluidPool(address(uint160(data & uint256(type(uint160).max)))); } } } @@ -299,21 +300,21 @@ library GDAv1StorageReader { return getFlowInfoByFlowHash(token, gda, GDAv1StorageLib.getFlowDistributionHash(from, to)); } - // Pool Member Data + // Pool Connectivity - /// @dev Get pool member data. - function getPoolMemberData + /// @dev Get pool connectivity. + function getPoolConnectivity (ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, address poolMember, ISuperfluidPool pool ) internal view - returns (bool exist, GDAv1StorageLib.PoolMemberData memory poolMemberData) + returns (bool exist, GDAv1StorageLib.PoolConnectivity memory poolConnectivity) { - bytes32 dataId = GDAv1StorageLib.getPoolMemberHash(poolMember, pool); + bytes32 dataId = GDAv1StorageLib.getPoolConnectivityHash(poolMember, pool); uint256 data = uint256(token.getAgreementData(address(gda), dataId, 1)[0]); - return GDAv1StorageLib.decodePoolMemberData(data); + return GDAv1StorageLib.decodePoolConnectivity(data); } /// @dev Check whether a pool member is connected. @@ -326,7 +327,7 @@ library GDAv1StorageReader { internal view returns (bool) { - (bool exist,) = getPoolMemberData(token, gda, member, pool); + (bool exist,) = getPoolConnectivity(token, gda, member, pool); return exist; } } @@ -397,29 +398,28 @@ library GDAv1StorageWriter { token.updateAgreementData(flowHash, GDAv1StorageLib.encodeFlowInfo(flowInfo)); } - // Pool Member Data + // Pool Connectivity - /// @dev Create a pool membership. - function createPoolMembership + /// @dev Create a pool connectivity. + function createPoolConnectivity (ISuperfluidToken token, address poolMember, - ISuperfluidPool pool, - GDAv1StorageLib.PoolMemberData memory poolMemberData + GDAv1StorageLib.PoolConnectivity memory poolConnectivity ) internal { token.createAgreement - (GDAv1StorageLib.getPoolMemberHash(poolMember, pool), - GDAv1StorageLib.encodePoolMemberData(poolMemberData)); + (GDAv1StorageLib.getPoolConnectivityHash(poolMember, poolConnectivity.pool), + GDAv1StorageLib.encodePoolConnectivity(poolConnectivity)); } - /// @dev Delete a pool membership. + /// @dev Delete a pool connectivity. function deletePoolMembership (ISuperfluidToken token, address poolMember, ISuperfluidPool pool) internal { - token.terminateAgreement(GDAv1StorageLib.getPoolMemberHash(poolMember, pool), 1); + token.terminateAgreement(GDAv1StorageLib.getPoolConnectivityHash(poolMember, pool), 1); } } diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 817417b506..560f380cb8 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -100,10 +100,10 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi function _assertPoolMembership(ISuperfluidToken token, address account, ISuperfluidPool pool) internal view { - (bool exist, GDAv1StorageLib.PoolMemberData memory poolMemberData) = - token.getPoolMemberData(this, account, ISuperfluidPool(pool)); + (bool exist, GDAv1StorageLib.PoolConnectivity memory poolMemberData) = + token.getPoolConnectivity(this, account, ISuperfluidPool(pool)); assert(exist); - assert(poolMemberData.pool == address(pool)); + assert(poolMemberData.pool == pool); } /// @dev Use block.timestamp for realtimeBalanceOf @@ -326,15 +326,14 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi uint32 poolSlotId = _findAndFillPoolConnectionsBitmap(token, msgSender, bytes32(uint256(uint160(address(pool))))); - token.createPoolMembership - (msgSender, pool, - GDAv1StorageLib.PoolMemberData({ poolId: poolSlotId, pool: address(pool) })); + token.createPoolConnectivity + (msgSender, GDAv1StorageLib.PoolConnectivity({ slotId: poolSlotId, pool: pool })); } else { - (, GDAv1StorageLib.PoolMemberData memory poolMemberData) = - token.getPoolMemberData(this, msgSender, pool); + (, GDAv1StorageLib.PoolConnectivity memory poolMemberData) = + token.getPoolConnectivity(this, msgSender, pool); token.deletePoolMembership(msgSender, pool); - _clearPoolConnectionsBitmap(token, msgSender, poolMemberData.poolId); + _clearPoolConnectionsBitmap(token, msgSender, poolMemberData.slotId); } emit PoolConnectionUpdated(token, pool, msgSender, doConnect, currentContext.userData); diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol index 4f4bdaabb3..3afa8ae863 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol @@ -145,28 +145,27 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen } // Pool Member Data Setters/Getters - function testSetGetPoolMemberData(address poolMember, ISuperfluidPool _pool, uint32 poolId) public { - vm.assume(poolId > 0); + function testSetGetPoolConnectivity(address poolMember, ISuperfluidPool _pool, uint32 slotId) public { + vm.assume(slotId > 0); vm.assume(address(_pool) != address(0)); vm.assume(address(poolMember) != address(0)); vm.startPrank(address(this)); - superToken.createPoolMembership + superToken.createPoolConnectivity (poolMember, - _pool, - GDAv1StorageLib.PoolMemberData ({ - poolId: poolId, - pool: address(_pool) + GDAv1StorageLib.PoolConnectivity ({ + slotId: slotId, + pool: _pool }) ); vm.stopPrank(); - (bool exist, GDAv1StorageLib.PoolMemberData memory setPoolMemberData) = - superToken.getPoolMemberData(this, poolMember, _pool); + (bool exist, GDAv1StorageLib.PoolConnectivity memory setPoolConnectivity) = + superToken.getPoolConnectivity(this, poolMember, _pool); assertEq(true, exist, "pool member data does not exist"); - assertEq(poolId, setPoolMemberData.poolId, "poolId not equal"); - assertEq(address(_pool), setPoolMemberData.pool, "pool not equal"); + assertEq(slotId, setPoolConnectivity.slotId, "slotId not equal"); + assertEq(address(_pool), address(setPoolConnectivity.pool), "pool not equal"); } // Proportional Distribution Pool Index Setters/Getters @@ -298,15 +297,15 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen assertEq(original.lastUpdated, decoded.lastUpdated, "lastUpdated not equal"); } - function testEncodeDecodePoolMemberData(address pool, uint32 poolId) public pure { + function testEncodeDecodePoolConnectivity(address pool, uint32 slotId) public pure { vm.assume(pool != address(0)); - GDAv1StorageLib.PoolMemberData memory original = - GDAv1StorageLib.PoolMemberData({ poolId: poolId, pool: pool }); - bytes32[] memory encoded = GDAv1StorageLib.encodePoolMemberData(original); - (, GDAv1StorageLib.PoolMemberData memory decoded) = - GDAv1StorageLib.decodePoolMemberData(uint256(encoded[0])); - - assertEq(original.poolId, decoded.poolId, "poolId not equal"); - assertEq(original.pool, decoded.pool, "pool not equal"); + GDAv1StorageLib.PoolConnectivity memory original = + GDAv1StorageLib.PoolConnectivity({ slotId: slotId, pool: ISuperfluidPool(pool) }); + bytes32[] memory encoded = GDAv1StorageLib.encodePoolConnectivity(original); + (, GDAv1StorageLib.PoolConnectivity memory decoded) = + GDAv1StorageLib.decodePoolConnectivity(uint256(encoded[0])); + + assertEq(original.slotId, decoded.slotId, "slotId not equal"); + assertEq(address(original.pool), address(decoded.pool), "pool not equal"); } } From f2fcc7fbeadeb81c299872acaa6da0892dd90eda Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 15 Jul 2025 13:21:46 +0300 Subject: [PATCH 12/16] fix a small typo --- .../agreements/gdav1/GeneralDistributionAgreementV1.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 560f380cb8..96adfb706d 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -809,13 +809,13 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } /// @inheritdoc TokenMonad - function _getFlowRate(bytes memory eff, bytes32 distributionFlowHash) + function _getFlowRate(bytes memory eff, bytes32 flowHash) internal view override returns (FlowRate) { ISuperfluidToken token = ISuperfluidToken(abi.decode(eff, (address))); - GDAv1StorageLib.FlowInfo memory data = token.getFlowInfoByFlowHash(this, distributionFlowHash); + GDAv1StorageLib.FlowInfo memory data = token.getFlowInfoByFlowHash(this, flowHash); return FlowRate.wrap(data.flowRate); } @@ -833,13 +833,13 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi returns (bytes memory) { ISuperfluidToken token = ISuperfluidToken(abi.decode(eff, (address))); - GDAv1StorageLib.FlowInfo memory flowDistributionData = token.getFlowInfoByFlowHash(this, flowHash); + GDAv1StorageLib.FlowInfo memory flowInfo = token.getFlowInfoByFlowHash(this, flowHash); token.setFlowInfoByFlowHash(flowHash, GDAv1StorageLib.FlowInfo({ lastUpdated: uint32(block.timestamp), flowRate: int256(FlowRate.unwrap(newFlowRate)).toInt96(), - buffer: flowDistributionData.buffer + buffer: flowInfo.buffer }) ); From 3a52cad79d325b7fc0e6e0a0dd1997076515b7bb Mon Sep 17 00:00:00 2001 From: didi Date: Tue, 15 Jul 2025 12:37:32 +0200 Subject: [PATCH 13/16] more renaming --- .../agreements/gdav1/GDAv1StorageLayout.sol | 16 ++++++++-------- .../gdav1/GeneralDistributionAgreementV1.sol | 8 ++++---- .../GeneralDistributionAgreementV1.prop.t.sol | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol index 6bf48fd23b..5ef4efe22c 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -37,15 +37,15 @@ import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPo * NOTE The Agreement Data slot is calculated with the following function: * keccak256(abi.encode("AgreementData", agreementClass, agreementId)) * agreementClass = address of GDAv1 - * agreementId = DistributionFlowId | PoolMemberId + * agreementId = DistributionFlowId | PoolConnectivityId * * DistributionFlowId = * keccak256(abi.encode(block.chainid, "distributionFlow", from, pool)) * DistributionFlowId stores FlowInfo between a sender (from) and pool. * - * PoolMemberId = + * PoolConnectivityId = * keccak256(abi.encode(block.chainid, "poolMember", member, pool)) - * PoolMemberId stores PoolMemberData for a member at a pool. + * PoolConnectivityId stores PoolConnectivity for a member at a pool. */ library GDAv1StorageLib { @@ -197,7 +197,7 @@ library GDAv1StorageLib { } } - // # Pool Member Data + // # Pool Connectivity Data // // ## Data Packing // @@ -207,19 +207,19 @@ library GDAv1StorageLib { // | 64 | 32 | 160 | // --------+----------+--------+----------------+ - /// @dev Pool member connectivity data struct. + /// @dev Pool connectivity data struct. struct PoolConnectivity { uint32 slotId; // the slot id in the member's subscription bitmap ISuperfluidPool pool; } - /// @dev Calculate pool member data hash for agreement data. + /// @dev Calculate pool connectivity data hash for agreement data. function getPoolConnectivityHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { // NOTE: it is called "poolMember" for legacy reasons; !! DO NOT CHANGE THIS !! return keccak256(abi.encode(block.chainid, "poolMember", poolMember, pool)); } - /// @dev Encode pool member data. + /// @dev Encode pool connectivity data. function encodePoolConnectivity(PoolConnectivity memory poolConnectivity) internal pure @@ -231,7 +231,7 @@ library GDAv1StorageLib { uint256(uint160(address(poolConnectivity.pool)))); } - /// @dev Decode pool member data. + /// @dev Decode pool connectivity data. function decodePoolConnectivity(uint256 data) internal pure diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 96adfb706d..5aca8cafeb 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -100,10 +100,10 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi function _assertPoolMembership(ISuperfluidToken token, address account, ISuperfluidPool pool) internal view { - (bool exist, GDAv1StorageLib.PoolConnectivity memory poolMemberData) = + (bool exist, GDAv1StorageLib.PoolConnectivity memory poolConnectivity) = token.getPoolConnectivity(this, account, ISuperfluidPool(pool)); assert(exist); - assert(poolMemberData.pool == pool); + assert(poolConnectivity.pool == pool); } /// @dev Use block.timestamp for realtimeBalanceOf @@ -329,11 +329,11 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi token.createPoolConnectivity (msgSender, GDAv1StorageLib.PoolConnectivity({ slotId: poolSlotId, pool: pool })); } else { - (, GDAv1StorageLib.PoolConnectivity memory poolMemberData) = + (, GDAv1StorageLib.PoolConnectivity memory poolConnectivity) = token.getPoolConnectivity(this, msgSender, pool); token.deletePoolMembership(msgSender, pool); - _clearPoolConnectionsBitmap(token, msgSender, poolMemberData.slotId); + _clearPoolConnectionsBitmap(token, msgSender, poolConnectivity.slotId); } emit PoolConnectionUpdated(token, pool, msgSender, doConnect, currentContext.userData); diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol index 3afa8ae863..0b1956df68 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol @@ -144,7 +144,7 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen ); } - // Pool Member Data Setters/Getters + // Pool Connectivity Data Setters/Getters function testSetGetPoolConnectivity(address poolMember, ISuperfluidPool _pool, uint32 slotId) public { vm.assume(slotId > 0); vm.assume(address(_pool) != address(0)); @@ -163,7 +163,7 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen (bool exist, GDAv1StorageLib.PoolConnectivity memory setPoolConnectivity) = superToken.getPoolConnectivity(this, poolMember, _pool); - assertEq(true, exist, "pool member data does not exist"); + assertEq(true, exist, "pool connectivity does not exist"); assertEq(slotId, setPoolConnectivity.slotId, "slotId not equal"); assertEq(address(_pool), address(setPoolConnectivity.pool), "pool not equal"); } From 992674d5840e5b911c28aeaf104a1e72f20d2369 Mon Sep 17 00:00:00 2001 From: didi Date: Tue, 15 Jul 2025 12:56:18 +0200 Subject: [PATCH 14/16] more renaming --- .../agreements/gdav1/GeneralDistributionAgreementV1.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 5aca8cafeb..3f776240f9 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -88,7 +88,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi (uint32[] memory slotIds, bytes32[] memory pidList) = _listPoolConnectionIds(token, account); for (uint256 i = 0; i < slotIds.length; ++i) { address pool = address(uint160(uint256(pidList[i]))); - _assertPoolMembership(token, account, ISuperfluidPool(pool)); + _assertPoolConnectivity(token, account, ISuperfluidPool(pool)); totalClaimableFromPools += ISuperfluidPool(pool).getClaimable(account, uint32(time)); } } @@ -98,10 +98,10 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi owedBuffer = 0; } - function _assertPoolMembership(ISuperfluidToken token, address account, ISuperfluidPool pool) internal view + function _assertPoolConnectivity(ISuperfluidToken token, address account, ISuperfluidPool pool) internal view { (bool exist, GDAv1StorageLib.PoolConnectivity memory poolConnectivity) = - token.getPoolConnectivity(this, account, ISuperfluidPool(pool)); + token.getPoolConnectivity(this, account, pool); assert(exist); assert(poolConnectivity.pool == pool); } From 89ea3addb8f24e27ba05f7236068dbc08fb589cc Mon Sep 17 00:00:00 2001 From: didi Date: Tue, 15 Jul 2025 16:03:24 +0200 Subject: [PATCH 15/16] updated changelog --- packages/ethereum-contracts/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index 78dc039b06..f291f1e658 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -3,6 +3,14 @@ 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] + +### Changed +- Refactored `GeneralDistributionAgreementV1`: extracted functionality which reads and writes agreement data to from/to the token contract into dedicated libraries: + - `GDAv1StorageLib` contains data structures and related encoders/decoders. + - `GDAv1StorageReader` contains getters reading agreement data from the token contract, allowing contracts to get this data without making a call to the GDA contract. + - `GDAv1StorageWriter` contains functions for writing agreement data to the token contract. This can only be used by the GDA contract itself. + ## [v1.13.0] ### Added From 69931f9bc390fe00f9a16cdbe4f816b9a143d00f Mon Sep 17 00:00:00 2001 From: didi Date: Tue, 15 Jul 2025 16:07:35 +0200 Subject: [PATCH 16/16] more renaming --- .../contracts/agreements/gdav1/GDAv1StorageLayout.sol | 2 +- .../agreements/gdav1/GeneralDistributionAgreementV1.sol | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol index 5ef4efe22c..ea768eb77c 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -414,7 +414,7 @@ library GDAv1StorageWriter { } /// @dev Delete a pool connectivity. - function deletePoolMembership + function deletePoolConnectivity (ISuperfluidToken token, address poolMember, ISuperfluidPool pool) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 3f776240f9..97aff72c57 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -83,16 +83,16 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi .rtb(Time.wrap(uint32(time)))); } - int256 totalClaimableFromPools; + int256 totalConnectedFromPools; { (uint32[] memory slotIds, bytes32[] memory pidList) = _listPoolConnectionIds(token, account); for (uint256 i = 0; i < slotIds.length; ++i) { address pool = address(uint160(uint256(pidList[i]))); _assertPoolConnectivity(token, account, ISuperfluidPool(pool)); - totalClaimableFromPools += ISuperfluidPool(pool).getClaimable(account, uint32(time)); + totalConnectedFromPools += ISuperfluidPool(pool).getClaimable(account, uint32(time)); } } - rtb += totalClaimableFromPools; + rtb += totalConnectedFromPools; buf = uint256(accountData.totalBuffer.toInt256()); // upcasting to uint256 is safe owedBuffer = 0; @@ -331,7 +331,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } else { (, GDAv1StorageLib.PoolConnectivity memory poolConnectivity) = token.getPoolConnectivity(this, msgSender, pool); - token.deletePoolMembership(msgSender, pool); + token.deletePoolConnectivity(msgSender, pool); _clearPoolConnectionsBitmap(token, msgSender, poolConnectivity.slotId); }