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}} 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 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..ea768eb77c --- /dev/null +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -0,0 +1,425 @@ +// 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 library for the GDAv1. + * @author Superfluid + * + * @notice Storage Layout Notes + * + * ## Agreement State + * + * ### 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. + * + * ## 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 | PoolConnectivityId + * + * DistributionFlowId = + * keccak256(abi.encode(block.chainid, "distributionFlow", from, pool)) + * DistributionFlowId stores FlowInfo between a sender (from) and pool. + * + * PoolConnectivityId = + * keccak256(abi.encode(block.chainid, "poolMember", member, pool)) + * PoolConnectivityId stores PoolConnectivity for a member at a pool. + */ +library GDAv1StorageLib { + + // # Account Data + // + // ## Data Packing + // + // Account data includes: + // - Semantic universal index (a basic particle) + // - buffer amount (96b) + // - isPool flag + // + // --------+----------------+-----------------+------------------+----------------+--------------+ + // 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; + + /// @dev Account data struct. + struct AccountData { + int96 flowRate; + uint32 settledAt; + uint256 totalBuffer; // stored as uint96 + 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( + // 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) | + (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) + ); + } + + /// @dev Decode account data. + 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); + } + } + + /// @dev Extract universal index from the decoded account data. + 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); + } + + // # Flow Info + // + // ## Data Packing + // + // --------+----------+-------------+----------+--------+ + // WORD A: | reserved | lastUpdated | flowRate | buffer | + // --------+----------+-------------+----------+--------+ + // | 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)); + } + + /// @dev Encode flow info. + function encodeFlowInfo(FlowInfo memory flowInfo) + internal pure + returns (bytes32[] memory data) + { + data = new bytes32[](1); + data[0] = bytes32( + (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) + { + 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)); + } + } + + // # Pool Connectivity Data + // + // ## Data Packing + // + // --------+----------+--------+----------------+ + // WORD A: | reserved | slotId | pool (address) | + // --------+----------+--------+----------------+ + // | 64 | 32 | 160 | + // --------+----------+--------+----------------+ + + /// @dev Pool connectivity data struct. + struct PoolConnectivity { + uint32 slotId; // the slot id in the member's subscription bitmap + ISuperfluidPool pool; + } + + /// @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 connectivity data. + function encodePoolConnectivity(PoolConnectivity memory poolConnectivity) + internal + pure + returns (bytes32[] memory data) + { + data = new bytes32[](1); + data[0] = bytes32( + (uint256(uint32(poolConnectivity.slotId)) << 160) | + uint256(uint160(address(poolConnectivity.pool)))); + } + + /// @dev Decode pool connectivity data. + function decodePoolConnectivity(uint256 data) + internal + pure + returns (bool exist, PoolConnectivity memory poolConnectivity) + { + exist = data > 0; + if (exist) { + poolConnectivity.slotId = uint32(data >> 160); + poolConnectivity.pool = ISuperfluidPool(address(uint160(data & uint256(type(uint160).max)))); + } + } +} + +/* @title Storage layout reader for the GDAv1. + * @author Superfluid + */ +library GDAv1StorageReader { + // Account Data + + /// @dev Get account data. + 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)); + } + + /// @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; + } + + // Flow Info + + /// @dev Get flow info by its flow hash. + function getFlowInfoByFlowHash + (ISuperfluidToken token, + IGeneralDistributionAgreementV1 gda, + bytes32 flowHash + ) + internal view + returns (GDAv1StorageLib.FlowInfo memory flowDistributionData) + { + uint256 data = uint256(token.getAgreementData(address(gda), flowHash, 1)[0]); + return GDAv1StorageLib.decodeFlowInfo(data); + } + + /// @dev Get flow info of a distribution flow. + 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)); + } + + // Pool Connectivity + + /// @dev Get pool connectivity. + function getPoolConnectivity + (ISuperfluidToken token, + IGeneralDistributionAgreementV1 gda, + address poolMember, + ISuperfluidPool pool + ) + internal view + returns (bool exist, GDAv1StorageLib.PoolConnectivity memory poolConnectivity) + { + bytes32 dataId = GDAv1StorageLib.getPoolConnectivityHash(poolMember, pool); + uint256 data = uint256(token.getAgreementData(address(gda), dataId, 1)[0]); + return GDAv1StorageLib.decodePoolConnectivity(data); + } + + /// @dev Check whether a pool member is connected. + function isPoolMemberConnected + (ISuperfluidToken token, + IGeneralDistributionAgreementV1 gda, + ISuperfluidPool pool, + address member + ) + internal view + returns (bool) + { + (bool exist,) = getPoolConnectivity(token, gda, member, pool); + return exist; + } +} + + +/* @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 { + // AccountData + + /// @dev Set unviversal index of an account. + function setUniversalIndex + (ISuperfluidToken token, + address owner, + BasicParticle memory uIndex + ) + internal + { + GDAv1StorageLib.AccountData memory accountData = + GDAv1StorageReader.getAccountData(token, IGeneralDistributionAgreementV1(address(this)), owner); + + token.updateAgreementStateSlot( + owner, + GDAv1StorageLib.ACCOUNT_DATA_STATE_SLOT_ID, + GDAv1StorageLib.encodeUpdatedUniversalIndex(accountData, uIndex) + ); + } + + /// @dev Set is pool flag for a pool. + function setIsPoolFlag(ISuperfluidToken token, ISuperfluidPool pool) + internal + { + bytes32[] memory data = new bytes32[](1); + data[0] = bytes32(uint256(1)); + 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, + uint256 totalBuffer + ) + internal + { + GDAv1StorageLib.AccountData memory accountData = + GDAv1StorageReader.getAccountData(token, IGeneralDistributionAgreementV1(address(this)), owner); + + token.updateAgreementStateSlot( + owner, + GDAv1StorageLib.ACCOUNT_DATA_STATE_SLOT_ID, + GDAv1StorageLib.encodeUpdatedTotalBuffer(accountData, totalBuffer) + ); + } + + // Flow Info + + /// @dev Set flow info. + function setFlowInfoByFlowHash + (ISuperfluidToken token, + bytes32 flowHash, + GDAv1StorageLib.FlowInfo memory flowInfo + ) + internal + { + token.updateAgreementData(flowHash, GDAv1StorageLib.encodeFlowInfo(flowInfo)); + } + + // Pool Connectivity + + /// @dev Create a pool connectivity. + function createPoolConnectivity + (ISuperfluidToken token, + address poolMember, + GDAv1StorageLib.PoolConnectivity memory poolConnectivity + ) + internal + { + token.createAgreement + (GDAv1StorageLib.getPoolConnectivityHash(poolMember, poolConnectivity.pool), + GDAv1StorageLib.encodePoolConnectivity(poolConnectivity)); + } + + /// @dev Delete a pool connectivity. + function deletePoolConnectivity + (ISuperfluidToken token, + address poolMember, + ISuperfluidPool pool) + internal + { + 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 c529ebc354..97aff72c57 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -28,97 +28,21 @@ 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"; -/// @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; - } - - struct PoolMemberData { - address pool; - uint32 poolID; // the slot id in the pool's subs bitmap - } - - struct FlowDistributionData { - uint32 lastUpdated; - int96 flowRate; - uint256 buffer; // stored as uint96 - } + using GDAv1StorageReader for ISuperfluidToken; + using GDAv1StorageWriter for ISuperfluidToken; address public constant SLOTS_BITMAP_LIBRARY_ADDRESS = address(SlotsBitmapLibrary); @@ -138,39 +62,51 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi superfluidPoolBeacon = superfluidPoolBeacon_; } + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // ISuperAgreement interface + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + /// @inheritdoc ISuperAgreement function realtimeBalanceOf(ISuperfluidToken token, address account, uint256 time) public view override returns (int256 rtb, uint256 buf, uint256 owedBuffer) { - UniversalIndexData memory universalIndexData = _getUIndexData(abi.encode(token), account); + GDAv1StorageLib.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(GDAv1StorageLib + .getUniversalIndexFromAccountData(accountData) + .rtb(Time.wrap(uint32(time)))); } - int256 fromPools; + 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]))); - (bool exist, PoolMemberData memory poolMemberData) = - _getPoolMemberData(token, account, ISuperfluidPool(pool)); - assert(exist); - assert(poolMemberData.pool == pool); - fromPools += ISuperfluidPool(pool).getClaimable(account, uint32(time)); + _assertPoolConnectivity(token, account, ISuperfluidPool(pool)); + totalConnectedFromPools += ISuperfluidPool(pool).getClaimable(account, uint32(time)); } } - rtb += fromPools; + rtb += totalConnectedFromPools; - buf = uint256(universalIndexData.totalBuffer.toInt256()); // upcasting to uint256 is safe + buf = uint256(accountData.totalBuffer.toInt256()); // upcasting to uint256 is safe owedBuffer = 0; } - /// @dev ISuperAgreement.realtimeBalanceOf implementation + function _assertPoolConnectivity(ISuperfluidToken token, address account, ISuperfluidPool pool) internal view + { + (bool exist, GDAv1StorageLib.PoolConnectivity memory poolConnectivity) = + token.getPoolConnectivity(this, account, pool); + assert(exist); + assert(poolConnectivity.pool == pool); + } + + /// @dev Use block.timestamp for realtimeBalanceOf function realtimeBalanceOfNow(ISuperfluidToken token, address account) external view @@ -180,11 +116,15 @@ 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(); - if (_isPool(this, token, account)) { + if (token.isPool(this, account)) { netFlowRate += ISuperfluidPool(account).getTotalDisconnectedFlowRate(); } @@ -204,7 +144,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (int96) { - (, FlowDistributionData memory data) = _getFlowDistributionData(token, _getFlowDistributionHash(from, to)); + GDAv1StorageLib.FlowInfo memory data = token.getDistributionFlowInfo(this, from, to); return data.flowRate; } @@ -215,7 +155,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (uint256 lastUpdated, int96 flowRate, uint256 deposit) { - (, FlowDistributionData memory data) = _getFlowDistributionData(token, _getFlowDistributionHash(from, to)); + GDAv1StorageLib.FlowInfo memory data = token.getDistributionFlowInfo(this, from, to); lastUpdated = data.lastUpdated; flowRate = data.flowRate; deposit = data.buffer; @@ -228,10 +168,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; + GDAv1StorageLib.AccountData memory accountData = token.getAccountData(this, account); + timestamp = accountData.settledAt; + flowRate = accountData.flowRate; + deposit = accountData.totalBuffer; } /// @inheritdoc IGeneralDistributionAgreementV1 @@ -242,7 +182,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 = GDAv1StorageLib.getFlowDistributionHash(from, to); BasicParticle memory fromUIndexData = _getUIndex(eff, from); @@ -293,7 +233,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 +243,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.setIsPoolFlag(pool); IPoolAdminNFT poolAdminNFT = IPoolAdminNFT(_getPoolAdminNFTAddress(token)); @@ -369,16 +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); - } - // @note setPoolConnection function naming function connectPool(ISuperfluidPool pool, bool doConnect, bytes calldata ctx) public @@ -388,7 +314,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 = token.isPoolMemberConnected(this, pool, msgSender); if (doConnect != isConnected) { assert( SuperfluidPool(address(pool)).operatorConnectMember( @@ -397,57 +323,36 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ); if (doConnect) { - uint32 poolSlotID = + 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.createPoolConnectivity + (msgSender, GDAv1StorageLib.PoolConnectivity({ slotId: poolSlotId, pool: pool })); } else { - (, PoolMemberData memory poolMemberData) = _getPoolMemberData(token, msgSender, pool); - token.terminateAgreement(_getPoolMemberHash(msgSender, pool), 1); + (, GDAv1StorageLib.PoolConnectivity memory poolConnectivity) = + token.getPoolConnectivity(this, msgSender, pool); + token.deletePoolConnectivity(msgSender, pool); - _clearPoolConnectionsBitmap(token, msgSender, poolMemberData.poolID); + _clearPoolConnectionsBitmap(token, msgSender, poolConnectivity.slotId); } emit PoolConnectionUpdated(token, pool, msgSender, doConnect, currentContext.userData); } } - function _isMemberConnected(ISuperfluidToken token, address pool, address member) internal view returns (bool) { - (bool exist,) = _getPoolMemberData(token, member, ISuperfluidPool(pool)); - return exist; - } - - function isMemberConnected(ISuperfluidPool pool, address member) external view override returns (bool) { - return _isMemberConnected(pool.superToken(), address(pool), member); + /// @inheritdoc IGeneralDistributionAgreementV1 + function connectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { + return connectPool(pool, true, ctx); } - function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) external returns (bool) { - if (_isPool(this, token, 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; + /// @inheritdoc IGeneralDistributionAgreementV1 + function disconnectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { + return connectPool(pool, false, ctx); } - function poolSettleClaim(ISuperfluidToken superToken, address claimRecipient, int256 amount) - external - returns (bool) - { - if (_isPool(this, superToken, 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 isMemberConnected(ISuperfluidPool pool, address member) external view override returns (bool) { + return pool.superToken().isPoolMemberConnected(this, pool, member); } /// @inheritdoc IGeneralDistributionAgreementV1 @@ -462,7 +367,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 +433,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(); @@ -540,7 +445,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi _StackVars_DistributeFlow memory flowVars; { flowVars.currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); - flowVars.distributionFlowHash = _getFlowDistributionHash(from, pool); + flowVars.distributionFlowHash = GDAv1StorageLib.getFlowDistributionHash(from, pool); flowVars.oldFlowRate = _getFlowRate(abi.encode(token), flowVars.distributionFlowHash); } @@ -582,8 +487,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 @@ -643,51 +548,13 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } } - function _makeLiquidationPayouts(_StackVars_Liquidation memory data) internal { - (, FlowDistributionData memory flowDistributionData) = - _getFlowDistributionData(ISuperfluidToken(data.token), 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 @@ -698,8 +565,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi (uint256 liquidationPeriod,) = SolvencyHelperLibrary.decode3PsData(ISuperfluid(_host), ISuperfluidToken(token)); - (, FlowDistributionData memory flowDistributionData) = - _getFlowDistributionData(ISuperfluidToken(token), 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))); @@ -710,39 +576,34 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi Value bufferDelta = newBufferAmount - Value.wrap(uint256(flowDistributionData.buffer).toInt256()); - { - bytes32[] memory data = _encodeFlowDistributionData( - FlowDistributionData({ - 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); - } + 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 + }) + ); - UniversalIndexData memory universalIndexData = _getUIndexData(abi.encode(token), from); - universalIndexData.totalBuffer = + GDAv1StorageLib.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 + // + // Solvency + // + + /// @inheritdoc IGeneralDistributionAgreementV1 function isPatricianPeriodNow(ISuperfluidToken token, address account) external view @@ -753,6 +614,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 +631,195 @@ 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 + function _makeLiquidationPayouts(_StackVars_Liquidation memory data) internal { + GDAv1StorageLib.FlowInfo memory flowDistributionData = + data.token.getFlowInfoByFlowHash(this, data.distributionFlowHash); + int256 signedSingleDeposit = flowDistributionData.buffer.toInt256(); - function _getPoolMemberHash(address poolMember, ISuperfluidPool pool) internal view returns (bytes32) { - return keccak256(abi.encode(block.chainid, "poolMember", poolMember, address(pool))); - } + bool isCurrentlyPatricianPeriod; - function _getFlowDistributionHash(address from, ISuperfluidPool to) internal view returns (bytes32) { - return keccak256(abi.encode(block.chainid, "distributionFlow", from, to)); - } + { + (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; - 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)); + // 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 + ); + } } - // # 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) + // pool info and operators + // + + /// @inheritdoc IGeneralDistributionAgreementV1 + function getPoolAdjustmentFlowInfo(ISuperfluidPool pool) + external + view + override + returns (address recipient, 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))); + return _getPoolAdjustmentFlowInfo(abi.encode(pool.superToken()), address(pool)); } - function _encodeUniversalIndexData(UniversalIndexData memory uIndexData) - internal - pure - returns (bytes32[] memory data) + /// @inheritdoc IGeneralDistributionAgreementV1 + function isPool(ISuperfluidToken token, address account) external view override returns (bool) { + return token.isPool(this, account); + } + + /// @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(); + } + + 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(uIndexData.flowRate)) << 160) | (uint256(uIndexData.settledAt) << 128) - | (uint256(uIndexData.totalBuffer.toUint96()) << 32) | (uIndexData.isPool ? 1 : 0) - ); - data[1] = bytes32(uint256(uIndexData.settledValue)); + // pool admin is always the adjustment recipient + adjustmentRecipient = ISuperfluidPool(pool).admin(); + flowHash = GDAv1StorageLib.getPoolAdjustmentFlowHash(pool, adjustmentRecipient); + return (adjustmentRecipient, flowHash, int256(FlowRate.unwrap(_getFlowRate(eff, flowHash))).toInt96()); } - function _decodeUniversalIndexData(bytes32[] memory data) + function _setPoolAdjustmentFlowRate(bytes memory eff, address pool, bool doShiftFlow, FlowRate flowRate, Time t) internal - pure - returns (bool exists, UniversalIndexData memory universalIndexData) + returns (bytes memory) { - uint256 a = uint256(data[0]); - uint256 b = uint256(data[1]); - - exists = a > 0 || b > 0; + // @note should this also always be + address adjustmentRecipient = ISuperfluidPool(pool).admin(); + bytes32 adjustmentFlowHash = GDAv1StorageLib.getPoolAdjustmentFlowHash(pool, adjustmentRecipient); - 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); + if (doShiftFlow) { + flowRate = flowRate + _getFlowRate(eff, adjustmentFlowHash); } + return _doFlow(eff, pool, adjustmentRecipient, adjustmentFlowHash, flowRate, t); } - function _getUIndexData(bytes memory eff, address owner) - internal - view - returns (UniversalIndexData memory universalIndexData) + // + // Pool-only operations + // + + function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) + external + returns (bool) { - (, universalIndexData) = _decodeUniversalIndexData( - ISuperfluidToken(abi.decode(eff, (address))).getAgreementStateSlot( - address(this), owner, _universalIndexStateSlotId(), 2 - ) - ); + 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 _getBasicParticleFromUIndex(UniversalIndexData memory universalIndexData) - internal - pure - returns (BasicParticle memory particle) + function poolSettleClaim(ISuperfluidToken superToken, address claimRecipient, int256 amount) + external + returns (bool) { - particle._flow_rate = FlowRate.wrap(universalIndexData.flowRate); - particle._settled_at = Time.wrap(universalIndexData.settledAt); - particle._settled_value = Value.wrap(universalIndexData.settledValue); + 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 - 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); + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // TokenMonad interface + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + /// @inheritdoc TokenMonad + function _getUIndex(bytes memory eff, address owner) + internal view + override + returns (BasicParticle memory uIndex) + { + ISuperfluidToken token = ISuperfluidToken(abi.decode(eff, (address))); + GDAv1StorageLib.AccountData memory accountData = token.getAccountData(this, owner); + uIndex = GDAv1StorageLib.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) { - (, FlowDistributionData memory data) = - _getFlowDistributionData(ISuperfluidToken(abi.decode(eff, (address))), distributionFlowHash); + /// @inheritdoc TokenMonad + 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, flowHash); return FlowRate.wrap(data.flowRate); } + /// @inheritdoc TokenMonad function _setFlowInfo( bytes memory eff, bytes32 flowHash, @@ -928,49 +827,28 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi address, // to, FlowRate newFlowRate, FlowRate // flowRateDelta - ) internal override returns (bytes memory) { - address token = abi.decode(eff, (address)); - (, FlowDistributionData memory flowDistributionData) = - _getFlowDistributionData(ISuperfluidToken(token), flowHash); - - ISuperfluidToken(token).updateAgreementData( - flowHash, - _encodeFlowDistributionData( - FlowDistributionData({ - lastUpdated: uint32(block.timestamp), - flowRate: int256(FlowRate.unwrap(newFlowRate)).toInt96(), - buffer: flowDistributionData.buffer - }) - ) - ); - - return eff; - } - - /// @inheritdoc IGeneralDistributionAgreementV1 - function getPoolAdjustmentFlowInfo(ISuperfluidPool pool) - external - view + ) + internal override - returns (address recipient, bytes32 flowHash, int96 flowRate) + returns (bytes memory) { - return _getPoolAdjustmentFlowInfo(abi.encode(pool.superToken()), address(pool)); - } + ISuperfluidToken token = ISuperfluidToken(abi.decode(eff, (address))); + GDAv1StorageLib.FlowInfo memory flowInfo = token.getFlowInfoByFlowHash(this, flowHash); - 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()); + token.setFlowInfoByFlowHash(flowHash, + GDAv1StorageLib.FlowInfo({ + lastUpdated: uint32(block.timestamp), + flowRate: int256(FlowRate.unwrap(newFlowRate)).toInt96(), + buffer: flowInfo.buffer + }) + ); + + return eff; } + /// @inheritdoc TokenMonad function _getPoolAdjustmentFlowRate(bytes memory eff, address pool) - internal - view + internal view override returns (FlowRate flowRate) { @@ -978,11 +856,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,106 +865,29 @@ 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 | - // -------- ---------- ------------- ---------- -------- - // | 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 | - // -------- ---------- -------- ------------- - // | 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]) - ); - } + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // 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. + */ - // SlotsBitmap Pool Data: function _findAndFillPoolConnectionsBitmap(ISuperfluidToken token, address poolMember, bytes32 poolID) private returns (uint32 slotId) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index c2df801724..b4faa49280 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()); @@ -441,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 (!superToken.isPoolMemberConnected(GDA, ISuperfluidPool(address(this)), memberAddr)) { _shiftDisconnectedUnits(wrappedUnits - mu.m.owned_units, Value.wrap(0), t); } @@ -472,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 = superToken.isPoolMemberConnected(GDA, this, memberAddr); uint32 time = uint32(ISuperfluid(superToken.getHost()).getNow()); int256 claimedAmount = _claimAll(memberAddr, time); if (!isConnected) { 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..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 @@ -12,15 +12,17 @@ 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"; + 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"; + /// @title GeneralDistributionAgreementV1 Property Tests /// @author Superfluid /// @notice This is a contract that runs property tests for the GDAv1 @@ -28,6 +30,8 @@ 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; + using GDAv1StorageWriter for ISuperToken; SuperfluidFrameworkDeployer internal immutable sfDeployer; SuperfluidFrameworkDeployer.Framework internal sf; @@ -91,17 +95,17 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen _settled_value: Value.wrap(settledValue) }); _setUIndex(eff, owner, p); - GeneralDistributionAgreementV1.UniversalIndexData memory setUIndexData = _getUIndexData(eff, owner); + GDAv1StorageLib.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 - function testSetGetFlowDistributionData( + function testSetGetFlowInfo( address from, ISuperfluidPool to, uint32 newFlowRate, @@ -109,7 +113,7 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen ) public { uint256 lastUpdated = block.timestamp; - bytes32 flowHash = _getFlowDistributionHash(from, to); + bytes32 flowHash = GDAv1StorageLib.getFlowDistributionHash(from, to); _setFlowInfo( abi.encode(superToken), @@ -121,17 +125,13 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen ); vm.warp(1000); + GDAv1StorageLib.FlowInfo memory setFlowInfo = superToken.getFlowInfoByFlowHash(this, flowHash); - (bool exist, FlowDistributionData memory setFlowDistributionData) = - _getFlowDistributionData(superToken, flowHash); - - assertEq(true, exist, "flow distribution data does not exist"); + assertEq(int96(uint96(newFlowRate)), setFlowInfo.flowRate, "flowRate not equal"); - assertEq(int96(uint96(newFlowRate)), setFlowDistributionData.flowRate, "flowRate not equal"); + assertEq(lastUpdated, setFlowInfo.lastUpdated, "lastUpdated not equal"); - assertEq(lastUpdated, setFlowDistributionData.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)), @@ -144,25 +144,28 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen ); } - // Pool Member Data Setters/Getters - function testSetGetPoolMemberData(address poolMember, ISuperfluidPool _pool, uint32 poolID) public { - vm.assume(poolID > 0); + // Pool Connectivity Data Setters/Getters + 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)); - bytes32 poolMemberId = _getPoolMemberHash(poolMember, _pool); vm.startPrank(address(this)); - superToken.updateAgreementData( - poolMemberId, - _encodePoolMemberData(PoolMemberData({ poolID: poolID, pool: address(_pool) })) - ); + superToken.createPoolConnectivity + (poolMember, + GDAv1StorageLib.PoolConnectivity ({ + slotId: slotId, + pool: _pool + }) + ); vm.stopPrank(); - (bool exist, PoolMemberData memory setPoolMemberData) = _getPoolMemberData(superToken, 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(true, exist, "pool connectivity does not exist"); + assertEq(slotId, setPoolConnectivity.slotId, "slotId not equal"); + assertEq(address(_pool), address(setPoolConnectivity.pool), "pool not equal"); } // Proportional Distribution Pool Index Setters/Getters @@ -210,141 +213,99 @@ contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreemen ); } - // // Adjust Buffer => FlowDistributionData modified - // function testAdjustBufferUpdatesFlowDistributionData(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.FlowDistributionData memory flowDistributionData) = - // _getFlowDistributionData(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 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 + { + GDAv1StorageLib.AccountData memory accountData = + GDAv1StorageLib.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 = 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"); 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 + { + GDAv1StorageLib.AccountData memory accountData = + GDAv1StorageLib.AccountData({ + flowRate: flowRate, + settledAt: settledAt, + settledValue: 0, + totalBuffer: 0, + isPool: isPool + }); + 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"); - 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(GDAv1StorageLib.AccountData memory data) + pure + public + { + 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); - FlowDistributionData memory original = - FlowDistributionData({ flowRate: flowRate, lastUpdated: uint32(block.timestamp), buffer: buffer }); - bytes32[] memory encoded = _encodeFlowDistributionData(original); - (, FlowDistributionData memory decoded) = _decodeFlowDistributionData(uint256(encoded[0])); + GDAv1StorageLib.FlowInfo memory original = + GDAv1StorageLib.FlowInfo({ + flowRate: flowRate, + lastUpdated: uint32(block.timestamp), + buffer: buffer + }); + 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"); 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)); - PoolMemberData memory original = PoolMemberData({ pool: pool, poolID: poolID }); - bytes32[] memory encoded = _encodePoolMemberData(original); - (, PoolMemberData memory decoded) = _decodePoolMemberData(uint256(encoded[0])); - - assertEq(original.pool, decoded.pool, "pool not equal"); - assertEq(original.poolID, decoded.poolID, "poolID 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"); } }