diff --git a/.github/workflows/call.deploy-dry-run.yml b/.github/workflows/call.deploy-dry-run.yml
new file mode 100644
index 0000000000..0ec83e44b7
--- /dev/null
+++ b/.github/workflows/call.deploy-dry-run.yml
@@ -0,0 +1,53 @@
+name: Reusable Workflow | Deploy Framework and Update Tokens on a Forked Network
+
+on:
+ workflow_call:
+ inputs:
+ network:
+ required: true
+ type: string
+ network-id:
+ required: true
+ type: string
+ provider-url:
+ required: true
+ type: string
+
+jobs:
+ deploy-to-forked-network:
+ name: Deploy Framework and Update Tokens on a Forked Network
+ runs-on: ubuntu-latest
+ env:
+ ethereum-contracts-working-directory: ./packages/ethereum-contracts
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install and Build
+ run: |
+ yarn install --frozen-lockfile
+ yarn build-for-contracts-dev
+
+ - name: Start ganache
+ run: npx ganache --port 47545 --mnemonic --fork.url ${{ github.event.inputs.provider-url }} --network-id ${{ github.event.inputs.network-id }} --chain.chainId ${{ github.event.inputs.network-id }}
+
+ - name: Deploy framework
+ run: |
+ echo "${{ github.event.inputs.environments }}" | sed 's/;/\n/' > .env
+ npx truffle exec --network ${{ github.event.inputs.network }} ops-scripts/deploy-test-environment.js
+ working-directory: ${{ env.ethereum-contracts-working-directory }}
+
+ - name: Validate deployment before token upgrade
+ run: |
+ npx hardhat run ops-scripts/validate-deployment.ts --network ${{ github.event.inputs.network }}
+ working-directory: ${{ env.ethereum-contracts-working-directory }}
+
+ - name: Update Super Token Logic for all tokens
+ run: |
+ npx truffle exec --network ${{ github.event.inputs.network }} ops-scripts/gov-upgrade-super-token-logic.js : ALL
+ working-directory: ${{ env.ethereum-contracts-working-directory }}
+
+ - name: Validate deployment post token upgrade
+ run: |
+ npx hardhat run ops-scripts/validate-deployment.ts --network ${{ github.event.inputs.network }}
+ working-directory: ${{ env.ethereum-contracts-working-directory }}
diff --git a/.github/workflows/call.test-ethereum-contracts.yml b/.github/workflows/call.test-ethereum-contracts.yml
index 095201cf08..c16f36276e 100644
--- a/.github/workflows/call.test-ethereum-contracts.yml
+++ b/.github/workflows/call.test-ethereum-contracts.yml
@@ -99,11 +99,12 @@ jobs:
#
# Upstream issue: https://github.com/NomicFoundation/hardhat/issues/4310
# Though more likely, it is an issue to https://github.com/sc-forks/solidity-coverage
- # env:
- # # NOTE: 4 workers would overwhelm the free-tier github runner
- # IS_COVERAGE_TEST: true
- # HARDHAT_TEST_JOBS: 2
- # HARDHAT_RUN_PARALLEL: true
+ env:
+ # NOTE: 4 workers would overwhelm the free-tier github runner
+ NODE_OPTIONS: --max_old_space_size=4096
+ IS_COVERAGE_TEST: true
+ HARDHAT_TEST_JOBS: 2
+ HARDHAT_RUN_PARALLEL: false
- name: Clean up and merge coverage artifacts
if: inputs.run-coverage-tests == true
diff --git a/README.md b/README.md
index 8cff7b072d..0b81b93adb 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,22 @@
Welcome to superfluid protocol-monorepo 👋
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/packages/automation-contracts/autowrap/package.json b/packages/automation-contracts/autowrap/package.json
index b69b4f5320..4d37366bfd 100644
--- a/packages/automation-contracts/autowrap/package.json
+++ b/packages/automation-contracts/autowrap/package.json
@@ -14,7 +14,7 @@
},
"dependencies": {
"@openzeppelin/contracts": "4.9.3",
- "@superfluid-finance/ethereum-contracts": "1.8.1",
+ "@superfluid-finance/ethereum-contracts": "1.9.0",
"@superfluid-finance/metadata": "1.1.22"
}
}
diff --git a/packages/automation-contracts/scheduler/package.json b/packages/automation-contracts/scheduler/package.json
index 542be34118..13d6c91cf9 100644
--- a/packages/automation-contracts/scheduler/package.json
+++ b/packages/automation-contracts/scheduler/package.json
@@ -14,7 +14,7 @@
},
"dependencies": {
"@openzeppelin/contracts": "4.9.3",
- "@superfluid-finance/ethereum-contracts": "1.8.1",
+ "@superfluid-finance/ethereum-contracts": "1.9.0",
"@superfluid-finance/metadata": "1.1.22"
}
}
diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md
index 1e022c7bd3..23338ed5d5 100644
--- a/packages/ethereum-contracts/CHANGELOG.md
+++ b/packages/ethereum-contracts/CHANGELOG.md
@@ -5,6 +5,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## Unreleased
+## [v1.9.0] - 2024-01-09
+
### Breaking
- `TokenInfo` and `ERC20WithTokenInfo` interface/abstract contract are removed from the codebase, including the bundled ABI contracts
@@ -14,6 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added
+- New agreement: `GeneralDistributionAgreement` added which enables 1-to-N flowing distributions in addition to 1-to-N instant distributions via the `SuperfluidPool` contract
- Added 'test-slither' yarn sub-task.
- Expose `SuperToken._underlyingDecimals` with `SuperToken.getUnderlyingDecimals()`
- Expose `_toUnderlyingAmount(uint256 amount)` with `toUnderlyingAmount(uint256 amount)`
diff --git a/packages/ethereum-contracts/audits/Superfluid - Finance GDA - Comprehensive Report with Fix Review.pdf b/packages/ethereum-contracts/audits/Superfluid - Finance GDA - Comprehensive Report with Fix Review.pdf
new file mode 100644
index 0000000000..0cd80588ff
Binary files /dev/null and b/packages/ethereum-contracts/audits/Superfluid - Finance GDA - Comprehensive Report with Fix Review.pdf differ
diff --git a/packages/ethereum-contracts/audits/changes-since-ToB-2023-audit.md b/packages/ethereum-contracts/audits/changes-since-ToB-2023-audit.md
new file mode 100644
index 0000000000..430ef2d5e0
--- /dev/null
+++ b/packages/ethereum-contracts/audits/changes-since-ToB-2023-audit.md
@@ -0,0 +1,13 @@
+# Changes Since ToB 2023 Audit
+
+Use `git diff 4ece1a3f4aff8b5a9cbf37118d261023960c0f0f.. packages/ethereum-contracts/contracts` to see the changes in the contract code since the audit commit hash.
+
+## High Level Summary of Changes
+
+### GeneralDistributionAgreementV1
+- The representation of totalBuffer is modified to ensure proper data fitting in a 256-bit field.
+- `realtimeBalanceVectorAt` removed
+- `PoolConnectionUpdated` event only emitted if the connection was changed
+
+### SuperfluidPool
+The method for obtaining timestamps and checking member connections is updated to use Superfluid framework methods instead of Ethereum's native functionalities.
\ No newline at end of file
diff --git a/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol
index 1586b332f4..d383e8ca97 100644
--- a/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol
+++ b/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol
@@ -18,6 +18,7 @@ import { AgreementBase } from "./AgreementBase.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { AgreementLibrary } from "./AgreementLibrary.sol";
import { SafeGasLibrary } from "../libs/SafeGasLibrary.sol";
+import { SolvencyHelperLibrary } from "../libs/SolvencyHelperLibrary.sol";
/**
* @title ConstantFlowAgreementV1 contract
@@ -164,7 +165,7 @@ contract ConstantFlowAgreementV1 is
external view override
returns (int96 flowRate)
{
- (uint256 liquidationPeriod, ) = _decode3PsData(token);
+ (uint256 liquidationPeriod, ) = SolvencyHelperLibrary.decode3PsData(ISuperfluid(_host), token);
flowRate = _getMaximumFlowRateFromDepositPure(liquidationPeriod, deposit);
}
@@ -209,11 +210,12 @@ contract ConstantFlowAgreementV1 is
return true;
}
- (uint256 liquidationPeriod, uint256 patricianPeriod) = _decode3PsData(token);
+ (uint256 liquidationPeriod, uint256 patricianPeriod) =
+ SolvencyHelperLibrary.decode3PsData(ISuperfluid(_host), token);
(,FlowData memory senderAccountState) = _getAccountFlowState(token, account);
int256 signedTotalCFADeposit = senderAccountState.deposit.toInt256();
- return _isPatricianPeriod(
+ return SolvencyHelperLibrary.isPatricianPeriod(
availableBalance,
signedTotalCFADeposit,
liquidationPeriod,
@@ -1345,7 +1347,9 @@ contract ConstantFlowAgreementV1 is
uint256 minimumDeposit;
// STEP 1: calculate deposit required for the flow
{
- (uint256 liquidationPeriod, ) = _decode3PsData(token);
+
+ (uint256 liquidationPeriod,) =
+ SolvencyHelperLibrary.decode3PsData(ISuperfluid(_host), token);
ISuperfluidGovernance gov = ISuperfluidGovernance(ISuperfluid(msg.sender).getGovernance());
minimumDeposit = gov.getConfigAsUint256(
ISuperfluid(msg.sender), token, SuperfluidGovernanceConfigs.SUPERTOKEN_MINIMUM_DEPOSIT_KEY);
@@ -1466,7 +1470,7 @@ contract ConstantFlowAgreementV1 is
(,FlowData memory senderAccountState) = _getAccountFlowState(token, flowParams.sender);
int256 signedSingleDeposit = flowData.deposit.toInt256();
- // TODO: GDA deposit should be considered here too
+
int256 signedTotalCFADeposit = senderAccountState.deposit.toInt256();
bytes memory liquidationTypeData;
bool isCurrentlyPatricianPeriod;
@@ -1482,8 +1486,9 @@ contract ConstantFlowAgreementV1 is
// To retrieve patrician period
// Note: curly brackets are to handle stack too deep overflow issue
{
- (uint256 liquidationPeriod, uint256 patricianPeriod) = _decode3PsData(token);
- isCurrentlyPatricianPeriod = _isPatricianPeriod(
+ (uint256 liquidationPeriod, uint256 patricianPeriod) =
+ SolvencyHelperLibrary.decode3PsData(ISuperfluid(_host), token);
+ isCurrentlyPatricianPeriod = SolvencyHelperLibrary.isPatricianPeriod(
availableBalance,
signedTotalCFADeposit,
liquidationPeriod,
@@ -1616,51 +1621,6 @@ contract ConstantFlowAgreementV1 is
}
}
- /**************************************************************************
- * 3P's Pure Functions
- *************************************************************************/
-
- //
- // Data packing:
- //
- // WORD A: | reserved | patricianPeriod | liquidationPeriod |
- // | 192 | 32 | 32 |
- //
- // NOTE:
- // - liquidation period has 32 bits length
- // - patrician period also has 32 bits length
-
- function _decode3PsData(
- ISuperfluidToken token
- )
- internal view
- returns(uint256 liquidationPeriod, uint256 patricianPeriod)
- {
- ISuperfluidGovernance gov = ISuperfluidGovernance(ISuperfluid(_host).getGovernance());
- uint256 pppConfig =
- gov.getConfigAsUint256(ISuperfluid(_host), token, SuperfluidGovernanceConfigs.CFAV1_PPP_CONFIG_KEY);
- (liquidationPeriod, patricianPeriod) = SuperfluidGovernanceConfigs.decodePPPConfig(pppConfig);
- }
-
- function _isPatricianPeriod(
- int256 availableBalance,
- int256 signedTotalCFADeposit,
- uint256 liquidationPeriod,
- uint256 patricianPeriod
- )
- internal pure
- returns (bool)
- {
- if (signedTotalCFADeposit == 0) {
- return false;
- }
-
- int256 totalRewardLeft = availableBalance + signedTotalCFADeposit;
- int256 totalCFAOutFlowrate = signedTotalCFADeposit / int256(liquidationPeriod);
- // divisor cannot be zero with existing outflow
- return totalRewardLeft / totalCFAOutFlowrate > int256(liquidationPeriod - patricianPeriod);
- }
-
/**************************************************************************
* ACL Pure Functions
*************************************************************************/
diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol
new file mode 100644
index 0000000000..19be6f0955
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol
@@ -0,0 +1,1096 @@
+// SPDX-License-Identifier: AGPLv3
+// solhint-disable not-rely-on-time
+pragma solidity 0.8.19;
+
+import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
+import { IBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
+
+import { ISuperfluid, ISuperfluidGovernance } from "../../interfaces/superfluid/ISuperfluid.sol";
+import {
+ BasicParticle,
+ PDPoolIndex,
+ SemanticMoney,
+ Value,
+ Time,
+ FlowRate
+} from "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol";
+import { TokenMonad } from "@superfluid-finance/solidity-semantic-money/src/TokenMonad.sol";
+import { SuperfluidPool } from "./SuperfluidPool.sol";
+import { SuperfluidPoolDeployerLibrary } from "./SuperfluidPoolDeployerLibrary.sol";
+import {
+ IGeneralDistributionAgreementV1,
+ PoolConfig
+} from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
+import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol";
+import { IConstantOutflowNFT } from "../../interfaces/superfluid/IConstantOutflowNFT.sol";
+import { ISuperToken } from "../../interfaces/superfluid/ISuperToken.sol";
+import { IPoolAdminNFT } from "../../interfaces/agreements/gdav1/IPoolAdminNFT.sol";
+import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol";
+import { SlotsBitmapLibrary } from "../../libs/SlotsBitmapLibrary.sol";
+import { SolvencyHelperLibrary } from "../../libs/SolvencyHelperLibrary.sol";
+import { SafeGasLibrary } from "../../libs/SafeGasLibrary.sol";
+import { AgreementBase } from "../AgreementBase.sol";
+import { AgreementLibrary } from "../AgreementLibrary.sol";
+
+/**
+ * @title General Distribution Agreement
+ * @author Superfluid
+ * @notice
+ *
+ * Storage Layout Notes
+ * Agreement State
+ *
+ * Universal Index Data
+ * slotId = _UNIVERSAL_INDEX_STATE_SLOT_ID 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;
+
+ address public constant SLOTS_BITMAP_LIBRARY_ADDRESS = address(SlotsBitmapLibrary);
+
+ address public constant SUPERFLUID_POOL_DEPLOYER_ADDRESS = address(SuperfluidPoolDeployerLibrary);
+
+ /// @dev Universal Index state slot id for storing universal index data
+ uint256 private constant _UNIVERSAL_INDEX_STATE_SLOT_ID = 0;
+ /// @dev Pool member state slot id for storing subs bitmap
+ uint256 private constant _POOL_SUBS_BITMAP_STATE_SLOT_ID = 1;
+ /// @dev Pool member state slot id starting point for pool connections
+ uint256 private constant _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START = 1 << 128;
+ /// @dev SuperToken minimum deposit key
+ bytes32 private constant SUPERTOKEN_MINIMUM_DEPOSIT_KEY =
+ keccak256("org.superfluid-finance.superfluid.superTokenMinimumDeposit");
+
+ IBeacon public superfluidPoolBeacon;
+
+ constructor(ISuperfluid host) AgreementBase(address(host)) { }
+
+ function initialize(IBeacon superfluidPoolBeacon_) external initializer {
+ superfluidPoolBeacon = superfluidPoolBeacon_;
+ }
+
+ 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);
+
+ if (_isPool(token, account)) {
+ rtb = ISuperfluidPool(account).getDisconnectedBalance(uint32(time));
+ } else {
+ rtb = Value.unwrap(_getBasicParticleFromUIndex(universalIndexData).rtb(Time.wrap(uint32(time))));
+ }
+
+ int256 fromPools;
+ {
+ (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));
+ }
+ }
+ rtb += fromPools;
+
+ buf = uint256(universalIndexData.totalBuffer.toInt256()); // upcasting to uint256 is safe
+ }
+
+ /// @dev ISuperAgreement.realtimeBalanceOf implementation
+ function realtimeBalanceOfNow(ISuperfluidToken token, address account)
+ external
+ view
+ returns (int256 availableBalance, uint256 buffer, uint256 owedBuffer, uint256 timestamp)
+ {
+ (availableBalance, buffer, owedBuffer) = realtimeBalanceOf(token, account, block.timestamp);
+ timestamp = block.timestamp;
+ }
+
+ /// @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(token, account)) {
+ netFlowRate += ISuperfluidPool(account).getTotalDisconnectedFlowRate();
+ }
+
+ {
+ (uint32[] memory slotIds, bytes32[] memory pidList) = _listPoolConnectionIds(token, account);
+ for (uint256 i = 0; i < slotIds.length; ++i) {
+ ISuperfluidPool pool = ISuperfluidPool(address(uint160(uint256(pidList[i]))));
+ netFlowRate += pool.getMemberFlowRate(account);
+ }
+ }
+ }
+
+ /// @inheritdoc IGeneralDistributionAgreementV1
+ function getFlowRate(ISuperfluidToken token, address from, ISuperfluidPool to)
+ external
+ view
+ override
+ returns (int96)
+ {
+ (, FlowDistributionData memory data) = _getFlowDistributionData(token, _getFlowDistributionHash(from, to));
+ return data.flowRate;
+ }
+
+ /// @inheritdoc IGeneralDistributionAgreementV1
+ function estimateFlowDistributionActualFlowRate(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool to,
+ int96 requestedFlowRate
+ ) external view override returns (int96 actualFlowRate, int96 totalDistributionFlowRate) {
+ bytes memory eff = abi.encode(token);
+ bytes32 distributionFlowHash = _getFlowDistributionHash(from, to);
+
+ BasicParticle memory fromUIndexData = _getUIndex(eff, from);
+
+ PDPoolIndex memory pdpIndex = _getPDPIndex("", address(to));
+
+ FlowRate oldFlowRate = _getFlowRate(eff, distributionFlowHash);
+ FlowRate newActualFlowRate;
+ FlowRate oldDistributionFlowRate = pdpIndex.flow_rate();
+ FlowRate newDistributionFlowRate;
+ FlowRate flowRateDelta = FlowRate.wrap(requestedFlowRate) - oldFlowRate;
+ FlowRate currentAdjustmentFlowRate = _getPoolAdjustmentFlowRate(eff, address(to));
+
+ Time t = Time.wrap(uint32(block.timestamp));
+ (fromUIndexData, pdpIndex, newDistributionFlowRate) =
+ fromUIndexData.shift_flow2b(pdpIndex, flowRateDelta + currentAdjustmentFlowRate, t);
+ newActualFlowRate =
+ oldFlowRate + (newDistributionFlowRate - oldDistributionFlowRate) - currentAdjustmentFlowRate;
+
+ actualFlowRate = int256(FlowRate.unwrap(newActualFlowRate)).toInt96();
+ totalDistributionFlowRate = int256(FlowRate.unwrap(newDistributionFlowRate)).toInt96();
+
+ if (actualFlowRate < 0) {
+ actualFlowRate = 0;
+ }
+ }
+
+ /// @inheritdoc IGeneralDistributionAgreementV1
+ function estimateDistributionActualAmount(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool to,
+ uint256 requestedAmount
+ ) external view override returns (uint256 actualAmount) {
+ bytes memory eff = abi.encode(token);
+
+ Value actualDistributionAmount;
+ (,, actualDistributionAmount) =
+ _getUIndex(eff, from).shift2b(_getPDPIndex("", address(to)), Value.wrap(requestedAmount.toInt256()));
+
+ actualAmount = uint256(Value.unwrap(actualDistributionAmount));
+ }
+
+ function _createPool(ISuperfluidToken token, address admin, PoolConfig memory config)
+ 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(token, admin)) revert GDA_ADMIN_CANNOT_BE_POOL();
+
+ pool = ISuperfluidPool(
+ address(SuperfluidPoolDeployerLibrary.deploy(address(superfluidPoolBeacon), admin, token, config))
+ );
+
+ // @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), _UNIVERSAL_INDEX_STATE_SLOT_ID, data);
+
+ IPoolAdminNFT poolAdminNFT = IPoolAdminNFT(_getPoolAdminNFTAddress(token));
+
+ if (address(poolAdminNFT) != address(0)) {
+ uint256 gasLeftBefore = gasleft();
+ // solhint-disable-next-line no-empty-blocks
+ try poolAdminNFT.mint(address(pool)) { }
+ catch {
+ SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
+ }
+ }
+
+ emit PoolCreated(token, admin, pool);
+ }
+
+ /// @inheritdoc IGeneralDistributionAgreementV1
+ function createPool(ISuperfluidToken token, address admin, PoolConfig memory config)
+ external
+ override
+ returns (ISuperfluidPool pool)
+ {
+ return _createPool(token, admin, config);
+ }
+
+ /// @inheritdoc IGeneralDistributionAgreementV1
+ function updateMemberUnits(ISuperfluidPool pool, address memberAddress, uint128 newUnits, bytes calldata ctx)
+ external
+ override
+ returns (bytes memory newCtx)
+ {
+ // Only the admin can update member units here
+ if (AgreementLibrary.authorizeTokenAccess(pool.superToken(), ctx).msgSender != pool.admin()) {
+ revert GDA_NOT_POOL_ADMIN();
+ }
+ newCtx = ctx;
+
+ pool.updateMemberUnits(memberAddress, newUnits);
+ }
+
+ /// @inheritdoc IGeneralDistributionAgreementV1
+ function claimAll(ISuperfluidPool pool, address memberAddress, bytes calldata ctx)
+ external
+ override
+ returns (bytes memory newCtx)
+ {
+ AgreementLibrary.authorizeTokenAccess(pool.superToken(), ctx);
+ newCtx = ctx;
+
+ 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
+ returns (bytes memory newCtx)
+ {
+ ISuperfluidToken token = pool.superToken();
+ ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx);
+ address msgSender = currentContext.msgSender;
+ newCtx = ctx;
+ bool isConnected = _isMemberConnected(token, address(pool), msgSender);
+ if (doConnect != isConnected) {
+ assert(
+ SuperfluidPool(address(pool)).operatorConnectMember(
+ msgSender, doConnect, uint32(currentContext.timestamp)
+ )
+ );
+
+ if (doConnect) {
+ 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) }))
+ );
+ } else {
+ (, PoolMemberData memory poolMemberData) = _getPoolMemberData(token, msgSender, pool);
+ token.terminateAgreement(_getPoolMemberHash(msgSender, pool), 1);
+
+ _clearPoolConnectionsBitmap(token, msgSender, poolMemberData.poolID);
+ }
+
+ 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);
+ }
+
+ function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) external returns (bool) {
+ if (_isPool(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;
+ }
+
+ function poolSettleClaim(ISuperfluidToken superToken, address claimRecipient, int256 amount)
+ external
+ returns (bool)
+ {
+ if (_isPool(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 distribute(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool pool,
+ uint256 requestedAmount,
+ bytes calldata ctx
+ ) external override returns (bytes memory newCtx) {
+ ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx);
+
+ newCtx = ctx;
+
+ if (_isPool(token, address(pool)) == false) {
+ revert GDA_ONLY_SUPER_TOKEN_POOL();
+ }
+
+ // you cannot distribute if admin is not equal to the ctx.msgSender
+ if (!pool.distributionFromAnyAddress()) {
+ if (pool.admin() != currentContext.msgSender) {
+ revert GDA_DISTRIBUTE_FROM_ANY_ADDRESS_NOT_ALLOWED();
+ }
+ }
+
+ // the from address must be the same as the ctx.msgSender
+ // there is no ACL support
+ if (from != currentContext.msgSender) {
+ revert GDA_DISTRIBUTE_FOR_OTHERS_NOT_ALLOWED();
+ }
+
+ (, Value actualAmount) = _doDistributeViaPool(
+ abi.encode(token), currentContext.msgSender, address(pool), Value.wrap(requestedAmount.toInt256())
+ );
+
+ if (token.isAccountCriticalNow(from)) {
+ revert GDA_INSUFFICIENT_BALANCE();
+ }
+
+ // TODO: tokens are moving from sender => pool, including a transfer event makes sense here
+ // trigger from the supertoken contract - @note this is possible since solc 0.8.21
+
+ emit InstantDistributionUpdated(
+ token,
+ pool,
+ from,
+ currentContext.msgSender,
+ requestedAmount,
+ uint256(Value.unwrap(actualAmount)), // upcast from int256 -> uint256 is safe
+ currentContext.userData
+ );
+ }
+
+ // solhint-disable-next-line contract-name-camelcase
+ struct _StackVars_DistributeFlow {
+ ISuperfluid.Context currentContext;
+ bytes32 distributionFlowHash;
+ FlowRate oldFlowRate;
+ }
+
+ /// @inheritdoc IGeneralDistributionAgreementV1
+ function distributeFlow(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool pool,
+ int96 requestedFlowRate,
+ bytes calldata ctx
+ ) external override returns (bytes memory newCtx) {
+ if (_isPool(token, address(pool)) == false) {
+ revert GDA_ONLY_SUPER_TOKEN_POOL();
+ }
+ if (requestedFlowRate < 0) {
+ revert GDA_NO_NEGATIVE_FLOW_RATE();
+ }
+
+ _StackVars_DistributeFlow memory flowVars;
+ {
+ flowVars.currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx);
+ flowVars.distributionFlowHash = _getFlowDistributionHash(from, pool);
+ flowVars.oldFlowRate = _getFlowRate(abi.encode(token), flowVars.distributionFlowHash);
+ }
+
+ newCtx = ctx;
+
+ // we must check if the requestedFlowRate is greater than 0 here
+ // otherwise we will block liquidators from closing streams in pools
+ // where the pool config has distributionFromAnyAddress set to false
+ if (requestedFlowRate > 0 && !pool.distributionFromAnyAddress()) {
+ if (pool.admin() != flowVars.currentContext.msgSender) {
+ revert GDA_DISTRIBUTE_FROM_ANY_ADDRESS_NOT_ALLOWED();
+ }
+ }
+
+ (, FlowRate actualFlowRate, FlowRate newDistributionFlowRate) = _doDistributeFlowViaPool(
+ abi.encode(token),
+ from,
+ address(pool),
+ flowVars.distributionFlowHash,
+ FlowRate.wrap(requestedFlowRate),
+ Time.wrap(uint32(flowVars.currentContext.timestamp))
+ );
+
+ // handle distribute flow on behalf of someone else
+ // @note move to internal maybe
+ {
+ if (from != flowVars.currentContext.msgSender) {
+ if (requestedFlowRate > 0) {
+ // @note no ACL support for now
+ // revert if trying to distribute on behalf of others
+ revert GDA_DISTRIBUTE_FOR_OTHERS_NOT_ALLOWED();
+ } else {
+ // liquidation case, requestedFlowRate == 0
+ (int256 availableBalance,,) = token.realtimeBalanceOf(from, flowVars.currentContext.timestamp);
+ // StackVarsLiquidation used to handle good ol' stack too deep
+ StackVarsLiquidation memory liquidationData;
+ {
+ liquidationData.token = token;
+ liquidationData.sender = from;
+ liquidationData.liquidator = flowVars.currentContext.msgSender;
+ liquidationData.distributionFlowHash = flowVars.distributionFlowHash;
+ liquidationData.signedTotalGDADeposit =
+ _getUIndexData(abi.encode(token), from).totalBuffer.toInt256();
+ liquidationData.availableBalance = availableBalance;
+ }
+ // closing stream on behalf of someone else: liquidation case
+ if (availableBalance < 0) {
+ _makeLiquidationPayouts(liquidationData);
+ } else {
+ revert GDA_NON_CRITICAL_SENDER();
+ }
+ }
+ }
+ }
+
+ {
+ _adjustBuffer(token, address(pool), from, flowVars.distributionFlowHash, actualFlowRate);
+ }
+
+ // ensure sender has enough balance to execute transaction
+ if (from == flowVars.currentContext.msgSender) {
+ (int256 availableBalance,,) = token.realtimeBalanceOf(from, flowVars.currentContext.timestamp);
+ // if from == msg.sender
+ if (requestedFlowRate > 0 && availableBalance < 0) {
+ revert GDA_INSUFFICIENT_BALANCE();
+ }
+ }
+
+ // handleFlowNFT() - mint/burn FlowNFT to flow distributor
+ {
+ address constantOutflowNFTAddress = _getConstantOutflowNFTAddress(token);
+
+ if (constantOutflowNFTAddress != address(0)) {
+ uint256 gasLeftBefore;
+ // create flow (mint)
+ if (requestedFlowRate > 0 && FlowRate.unwrap(flowVars.oldFlowRate) == 0) {
+ gasLeftBefore = gasleft();
+ // solhint-disable-next-line no-empty-blocks
+ try IConstantOutflowNFT(constantOutflowNFTAddress).onCreate(token, from, address(pool)) { }
+ catch {
+ SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
+ }
+ }
+
+ // update flow (update metadata)
+ if (requestedFlowRate > 0 && FlowRate.unwrap(flowVars.oldFlowRate) > 0) {
+ gasLeftBefore = gasleft();
+ // solhint-disable-next-line no-empty-blocks
+ try IConstantOutflowNFT(constantOutflowNFTAddress).onUpdate(token, from, address(pool)) { }
+ catch {
+ SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
+ }
+ }
+
+ // delete flow (burn)
+ if (requestedFlowRate == 0) {
+ gasLeftBefore = gasleft();
+ // solhint-disable-next-line no-empty-blocks
+ try IConstantOutflowNFT(constantOutflowNFTAddress).onDelete(token, from, address(pool)) { }
+ catch {
+ SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
+ }
+ }
+ }
+ }
+
+ {
+ (address adjustmentFlowRecipient,, int96 adjustmentFlowRate) =
+ _getPoolAdjustmentFlowInfo(abi.encode(token), address(pool));
+
+ emit FlowDistributionUpdated(
+ token,
+ pool,
+ from,
+ flowVars.currentContext.msgSender,
+ int256(FlowRate.unwrap(flowVars.oldFlowRate)).toInt96(),
+ int256(FlowRate.unwrap(actualFlowRate)).toInt96(),
+ int256(FlowRate.unwrap(newDistributionFlowRate)).toInt96(),
+ adjustmentFlowRecipient,
+ adjustmentFlowRate,
+ flowVars.currentContext.userData
+ );
+ }
+ }
+
+ /**
+ * @notice Checks whether or not the NFT hook can be called.
+ * @dev A staticcall, so `CONSTANT_OUTFLOW_NFT` must be a view otherwise the assumption is that it reverts
+ * @param token the super token that is being streamed
+ * @return constantOutflowNFTAddress the address returned by low level call
+ */
+ function _getConstantOutflowNFTAddress(ISuperfluidToken token)
+ internal
+ view
+ returns (address constantOutflowNFTAddress)
+ {
+ // solhint-disable-next-line avoid-low-level-calls
+ (bool success, bytes memory data) =
+ address(token).staticcall(abi.encodeWithSelector(ISuperToken.CONSTANT_OUTFLOW_NFT.selector));
+
+ if (success) {
+ // @note We are aware this may revert if a Custom SuperToken's
+ // CONSTANT_OUTFLOW_NFT does not return data that can be
+ // decoded to an address. This would mean it was intentionally
+ // done by the creator of the Custom SuperToken logic and is
+ // fully expected to revert in that case as the author desired.
+ constantOutflowNFTAddress = abi.decode(data, (address));
+ }
+ }
+
+ function _getPoolAdminNFTAddress(ISuperfluidToken token) internal view returns (address poolAdminNFTAddress) {
+ // solhint-disable-next-line avoid-low-level-calls
+ (bool success, bytes memory data) =
+ address(token).staticcall(abi.encodeWithSelector(ISuperToken.POOL_ADMIN_NFT.selector));
+
+ if (success) {
+ // @note We are aware this may revert if a Custom SuperToken's
+ // POOL_ADMIN_NFT does not return data that can be
+ // decoded to an address. This would mean it was intentionally
+ // done by the creator of the Custom SuperToken logic and is
+ // fully expected to revert in that case as the author desired.
+ poolAdminNFTAddress = abi.decode(data, (address));
+ }
+ }
+
+ function _makeLiquidationPayouts(StackVarsLiquidation 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)
+ internal
+ {
+ // not using oldFlowRate in this model
+ // surprising effect: reducing flow rate may require more buffer when liquidation_period adjusted upward
+ ISuperfluidGovernance gov = ISuperfluidGovernance(ISuperfluid(_host).getGovernance());
+ uint256 minimumDeposit =
+ gov.getConfigAsUint256(ISuperfluid(msg.sender), ISuperfluidToken(token), SUPERTOKEN_MINIMUM_DEPOSIT_KEY);
+
+ (uint256 liquidationPeriod,) = SolvencyHelperLibrary.decode3PsData(ISuperfluid(_host), ISuperfluidToken(token));
+
+ (, FlowDistributionData memory flowDistributionData) =
+ _getFlowDistributionData(ISuperfluidToken(token), flowHash);
+
+ // @note downcasting from uint256 -> uint32 for liquidation period
+ Value newBufferAmount = newFlowRate.mul(Time.wrap(uint32(liquidationPeriod)));
+
+ if (Value.unwrap(newBufferAmount).toUint256() < minimumDeposit && FlowRate.unwrap(newFlowRate) > 0) {
+ newBufferAmount = Value.wrap(minimumDeposit.toInt256());
+ }
+
+ 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);
+ }
+
+ UniversalIndexData memory universalIndexData = _getUIndexData(abi.encode(token), from);
+ universalIndexData.totalBuffer =
+ // new buffer
+ (universalIndexData.totalBuffer.toInt256() + Value.unwrap(bufferDelta)).toUint256();
+ ISuperfluidToken(token).updateAgreementStateSlot(
+ from, _UNIVERSAL_INDEX_STATE_SLOT_ID, _encodeUniversalIndexData(universalIndexData)
+ );
+
+ {
+ emit BufferAdjusted(
+ ISuperfluidToken(token),
+ ISuperfluidPool(pool),
+ from,
+ Value.unwrap(bufferDelta),
+ Value.unwrap(newBufferAmount).toUint256(),
+ universalIndexData.totalBuffer
+ );
+ }
+ }
+
+ // Solvency Related Getters
+ function isPatricianPeriodNow(ISuperfluidToken token, address account)
+ external
+ view
+ override
+ returns (bool isCurrentlyPatricianPeriod, uint256 timestamp)
+ {
+ timestamp = ISuperfluid(_host).getNow();
+ isCurrentlyPatricianPeriod = isPatricianPeriod(token, account, timestamp);
+ }
+
+ function isPatricianPeriod(ISuperfluidToken token, address account, uint256 timestamp)
+ public
+ view
+ override
+ returns (bool)
+ {
+ (int256 availableBalance,,) = token.realtimeBalanceOf(account, timestamp);
+ if (availableBalance >= 0) {
+ return true;
+ }
+
+ (uint256 liquidationPeriod, uint256 patricianPeriod) =
+ SolvencyHelperLibrary.decode3PsData(ISuperfluid(_host), token);
+
+ return SolvencyHelperLibrary.isPatricianPeriod(
+ availableBalance,
+ _getUIndexData(abi.encode(token), account).totalBuffer.toInt256(),
+ liquidationPeriod,
+ patricianPeriod
+ );
+ }
+
+ // 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));
+ }
+
+ // # 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)
+ {
+ 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)));
+ }
+
+ function _encodeUniversalIndexData(UniversalIndexData memory uIndexData)
+ internal
+ pure
+ returns (bytes32[] memory data)
+ {
+ 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));
+ }
+
+ function _decodeUniversalIndexData(bytes32[] memory data)
+ internal
+ pure
+ returns (bool exists, UniversalIndexData memory universalIndexData)
+ {
+ uint256 a = uint256(data[0]);
+ uint256 b = uint256(data[1]);
+
+ exists = a > 0 || b > 0;
+
+ 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 _getUIndexData(bytes memory eff, address owner)
+ internal
+ view
+ returns (UniversalIndexData memory universalIndexData)
+ {
+ (, universalIndexData) = _decodeUniversalIndexData(
+ ISuperfluidToken(abi.decode(eff, (address))).getAgreementStateSlot(
+ address(this), owner, _UNIVERSAL_INDEX_STATE_SLOT_ID, 2
+ )
+ );
+ }
+
+ 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);
+ }
+
+ // 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, _UNIVERSAL_INDEX_STATE_SLOT_ID, 2
+ )
+ );
+ uIndex = _getBasicParticleFromUIndex(universalIndexData);
+ }
+
+ function _setUIndex(bytes memory eff, address owner, BasicParticle memory p)
+ internal
+ override
+ returns (bytes memory)
+ {
+ UniversalIndexData memory universalIndexData = _getUIndexData(eff, owner);
+
+ ISuperfluidToken(abi.decode(eff, (address))).updateAgreementStateSlot(
+ owner,
+ _UNIVERSAL_INDEX_STATE_SLOT_ID,
+ _encodeUniversalIndexData(p, universalIndexData.totalBuffer, universalIndexData.isPool)
+ );
+
+ return eff;
+ }
+
+ function _getPDPIndex(
+ bytes memory, // eff,
+ address pool
+ ) internal view override returns (PDPoolIndex memory) {
+ ISuperfluidPool.PoolIndexData memory data = SuperfluidPool(pool).getIndex();
+ return SuperfluidPool(pool).poolIndexDataToPDPoolIndex(data);
+ }
+
+ 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);
+ return FlowRate.wrap(data.flowRate);
+ }
+
+ function _setFlowInfo(
+ bytes memory eff,
+ bytes32 flowHash,
+ address, // from,
+ 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
+ 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());
+ }
+
+ function _getPoolAdjustmentFlowRate(bytes memory eff, address pool)
+ internal
+ view
+ override
+ returns (FlowRate flowRate)
+ {
+ (,, int96 rawFlowRate) = _getPoolAdjustmentFlowInfo(eff, pool);
+ 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();
+ }
+
+ function _setPoolAdjustmentFlowRate(bytes memory eff, address pool, FlowRate flowRate, Time t)
+ internal
+ override
+ returns (bytes memory)
+ {
+ 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(token, account);
+ }
+
+ function _isPool(ISuperfluidToken token, address account) internal view returns (bool exists) {
+ // @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(this), account, _UNIVERSAL_INDEX_STATE_SLOT_ID, 1)[0]) << 224)
+ >> 224
+ ) & 1 == 1;
+ }
+
+ // 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])
+ );
+ }
+
+ // SlotsBitmap Pool Data:
+ function _findAndFillPoolConnectionsBitmap(ISuperfluidToken token, address poolMember, bytes32 poolID)
+ private
+ returns (uint32 slotId)
+ {
+ return SlotsBitmapLibrary.findEmptySlotAndFill(
+ token, poolMember, _POOL_SUBS_BITMAP_STATE_SLOT_ID, _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START, poolID
+ );
+ }
+
+ function _clearPoolConnectionsBitmap(ISuperfluidToken token, address poolMember, uint32 slotId) private {
+ SlotsBitmapLibrary.clearSlot(token, poolMember, _POOL_SUBS_BITMAP_STATE_SLOT_ID, slotId);
+ }
+
+ function _listPoolConnectionIds(ISuperfluidToken token, address subscriber)
+ private
+ view
+ returns (uint32[] memory slotIds, bytes32[] memory pidList)
+ {
+ (slotIds, pidList) = SlotsBitmapLibrary.listData(
+ token, subscriber, _POOL_SUBS_BITMAP_STATE_SLOT_ID, _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START
+ );
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/PoolAdminNFT.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolAdminNFT.sol
new file mode 100644
index 0000000000..937fd09ab6
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolAdminNFT.sol
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
+import { IPoolAdminNFT } from "../../interfaces/agreements/gdav1/IPoolAdminNFT.sol";
+import { PoolNFTBase } from "./PoolNFTBase.sol";
+import { ISuperfluid } from "../../interfaces/superfluid/ISuperfluid.sol";
+import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol";
+import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol";
+
+contract PoolAdminNFT is PoolNFTBase, IPoolAdminNFT {
+ //// Storage Variables ////
+
+ /// NOTE: The storage variables in this contract MUST NOT:
+ /// - change the ordering of the existing variables
+ /// - change any of the variable types
+ /// - rename any of the existing variables
+ /// - remove any of the existing variables
+
+ /// @notice A mapping from token id to PoolAdminNFT data
+ /// PoolAdminNFTData: { address pool, address admin }
+ /// @dev The token id is uint256(keccak256(abi.encode(pool, admin)))
+ mapping(uint256 => PoolAdminNFTData) internal _poolAdminDataByTokenId;
+
+ constructor(ISuperfluid host) PoolNFTBase(host) { }
+
+ // note that this is used so we don't upgrade to wrong logic contract
+ function proxiableUUID() public pure override returns (bytes32) {
+ return keccak256("org.superfluid-finance.contracts.PoolAdminNFT.implementation");
+ }
+
+ function _ownerOf(uint256 tokenId) internal view override returns (address) {
+ return _poolAdminDataByTokenId[tokenId].admin;
+ }
+
+ function poolAdminDataByTokenId(uint256 tokenId) external view override returns (PoolAdminNFTData memory data) {
+ return _poolAdminDataByTokenId[tokenId];
+ }
+
+ /// @notice Reverts - Transfer of pool member NFT is not allowed.
+ /// @dev We revert when users attempt to transfer pool member NFTs.
+ function _transfer(
+ address, // from,
+ address, // to,
+ uint256 // tokenId
+ ) internal pure override {
+ revert POOL_NFT_TRANSFER_NOT_ALLOWED();
+ }
+
+ function getTokenId(address pool, address admin) external view override returns (uint256 tokenId) {
+ return _getTokenId(pool, admin);
+ }
+
+ function _getTokenId(address pool, address admin) internal view returns (uint256 tokenId) {
+ return uint256(keccak256(abi.encode("PoolAdminNFT", block.chainid, pool, admin)));
+ }
+
+ /// @inheritdoc PoolNFTBase
+ function tokenURI(uint256 tokenId) external view override(IERC721Metadata, PoolNFTBase) returns (string memory) {
+ return super._tokenURI(tokenId);
+ }
+
+ function mint(address pool) external {
+ _mint(pool);
+ }
+
+ /// @notice Mints `newTokenId` and transfers it to `admin`
+ /// @dev `pool` must be a registered pool in the GDA.
+ /// `newTokenId` must not exist, `admin` cannot be `address(0)` and we emit a {Transfer} event.
+ /// `admin` cannot be equal to `pool`.
+ /// @param pool The pool address
+ function _mint(address pool) internal {
+ ISuperfluidToken superToken = ISuperfluidPool(pool).superToken();
+ if (!GENERAL_DISTRIBUTION_AGREEMENT_V1.isPool(superToken, pool)) {
+ revert POOL_NFT_NOT_REGISTERED_POOL();
+ }
+ ISuperfluidPool poolContract = ISuperfluidPool(pool);
+ address admin = poolContract.admin();
+ assert(pool != admin);
+
+ uint256 newTokenId = _getTokenId(pool, admin);
+ assert(!_exists(newTokenId));
+
+ // update mapping for new NFT to be minted
+ _poolAdminDataByTokenId[newTokenId] = PoolAdminNFTData(pool, admin);
+
+ // emit mint of new pool admin token with newTokenId
+ emit Transfer(address(0), admin, newTokenId);
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/PoolMemberNFT.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolMemberNFT.sol
new file mode 100644
index 0000000000..8999f09a98
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolMemberNFT.sol
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
+import { IPoolMemberNFT } from "../../interfaces/agreements/gdav1/IPoolMemberNFT.sol";
+import { PoolNFTBase } from "./PoolNFTBase.sol";
+import { ISuperfluid } from "../../interfaces/superfluid/ISuperfluid.sol";
+import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol";
+import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol";
+
+contract PoolMemberNFT is PoolNFTBase, IPoolMemberNFT {
+ //// Storage Variables ////
+
+ /// NOTE: The storage variables in this contract MUST NOT:
+ /// - change the ordering of the existing variables
+ /// - change any of the variable types
+ /// - rename any of the existing variables
+ /// - remove any of the existing variables
+
+ /// @notice A mapping from token id to PoolMemberNFT data
+ /// PoolMemberNFTData: { address pool, address member, uint128 units }
+ /// @dev The token id is uint256(keccak256(abi.encode(pool, member)))
+ mapping(uint256 => PoolMemberNFTData) internal _poolMemberDataByTokenId;
+
+ constructor(ISuperfluid host) PoolNFTBase(host) { }
+
+ // note that this is used so we don't upgrade to wrong logic contract
+ function proxiableUUID() public pure override returns (bytes32) {
+ return keccak256("org.superfluid-finance.contracts.PoolMemberNFT.implementation");
+ }
+
+ function _ownerOf(uint256 tokenId) internal view override returns (address) {
+ return _poolMemberDataByTokenId[tokenId].member;
+ }
+
+ function poolMemberDataByTokenId(uint256 tokenId) public view override returns (PoolMemberNFTData memory data) {
+ return _poolMemberDataByTokenId[tokenId];
+ }
+
+ /// @notice Reverts - Transfer of pool member NFT is not allowed.
+ /// @dev We revert when users attempt to transfer pool member NFTs.
+ function _transfer(
+ address, // from,
+ address, // to,
+ uint256 // tokenId
+ ) internal pure override {
+ revert POOL_NFT_TRANSFER_NOT_ALLOWED();
+ }
+
+ function getTokenId(address pool, address member) external view override returns (uint256 tokenId) {
+ return _getTokenId(pool, member);
+ }
+
+ function _getTokenId(address pool, address member) internal view returns (uint256 tokenId) {
+ return uint256(keccak256(abi.encode("PoolMemberNFT", block.chainid, pool, member)));
+ }
+
+ /// @inheritdoc PoolNFTBase
+ function tokenURI(uint256 tokenId) external view override(IERC721Metadata, PoolNFTBase) returns (string memory) {
+ return super._tokenURI(tokenId);
+ }
+
+ /// @notice Mints `newTokenId` and transfers it to `member`
+ /// @dev `pool` must be a registered pool in the GDA.
+ /// `newTokenId` must not exist, `member` cannot be `address(0)`, `pool` cannot be `address(0)`,
+ /// and `pool` cannot be `member`.
+ /// We emit a {Transfer} event.
+ /// @param pool The pool address
+ /// @param member The member address
+ function onCreate(address pool, address member) external override {
+ _mint(pool, member);
+ }
+
+ /// @notice Updates token with `tokenId`.
+ /// @dev `tokenId` must exist AND we emit a {MetadataUpdate} event
+ /// @param pool The pool address
+ /// @param member The member address
+ function onUpdate(address pool, address member) external override {
+ uint256 tokenId = _getTokenId(pool, member);
+ address owner = _ownerOf(tokenId);
+ assert(owner != address(0));
+ PoolMemberNFTData storage data = _poolMemberDataByTokenId[tokenId];
+ data.units = ISuperfluidPool(data.pool).getUnits(data.member);
+
+ _triggerMetadataUpdate(tokenId);
+ }
+
+ /// @notice Destroys token with `tokenId` and clears approvals from previous owner.
+ /// @dev `tokenId` must exist AND we emit a {Transfer} event
+ /// @param pool The pool address
+ /// @param member The member address
+ function onDelete(address pool, address member) external override {
+ uint256 tokenId = _getTokenId(pool, member);
+ _burn(tokenId);
+ }
+
+ function _mint(address pool, address member) internal {
+ ISuperfluidToken superToken = ISuperfluidPool(pool).superToken();
+ if (!GENERAL_DISTRIBUTION_AGREEMENT_V1.isPool(superToken, pool)) {
+ revert POOL_NFT_NOT_REGISTERED_POOL();
+ }
+
+ assert(pool != address(0));
+ assert(member != address(0));
+ assert(pool != member);
+
+ uint256 newTokenId = _getTokenId(pool, member);
+ assert(!_exists(newTokenId));
+
+ uint128 units = ISuperfluidPool(pool).getUnits(member);
+
+ if (units == 0) {
+ revert POOL_MEMBER_NFT_NO_UNITS();
+ }
+
+ // update mapping for new NFT to be minted
+ _poolMemberDataByTokenId[newTokenId] = PoolMemberNFTData(pool, member, units);
+
+ // emit mint of new pool member token with newTokenId
+ emit Transfer(address(0), member, newTokenId);
+ }
+
+ function _burn(uint256 tokenId) internal override {
+ PoolMemberNFTData storage data = _poolMemberDataByTokenId[tokenId];
+ if (ISuperfluidPool(data.pool).getUnits(data.member) > 0) {
+ revert POOL_MEMBER_NFT_HAS_UNITS();
+ }
+
+ address owner = _ownerOf(tokenId);
+ assert(owner != address(0));
+ super._burn(tokenId);
+
+ // remove previous tokenId flow data mapping
+ delete _poolMemberDataByTokenId[tokenId];
+
+ // emit burn of pool member token with tokenId
+ emit Transfer(owner, address(0), tokenId);
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol
new file mode 100644
index 0000000000..bda4fe2e90
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+// Notes: We use reserved slots for upgradable contracts.
+// solhint-disable max-states-count
+
+// Notes: We use these interfaces in natspec documentation below, grep @inheritdoc
+// solhint-disable-next-line no-unused-import
+import { IERC165, IERC721, IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
+import { UUPSProxiable } from "../../upgradability/UUPSProxiable.sol";
+import { ISuperfluid } from "../../interfaces/superfluid/ISuperfluid.sol";
+import { ISuperTokenFactory } from "../../interfaces/superfluid/ISuperTokenFactory.sol";
+import { IPoolNFTBase } from "../../interfaces/agreements/gdav1/IPoolNFTBase.sol";
+import { IGeneralDistributionAgreementV1 } from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
+
+abstract contract PoolNFTBase is UUPSProxiable, IPoolNFTBase {
+ string public constant DEFAULT_BASE_URI = "https://nft.superfluid.finance/pool/v2/getmeta";
+
+ function baseURI() public pure returns (string memory) { return DEFAULT_BASE_URI; }
+
+ /// @notice Superfluid host contract address
+ ISuperfluid public immutable HOST;
+
+ /// @notice Superfluid GDAv1 contract address
+ IGeneralDistributionAgreementV1 public immutable GENERAL_DISTRIBUTION_AGREEMENT_V1;
+
+ //// Storage Variables ////
+
+ /// NOTE: The storage variables in this contract MUST NOT:
+ /// - change the ordering of the existing variables
+ /// - change any of the variable types
+ /// - rename any of the existing variables
+ /// - remove any of the existing variables
+
+ string internal _name;
+ string internal _symbol;
+
+ /// @notice Mapping for token approvals
+ /// @dev tokenID => approved address mapping
+ mapping(uint256 => address) internal _tokenApprovals;
+
+ /// @notice Mapping for operator approvals
+ mapping(address => mapping(address => bool)) internal _operatorApprovals;
+
+ /// @notice This allows us to add new storage variables in the base contract
+ /// without having to worry about messing up the storage layout that exists in COFNFT or CIFNFT.
+ /// @dev This empty reserved space is put in place to allow future versions to add new
+ /// variables without shifting down storage in the inheritance chain.
+ /// Slots 5-21 are reserved for future use.
+ /// We use this pattern in SuperToken.sol and favor this over the OpenZeppelin pattern
+ /// as this prevents silly footgunning.
+ /// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
+ uint256 internal _reserve5;
+ uint256 private _reserve6;
+ uint256 private _reserve7;
+ uint256 private _reserve8;
+ uint256 private _reserve9;
+ uint256 private _reserve10;
+ uint256 private _reserve11;
+ uint256 private _reserve12;
+ uint256 private _reserve13;
+ uint256 private _reserve14;
+ uint256 private _reserve15;
+ uint256 private _reserve16;
+ uint256 private _reserve17;
+ uint256 private _reserve18;
+ uint256 private _reserve19;
+ uint256 private _reserve20;
+ uint256 internal _reserve21;
+
+ constructor(ISuperfluid host) {
+ HOST = host;
+ GENERAL_DISTRIBUTION_AGREEMENT_V1 = IGeneralDistributionAgreementV1(
+ address(
+ ISuperfluid(host).getAgreementClass(
+ keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1")
+ )
+ )
+ );
+ }
+
+ function initialize(string memory nftName, string memory nftSymbol)
+ external
+ override
+ initializer // OpenZeppelin Initializable
+ {
+ _name = nftName;
+ _symbol = nftSymbol;
+ }
+
+ function updateCode(address newAddress) external override {
+ ISuperTokenFactory superTokenFactory = HOST.getSuperTokenFactory();
+ if (msg.sender != address(superTokenFactory)) {
+ revert POOL_NFT_ONLY_SUPER_TOKEN_FACTORY();
+ }
+
+ UUPSProxiable._updateCodeAddress(newAddress);
+ }
+
+ /// @notice Emits the MetadataUpdate event with `tokenId` as the argument.
+ /// @dev Callable by anyone.
+ /// @param tokenId the token id to trigger a metaupdate for
+ function triggerMetadataUpdate(uint256 tokenId) external {
+ _triggerMetadataUpdate(tokenId);
+ }
+
+ /// @notice This contract supports IERC165, IERC721 and IERC721Metadata
+ /// @dev This is part of the Standard Interface Detection EIP: https://eips.ethereum.org/EIPS/eip-165
+ /// @param interfaceId the XOR of all function selectors in the interface
+ /// @return boolean true if the interface is supported
+ /// @inheritdoc IERC165
+ function supportsInterface(bytes4 interfaceId) external pure virtual override returns (bool) {
+ return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165
+ || interfaceId == 0x80ac58cd // ERC165 Interface ID for ERC721
+ || interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
+ }
+
+ /// @inheritdoc IERC721
+ function ownerOf(uint256 tokenId) public view virtual override returns (address) {
+ address owner = _ownerOf(tokenId);
+ if (owner == address(0)) {
+ revert POOL_NFT_INVALID_TOKEN_ID();
+ }
+ return owner;
+ }
+
+ /// @notice Returns a hardcoded balance of 1
+ /// @dev We always return 1 to avoid the need for additional mapping
+ /// @return balance = 1
+ function balanceOf(
+ address // owner
+ ) external pure returns (uint256 balance) {
+ balance = 1;
+ }
+
+ /// @notice Returns the name of the NFT
+ /// @dev Should follow the naming convention: (Pool Admin|Pool Member) NFT
+ /// @return name of the NFT
+ function name() external view virtual override returns (string memory) {
+ return _name;
+ }
+
+ /// @notice Returns the symbol of the NFT
+ /// @dev Should follow the naming convention: PA|PM
+ /// @return symbol of the NFT
+ function symbol() external view virtual override returns (string memory) {
+ return _symbol;
+ }
+
+ /// @notice This returns the Uniform Resource Identifier (URI), where the metadata for the NFT lives.
+ /// @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
+ /// @return the token URI
+ function tokenURI(uint256 tokenId) external view virtual returns (string memory);
+
+ function _tokenURI(uint256 /*tokenId*/) internal view virtual returns (string memory) {
+ return string(abi.encodePacked(baseURI()));
+ }
+
+ /// @inheritdoc IERC721
+ function approve(address to, uint256 tokenId) public virtual override {
+ address owner = PoolNFTBase.ownerOf(tokenId);
+ if (to == owner) {
+ revert POOL_NFT_APPROVE_TO_CURRENT_OWNER();
+ }
+
+ if (msg.sender != owner && !isApprovedForAll(owner, msg.sender)) {
+ revert POOL_NFT_APPROVE_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL();
+ }
+
+ _approve(to, tokenId);
+ }
+
+ /// @inheritdoc IERC721
+ function getApproved(uint256 tokenId) public view virtual override returns (address) {
+ _requireMinted(tokenId);
+
+ return _tokenApprovals[tokenId];
+ }
+
+ /// @inheritdoc IERC721
+ function setApprovalForAll(address operator, bool approved) external virtual override {
+ _setApprovalForAll(msg.sender, operator, approved);
+ }
+
+ /// @inheritdoc IERC721
+ function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
+ return _operatorApprovals[owner][operator];
+ }
+
+ /// @inheritdoc IERC721
+ function transferFrom(address from, address to, uint256 tokenId) external virtual override {
+ if (!_isApprovedOrOwner(msg.sender, tokenId)) {
+ revert POOL_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL();
+ }
+
+ _transfer(from, to, tokenId);
+ }
+
+ /// @inheritdoc IERC721
+ function safeTransferFrom(address from, address to, uint256 tokenId) external virtual override {
+ safeTransferFrom(from, to, tokenId, "");
+ }
+
+ /// @inheritdoc IERC721
+ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
+ if (!_isApprovedOrOwner(msg.sender, tokenId)) {
+ revert POOL_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL();
+ }
+
+ _safeTransfer(from, to, tokenId, data);
+ }
+
+ /// @notice Returns whether `spender` is allowed to manage `tokenId`.
+ /// @dev Will revert if `tokenId` doesn't exist.
+ /// @param spender the spender of the token
+ /// @param tokenId the id of the token to be spent
+ /// @return whether `tokenId` can be spent by `spender`
+ function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
+ address owner = PoolNFTBase.ownerOf(tokenId);
+ return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
+ }
+
+ /// @notice Reverts if `tokenId` doesn't exist
+ /// @param tokenId the token id whose existence we are checking
+ function _requireMinted(uint256 tokenId) internal view {
+ if (!_exists(tokenId)) revert POOL_NFT_INVALID_TOKEN_ID();
+ }
+
+ /// @notice Returns whether `tokenId` exists
+ /// @dev Tokens can be managed by their owner or approved accounts via `approve` or `setApprovalForAll`.
+ /// Tokens start existing when they are minted (`_mint`),
+ /// and stop existing when they are burned (`_burn`).
+ /// @param tokenId the token id we're interested in seeing if exists
+ /// @return bool whether ot not the token exists
+ function _exists(uint256 tokenId) internal view returns (bool) {
+ return _ownerOf(tokenId) != address(0);
+ }
+
+ function _triggerMetadataUpdate(uint256 tokenId) internal {
+ emit MetadataUpdate(tokenId);
+ }
+
+ function _approve(address to, uint256 tokenId) internal {
+ _tokenApprovals[tokenId] = to;
+
+ emit Approval(_ownerOf(tokenId), to, tokenId);
+ }
+
+ function _setApprovalForAll(address owner, address operator, bool approved) internal {
+ if (owner == operator) revert POOL_NFT_APPROVE_TO_CALLER();
+
+ _operatorApprovals[owner][operator] = approved;
+
+ emit ApprovalForAll(owner, operator, approved);
+ }
+
+ /// @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist.
+ /// @param tokenId the token id whose existence we're checking
+ /// @return address the address of the owner of `tokenId`
+ function _ownerOf(uint256 tokenId) internal view virtual returns (address);
+
+ function _transfer(address from, address to, uint256 tokenId) internal virtual;
+
+ function _safeTransfer(
+ address from,
+ address to,
+ uint256 tokenId,
+ bytes memory // data
+ ) internal virtual {
+ _transfer(from, to, tokenId);
+ }
+
+ /// @dev Deletes the tokenApprovals for `tokenId`
+ /// @param tokenId the token id whose approvals we're clearing
+ function _burn(uint256 tokenId) internal virtual {
+ // clear approvals from the previous owner
+ delete _tokenApprovals[tokenId];
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol
new file mode 100644
index 0000000000..fcb5714a4a
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol
@@ -0,0 +1,488 @@
+// SPDX-License-Identifier: AGPLv3
+// solhint-disable not-rely-on-time
+pragma solidity 0.8.19;
+
+// Notes: We use these interfaces in natspec documentation below, grep @inheritdoc
+// solhint-disable-next-line no-unused-import
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
+import {
+ BasicParticle,
+ SemanticMoney,
+ PDPoolMember,
+ PDPoolIndex,
+ PDPoolMemberMU,
+ Value,
+ Time,
+ FlowRate,
+ Unit
+} from "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol";
+import { ISuperfluid } from "../../interfaces/superfluid/ISuperfluid.sol";
+import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol";
+import { ISuperToken } from "../../interfaces/superfluid/ISuperToken.sol";
+import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol";
+import { GeneralDistributionAgreementV1 } from "../../agreements/gdav1/GeneralDistributionAgreementV1.sol";
+import { BeaconProxiable } from "../../upgradability/BeaconProxiable.sol";
+import { IPoolMemberNFT } from "../../interfaces/agreements/gdav1/IPoolMemberNFT.sol";
+import { SafeGasLibrary } from "../../libs/SafeGasLibrary.sol";
+
+/**
+ * @title SuperfluidPool
+ * @author Superfluid
+ * @notice A SuperfluidPool which can be used to distribute any SuperToken.
+ * @dev Because we are using uint128, uint256 doesn't work here.
+ */
+contract SuperfluidPool is ISuperfluidPool, BeaconProxiable {
+ using SemanticMoney for BasicParticle;
+ using SafeCast for uint256;
+ using SafeCast for int256;
+
+ GeneralDistributionAgreementV1 public immutable GDA;
+
+ ISuperfluidToken public superToken;
+ address public admin;
+ PoolIndexData internal _index;
+ mapping(address => MemberData) internal _membersData;
+
+ /// @dev This is a pseudo member, representing all the disconnected members
+ MemberData internal _disconnectedMembers;
+
+ /// @dev owner => (spender => amount)
+ mapping(address => mapping(address => uint256)) internal _allowances;
+
+ /// @inheritdoc ISuperfluidPool
+ bool public transferabilityForUnitsOwner;
+
+ /// @inheritdoc ISuperfluidPool
+ bool public distributionFromAnyAddress;
+
+ constructor(GeneralDistributionAgreementV1 gda) {
+ GDA = gda;
+ }
+
+ function initialize(
+ address admin_,
+ ISuperfluidToken superToken_,
+ bool transferabilityForUnitsOwner_,
+ bool distributionFromAnyAddress_
+ ) external initializer {
+ admin = admin_;
+ superToken = superToken_;
+ transferabilityForUnitsOwner = transferabilityForUnitsOwner_;
+ distributionFromAnyAddress = distributionFromAnyAddress_;
+ }
+
+ function proxiableUUID() public pure override returns (bytes32) {
+ return keccak256("org.superfluid-finance.contracts.SuperfluidPool.implementation");
+ }
+
+ function getIndex() external view returns (PoolIndexData memory) {
+ return _index;
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function getTotalUnits() external view override returns (uint128) {
+ return _getTotalUnits();
+ }
+
+ function _getTotalUnits() internal view returns (uint128) {
+ return _index.totalUnits;
+ }
+
+ /// @inheritdoc IERC20
+ function allowance(address owner, address spender) external view override returns (uint256) {
+ return _allowances[owner][spender];
+ }
+
+ /// @inheritdoc IERC20
+ function approve(address spender, uint256 amount) external override returns (bool) {
+ _approve(msg.sender, spender, amount);
+ return true;
+ }
+ /// @inheritdoc ISuperfluidPool
+
+ function increaseAllowance(address spender, uint256 addedValue) external returns (bool) {
+ _approve(msg.sender, spender, _allowances[msg.sender][spender] + addedValue);
+ return true;
+ }
+ /// @inheritdoc ISuperfluidPool
+
+ function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool) {
+ _approve(msg.sender, spender, _allowances[msg.sender][spender] - subtractedValue);
+ return true;
+ }
+
+ function _approve(address owner, address spender, uint256 amount) internal {
+ _allowances[owner][spender] = amount;
+
+ emit Approval(owner, spender, amount);
+ }
+
+ /// @dev Transfers `amount` units from `msg.sender` to `to`
+ function transfer(address to, uint256 amount) external override returns (bool) {
+ _transfer(msg.sender, to, amount);
+
+ return true;
+ }
+
+ /// @dev Transfers `amount` units from `from` to `to`
+ function transferFrom(address from, address to, uint256 amount) external override returns (bool) {
+ uint256 allowed = _allowances[from][msg.sender];
+
+ // if allowed - amount is negative, this reverts due to overflow
+ if (allowed != type(uint256).max) _allowances[from][msg.sender] = allowed - amount;
+
+ _transfer(from, to, amount);
+
+ return true;
+ }
+
+ function _transfer(address from, address to, uint256 amount) internal {
+ if (!transferabilityForUnitsOwner) revert SUPERFLUID_POOL_TRANSFER_UNITS_NOT_ALLOWED();
+
+ uint128 fromUnitsBefore = _getUnits(from);
+ uint128 toUnitsBefore = _getUnits(to);
+ _updateMemberUnits(from, fromUnitsBefore - amount.toUint128());
+ _updateMemberUnits(to, toUnitsBefore + amount.toUint128());
+ // assert that the units are updated correctly for from and for to.
+ emit Transfer(from, to, amount);
+ }
+
+ /// @notice Returns the total number of units for a pool
+ function totalSupply() external view override returns (uint256) {
+ return _getTotalUnits();
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function getTotalConnectedUnits() external view override returns (uint128) {
+ return _index.totalUnits - _disconnectedMembers.ownedUnits;
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function getTotalDisconnectedUnits() external view override returns (uint128) {
+ return _disconnectedMembers.ownedUnits;
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function getUnits(address memberAddr) external view override returns (uint128) {
+ return _getUnits(memberAddr);
+ }
+
+ function _getUnits(address memberAddr) internal view returns (uint128) {
+ return _membersData[memberAddr].ownedUnits;
+ }
+
+ /// @notice Returns the total number of units for an account for this pool
+ /// @dev Although the type is uint256, this can never be greater than type(int128).max
+ /// because the custom user type Unit is int128 in the SemanticMoney library
+ /// @param account The account to query
+ /// @return The total number of owned units of the account
+ function balanceOf(address account) external view override returns (uint256) {
+ return uint256(_getUnits(account));
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function getTotalFlowRate() external view override returns (int96) {
+ return _getTotalFlowRate();
+ }
+
+ function _getTotalFlowRate() internal view returns (int96) {
+ return (_index.wrappedFlowRate * uint256(_index.totalUnits).toInt256()).toInt96();
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function getTotalConnectedFlowRate() external view override returns (int96) {
+ return _getTotalFlowRate() - _getTotalDisconnectedFlowRate();
+ }
+
+ function _getTotalDisconnectedFlowRate() internal view returns (int96 flowRate) {
+ PDPoolIndex memory pdPoolIndex = poolIndexDataToPDPoolIndex(_index);
+ PDPoolMember memory disconnectedMembers = _memberDataToPDPoolMember(_disconnectedMembers);
+
+ return int256(FlowRate.unwrap(pdPoolIndex.flow_rate_per_unit().mul(disconnectedMembers.owned_units))).toInt96();
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function getTotalDisconnectedFlowRate() external view override returns (int96 flowRate) {
+ return _getTotalDisconnectedFlowRate();
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function getDisconnectedBalance(uint32 time) external view override returns (int256 balance) {
+ PDPoolIndex memory pdPoolIndex = poolIndexDataToPDPoolIndex(_index);
+ PDPoolMember memory pdPoolMember = _memberDataToPDPoolMember(_disconnectedMembers);
+ return Value.unwrap(PDPoolMemberMU(pdPoolIndex, pdPoolMember).rtb(Time.wrap(time)));
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function getMemberFlowRate(address memberAddr) external view override returns (int96) {
+ uint128 units = _getUnits(memberAddr);
+ if (units == 0) return 0;
+ // @note total units must never exceed type(int96).max
+ else return (_index.wrappedFlowRate * uint256(units).toInt256()).toInt96();
+ }
+
+ function _poolIndexDataToWrappedParticle(PoolIndexData memory data)
+ internal
+ pure
+ returns (BasicParticle memory wrappedParticle)
+ {
+ wrappedParticle = BasicParticle({
+ _settled_at: Time.wrap(data.wrappedSettledAt),
+ _flow_rate: FlowRate.wrap(int128(data.wrappedFlowRate)), // upcast from int96 is safe
+ _settled_value: Value.wrap(data.wrappedSettledValue)
+ });
+ }
+
+ function poolIndexDataToPDPoolIndex(PoolIndexData memory data)
+ public
+ pure
+ returns (PDPoolIndex memory pdPoolIndex)
+ {
+ pdPoolIndex = PDPoolIndex({
+ total_units: _toSemanticMoneyUnit(data.totalUnits),
+ _wrapped_particle: _poolIndexDataToWrappedParticle(data)
+ });
+ }
+
+ function _pdPoolIndexToPoolIndexData(PDPoolIndex memory pdPoolIndex)
+ internal
+ pure
+ returns (PoolIndexData memory data)
+ {
+ data = PoolIndexData({
+ totalUnits: int256(Unit.unwrap(pdPoolIndex.total_units)).toUint256().toUint128(),
+ wrappedSettledAt: Time.unwrap(pdPoolIndex.settled_at()),
+ wrappedFlowRate: int256(FlowRate.unwrap(pdPoolIndex.flow_rate_per_unit())).toInt96(),
+ wrappedSettledValue: Value.unwrap(pdPoolIndex._wrapped_particle._settled_value)
+ });
+ }
+
+ function _memberDataToPDPoolMember(MemberData memory memberData)
+ internal
+ pure
+ returns (PDPoolMember memory pdPoolMember)
+ {
+ pdPoolMember = PDPoolMember({
+ owned_units: _toSemanticMoneyUnit(memberData.ownedUnits),
+ _synced_particle: BasicParticle({
+ _settled_at: Time.wrap(memberData.syncedSettledAt),
+ _flow_rate: FlowRate.wrap(int128(memberData.syncedFlowRate)), // upcast from int96 is safe
+ _settled_value: Value.wrap(memberData.syncedSettledValue)
+ }),
+ _settled_value: Value.wrap(memberData.settledValue)
+ });
+ }
+
+ function _toSemanticMoneyUnit(uint128 units) internal pure returns (Unit) {
+ // @note safe upcasting from uint128 to uint256
+ // and use of safecast library for downcasting from uint256 to int128
+ return Unit.wrap(uint256(units).toInt256().toInt128());
+ }
+
+ function _pdPoolMemberToMemberData(PDPoolMember memory pdPoolMember, int256 claimedValue)
+ internal
+ pure
+ returns (MemberData memory memberData)
+ {
+ memberData = MemberData({
+ ownedUnits: uint256(int256(Unit.unwrap(pdPoolMember.owned_units))).toUint128(),
+ syncedSettledAt: Time.unwrap(pdPoolMember._synced_particle._settled_at),
+ syncedFlowRate: int256(FlowRate.unwrap(pdPoolMember._synced_particle._flow_rate)).toInt96(),
+ syncedSettledValue: Value.unwrap(pdPoolMember._synced_particle._settled_value),
+ settledValue: Value.unwrap(pdPoolMember._settled_value),
+ claimedValue: claimedValue
+ });
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function getClaimableNow(address memberAddr)
+ external
+ view
+ override
+ returns (int256 claimableBalance, uint256 timestamp)
+ {
+ timestamp = ISuperfluid(superToken.getHost()).getNow();
+ return (getClaimable(memberAddr, uint32(timestamp)), timestamp);
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function getClaimable(address memberAddr, uint32 time) public view override returns (int256) {
+ Time t = Time.wrap(time);
+ PDPoolIndex memory pdPoolIndex = poolIndexDataToPDPoolIndex(_index);
+ PDPoolMember memory pdPoolMember = _memberDataToPDPoolMember(_membersData[memberAddr]);
+ return Value.unwrap(
+ PDPoolMemberMU(pdPoolIndex, pdPoolMember).rtb(t) - Value.wrap(_membersData[memberAddr].claimedValue)
+ );
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function updateMemberUnits(address memberAddr, uint128 newUnits) external returns (bool) {
+ if (msg.sender != admin && msg.sender != address(GDA)) revert SUPERFLUID_POOL_NOT_POOL_ADMIN_OR_GDA();
+
+ _updateMemberUnits(memberAddr, newUnits);
+
+ return true;
+ }
+
+ /**
+ * @notice Checks whether or not the NFT hook can be called.
+ * @dev A staticcall, so `POOL_MEMBER_NFT` must be a view otherwise the assumption is that it reverts
+ * @param token the super token that is being streamed
+ * @return poolMemberNFT the address returned by low level call
+ */
+ function _canCallNFTHook(ISuperfluidToken token) internal view returns (address poolMemberNFT) {
+ // solhint-disable-next-line avoid-low-level-calls
+ (bool success, bytes memory data) =
+ address(token).staticcall(abi.encodeWithSelector(ISuperToken.POOL_MEMBER_NFT.selector));
+
+ if (success) {
+ // @note We are aware this may revert if a Custom SuperToken's
+ // POOL_MEMBER_NFT does not return data that can be
+ // decoded to an address. This would mean it was intentionally
+ // done by the creator of the Custom SuperToken logic and is
+ // fully expected to revert in that case as the author desired.
+ poolMemberNFT = abi.decode(data, (address));
+ }
+ }
+
+ function _handlePoolMemberNFT(address memberAddr, uint128 newUnits) internal {
+ // Pool Member NFT Logic
+ IPoolMemberNFT poolMemberNFT = IPoolMemberNFT(_canCallNFTHook(superToken));
+ if (address(poolMemberNFT) != address(0)) {
+ uint256 tokenId = poolMemberNFT.getTokenId(address(this), memberAddr);
+ uint256 gasLeftBefore;
+ if (newUnits == 0) {
+ if (poolMemberNFT.poolMemberDataByTokenId(tokenId).member != address(0)) {
+ gasLeftBefore = gasleft();
+ // solhint-disable-next-line no-empty-blocks
+ try poolMemberNFT.onDelete(address(this), memberAddr) { }
+ catch {
+ SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
+ }
+ }
+ } else {
+ // if not minted, we mint a new pool member nft
+ if (poolMemberNFT.poolMemberDataByTokenId(tokenId).member == address(0)) {
+ gasLeftBefore = gasleft();
+ // solhint-disable-next-line no-empty-blocks
+ try poolMemberNFT.onCreate(address(this), memberAddr) { }
+ catch {
+ SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
+ }
+ // if minted, we update the pool member nft
+ } else {
+ gasLeftBefore = gasleft();
+ // solhint-disable-next-line no-empty-blocks
+ try poolMemberNFT.onUpdate(address(this), memberAddr) { }
+ catch {
+ SafeGasLibrary._revertWhenOutOfGas(gasLeftBefore);
+ }
+ }
+ }
+ }
+ }
+
+ function _updateMemberUnits(address memberAddr, uint128 newUnits) internal returns (bool) {
+ // @note normally we keep the sanitization in the external functions, but here
+ // this is used in both updateMemberUnits and transfer
+ if (GDA.isPool(superToken, memberAddr)) revert SUPERFLUID_POOL_NO_POOL_MEMBERS();
+ if (memberAddr == address(0)) revert SUPERFLUID_POOL_NO_ZERO_ADDRESS();
+
+ uint32 time = uint32(ISuperfluid(superToken.getHost()).getNow());
+ Time t = Time.wrap(time);
+ Unit wrappedUnits = _toSemanticMoneyUnit(newUnits);
+
+ PDPoolIndex memory pdPoolIndex = poolIndexDataToPDPoolIndex(_index);
+ MemberData memory memberData = _membersData[memberAddr];
+ PDPoolMember memory pdPoolMember = _memberDataToPDPoolMember(memberData);
+
+ uint128 oldUnits = memberData.ownedUnits;
+
+ PDPoolMemberMU memory mu = PDPoolMemberMU(pdPoolIndex, pdPoolMember);
+
+ // update pool's disconnected units
+ if (!GDA.isMemberConnected(ISuperfluidPool(address(this)), memberAddr)) {
+ // trigger the side effect of claiming all if not connected
+ // @note claiming is a bit surprising here given the function name
+ int256 claimedAmount = _claimAll(memberAddr, time);
+
+ // update pool's disconnected units
+ _shiftDisconnectedUnits(wrappedUnits - mu.m.owned_units, Value.wrap(claimedAmount), t);
+ }
+
+ // update pool member's units
+ {
+ BasicParticle memory p;
+ (pdPoolIndex, pdPoolMember, p) = mu.pool_member_update(p, wrappedUnits, t);
+ _index = _pdPoolIndexToPoolIndexData(pdPoolIndex);
+ int256 claimedValue = _membersData[memberAddr].claimedValue;
+ _membersData[memberAddr] = _pdPoolMemberToMemberData(pdPoolMember, claimedValue);
+ assert(GDA.appendIndexUpdateByPool(superToken, p, t));
+ }
+ emit MemberUnitsUpdated(superToken, memberAddr, oldUnits, newUnits);
+
+ _handlePoolMemberNFT(memberAddr, newUnits);
+
+ return true;
+ }
+
+ function _claimAll(address memberAddr, uint32 time) internal returns (int256 amount) {
+ amount = getClaimable(memberAddr, time);
+ assert(GDA.poolSettleClaim(superToken, memberAddr, (amount)));
+ _membersData[memberAddr].claimedValue += amount;
+
+ emit DistributionClaimed(superToken, memberAddr, amount, _membersData[memberAddr].claimedValue);
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function claimAll() external returns (bool) {
+ return claimAll(msg.sender);
+ }
+
+ /// @inheritdoc ISuperfluidPool
+ function claimAll(address memberAddr) public returns (bool) {
+ bool isConnected = GDA.isMemberConnected(ISuperfluidPool(address(this)), memberAddr);
+ uint32 time = uint32(ISuperfluid(superToken.getHost()).getNow());
+ int256 claimedAmount = _claimAll(memberAddr, time);
+ if (!isConnected) {
+ _shiftDisconnectedUnits(Unit.wrap(0), Value.wrap(claimedAmount), Time.wrap(time));
+ }
+
+ return true;
+ }
+
+ function operatorSetIndex(PDPoolIndex calldata index) external onlyGDA returns (bool) {
+ _index = _pdPoolIndexToPoolIndexData(index);
+
+ return true;
+ }
+
+ // WARNING for operators: it is undefined behavior if member is already connected or disconnected
+ function operatorConnectMember(address memberAddr, bool doConnect, uint32 time) external onlyGDA returns (bool) {
+ int256 claimedAmount = _claimAll(memberAddr, time);
+ int128 units = uint256(_getUnits(memberAddr)).toInt256().toInt128();
+ if (doConnect) {
+ _shiftDisconnectedUnits(Unit.wrap(-units), Value.wrap(claimedAmount), Time.wrap(time));
+ } else {
+ _shiftDisconnectedUnits(Unit.wrap(units), Value.wrap(0), Time.wrap(time));
+ }
+ return true;
+ }
+
+ function _shiftDisconnectedUnits(Unit shiftUnits, Value claimedAmount, Time t) internal {
+ PDPoolIndex memory pdPoolIndex = poolIndexDataToPDPoolIndex(_index);
+ PDPoolMember memory disconnectedMembers = _memberDataToPDPoolMember(_disconnectedMembers);
+ PDPoolMemberMU memory mu = PDPoolMemberMU(pdPoolIndex, disconnectedMembers);
+ mu = mu.settle(t);
+ mu.m.owned_units = mu.m.owned_units + shiftUnits;
+ // offset the claimed amount from the settled value if any
+ mu.m._settled_value = mu.m._settled_value - claimedAmount;
+ _disconnectedMembers = _pdPoolMemberToMemberData(mu.m, 0);
+ }
+
+ modifier onlyGDA() {
+ if (msg.sender != address(GDA)) revert SUPERFLUID_POOL_NOT_GDA();
+ _;
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol
new file mode 100644
index 0000000000..c3699a2c41
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
+import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol";
+import { SuperfluidPool } from "./SuperfluidPool.sol";
+import { PoolConfig } from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
+
+library SuperfluidPoolDeployerLibrary {
+ function deploy(
+ address beacon,
+ address admin,
+ ISuperfluidToken token,
+ PoolConfig memory config
+ ) external returns (SuperfluidPool pool) {
+ bytes memory initializeCallData = abi.encodeWithSelector(
+ SuperfluidPool.initialize.selector,
+ admin,
+ token,
+ config.transferabilityForUnitsOwner,
+ config.distributionFromAnyAddress
+ );
+ BeaconProxy superfluidPoolBeaconProxy = new BeaconProxy(
+ beacon,
+ initializeCallData
+ );
+ pool = SuperfluidPool(address(superfluidPoolBeaconProxy));
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol
index b749b8046d..df79a017ad 100644
--- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol
+++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol
@@ -8,6 +8,12 @@ import {
IInstantDistributionAgreementV1
} from "../interfaces/superfluid/ISuperfluid.sol";
+import {
+ IGeneralDistributionAgreementV1,
+ ISuperfluidPool,
+ PoolConfig
+} from "../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
+
/**
* @title Library for Token Centric Interface
* @author Superfluid
@@ -819,18 +825,47 @@ library SuperTokenV1Library {
}
/**
- * @dev get net flow rate for given account for given token
+ * @dev get net flow rate for given account for given token (CFA + GDA)
* @param token Super token address
* @param account Account to query
* @return flowRate The net flow rate of the account
*/
function getNetFlowRate(ISuperToken token, address account)
internal view returns (int96 flowRate)
+ {
+ (, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token);
+ (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token);
+ int96 cfaNetFlow = cfa.getNetFlow(token, account);
+ int96 gdaNetFlow = gda.getNetFlow(token, account);
+ return cfaNetFlow + gdaNetFlow;
+ }
+
+ /**
+ * @dev get CFA net flow rate for given account for given token
+ * @param token Super token address
+ * @param account Account to query
+ * @return flowRate The net flow rate of the account
+ */
+ function getCFANetFlowRate(ISuperToken token, address account)
+ internal view returns (int96 flowRate)
{
(, IConstantFlowAgreementV1 cfa) = _getHostAndCFA(token);
return cfa.getNetFlow(token, account);
}
+ /**
+ * @dev get GDA net flow rate for given account for given token
+ * @param token Super token address
+ * @param account Account to query
+ * @return flowRate The net flow rate of the account
+ */
+ function getGDANetFlowRate(ISuperToken token, address account)
+ internal view returns (int96 flowRate)
+ {
+ (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token);
+ return gda.getNetFlow(token, account);
+ }
+
/**
* @dev get the aggregated flow info of the account
* @param token Super token address
@@ -988,6 +1023,41 @@ library SuperTokenV1Library {
return ida.getSubscriptionByID(token, agreementId);
}
+ /** GDA VIEW FUNCTIONS ************************************* */
+ function getFlowDistributionFlowRate(ISuperToken token, address from, ISuperfluidPool to)
+ internal
+ view
+ returns (int96)
+ {
+ (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token);
+ return gda.getFlowRate(token, from, to);
+ }
+
+ function estimateFlowDistributionActualFlowRate(
+ ISuperToken token,
+ address from,
+ ISuperfluidPool to,
+ int96 requestedFlowRate
+ ) internal view returns (int96 actualFlowRate, int96 totalDistributionFlowRate) {
+ (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token);
+ return gda.estimateFlowDistributionActualFlowRate(token, from, to, requestedFlowRate);
+ }
+
+ function estimateDistributionActualAmount(
+ ISuperToken token,
+ address from,
+ ISuperfluidPool to,
+ uint256 requestedAmount
+ ) internal view returns (uint256 actualAmount) {
+ (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token);
+ return gda.estimateDistributionActualAmount(token, from, to, requestedAmount);
+ }
+
+ function isMemberConnected(ISuperToken token, address pool, address member) internal view returns (bool) {
+ (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token);
+ return gda.isMemberConnected(ISuperfluidPool(pool), member);
+ }
+
/** IDA BASE FUNCTIONS ************************************* */
@@ -1636,6 +1706,250 @@ library SuperTokenV1Library {
);
}
+ /** GDA BASE FUNCTIONS ************************************* */
+
+ function createPool(ISuperToken token, address admin, PoolConfig memory poolConfig)
+ internal
+ returns (ISuperfluidPool pool)
+ {
+ (, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ pool = gda.createPool(token, admin, poolConfig);
+ }
+
+ function updateMemberUnits(ISuperToken token, ISuperfluidPool pool, address memberAddress, uint128 newUnits)
+ internal
+ returns (bool)
+ {
+ return updateMemberUnits(token, pool, memberAddress, newUnits, new bytes(0));
+ }
+
+ function updateMemberUnits(
+ ISuperToken token,
+ ISuperfluidPool pool,
+ address memberAddress,
+ uint128 newUnits,
+ bytes memory userData
+ ) internal returns (bool) {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ host.callAgreement(
+ gda, abi.encodeCall(gda.updateMemberUnits, (pool, memberAddress, newUnits, new bytes(0))), userData
+ );
+
+ return true;
+ }
+
+ function claimAll(ISuperToken token, ISuperfluidPool pool, address memberAddress) internal returns (bool) {
+ return claimAll(token, pool, memberAddress, new bytes(0));
+ }
+
+ function claimAll(ISuperToken token, ISuperfluidPool pool, address memberAddress, bytes memory userData)
+ internal
+ returns (bool)
+ {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ host.callAgreement(gda, abi.encodeCall(gda.claimAll, (pool, memberAddress, new bytes(0))), userData);
+
+ return true;
+ }
+
+ function connectPool(ISuperToken token, ISuperfluidPool pool) internal returns (bool) {
+ return connectPool(token, pool, new bytes(0));
+ }
+
+ function connectPool(ISuperToken token, ISuperfluidPool pool, bytes memory userData) internal returns (bool) {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ host.callAgreement(gda, abi.encodeCall(gda.connectPool, (pool, new bytes(0))), userData);
+
+ return true;
+ }
+
+ function disconnectPool(ISuperToken token, ISuperfluidPool pool) internal returns (bool) {
+ return disconnectPool(token, pool, new bytes(0));
+ }
+
+ function disconnectPool(ISuperToken token, ISuperfluidPool pool, bytes memory userData) internal returns (bool) {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ host.callAgreement(gda, abi.encodeCall(gda.disconnectPool, (pool, new bytes(0))), userData);
+ return true;
+ }
+
+ // @note we already have a distribute function from IDA, do we want this too? do we want to differentiate this?
+ function distributeToPool(ISuperToken token, address from, ISuperfluidPool pool, uint256 requestedAmount)
+ internal
+ returns (bool)
+ {
+ return distribute(token, from, pool, requestedAmount, new bytes(0));
+ }
+
+ function distribute(
+ ISuperToken token,
+ address from,
+ ISuperfluidPool pool,
+ uint256 requestedAmount,
+ bytes memory userData
+ ) internal returns (bool) {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ host.callAgreement(
+ gda, abi.encodeCall(gda.distribute, (token, from, pool, requestedAmount, new bytes(0))), userData
+ );
+ return true;
+ }
+
+ function distributeFlow(ISuperToken token, address from, ISuperfluidPool pool, int96 requestedFlowRate)
+ internal
+ returns (bool)
+ {
+ return distributeFlow(token, from, pool, requestedFlowRate, new bytes(0));
+ }
+
+ function distributeFlow(
+ ISuperToken token,
+ address from,
+ ISuperfluidPool pool,
+ int96 requestedFlowRate,
+ bytes memory userData
+ ) internal returns (bool) {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ host.callAgreement(
+ gda, abi.encodeCall(gda.distributeFlow, (token, from, pool, requestedFlowRate, new bytes(0))), userData
+ );
+ return true;
+ }
+
+ /** GDA WITH CTX FUNCTIONS ************************************* */
+
+ function updateMemberUnitsWithCtx(
+ ISuperToken token,
+ ISuperfluidPool pool,
+ address memberAddress,
+ uint128 newUnits,
+ bytes memory ctx
+ ) internal returns (bytes memory newCtx) {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ (newCtx,) = host.callAgreementWithContext(
+ gda,
+ abi.encodeCall(
+ gda.updateMemberUnits,
+ (
+ pool,
+ memberAddress,
+ newUnits,
+ new bytes(0) // ctx placeholder
+ )
+ ),
+ "0x",
+ ctx
+ );
+ }
+
+ function claimAllWithCtx(ISuperToken token, ISuperfluidPool pool, address memberAddress, bytes memory ctx)
+ internal
+ returns (bytes memory newCtx)
+ {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ (newCtx,) = host.callAgreementWithContext(
+ gda,
+ abi.encodeCall(
+ gda.claimAll,
+ (
+ pool,
+ memberAddress,
+ new bytes(0) // ctx placeholder
+ )
+ ),
+ "0x",
+ ctx
+ );
+ }
+
+ function connectPoolWithCtx(ISuperToken token, ISuperfluidPool pool, bytes memory ctx)
+ internal
+ returns (bytes memory newCtx)
+ {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ (newCtx,) = host.callAgreementWithContext(
+ gda,
+ abi.encodeCall(
+ gda.connectPool,
+ (
+ pool,
+ new bytes(0) // ctx placeholder
+ )
+ ),
+ "0x",
+ ctx
+ );
+ }
+
+ function disconnectPoolWithCtx(ISuperToken token, ISuperfluidPool pool, bytes memory ctx)
+ internal
+ returns (bytes memory newCtx)
+ {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ (newCtx,) = host.callAgreementWithContext(
+ gda,
+ abi.encodeCall(
+ gda.disconnectPool,
+ (
+ pool,
+ new bytes(0) // ctx placeholder
+ )
+ ),
+ "0x",
+ ctx
+ );
+ }
+
+ function distributeWithCtx(
+ ISuperToken token,
+ ISuperfluidPool pool,
+ address from,
+ uint256 requestedAmount,
+ bytes memory ctx
+ ) internal returns (bytes memory newCtx) {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ (newCtx,) = host.callAgreementWithContext(
+ gda,
+ abi.encodeCall(
+ gda.distribute,
+ (
+ token,
+ from,
+ pool,
+ requestedAmount,
+ new bytes(0) // ctx placeholder
+ )
+ ),
+ "0x",
+ ctx
+ );
+ }
+
+ function distributeFlowWithCtx(
+ ISuperToken token,
+ address from,
+ ISuperfluidPool pool,
+ int96 requestedFlowRate,
+ bytes memory ctx
+ ) internal returns (bytes memory newCtx) {
+ (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token);
+ (newCtx,) = host.callAgreementWithContext(
+ gda,
+ abi.encodeCall(
+ gda.distributeFlow,
+ (
+ token,
+ from,
+ pool,
+ requestedFlowRate,
+ new bytes(0) // ctx placeholder
+ )
+ ),
+ "0x",
+ ctx
+ );
+ }
+
// ************** private helpers **************
// @note We must use hardcoded constants here because:
@@ -1646,14 +1960,18 @@ library SuperTokenV1Library {
bytes32 private constant _CFA_SLOT = 0xb969d79d88acd02d04ed7ee7d43b949e7daf093d363abcfbbc43dfdfd1ce969a;
// keccak256("org.superfluid-finance.apps.SuperTokenLibrary.v1.ida");
bytes32 private constant _IDA_SLOT = 0xa832ee1924ea960211af2df07d65d166232018f613ac6708043cd8f8773eddeb;
+ // keccak256("org.superfluid-finance.apps.SuperTokenLibrary.v1.gda");
+ bytes32 private constant _GDA_SLOT = 0xc36f6c05164a669ecb6da53e218d77ae44d51cfc99f91e5a125a18de0949bee4;
// gets the host and cfa addrs for the token and caches it in storage for gas efficiency
// to be used in state changing methods
- function _getAndCacheHostAndCFA(ISuperToken token) private
- returns(ISuperfluid host, IConstantFlowAgreementV1 cfa)
+ function _getAndCacheHostAndCFA(ISuperToken token)
+ private
+ returns (ISuperfluid host, IConstantFlowAgreementV1 cfa)
{
// check if already in contract storage...
- assembly { // solium-disable-line
+ assembly {
+ // solium-disable-line
host := sload(_HOST_SLOT)
cfa := sload(_CFA_SLOT)
}
@@ -1662,11 +1980,12 @@ library SuperTokenV1Library {
if (address(host) == address(0)) {
host = ISuperfluid(token.getHost());
}
+
cfa = IConstantFlowAgreementV1(address(ISuperfluid(host).getAgreementClass(
keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1"))));
// now that we got them and are in a transaction context, persist in storage
assembly {
- // solium-disable-line
+ // solium-disable-line
sstore(_HOST_SLOT, host)
sstore(_CFA_SLOT, cfa)
}
@@ -1677,11 +1996,13 @@ library SuperTokenV1Library {
// gets the host and ida addrs for the token and caches it in storage for gas efficiency
// to be used in state changing methods
- function _getAndCacheHostAndIDA(ISuperToken token) private
- returns(ISuperfluid host, IInstantDistributionAgreementV1 ida)
+ function _getAndCacheHostAndIDA(ISuperToken token)
+ private
+ returns (ISuperfluid host, IInstantDistributionAgreementV1 ida)
{
// check if already in contract storage...
- assembly { // solium-disable-line
+ assembly {
+ // solium-disable-line
host := sload(_HOST_SLOT)
ida := sload(_IDA_SLOT)
}
@@ -1694,7 +2015,7 @@ library SuperTokenV1Library {
keccak256("org.superfluid-finance.agreements.InstantDistributionAgreement.v1"))));
// now that we got them and are in a transaction context, persist in storage
assembly {
- // solium-disable-line
+ // solium-disable-line
sstore(_HOST_SLOT, host)
sstore(_IDA_SLOT, ida)
}
@@ -1703,13 +2024,47 @@ library SuperTokenV1Library {
assert(address(ida) != address(0));
}
+ // gets the host and gda addrs for the token and caches it in storage for gas efficiency
+ // to be used in state changing methods
+ function _getAndCacheHostAndGDA(ISuperToken token)
+ private
+ returns (ISuperfluid host, IGeneralDistributionAgreementV1 gda)
+ {
+ // check if already in contract storage...
+ assembly {
+ // solium-disable-line
+ host := sload(_HOST_SLOT)
+ gda := sload(_GDA_SLOT)
+ }
+ if (address(gda) == address(0)) {
+ // framework contract addrs not yet cached, retrieving now...
+ if (address(host) == address(0)) {
+ host = ISuperfluid(token.getHost());
+ }
+ gda = IGeneralDistributionAgreementV1(
+ address(
+ ISuperfluid(host).getAgreementClass(
+ keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1")
+ )
+ )
+ );
+ // now that we got them and are in a transaction context, persist in storage
+ assembly {
+ // solium-disable-line
+ sstore(_HOST_SLOT, host)
+ sstore(_GDA_SLOT, gda)
+ }
+ }
+ assert(address(host) != address(0));
+ assert(address(gda) != address(0));
+ }
+
// gets the host and cfa addrs for the token
// to be used in non-state changing methods (view functions)
- function _getHostAndCFA(ISuperToken token) private view
- returns(ISuperfluid host, IConstantFlowAgreementV1 cfa)
- {
+ function _getHostAndCFA(ISuperToken token) private view returns (ISuperfluid host, IConstantFlowAgreementV1 cfa) {
// check if already in contract storage...
- assembly { // solium-disable-line
+ assembly {
+ // solium-disable-line
host := sload(_HOST_SLOT)
cfa := sload(_CFA_SLOT)
}
@@ -1727,11 +2082,14 @@ library SuperTokenV1Library {
// gets the host and ida addrs for the token
// to be used in non-state changing methods (view functions)
- function _getHostAndIDA(ISuperToken token) private view
- returns(ISuperfluid host, IInstantDistributionAgreementV1 ida)
+ function _getHostAndIDA(ISuperToken token)
+ private
+ view
+ returns (ISuperfluid host, IInstantDistributionAgreementV1 ida)
{
// check if already in contract storage...
- assembly { // solium-disable-line
+ assembly {
+ // solium-disable-line
host := sload(_HOST_SLOT)
ida := sload(_IDA_SLOT)
}
@@ -1746,4 +2104,34 @@ library SuperTokenV1Library {
assert(address(host) != address(0));
assert(address(ida) != address(0));
}
+
+ // gets the host and gda addrs for the token
+ // to be used in non-state changing methods (view functions)
+ function _getHostAndGDA(ISuperToken token)
+ private
+ view
+ returns (ISuperfluid host, IGeneralDistributionAgreementV1 gda)
+ {
+ // check if already in contract storage...
+ assembly {
+ // solium-disable-line
+ host := sload(_HOST_SLOT)
+ gda := sload(_GDA_SLOT)
+ }
+ if (address(gda) == address(0)) {
+ // framework contract addrs not yet cached in storage, retrieving now...
+ if (address(host) == address(0)) {
+ host = ISuperfluid(token.getHost());
+ }
+ gda = IGeneralDistributionAgreementV1(
+ address(
+ ISuperfluid(host).getAgreementClass(
+ keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1")
+ )
+ )
+ );
+ }
+ assert(address(host) != address(0));
+ assert(address(gda) != address(0));
+ }
}
diff --git a/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol b/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol
index 3ed7e16750..4473fe2f18 100644
--- a/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol
+++ b/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol
@@ -64,7 +64,8 @@ abstract contract SuperfluidGovernanceBase is ISuperfluidGovernance
ISuperfluid host,
address hostNewLogic,
address[] calldata agreementClassNewLogics,
- address superTokenFactoryNewLogic
+ address superTokenFactoryNewLogic,
+ address poolBeaconNewLogic
)
external override
onlyAuthorized(host)
@@ -87,6 +88,9 @@ abstract contract SuperfluidGovernanceBase is ISuperfluidGovernance
// solhint-disable-next-line no-empty-blocks
catch {}
}
+ if (poolBeaconNewLogic != address(0)) {
+ host.updatePoolBeaconLogic(poolBeaconNewLogic);
+ }
}
function batchUpdateSuperTokenLogic(
diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol
new file mode 100644
index 0000000000..5b4e1fdb3b
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity >=0.8.4;
+
+import { ISuperAgreement } from "../../superfluid/ISuperAgreement.sol";
+import { ISuperfluidToken } from "../../superfluid/ISuperfluidToken.sol";
+import { ISuperfluidPool } from "../../agreements/gdav1/ISuperfluidPool.sol";
+
+struct PoolConfig {
+ /// @dev if true, the pool members can transfer their owned units
+ /// else, only the pool admin can manipulate the units for pool members
+ bool transferabilityForUnitsOwner;
+ /// @dev if true, anyone can execute distributions via the pool
+ /// else, only the pool admin can execute distributions via the pool
+ bool distributionFromAnyAddress;
+}
+
+/**
+ * @title General Distribution Agreement interface
+ * @author Superfluid
+ */
+abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement {
+ // Structs
+ struct UniversalIndexData {
+ int96 flowRate;
+ uint32 settledAt;
+ uint256 totalBuffer;
+ bool isPool;
+ int256 settledValue;
+ }
+
+ struct FlowDistributionData {
+ uint32 lastUpdated;
+ int96 flowRate;
+ uint256 buffer; // stored as uint96
+ }
+
+ struct PoolMemberData {
+ address pool;
+ uint32 poolID; // the slot id in the pool's subs bitmap
+ }
+
+ struct StackVarsLiquidation {
+ ISuperfluidToken token;
+ int256 availableBalance;
+ address sender;
+ bytes32 distributionFlowHash;
+ int256 signedTotalGDADeposit;
+ address liquidator;
+ }
+
+
+ // Custom Errors
+ error GDA_DISTRIBUTE_FOR_OTHERS_NOT_ALLOWED(); // 0xf67d263e
+ error GDA_DISTRIBUTE_FROM_ANY_ADDRESS_NOT_ALLOWED(); // 0x7761a5e5
+ error GDA_FLOW_DOES_NOT_EXIST(); // 0x29f4697e
+ error GDA_NON_CRITICAL_SENDER(); // 0x666f381d
+ error GDA_INSUFFICIENT_BALANCE(); // 0x33115c3f
+ error GDA_NO_NEGATIVE_FLOW_RATE(); // 0x15f25663
+ error GDA_ADMIN_CANNOT_BE_POOL(); // 0x9ab88a26
+ error GDA_NOT_POOL_ADMIN(); // 0x3a87e565
+ error GDA_NO_ZERO_ADDRESS_ADMIN(); // 0x82c5d837
+ error GDA_ONLY_SUPER_TOKEN_POOL(); // 0x90028c37
+
+
+ // Events
+ event InstantDistributionUpdated(
+ ISuperfluidToken indexed token,
+ ISuperfluidPool indexed pool,
+ address indexed distributor,
+ address operator,
+ uint256 requestedAmount,
+ uint256 actualAmount,
+ bytes userData
+ );
+
+ event FlowDistributionUpdated(
+ ISuperfluidToken indexed token,
+ ISuperfluidPool indexed pool,
+ address indexed distributor,
+ // operator's have permission to liquidate critical flows
+ // on behalf of others
+ address operator,
+ int96 oldFlowRate,
+ int96 newDistributorToPoolFlowRate,
+ int96 newTotalDistributionFlowRate,
+ address adjustmentFlowRecipient,
+ int96 adjustmentFlowRate,
+ bytes userData
+ );
+
+ event PoolCreated(ISuperfluidToken indexed token, address indexed admin, ISuperfluidPool pool);
+
+ event PoolConnectionUpdated(
+ ISuperfluidToken indexed token,
+ ISuperfluidPool indexed pool,
+ address indexed account,
+ bool connected,
+ bytes userData
+ );
+
+ event BufferAdjusted(
+ ISuperfluidToken indexed token,
+ ISuperfluidPool indexed pool,
+ address indexed from,
+ int256 bufferDelta,
+ uint256 newBufferAmount,
+ uint256 totalBufferAmount
+ );
+
+ /// @dev ISuperAgreement.agreementType implementation
+ function agreementType() external pure override returns (bytes32) {
+ return keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1");
+ }
+
+ /// @dev Gets the GDA net flow rate of `account` for `token`.
+ /// @param token The token address
+ /// @param account The account address
+ /// @return net flow rate
+ function getNetFlow(ISuperfluidToken token, address account) external view virtual returns (int96);
+
+ /// @notice Gets the GDA flow rate of `from` to `to` for `token`.
+ /// @dev This is primarily used to get the flow distribution flow rate from a distributor to a pool or the
+ /// adjustment flow rate of a pool.
+ /// @param token The token address
+ /// @param from The sender address
+ /// @param to The receiver address (the pool)
+ /// @return flow rate
+ function getFlowRate(ISuperfluidToken token, address from, ISuperfluidPool to)
+ external
+ view
+ virtual
+ returns (int96);
+
+ /// @notice Executes an optimistic estimation of what the actual flow distribution flow rate may be.
+ /// The actual flow distribution flow rate is the flow rate that will be sent from `from`.
+ /// NOTE: this is only precise in an atomic transaction. DO NOT rely on this if querying off-chain.
+ /// @dev The difference between the requested flow rate and the actual flow rate is the adjustment flow rate,
+ /// this adjustment flow rate goes to the pool admin.
+ /// @param token The token address
+ /// @param from The sender address
+ /// @param to The pool address
+ /// @param requestedFlowRate The requested flow rate
+ /// @return actualFlowRate and totalDistributionFlowRate
+ function estimateFlowDistributionActualFlowRate(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool to,
+ int96 requestedFlowRate
+ ) external view virtual returns (int96 actualFlowRate, int96 totalDistributionFlowRate);
+
+ /// @notice Executes an optimistic estimation of what the actual amount distributed may be.
+ /// The actual amount distributed is the amount that will be sent from `from`.
+ /// NOTE: this is only precise in an atomic transaction. DO NOT rely on this if querying off-chain.
+ /// @dev The difference between the requested amount and the actual amount is the adjustment amount.
+ /// @param token The token address
+ /// @param from The sender address
+ /// @param to The pool address
+ /// @param requestedAmount The requested amount
+ /// @return actualAmount
+ function estimateDistributionActualAmount(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool to,
+ uint256 requestedAmount
+ ) external view virtual returns (uint256 actualAmount);
+
+ /// @notice Gets the adjustment flow rate of `pool` for `token`.
+ /// @param pool The pool address
+ /// @return adjustment flow rate
+ function getPoolAdjustmentFlowRate(address pool) external view virtual returns (int96);
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // Pool Operations
+ ////////////////////////////////////////////////////////////////////////////////
+
+ /// @notice Creates a new pool for `token` where the admin is `admin`.
+ /// @param token The token address
+ /// @param admin The admin of the pool
+ /// @param poolConfig The pool configuration (see PoolConfig struct)
+ function createPool(ISuperfluidToken token, address admin, PoolConfig memory poolConfig)
+ external
+ virtual
+ returns (ISuperfluidPool pool);
+
+ function updateMemberUnits(ISuperfluidPool pool, address memberAddress, uint128 newUnits, bytes calldata ctx)
+ external
+ virtual
+ returns (bytes memory newCtx);
+
+ function claimAll(ISuperfluidPool pool, address memberAddress, bytes calldata ctx)
+ external
+ virtual
+ returns (bytes memory newCtx);
+
+ /// @notice Connects `msg.sender` to `pool`.
+ /// @dev This is used to connect a pool to the GDA.
+ /// @param pool The pool address
+ /// @param ctx Context bytes (see ISuperfluid.sol for Context struct)
+ /// @return newCtx the new context bytes
+ function connectPool(ISuperfluidPool pool, bytes calldata ctx) external virtual returns (bytes memory newCtx);
+
+ /// @notice Disconnects `msg.sender` from `pool`.
+ /// @dev This is used to disconnect a pool from the GDA.
+ /// @param pool The pool address
+ /// @param ctx Context bytes (see ISuperfluidPoolAdmin for Context struct)
+ /// @return newCtx the new context bytes
+ function disconnectPool(ISuperfluidPool pool, bytes calldata ctx) external virtual returns (bytes memory newCtx);
+
+ /// @notice Checks whether `account` is a pool.
+ /// @param token The token address
+ /// @param account The account address
+ /// @return true if `account` is a pool
+ function isPool(ISuperfluidToken token, address account) external view virtual returns (bool);
+
+ /// Check if an address is connected to the pool
+ function isMemberConnected(ISuperfluidPool pool, address memberAddr) external view virtual returns (bool);
+
+ /// Get pool adjustment flow information: (recipient, flowHash, flowRate)
+ function getPoolAdjustmentFlowInfo(ISuperfluidPool pool) external view virtual returns (address, bytes32, int96);
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // Agreement Operations
+ ////////////////////////////////////////////////////////////////////////////////
+
+ /// @notice Tries to distribute `requestedAmount` of `token` from `from` to `pool`.
+ /// @dev NOTE: The actual amount distributed may differ.
+ /// @param token The token address
+ /// @param from The sender address
+ /// @param pool The pool address
+ /// @param requestedAmount The requested amount
+ /// @param ctx Context bytes (see ISuperfluidPool for Context struct)
+ /// @return newCtx the new context bytes
+ function distribute(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool pool,
+ uint256 requestedAmount,
+ bytes calldata ctx
+ ) external virtual returns (bytes memory newCtx);
+
+ /// @notice Tries to distributeFlow `requestedFlowRate` of `token` from `from` to `pool`.
+ /// @dev NOTE: The actual distribution flow rate may differ.
+ /// @param token The token address
+ /// @param from The sender address
+ /// @param pool The pool address
+ /// @param requestedFlowRate The requested flow rate
+ /// @param ctx Context bytes (see ISuperfluidPool for Context struct)
+ /// @return newCtx the new context bytes
+ function distributeFlow(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool pool,
+ int96 requestedFlowRate,
+ bytes calldata ctx
+ ) external virtual returns (bytes memory newCtx);
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // Solvency Functions
+ ////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @dev Returns whether it is the patrician period based on host.getNow()
+ * @param account The account we are interested in
+ * @return isCurrentlyPatricianPeriod Whether it is currently the patrician period dictated by governance
+ * @return timestamp The value of host.getNow()
+ */
+ function isPatricianPeriodNow(ISuperfluidToken token, address account)
+ external
+ view
+ virtual
+ returns (bool isCurrentlyPatricianPeriod, uint256 timestamp);
+
+ /**
+ * @dev Returns whether it is the patrician period based on timestamp
+ * @param account The account we are interested in
+ * @param timestamp The timestamp we are interested in observing the result of isPatricianPeriod
+ * @return bool Whether it is currently the patrician period dictated by governance
+ */
+ function isPatricianPeriod(ISuperfluidToken token, address account, uint256 timestamp)
+ public
+ view
+ virtual
+ returns (bool);
+}
diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolAdminNFT.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolAdminNFT.sol
new file mode 100644
index 0000000000..f9a760a037
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolAdminNFT.sol
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.8.11;
+
+import { IPoolNFTBase } from "./IPoolNFTBase.sol";
+
+interface IPoolAdminNFT is IPoolNFTBase {
+ // PoolAdminNFTData struct storage packing:
+ // b = bits
+ // WORD 1: | pool | FREE
+ // | 160b | 96b
+ // WORD 2: | admin | FREE
+ // | 160b | 96b
+ struct PoolAdminNFTData {
+ address pool;
+ address admin;
+ }
+
+ /// Write Functions ///
+ function mint(address pool) external;
+
+ function poolAdminDataByTokenId(uint256 tokenId) external view returns (PoolAdminNFTData memory data);
+}
\ No newline at end of file
diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolMemberNFT.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolMemberNFT.sol
new file mode 100644
index 0000000000..92058adb03
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolMemberNFT.sol
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.8.11;
+
+import { IPoolNFTBase } from "./IPoolNFTBase.sol";
+
+interface IPoolMemberNFT is IPoolNFTBase {
+ // PoolMemberNFTData struct storage packing:
+ // b = bits
+ // WORD 1: | pool | FREE
+ // | 160b | 96b
+ // WORD 2: | member | FREE
+ // | 160b | 96b
+ // WORD 3: | units | FREE
+ // | 128b | 128b
+ struct PoolMemberNFTData {
+ address pool;
+ address member;
+ uint128 units;
+ }
+
+ /// Errors ///
+
+ error POOL_MEMBER_NFT_NO_ZERO_POOL();
+ error POOL_MEMBER_NFT_NO_ZERO_MEMBER();
+ error POOL_MEMBER_NFT_NO_UNITS();
+ error POOL_MEMBER_NFT_HAS_UNITS();
+
+ function onCreate(address pool, address member) external;
+
+ function onUpdate(address pool, address member) external;
+
+ function onDelete(address pool, address member) external;
+
+ /// View Functions ///
+
+ function poolMemberDataByTokenId(uint256 tokenId) external view returns (PoolMemberNFTData memory data);
+}
\ No newline at end of file
diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol
new file mode 100644
index 0000000000..587c7c97d5
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity >=0.8.4;
+
+import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
+
+interface IPoolNFTBase is IERC721Metadata {
+ error POOL_NFT_APPROVE_TO_CALLER(); // 0x9212b333
+ error POOL_NFT_ONLY_SUPER_TOKEN_FACTORY(); // 0x1fd7e3d8
+ error POOL_NFT_INVALID_TOKEN_ID(); // 0x09275994
+ error POOL_NFT_APPROVE_TO_CURRENT_OWNER(); // 0x020226d3
+ error POOL_NFT_APPROVE_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL(); // 0x1e82f255
+ error POOL_NFT_NOT_REGISTERED_POOL(); // 0x6421912e
+ error POOL_NFT_TRANSFER_NOT_ALLOWED(); // 0x432fb160
+ error POOL_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL(); // 0x4028ee0e
+
+ /// @notice Informs third-party platforms that NFT metadata should be updated
+ /// @dev This event comes from https://eips.ethereum.org/EIPS/eip-4906
+ /// @param tokenId the id of the token that should have its metadata updated
+ event MetadataUpdate(uint256 tokenId);
+
+ function initialize(string memory nftName, string memory nftSymbol) external; // initializer;
+
+ function triggerMetadataUpdate(uint256 tokenId) external;
+
+ /// @notice Gets the token id
+ /// @dev For PoolAdminNFT, `account` is admin and for PoolMemberNFT, `account` is member
+ function getTokenId(address pool, address account) external view returns (uint256 tokenId);
+}
diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol
new file mode 100644
index 0000000000..4dee5ca426
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity >=0.8.4;
+
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import { ISuperfluidToken } from "../../superfluid/ISuperfluidToken.sol";
+
+/**
+ * @dev The interface for any super token pool regardless of the distribution schemes.
+ */
+interface ISuperfluidPool is IERC20 {
+
+ // Structs
+ struct PoolIndexData {
+ uint128 totalUnits;
+ uint32 wrappedSettledAt;
+ int96 wrappedFlowRate;
+ int256 wrappedSettledValue;
+ }
+
+ struct MemberData {
+ uint128 ownedUnits;
+ uint32 syncedSettledAt;
+ int96 syncedFlowRate;
+ int256 syncedSettledValue;
+ int256 settledValue;
+ int256 claimedValue;
+ }
+
+ // Custom Errors
+
+ error SUPERFLUID_POOL_INVALID_TIME(); // 0x83c35016
+ error SUPERFLUID_POOL_NO_POOL_MEMBERS(); // 0xe10f405a
+ error SUPERFLUID_POOL_NO_ZERO_ADDRESS(); // 0x54eb6ee6
+ error SUPERFLUID_POOL_NOT_POOL_ADMIN_OR_GDA(); // 0x1c5fbdcb
+ error SUPERFLUID_POOL_NOT_GDA(); // 0xfcbe3f9e
+ error SUPERFLUID_POOL_TRANSFER_UNITS_NOT_ALLOWED(); // 0x2285efba
+
+ // Events
+ event MemberUnitsUpdated(
+ ISuperfluidToken indexed token, address indexed member, uint128 oldUnits, uint128 newUnits
+ );
+ event DistributionClaimed(
+ ISuperfluidToken indexed token, address indexed member, int256 claimedAmount, int256 totalClaimed
+ );
+
+ /// @notice A boolean indicating whether pool members can transfer their units
+ function transferabilityForUnitsOwner() external view returns (bool);
+
+ /// @notice A boolean indicating whether addresses other than the pool admin can distribute via the pool
+ function distributionFromAnyAddress() external view returns (bool);
+
+ /// @notice The pool admin
+ /// @dev The admin is the creator of the pool and has permissions to update member units
+ /// and is the recipient of the adjustment flow rate
+ function admin() external view returns (address);
+
+ /// @notice The SuperToken for the pool
+ function superToken() external view returns (ISuperfluidToken);
+
+ /// @notice The total units of the pool
+ function getTotalUnits() external view returns (uint128);
+
+ /// @notice The total number of units of connected members
+ function getTotalConnectedUnits() external view returns (uint128);
+
+ /// @notice The total number of units of disconnected members
+ function getTotalDisconnectedUnits() external view returns (uint128);
+
+ /// @notice The total number of units for `memberAddress`
+ /// @param memberAddress The address of the member
+ function getUnits(address memberAddress) external view returns (uint128);
+
+ /// @notice The total flow rate of the pool
+ function getTotalFlowRate() external view returns (int96);
+
+ /// @notice The flow rate of the connected members
+ function getTotalConnectedFlowRate() external view returns (int96);
+
+ /// @notice The flow rate of the disconnected members
+ function getTotalDisconnectedFlowRate() external view returns (int96);
+
+ /// @notice The balance of all the disconnected members at `time`
+ /// @param time The time to query
+ function getDisconnectedBalance(uint32 time) external view returns (int256 balance);
+
+ /// @notice The flow rate a member is receiving from the pool
+ /// @param memberAddress The address of the member
+ function getMemberFlowRate(address memberAddress) external view returns (int96);
+
+ /// @notice The claimable balance for `memberAddr` at `time` in the pool
+ /// @param memberAddr The address of the member
+ /// @param time The time to query
+ function getClaimable(address memberAddr, uint32 time) external view returns (int256);
+
+ /// @notice The claimable balance for `memberAddr` at `block.timestamp` in the pool
+ /// @param memberAddr The address of the member
+ function getClaimableNow(address memberAddr) external view returns (int256 claimableBalance, uint256 timestamp);
+
+ /// @notice Sets `memberAddr` ownedUnits to `newUnits`
+ /// @param memberAddr The address of the member
+ /// @param newUnits The new units for the member
+ function updateMemberUnits(address memberAddr, uint128 newUnits) external returns (bool);
+
+ /// @notice Claims the claimable balance for `memberAddr` at `block.timestamp`
+ /// @param memberAddr The address of the member
+ function claimAll(address memberAddr) external returns (bool);
+
+ /// @notice Claims the claimable balance for `msg.sender` at `block.timestamp`
+ function claimAll() external returns (bool);
+
+ /// @notice Increases the allowance of `spender` by `addedValue`
+ /// @param spender The address of the spender
+ /// @param addedValue The amount to increase the allowance by
+ /// @return true if successful
+ function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
+
+ /// @notice Decreases the allowance of `spender` by `subtractedValue`
+ /// @param spender The address of the spender
+ /// @param subtractedValue The amount to decrease the allowance by
+ /// @return true if successful
+ function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
+}
diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/IPoolAdminNFT.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/IPoolAdminNFT.sol
deleted file mode 100644
index 1475902cb6..0000000000
--- a/packages/ethereum-contracts/contracts/interfaces/superfluid/IPoolAdminNFT.sol
+++ /dev/null
@@ -1,6 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity >=0.8.11;
-
-// TODO
-// solhint-disable-next-line no-empty-blocks
-interface IPoolAdminNFT {}
diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/IPoolMemberNFT.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/IPoolMemberNFT.sol
deleted file mode 100644
index bf6776fb7c..0000000000
--- a/packages/ethereum-contracts/contracts/interfaces/superfluid/IPoolMemberNFT.sol
+++ /dev/null
@@ -1,6 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity >=0.8.11;
-
-// TODO
-// solhint-disable-next-line no-empty-blocks
-interface IPoolMemberNFT {}
diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol
index ab588f7106..ef62a0e93c 100644
--- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol
+++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol
@@ -6,6 +6,8 @@ import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/exte
import { IERC777 } from "@openzeppelin/contracts/token/ERC777/IERC777.sol";
import { IConstantOutflowNFT } from "./IConstantOutflowNFT.sol";
import { IConstantInflowNFT } from "./IConstantInflowNFT.sol";
+import { IPoolAdminNFT } from "../agreements/gdav1/IPoolAdminNFT.sol";
+import { IPoolMemberNFT } from "../agreements/gdav1/IPoolMemberNFT.sol";
/**
* @title Super token (Superfluid Token + ERC20 + ERC777) interface
@@ -75,6 +77,10 @@ interface ISuperToken is ISuperfluidToken, IERC20Metadata, IERC777 {
function CONSTANT_OUTFLOW_NFT() external view returns (IConstantOutflowNFT);
// solhint-disable-next-line func-name-mixedcase
function CONSTANT_INFLOW_NFT() external view returns (IConstantInflowNFT);
+ // solhint-disable-next-line func-name-mixedcase
+ function POOL_ADMIN_NFT() external view returns (IPoolAdminNFT);
+ // solhint-disable-next-line func-name-mixedcase
+ function POOL_MEMBER_NFT() external view returns (IPoolMemberNFT);
/**************************************************************************
* IERC20Metadata & ERC777
@@ -589,6 +595,22 @@ interface ISuperToken is ISuperfluidToken, IERC20Metadata, IERC777 {
IConstantInflowNFT indexed constantInflowNFT
);
+ /**
+ * @dev Pool Admin NFT proxy created event
+ * @param poolAdminNFT pool admin nft address
+ */
+ event PoolAdminNFTCreated(
+ IPoolAdminNFT indexed poolAdminNFT
+ );
+
+ /**
+ * @dev Pool Member NFT proxy created event
+ * @param poolMemberNFT pool member nft address
+ */
+ event PoolMemberNFTCreated(
+ IPoolMemberNFT indexed poolMemberNFT
+ );
+
/**************************************************************************
* Function modifiers for access control and parameter validations
*
diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol
index 16d40c13f1..9796eb6240 100644
--- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol
+++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol
@@ -19,10 +19,6 @@ interface ISuperTokenFactory {
error SUPER_TOKEN_FACTORY_NON_UPGRADEABLE_IS_DEPRECATED(); // 0xc4901a43
error SUPER_TOKEN_FACTORY_ZERO_ADDRESS(); // 0x305c9e82
- /**************************************************************************
- * Immutable Variables
- **************************************************************************/
-
/**
* @dev Get superfluid host contract address
*/
diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol
index 2137ccdcf6..b6abefb953 100644
--- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol
+++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol
@@ -26,12 +26,14 @@ import { ISETH } from "../tokens/ISETH.sol";
import { IFlowNFTBase } from "./IFlowNFTBase.sol";
import { IConstantOutflowNFT } from "./IConstantOutflowNFT.sol";
import { IConstantInflowNFT } from "./IConstantInflowNFT.sol";
-import { IPoolAdminNFT } from "./IPoolAdminNFT.sol";
-import { IPoolMemberNFT } from "./IPoolMemberNFT.sol";
+import { IPoolAdminNFT } from "../agreements/gdav1/IPoolAdminNFT.sol";
+import { IPoolMemberNFT } from "../agreements/gdav1/IPoolMemberNFT.sol";
/// Superfluid agreement interfaces:
import { ISuperAgreement } from "./ISuperAgreement.sol";
import { IConstantFlowAgreementV1 } from "../agreements/IConstantFlowAgreementV1.sol";
import { IInstantDistributionAgreementV1 } from "../agreements/IInstantDistributionAgreementV1.sol";
+import { IGeneralDistributionAgreementV1, PoolConfig } from "../agreements/gdav1/IGeneralDistributionAgreementV1.sol";
+import { ISuperfluidPool } from "../agreements/gdav1/ISuperfluidPool.sol";
/// Superfluid App interfaces:
import { ISuperApp } from "./ISuperApp.sol";
/// Superfluid governance
@@ -238,6 +240,19 @@ interface ISuperfluid {
*/
function changeSuperTokenAdmin(ISuperToken token, address newAdmin) external;
+ /**
+ * @notice Change the implementation address the pool beacon points to
+ * @dev Updating the logic the beacon points to will update the logic of all the Pool BeaconProxy instances
+ */
+ function updatePoolBeaconLogic(address newBeaconLogic) external;
+
+ /**
+ * @dev Pool Beacon logic updated event
+ * @param beaconProxy addrss of the beacon proxy
+ * @param newBeaconLogic address of the new beacon logic
+ */
+ event PoolBeaconLogicUpdated(address indexed beaconProxy, address newBeaconLogic);
+
/**************************************************************************
* App Registry
*************************************************************************/
diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidGovernance.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidGovernance.sol
index 6ef45d6f79..0aa7e90b13 100644
--- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidGovernance.sol
+++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidGovernance.sol
@@ -41,7 +41,8 @@ interface ISuperfluidGovernance {
ISuperfluid host,
address hostNewLogic,
address[] calldata agreementClassNewLogics,
- address superTokenFactoryNewLogic
+ address superTokenFactoryNewLogic,
+ address beaconNewLogic
) external;
/**
diff --git a/packages/ethereum-contracts/contracts/libs/SafeGasLibrary.sol b/packages/ethereum-contracts/contracts/libs/SafeGasLibrary.sol
index eedfd60f4e..33e24bb4b8 100644
--- a/packages/ethereum-contracts/contracts/libs/SafeGasLibrary.sol
+++ b/packages/ethereum-contracts/contracts/libs/SafeGasLibrary.sol
@@ -8,7 +8,7 @@ library SafeGasLibrary {
error OUT_OF_GAS(); // 0x20afada5
function _isOutOfGas(uint256 gasLeftBefore) internal view returns (bool) {
- return gasleft() <= gasLeftBefore / 63;
+ return gasleft() <= gasLeftBefore / 64;
}
/// @dev A function used in the catch block to handle true out of gas errors
diff --git a/packages/ethereum-contracts/contracts/libs/SolvencyHelperLibrary.sol b/packages/ethereum-contracts/contracts/libs/SolvencyHelperLibrary.sol
new file mode 100644
index 0000000000..33682ee75c
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/libs/SolvencyHelperLibrary.sol
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: AGPLv3
+
+import {
+ ISuperfluid,
+ ISuperfluidToken,
+ ISuperfluidGovernance,
+ SuperfluidGovernanceConfigs
+} from "../interfaces/superfluid/ISuperfluid.sol";
+
+pragma solidity 0.8.19;
+
+library SolvencyHelperLibrary {
+ function decode3PsData(ISuperfluid host, ISuperfluidToken token)
+ internal
+ view
+ returns (uint256 liquidationPeriod, uint256 patricianPeriod)
+ {
+ ISuperfluidGovernance gov = ISuperfluidGovernance(host.getGovernance());
+ // @note we are explicitly using CFAV1_PPP_CONFIG_KEY for both CFA and GDA
+ uint256 pppConfig = gov.getConfigAsUint256(host, token, SuperfluidGovernanceConfigs.CFAV1_PPP_CONFIG_KEY);
+ (liquidationPeriod, patricianPeriod) = SuperfluidGovernanceConfigs.decodePPPConfig(pppConfig);
+ }
+
+ function isPatricianPeriod(
+ int256 availableBalance,
+ int256 signedTotalDeposit,
+ uint256 liquidationPeriod,
+ uint256 patricianPeriod
+ ) internal pure returns (bool) {
+ if (signedTotalDeposit == 0) {
+ return false;
+ }
+
+ int256 totalRewardLeft = availableBalance + signedTotalDeposit;
+ int256 totalOutflowRate = signedTotalDeposit / int256(liquidationPeriod);
+
+ return totalRewardLeft / totalOutflowRate > int256(liquidationPeriod - patricianPeriod);
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/mocks/CFAv1NFTMock.sol b/packages/ethereum-contracts/contracts/mocks/CFAv1NFTMock.sol
index 26b6c2c869..b9a1858aae 100644
--- a/packages/ethereum-contracts/contracts/mocks/CFAv1NFTMock.sol
+++ b/packages/ethereum-contracts/contracts/mocks/CFAv1NFTMock.sol
@@ -1,29 +1,66 @@
// SPDX-License-Identifier: AGPLv3
// solhint-disable reason-string
+// solhint-disable not-rely-on-time
pragma solidity 0.8.19;
-import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
-import { UUPSProxiable } from "../upgradability/UUPSProxiable.sol";
-import { ERC777Helper } from "../libs/ERC777Helper.sol";
-import { SuperfluidToken } from "../superfluid/SuperfluidToken.sol";
+import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { ISuperfluid, IConstantInflowNFT, IConstantOutflowNFT } from "../interfaces/superfluid/ISuperfluid.sol";
-import { ConstantInflowNFT } from "../superfluid/ConstantInflowNFT.sol";
import { ConstantOutflowNFT } from "../superfluid/ConstantOutflowNFT.sol";
+import { ConstantInflowNFT } from "../superfluid/ConstantInflowNFT.sol";
+import { FlowNFTBase } from "../superfluid/FlowNFTBase.sol";
+
+/// @title FlowNFTBaseMock
+/// @author Superfluid
+/// @dev A mock contract for testing the functionality on FlowNFTBase
+contract FlowNFTBaseMock is FlowNFTBase {
+ using Strings for uint256;
+
+ mapping(uint256 => FlowNFTData) internal _flowDataByTokenId;
+
+ constructor(ISuperfluid host) FlowNFTBase(host) { }
+
+ function proxiableUUID() public pure override returns (bytes32) {
+ return keccak256("org.superfluid-finance.contracts.FlowNFTBaseMock.implementation");
+ }
+
+ /// @dev The owner of here is always the flow sender
+ function _ownerOf(uint256 tokenId) internal view override returns (address) {
+ return _flowDataByTokenId[tokenId].flowSender;
+ }
+
+ /// @dev a mock mint function that sets the FlowNFTData
+ function mockMint(address _superToken, address _flowSender, address _flowReceiver) public {
+ uint256 tokenId = _getTokenId(_superToken, _flowSender, _flowReceiver);
+ _flowDataByTokenId[tokenId] = FlowNFTData({
+ flowSender: _flowSender,
+ flowStartDate: uint32(block.timestamp),
+ flowReceiver: _flowReceiver,
+ superToken: _superToken
+ });
+ }
+
+ function _transfer(
+ address, //from,
+ address, //to,
+ uint256 //tokenId
+ ) internal pure override {
+ revert CFA_NFT_TRANSFER_IS_NOT_ALLOWED();
+ }
+
+ function flowDataByTokenId(uint256 tokenId) public view override returns (FlowNFTData memory flowData) {
+ return _flowDataByTokenId[tokenId];
+ }
+
+ function tokenURI(uint256 tokenId) external pure override returns (string memory) {
+ return string(abi.encodePacked("tokenId=", tokenId.toString()));
+ }
+}
contract ConstantOutflowNFTMock is ConstantOutflowNFT {
- constructor(
- ISuperfluid host,
- IConstantInflowNFT constantInflowNFT
- ) ConstantOutflowNFT(host, constantInflowNFT) {}
+ constructor(ISuperfluid host, IConstantInflowNFT constantInflowNFT) ConstantOutflowNFT(host, constantInflowNFT) { }
/// @dev a mock mint function that exposes the internal _mint function
- function mockMint(
- address _superToken,
- address _to,
- address _flowReceiver,
- uint256 _newTokenId
- ) public {
+ function mockMint(address _superToken, address _to, address _flowReceiver, uint256 _newTokenId) public {
_mint(_superToken, _to, _flowReceiver, _newTokenId);
}
@@ -47,7 +84,7 @@ contract ConstantInflowNFTMock is ConstantInflowNFT {
constructor(
ISuperfluid host,
IConstantOutflowNFT constantOutflowNFT
- ) ConstantInflowNFT(host, constantOutflowNFT) {}
+ ) ConstantInflowNFT(host, constantOutflowNFT) { }
/// @dev a mock mint function to emit the mint Transfer event
function mockMint(address _to, uint256 _newTokenId) public {
@@ -64,157 +101,8 @@ contract ConstantInflowNFTMock is ConstantInflowNFT {
return _ownerOf(_tokenId);
}
- /// @dev this exposes the internal flow data by token id for testing purposes
- function mockFlowNFTDataByTokenId(
- uint256 _tokenId
- ) public view returns (FlowNFTData memory flowData) {
- return flowDataByTokenId(_tokenId);
- }
-
/// @dev This exposes the _tokenApprovals storage without the requireMinted call
function mockGetApproved(uint256 _tokenId) public view returns (address) {
return _tokenApprovals[_tokenId];
}
-}
-
-/// @title NoNFTSuperTokenMock
-/// @author Superfluid
-/// @notice Minimal SuperToken implementation to test flow creation if no NFT proxy contract variable exists.
-/// Storage layout is made to mimic SuperToken.
-contract NoNFTSuperTokenMock is UUPSProxiable, SuperfluidToken {
- using SafeERC20 for IERC20;
-
- /// @dev The underlying ERC20 token
- IERC20 internal _underlyingToken;
-
- /// @dev Decimals of the underlying token
- uint8 internal _underlyingDecimals;
-
- /// @dev IERC20Metadata Name property
- string internal _name;
-
- /// @dev IERC20Metadata Symbol property
- string internal _symbol;
-
- /// @dev ERC20 Allowances Storage
- mapping(address => mapping(address => uint256)) internal _allowances;
-
- /// @dev ERC777 operators support data
- ERC777Helper.Operators internal _operators;
-
- constructor(ISuperfluid host) SuperfluidToken(host) {}
-
- /// @dev Initialize the Super Token proxy
- function initialize(
- IERC20 underlyingToken,
- uint8 underlyingDecimals,
- string calldata n,
- string calldata s
- )
- external
- initializer // OpenZeppelin Initializable
- {
- _underlyingToken = underlyingToken;
- _underlyingDecimals = underlyingDecimals;
-
- _name = n;
- _symbol = s;
-
- // register interfaces
- ERC777Helper.register(address(this));
- }
-
- /// @dev ISuperToken.upgrade implementation
- function upgrade(uint256 amount) external {
- _upgrade(msg.sender, msg.sender, msg.sender, amount, "", "");
- }
-
- /**
- * @dev Handle decimal differences between underlying token and super token
- */
- function _toUnderlyingAmount(
- uint256 amount
- ) private view returns (uint256 underlyingAmount, uint256 adjustedAmount) {
- uint256 factor;
- if (_underlyingDecimals < 18) {
- // if underlying has less decimals
- // one can upgrade less "granualar" amount of tokens
- factor = 10 ** (18 - _underlyingDecimals);
- underlyingAmount = amount / factor;
- // remove precision errors
- adjustedAmount = underlyingAmount * factor;
- } else if (_underlyingDecimals > 18) {
- // if underlying has more decimals
- // one can upgrade more "granualar" amount of tokens
- factor = 10 ** (_underlyingDecimals - 18);
- underlyingAmount = amount * factor;
- adjustedAmount = amount;
- } else {
- underlyingAmount = adjustedAmount = amount;
- }
- }
-
- function _upgrade(
- address operator,
- address account,
- address to,
- uint256 amount,
- bytes memory userData,
- bytes memory operatorData
- ) private {
- if (address(_underlyingToken) == address(0)) revert();
-
- (
- uint256 underlyingAmount,
- uint256 adjustedAmount
- ) = _toUnderlyingAmount(amount);
-
- uint256 amountBefore = _underlyingToken.balanceOf(address(this));
- _underlyingToken.safeTransferFrom(
- account,
- address(this),
- underlyingAmount
- );
- uint256 amountAfter = _underlyingToken.balanceOf(address(this));
- uint256 actualUpgradedAmount = amountAfter - amountBefore;
- if (underlyingAmount != actualUpgradedAmount) revert();
-
- _mint(
- operator,
- to,
- adjustedAmount,
- // if `userData.length` than 0, we requireReceptionAck
- userData.length != 0,
- userData,
- operatorData
- );
- }
-
- /// dummy impl
- function _mint(
- address, // operator,
- address account,
- uint256 amount,
- bool, // requireReceptionAck,
- bytes memory, // userData,
- bytes memory // operatorData
- ) internal {
- if (account == address(0)) {
- revert();
- }
-
- SuperfluidToken._mint(account, amount);
- }
-
- function proxiableUUID() public pure override returns (bytes32) {
- return
- keccak256(
- "org.superfluid-finance.contracts.SuperToken.implementation"
- );
- }
-
- // solhint-disable-next-line no-empty-blocks
- function updateCode(address newAddress) external override {
- // dummy impl
- }
-}
+}
\ No newline at end of file
diff --git a/packages/ethereum-contracts/contracts/mocks/CFAv1NFTUpgradabilityMock.sol b/packages/ethereum-contracts/contracts/mocks/CFAv1NFTUpgradabilityMock.sol
index 593180a7f0..af0ad2d344 100644
--- a/packages/ethereum-contracts/contracts/mocks/CFAv1NFTUpgradabilityMock.sol
+++ b/packages/ethereum-contracts/contracts/mocks/CFAv1NFTUpgradabilityMock.sol
@@ -5,23 +5,17 @@ import { ISuperfluid } from "../interfaces/superfluid/ISuperfluid.sol";
import { ConstantInflowNFT, IConstantInflowNFT } from "../superfluid/ConstantInflowNFT.sol";
import { ConstantOutflowNFT, IConstantOutflowNFT } from "../superfluid/ConstantOutflowNFT.sol";
import { FlowNFTBase } from "../superfluid/FlowNFTBase.sol";
+import { IStorageLayoutBase } from "./IStorageLayoutBase.sol";
/*//////////////////////////////////////////////////////////////////////////
FlowNFTBase Mocks
//////////////////////////////////////////////////////////////////////////*/
-interface IFlowNFTBaseMockErrors {
- error STORAGE_LOCATION_CHANGED(string _name);
-}
-
/// @title FlowNFTBaseStorageLayoutMock
/// @author Superfluid
/// @notice A mock FlowNFTBase contract for testing storage layout.
/// @dev This contract *MUST* have the same storage layout as FlowNFTBase.sol
-contract FlowNFTBaseStorageLayoutMock is FlowNFTBase {
-
- error STORAGE_LOCATION_CHANGED(string _name);
-
+contract FlowNFTBaseStorageLayoutMock is FlowNFTBase, IStorageLayoutBase {
constructor(
ISuperfluid host
) FlowNFTBase(host) {}
@@ -96,10 +90,7 @@ contract FlowNFTBaseStorageLayoutMock is FlowNFTBase {
/// @author Superfluid
/// @notice A mock ConstantOutflowNFT contract for testing storage layout.
/// @dev This contract *MUST* have the same storage layout as ConstantOutflowNFT.sol
-contract ConstantInflowNFTStorageLayoutMock is ConstantInflowNFT {
-
- error STORAGE_LOCATION_CHANGED(string _name);
-
+contract ConstantInflowNFTStorageLayoutMock is ConstantInflowNFT, IStorageLayoutBase {
constructor(
ISuperfluid host,
@@ -136,18 +127,6 @@ contract ConstantInflowNFTStorageLayoutMock is ConstantInflowNFT {
}
// Dummy implementations for abstract functions
- function _ownerOf(
- uint256 //tokenId
- ) internal pure override returns (address) {
- return address(0);
- }
- function _transfer(
- address, //from,
- address, //to,
- uint256 //tokenId
- ) internal pure override {
- return;
- }
function _safeTransfer(
address from,
address to,
@@ -162,10 +141,7 @@ contract ConstantInflowNFTStorageLayoutMock is ConstantInflowNFT {
/// @author Superfluid
/// @notice A mock ConstantOutflowNFT contract for testing storage layout.
/// @dev This contract *MUST* have the same storage layout as ConstantOutflowNFT.sol
-contract ConstantOutflowNFTStorageLayoutMock is ConstantOutflowNFT {
-
- error STORAGE_LOCATION_CHANGED(string _name);
-
+contract ConstantOutflowNFTStorageLayoutMock is ConstantOutflowNFT, IStorageLayoutBase {
constructor(
ISuperfluid host,
@@ -201,21 +177,10 @@ contract ConstantOutflowNFTStorageLayoutMock is ConstantOutflowNFT {
if (slot != 21 || offset != 0) revert STORAGE_LOCATION_CHANGED("_reserve21");
assembly { slot := _flowDataByTokenId.slot offset := _flowDataByTokenId.offset }
+ if (slot != 22 || offset != 0) revert STORAGE_LOCATION_CHANGED("_flowDataByTokenId");
}
// Dummy implementations for abstract functions
- function _ownerOf(
- uint256 //tokenId
- ) internal pure override returns (address) {
- return address(0);
- }
- function _transfer(
- address, //from,
- address, //to,
- uint256 //tokenId
- ) internal pure override {
- return;
- }
function _safeTransfer(
address from,
address to,
diff --git a/packages/ethereum-contracts/contracts/mocks/IStorageLayoutBase.sol b/packages/ethereum-contracts/contracts/mocks/IStorageLayoutBase.sol
new file mode 100644
index 0000000000..4a1cffbd80
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/mocks/IStorageLayoutBase.sol
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+interface IStorageLayoutBase {
+ error STORAGE_LOCATION_CHANGED(string _name);
+}
\ No newline at end of file
diff --git a/packages/ethereum-contracts/contracts/mocks/PoolNFTMock.sol b/packages/ethereum-contracts/contracts/mocks/PoolNFTMock.sol
new file mode 100644
index 0000000000..5e3e642479
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/mocks/PoolNFTMock.sol
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: AGPLv3
+// solhint-disable reason-string
+pragma solidity 0.8.19;
+
+import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
+import { ISuperfluid } from "../interfaces/superfluid/ISuperfluid.sol";
+import { PoolAdminNFT } from "../agreements/gdav1/PoolAdminNFT.sol";
+import { PoolMemberNFT } from "../agreements/gdav1/PoolMemberNFT.sol";
+import { PoolNFTBase } from "../agreements/gdav1/PoolNFTBase.sol";
+
+contract PoolNFTBaseMock is PoolNFTBase {
+ using Strings for uint256;
+
+ mapping(uint256 => address) private _owners;
+
+ constructor(ISuperfluid host) PoolNFTBase(host) { }
+
+ function proxiableUUID() public pure override returns (bytes32) {
+ return keccak256("org.superfluid-finance.contracts.PoolNFTBaseMock.implementation");
+ }
+
+ /// @dev The owner of here is always the flow sender
+ function _ownerOf(uint256 tokenId) internal view override returns (address) {
+ return _owners[tokenId];
+ }
+
+ function getTokenId(address pool, address account) external view override returns (uint256 tokenId) {
+ return _getTokenId(pool, account);
+ }
+
+ function _getTokenId(address pool, address account) internal view returns (uint256 tokenId) {
+ return uint256(keccak256(abi.encode("PoolNFTMock", block.chainid, pool, account)));
+ }
+
+ /// @dev a mock mint function that sets the owner
+ function mockMint(address pool, address account) public {
+ uint256 tokenId = _getTokenId(pool, account);
+ _owners[tokenId] = account;
+ }
+
+ function _transfer(
+ address, //from,
+ address, //to,
+ uint256 //tokenId
+ ) internal pure override {
+ revert POOL_NFT_TRANSFER_NOT_ALLOWED();
+ }
+
+ function tokenURI(uint256 tokenId) external pure override returns (string memory) {
+ return string(abi.encodePacked("tokenId=", tokenId.toString()));
+ }
+}
+
+contract PoolAdminNFTMock is PoolAdminNFT {
+ constructor(ISuperfluid host) PoolAdminNFT(host) { }
+
+ /// @dev a mock mint function that exposes the internal _mint function
+ function mockMint(address _pool) public {
+ _mint(_pool);
+ }
+
+ /// @dev this ownerOf doesn't revert if _tokenId doesn't exist
+ function mockOwnerOf(uint256 _tokenId) public view returns (address) {
+ return _ownerOf(_tokenId);
+ }
+
+ /// @dev This exposes the _tokenApprovals storage without the requireMinted call
+ function mockGetApproved(uint256 _tokenId) public view returns (address) {
+ return _tokenApprovals[_tokenId];
+ }
+}
+
+contract PoolMemberNFTMock is PoolMemberNFT {
+ constructor(ISuperfluid host) PoolMemberNFT(host) { }
+
+ /// @dev a mock mint function that exposes the internal _mint function
+ function mockMint(address _pool, address _member) public {
+ _mint(_pool, _member);
+ }
+
+ /// @dev a mock burn function that exposes the internal _burn function
+ function mockBurn(uint256 _tokenId) public {
+ _burn(_tokenId);
+ }
+
+ /// @dev this ownerOf doesn't revert if _tokenId doesn't exist
+ function mockOwnerOf(uint256 _tokenId) public view returns (address) {
+ return _ownerOf(_tokenId);
+ }
+
+ /// @dev This exposes the _tokenApprovals storage without the requireMinted call
+ function mockGetApproved(uint256 _tokenId) public view returns (address) {
+ return _tokenApprovals[_tokenId];
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/mocks/PoolNFTUpgradabilityMock.sol b/packages/ethereum-contracts/contracts/mocks/PoolNFTUpgradabilityMock.sol
new file mode 100644
index 0000000000..2cda01e0f4
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/mocks/PoolNFTUpgradabilityMock.sol
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: AGPLv3
+// solhint-disable reason-string
+pragma solidity 0.8.19;
+
+import { PoolNFTBase } from "../agreements/gdav1/PoolNFTBase.sol";
+import { ISuperfluid } from "../interfaces/superfluid/ISuperfluid.sol";
+import { PoolMemberNFT } from "../agreements/gdav1/PoolMemberNFT.sol";
+import { PoolAdminNFT } from "../agreements/gdav1/PoolAdminNFT.sol";
+import { IStorageLayoutBase } from "./IStorageLayoutBase.sol";
+
+contract PoolNFTBaseStorageLayoutMock is PoolNFTBase, IStorageLayoutBase {
+ constructor(ISuperfluid host) PoolNFTBase(host) { }
+
+ function validateStorageLayout() public virtual {
+ uint256 slot;
+ uint256 offset;
+
+ assembly { slot := _name.slot offset := _name.offset }
+ if (slot != 1 || offset != 0) revert STORAGE_LOCATION_CHANGED("_name");
+
+ assembly { slot := _symbol.slot offset := _symbol.offset }
+ if (slot != 2 || offset != 0) revert STORAGE_LOCATION_CHANGED("_symbol");
+
+ assembly { slot := _tokenApprovals.slot offset := _tokenApprovals.offset }
+ if (slot != 3 || offset != 0) revert STORAGE_LOCATION_CHANGED("_tokenApprovals");
+
+ assembly { slot := _operatorApprovals.slot offset := _operatorApprovals.offset }
+ if (slot != 4 || offset != 0) revert STORAGE_LOCATION_CHANGED("_operatorApprovals");
+
+ assembly { slot := _reserve5.slot offset := _reserve5.offset }
+ if (slot != 5 || offset != 0) revert STORAGE_LOCATION_CHANGED("_reserve5");
+
+ assembly { slot := _reserve21.slot offset := _reserve21.offset }
+ if (slot != 21 || offset != 0) revert STORAGE_LOCATION_CHANGED("_reserve21");
+ }
+
+ // Dummy implementations for abstract functions
+ function _ownerOf(
+ uint256 //tokenId
+ ) internal pure override returns (address) {
+ return address(0);
+ }
+
+ function getTokenId(address /*pool*/, address /*account*/) external pure override returns (uint256 tokenId) {
+ return 0;
+ }
+
+ function _transfer(
+ address, //from,
+ address, //to,
+ uint256 //tokenId
+ ) internal pure override {
+ return;
+ }
+
+ function _safeTransfer(
+ address from,
+ address to,
+ uint256 tokenId,
+ bytes memory // data
+ ) internal pure override {
+ _transfer(from, to, tokenId);
+ }
+
+ function proxiableUUID() public pure override returns (bytes32) {
+ return keccak256("");
+ }
+
+ function tokenURI(uint256 /*tokenId*/) external pure override returns (string memory) {
+ return "";
+ }
+}
+
+contract PoolAdminNFTStorageLayoutMock is PoolAdminNFT, IStorageLayoutBase {
+ constructor(ISuperfluid host) PoolAdminNFT(host) { }
+
+ function validateStorageLayout() public virtual {
+ uint256 slot;
+ uint256 offset;
+
+ assembly { slot := _name.slot offset := _name.offset }
+ if (slot != 1 || offset != 0) revert STORAGE_LOCATION_CHANGED("_name");
+
+ assembly { slot := _symbol.slot offset := _symbol.offset }
+ if (slot != 2 || offset != 0) revert STORAGE_LOCATION_CHANGED("_symbol");
+
+ assembly { slot := _tokenApprovals.slot offset := _tokenApprovals.offset }
+ if (slot != 3 || offset != 0) revert STORAGE_LOCATION_CHANGED("_tokenApprovals");
+
+ assembly { slot := _operatorApprovals.slot offset := _operatorApprovals.offset }
+ if (slot != 4 || offset != 0) revert STORAGE_LOCATION_CHANGED("_operatorApprovals");
+
+ assembly { slot := _reserve5.slot offset := _reserve5.offset }
+ if (slot != 5 || offset != 0) revert STORAGE_LOCATION_CHANGED("_reserve5");
+
+ assembly { slot := _reserve21.slot offset := _reserve21.offset }
+ if (slot != 21 || offset != 0) revert STORAGE_LOCATION_CHANGED("_reserve21");
+
+ assembly { slot := _poolAdminDataByTokenId.slot offset := _poolAdminDataByTokenId.offset }
+ if (slot != 22 || offset != 0) revert STORAGE_LOCATION_CHANGED("_poolAdminDataByTokenId");
+ }
+
+ // Dummy implementations for abstract functions
+ function _safeTransfer(
+ address from,
+ address to,
+ uint256 tokenId,
+ bytes memory // data
+ ) internal pure override {
+ _transfer(from, to, tokenId);
+ }
+}
+
+contract PoolMemberNFTStorageLayoutMock is PoolMemberNFT, IStorageLayoutBase {
+ constructor(ISuperfluid host) PoolMemberNFT(host) { }
+
+ function validateStorageLayout() public virtual {
+ uint256 slot;
+ uint256 offset;
+
+ assembly { slot := _name.slot offset := _name.offset }
+ if (slot != 1 || offset != 0) revert STORAGE_LOCATION_CHANGED("_name");
+
+ assembly { slot := _symbol.slot offset := _symbol.offset }
+ if (slot != 2 || offset != 0) revert STORAGE_LOCATION_CHANGED("_symbol");
+
+ assembly { slot := _tokenApprovals.slot offset := _tokenApprovals.offset }
+ if (slot != 3 || offset != 0) revert STORAGE_LOCATION_CHANGED("_tokenApprovals");
+
+ assembly { slot := _operatorApprovals.slot offset := _operatorApprovals.offset }
+ if (slot != 4 || offset != 0) revert STORAGE_LOCATION_CHANGED("_operatorApprovals");
+
+ assembly { slot := _reserve5.slot offset := _reserve5.offset }
+ if (slot != 5 || offset != 0) revert STORAGE_LOCATION_CHANGED("_reserve5");
+
+ assembly { slot := _reserve21.slot offset := _reserve21.offset }
+ if (slot != 21 || offset != 0) revert STORAGE_LOCATION_CHANGED("_reserve21");
+
+ assembly { slot := _poolMemberDataByTokenId.slot offset := _poolMemberDataByTokenId.offset }
+ if (slot != 22 || offset != 0) revert STORAGE_LOCATION_CHANGED("_poolMemberDataByTokenId");
+ }
+
+ // Dummy implementations for abstract functions
+ function _safeTransfer(
+ address from,
+ address to,
+ uint256 tokenId,
+ bytes memory // data
+ ) internal pure override {
+ _transfer(from, to, tokenId);
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/mocks/SuperTokenFactoryMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperTokenFactoryMock.sol
index 6344859281..ae69db9200 100644
--- a/packages/ethereum-contracts/contracts/mocks/SuperTokenFactoryMock.sol
+++ b/packages/ethereum-contracts/contracts/mocks/SuperTokenFactoryMock.sol
@@ -4,28 +4,25 @@ pragma solidity 0.8.19;
import {
ISuperfluid,
ISuperToken,
- SuperTokenFactoryBase,
IConstantInflowNFT,
- IConstantOutflowNFT
-} from "../superfluid/SuperTokenFactory.sol";
+ IConstantOutflowNFT,
+ IPoolAdminNFT,
+ IPoolMemberNFT
+} from "../interfaces/superfluid/ISuperfluid.sol";
+import { SuperTokenFactoryBase } from "../superfluid/SuperTokenFactory.sol";
contract SuperTokenFactoryStorageLayoutTester is SuperTokenFactoryBase {
constructor(
ISuperfluid host,
ISuperToken superTokenLogic,
IConstantOutflowNFT constantOutflowNFT,
- IConstantInflowNFT constantInflowNFT
+ IConstantInflowNFT constantInflowNFT,
+ IPoolAdminNFT poolAdminNFT,
+ IPoolMemberNFT poolMemberNFT
)
- SuperTokenFactoryBase(
- host,
- superTokenLogic,
- constantOutflowNFT,
- constantInflowNFT
- )
+ SuperTokenFactoryBase(host, superTokenLogic, constantOutflowNFT, constantInflowNFT, poolAdminNFT, poolMemberNFT)
// solhint-disable-next-line no-empty-blocks
- {
-
- }
+ { }
// @dev Make sure the storage layout never change over the course of the development
function validateStorageLayout() external pure {
@@ -49,18 +46,13 @@ contract SuperTokenFactoryUpdateLogicContractsTester is SuperTokenFactoryBase {
ISuperfluid host,
ISuperToken superTokenLogic,
IConstantOutflowNFT constantOutflowNFT,
- IConstantInflowNFT constantInflowNFT
+ IConstantInflowNFT constantInflowNFT,
+ IPoolAdminNFT poolAdminNFT,
+ IPoolMemberNFT poolMemberNFT
)
- SuperTokenFactoryBase(
- host,
- superTokenLogic,
- constantOutflowNFT,
- constantInflowNFT
- )
+ SuperTokenFactoryBase(host, superTokenLogic, constantOutflowNFT, constantInflowNFT, poolAdminNFT, poolMemberNFT)
// solhint-disable-next-line no-empty-blocks
- {
-
- }
+ { }
}
contract SuperTokenFactoryMock is SuperTokenFactoryBase {
@@ -68,18 +60,13 @@ contract SuperTokenFactoryMock is SuperTokenFactoryBase {
ISuperfluid host,
ISuperToken superTokenLogic,
IConstantOutflowNFT constantOutflowNFT,
- IConstantInflowNFT constantInflowNFT
+ IConstantInflowNFT constantInflowNFT,
+ IPoolAdminNFT poolAdminNFT,
+ IPoolMemberNFT poolMemberNFT
)
- SuperTokenFactoryBase(
- host,
- superTokenLogic,
- constantOutflowNFT,
- constantInflowNFT
- )
+ SuperTokenFactoryBase(host, superTokenLogic, constantOutflowNFT, constantInflowNFT, poolAdminNFT, poolMemberNFT)
// solhint-disable-next-line no-empty-blocks
- {
-
- }
+ { }
}
contract SuperTokenFactoryMock42 is SuperTokenFactoryBase {
@@ -87,16 +74,11 @@ contract SuperTokenFactoryMock42 is SuperTokenFactoryBase {
ISuperfluid host,
ISuperToken superTokenLogic,
IConstantOutflowNFT constantOutflowNFT,
- IConstantInflowNFT constantInflowNFT
+ IConstantInflowNFT constantInflowNFT,
+ IPoolAdminNFT poolAdminNFT,
+ IPoolMemberNFT poolMemberNFT
)
- SuperTokenFactoryBase(
- host,
- superTokenLogic,
- constantOutflowNFT,
- constantInflowNFT
- )
+ SuperTokenFactoryBase(host, superTokenLogic, constantOutflowNFT, constantInflowNFT, poolAdminNFT, poolMemberNFT)
// solhint-disable-next-line no-empty-blocks
- {
-
- }
+ { }
}
diff --git a/packages/ethereum-contracts/contracts/mocks/SuperTokenLibraryV1Mock.sol b/packages/ethereum-contracts/contracts/mocks/SuperTokenLibraryV1Mock.sol
index dd85539be0..3e5dc35c23 100644
--- a/packages/ethereum-contracts/contracts/mocks/SuperTokenLibraryV1Mock.sol
+++ b/packages/ethereum-contracts/contracts/mocks/SuperTokenLibraryV1Mock.sol
@@ -3,6 +3,8 @@ pragma solidity 0.8.19;
import { ISuperfluid, ISuperToken } from "../interfaces/superfluid/ISuperfluid.sol";
import { SuperAppDefinitions } from "../interfaces/superfluid/ISuperfluid.sol";
+import { ISuperfluidPool } from "../interfaces/agreements/gdav1/ISuperfluidPool.sol";
+import { PoolConfig } from "../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
import { SuperAppBase } from "../apps/SuperAppBase.sol";
import { SuperTokenV1Library } from "../apps/SuperTokenV1Library.sol";
@@ -491,6 +493,74 @@ contract SuperTokenLibraryIDAMock {
}
}
+contract SuperTokenLibraryGDAMock {
+ using SuperTokenV1Library for ISuperToken;
+ //// View Functions ////
+
+ function getFlowDistributionFlowRateTest(ISuperToken token, address from, ISuperfluidPool to)
+ external
+ view
+ returns (int96)
+ {
+ return token.getFlowDistributionFlowRate(from, to);
+ }
+
+ function estimateFlowDistributionActualFlowRateTest(
+ ISuperToken token,
+ address from,
+ ISuperfluidPool to,
+ int96 requestedFlowRate
+ ) external view returns (int96 actualFlowRate, int96 totalDistributionFlowRate) {
+ return token.estimateFlowDistributionActualFlowRate(from, to, requestedFlowRate);
+ }
+ function estimateDistributionActualAmountTest(
+ ISuperToken token,
+ address from,
+ ISuperfluidPool to,
+ uint256 requestedAmount
+ ) external view returns (uint256 actualAmount) {
+ return token.estimateDistributionActualAmount(from, to, requestedAmount);
+ }
+
+ function isMemberConnectedTest(ISuperToken token, address pool, address member)
+ external
+ view
+ returns (bool)
+ {
+ return token.isMemberConnected(pool, member);
+ }
+
+ //// Admin/Distributor Operations ////
+
+ function createPoolTest(ISuperToken token, address admin, PoolConfig memory config)
+ external
+ {
+ token.createPool(admin, config);
+ }
+
+ function distributeToPoolTest(ISuperToken token, address from, ISuperfluidPool pool, uint256 requestedAmount)
+ external
+ {
+ token.distributeToPool(from, pool, requestedAmount);
+ }
+
+ function distributeFlowTest(ISuperToken token, address from, ISuperfluidPool pool, int96 requestedFlowRate)
+ external
+ {
+ token.distributeFlow(from, pool, requestedFlowRate);
+ }
+
+ //// Member Operations ////
+
+ function connectPoolTest(ISuperToken token, ISuperfluidPool pool) external {
+ token.connectPool(pool);
+ }
+
+ function disconnectPoolTest(ISuperToken token, ISuperfluidPool pool) external {
+ token.disconnectPool(pool);
+ }
+}
+
contract SuperTokenLibraryCFASuperAppMock is SuperAppBase {
using SuperTokenV1Library for ISuperToken;
@@ -499,7 +569,7 @@ contract SuperTokenLibraryCFASuperAppMock is SuperAppBase {
address internal immutable sender;
address internal immutable receiver;
address internal immutable flowOperator;
- ISuperfluid internal host;
+ ISuperfluid internal immutable host;
// for selectively testing functions in the same callback
enum FunctionIndex {
@@ -593,16 +663,13 @@ contract SuperTokenLibraryIDASuperAppMock is SuperTokenLibraryIDAMock, SuperAppB
using SuperTokenV1Library for ISuperToken;
- bytes internal constant _MOCK_USER_DATA = abi.encode("oh hello");
- ISuperfluid internal host;
+ ISuperfluid internal immutable host;
constructor(ISuperfluid _host) SuperTokenLibraryIDAMock() {
host = _host;
uint256 configWord = SuperAppDefinitions.APP_LEVEL_FINAL |
SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP |
- // SuperAppDefinitions.AFTER_AGREEMENT_CREATED_NOOP |
SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP |
- // SuperAppDefinitions.AFTER_AGREEMENT_UPDATED_NOOP |
SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP |
SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP;
@@ -729,3 +796,84 @@ contract SuperTokenLibraryIDASuperAppMock is SuperTokenLibraryIDAMock, SuperAppB
}
}
}
+
+// GDA LIBRARY SUPER APP CALLBACK MOCK
+contract SuperTokenLibraryGDASuperAppMock is SuperTokenLibraryGDAMock, SuperAppBase {
+ using SuperTokenV1Library for ISuperToken;
+
+ ISuperfluid internal immutable host;
+
+ constructor(ISuperfluid _host) {
+ host = _host;
+ uint256 configWord = SuperAppDefinitions.APP_LEVEL_FINAL | SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP
+ | SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP | SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP
+ | SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP;
+
+ host.registerAppWithKey(configWord, "");
+ }
+
+ function afterAgreementCreated(
+ ISuperToken token,
+ address,
+ bytes32,
+ bytes calldata,
+ bytes calldata,
+ bytes calldata ctx
+ ) external override returns (bytes memory newCtx) {
+ return _callbackTest(token, ctx);
+ }
+
+ function afterAgreementUpdated(
+ ISuperToken token,
+ address,
+ bytes32,
+ bytes calldata,
+ bytes calldata,
+ bytes calldata ctx
+ ) external override returns (bytes memory newCtx) {
+ return _callbackTest(token, ctx);
+ }
+
+ enum FunctionIndex {
+ UPDATE_MEMBER_UNITS,
+ CONNECT_POOL,
+ DISCONNECT_POOL,
+ CLAIM_ALL,
+ DISTRIBUTE,
+ DISTRIBUTE_FLOW
+ }
+
+ /// @dev extracts some user data to test out all callback library functions
+ /// @param token super token
+ /// @param ctx Context string
+ /// @return New Context
+ function _callbackTest(ISuperToken token, bytes memory ctx) internal returns (bytes memory) {
+ // extract userData, then decode everything else
+ bytes memory userData = host.decodeCtx(ctx).userData;
+ (
+ uint8 functionIndex,
+ address pool,
+ address member,
+ address from,
+ uint128 units,
+ uint256 requestedAmount,
+ int96 requestedFlowRate
+ ) = abi.decode(userData, (uint8, address, address, address, uint128, uint256, int96));
+
+ if (functionIndex == uint8(FunctionIndex.UPDATE_MEMBER_UNITS)) {
+ return token.updateMemberUnitsWithCtx(ISuperfluidPool(pool), member, units, ctx);
+ } else if (functionIndex == uint8(FunctionIndex.CONNECT_POOL)) {
+ return token.connectPoolWithCtx(ISuperfluidPool(pool), ctx);
+ } else if (functionIndex == uint8(FunctionIndex.DISCONNECT_POOL)) {
+ return token.disconnectPoolWithCtx(ISuperfluidPool(pool), ctx);
+ } else if (functionIndex == uint8(FunctionIndex.CLAIM_ALL)) {
+ return token.claimAllWithCtx(ISuperfluidPool(pool), member, ctx);
+ } else if (functionIndex == uint8(FunctionIndex.DISTRIBUTE)) {
+ return token.distributeWithCtx(ISuperfluidPool(pool), from, requestedAmount, ctx);
+ } else if (functionIndex == uint8(FunctionIndex.DISTRIBUTE_FLOW)) {
+ return token.distributeFlowWithCtx(from, ISuperfluidPool(pool), requestedFlowRate, ctx);
+ } else {
+ revert("invalid function index");
+ }
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.sol
index f70d5c4b95..7e336c249f 100644
--- a/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.sol
+++ b/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.sol
@@ -1,18 +1,25 @@
// SPDX-License-Identifier: AGPLv3
pragma solidity 0.8.19;
-import { ISuperfluid, IConstantInflowNFT, IConstantOutflowNFT } from "../interfaces/superfluid/ISuperfluid.sol";
+import {
+ ISuperfluid, IERC20, IConstantInflowNFT, IConstantOutflowNFT, IPoolAdminNFT, IPoolMemberNFT
+} from "../interfaces/superfluid/ISuperfluid.sol";
+import { UUPSProxiable } from "../upgradability/UUPSProxiable.sol";
+import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+import { ERC777Helper } from "../libs/ERC777Helper.sol";
import { SuperToken } from "../superfluid/SuperToken.sol";
+import { SuperfluidToken } from "../superfluid/SuperfluidToken.sol";
contract SuperTokenStorageLayoutTester is SuperToken {
-
constructor(
ISuperfluid host,
IConstantOutflowNFT constantOutflowNFTProxy,
- IConstantInflowNFT constantInflowNFTProxy
- )
- SuperToken(host, constantOutflowNFTProxy, constantInflowNFTProxy) // solhint-disable-next-line no-empty-blocks
- {}
+ IConstantInflowNFT constantInflowNFTProxy,
+ IPoolAdminNFT poolAdminNFTProxy,
+ IPoolMemberNFT poolMemberNFTProxy
+ ) SuperToken(host, constantOutflowNFTProxy, constantInflowNFTProxy, poolAdminNFTProxy, poolMemberNFTProxy)
+ // solhint-disable-next-line no-empty-blocks
+ { }
// @dev Make sure the storage layout never change over the course of the development
function validateStorageLayout() external pure {
@@ -72,15 +79,16 @@ contract SuperTokenStorageLayoutTester is SuperToken {
}
contract SuperTokenMock is SuperToken {
-
- uint256 immutable public waterMark;
+ uint256 public immutable waterMark;
constructor(
ISuperfluid host,
uint256 w,
IConstantOutflowNFT constantOutflowNFTProxy,
- IConstantInflowNFT constantInflowNFTProxy
- ) SuperToken(host, constantOutflowNFTProxy, constantInflowNFTProxy) {
+ IConstantInflowNFT constantInflowNFTProxy,
+ IPoolAdminNFT poolAdminNFTProxy,
+ IPoolMemberNFT poolMemberNFTProxy
+ ) SuperToken(host, constantOutflowNFTProxy, constantInflowNFTProxy, poolAdminNFTProxy, poolMemberNFTProxy) {
waterMark = w;
}
@@ -102,12 +110,134 @@ contract SuperTokenMock is SuperToken {
_setupDefaultOperators(operators);
}
- function mintInternal(
+ function mintInternal(address to, uint256 amount, bytes memory userData, bytes memory operatorData) external {
+ _mint(msg.sender, to, amount, true, /* invokeHook */ true, /* requireReceptionAck */ userData, operatorData);
+ }
+}
+
+/// @title NoNFTSuperTokenMock
+/// @author Superfluid
+/// @notice Minimal SuperToken implementation to test flow creation if no NFT proxy contract variable exists.
+/// Storage layout is made to mimic SuperToken.
+contract NoNFTSuperTokenMock is UUPSProxiable, SuperfluidToken {
+ using SafeERC20 for IERC20;
+
+ /// @dev The underlying ERC20 token
+ IERC20 internal _underlyingToken;
+
+ /// @dev Decimals of the underlying token
+ uint8 internal _underlyingDecimals;
+
+ /// @dev TokenInfo Name property
+ string internal _name;
+
+ /// @dev TokenInfo Symbol property
+ string internal _symbol;
+
+ /// @dev ERC20 Allowances Storage
+ mapping(address => mapping(address => uint256)) internal _allowances;
+
+ /// @dev ERC777 operators support data
+ ERC777Helper.Operators internal _operators;
+
+ constructor(ISuperfluid host) SuperfluidToken(host) { }
+
+ /// @dev Initialize the Super Token proxy
+ function initialize(IERC20 underlyingToken, uint8 underlyingDecimals, string calldata n, string calldata s)
+ external
+ initializer // OpenZeppelin Initializable
+ {
+ _underlyingToken = underlyingToken;
+ _underlyingDecimals = underlyingDecimals;
+
+ _name = n;
+ _symbol = s;
+
+ // register interfaces
+ ERC777Helper.register(address(this));
+ }
+
+ /// @dev ISuperToken.upgrade implementation
+ function upgrade(uint256 amount) external {
+ _upgrade(msg.sender, msg.sender, msg.sender, amount, "", "");
+ }
+
+ /**
+ * @dev Handle decimal differences between underlying token and super token
+ */
+ function _toUnderlyingAmount(uint256 amount)
+ private
+ view
+ returns (uint256 underlyingAmount, uint256 adjustedAmount)
+ {
+ uint256 factor;
+ if (_underlyingDecimals < 18) {
+ // if underlying has less decimals
+ // one can upgrade less "granualar" amount of tokens
+ factor = 10 ** (18 - _underlyingDecimals);
+ underlyingAmount = amount / factor;
+ // remove precision errors
+ adjustedAmount = underlyingAmount * factor;
+ } else if (_underlyingDecimals > 18) {
+ // if underlying has more decimals
+ // one can upgrade more "granualar" amount of tokens
+ factor = 10 ** (_underlyingDecimals - 18);
+ underlyingAmount = amount * factor;
+ adjustedAmount = amount;
+ } else {
+ underlyingAmount = adjustedAmount = amount;
+ }
+ }
+
+ function _upgrade(
+ address operator,
+ address account,
address to,
uint256 amount,
bytes memory userData,
bytes memory operatorData
- ) external {
- _mint(msg.sender, to, amount, true /* invokeHook */, true /* requireReceptionAck */, userData, operatorData);
+ ) private {
+ if (address(_underlyingToken) == address(0)) revert("");
+
+ (uint256 underlyingAmount, uint256 adjustedAmount) = _toUnderlyingAmount(amount);
+
+ uint256 amountBefore = _underlyingToken.balanceOf(address(this));
+ _underlyingToken.safeTransferFrom(account, address(this), underlyingAmount);
+ uint256 amountAfter = _underlyingToken.balanceOf(address(this));
+ uint256 actualUpgradedAmount = amountAfter - amountBefore;
+ if (underlyingAmount != actualUpgradedAmount) revert("");
+
+ _mint(
+ operator,
+ to,
+ adjustedAmount,
+ // if `userData.length` than 0, we requireReceptionAck
+ userData.length != 0,
+ userData,
+ operatorData
+ );
+ }
+
+ /// dummy impl
+ function _mint(
+ address, // operator,
+ address account,
+ uint256 amount,
+ bool, // requireReceptionAck,
+ bytes memory, // userData,
+ bytes memory // operatorData
+ ) internal {
+ if (account == address(0)) {
+ revert("");
+ }
+
+ SuperfluidToken._mint(account, amount);
}
+
+ function proxiableUUID() public pure override returns (bytes32) {
+ return keccak256("org.superfluid-finance.contracts.SuperToken.implementation");
+ }
+
+ // solhint-disable-next-line no-empty-blocks
+ function updateCode(address newAddress) external override { }
}
diff --git a/packages/ethereum-contracts/contracts/mocks/SuperfluidPoolUpgradabilityMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperfluidPoolUpgradabilityMock.sol
new file mode 100644
index 0000000000..8af9ec6002
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/mocks/SuperfluidPoolUpgradabilityMock.sol
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { GeneralDistributionAgreementV1 } from "../agreements/gdav1/GeneralDistributionAgreementV1.sol";
+import { SuperfluidPool } from "../agreements/gdav1/SuperfluidPool.sol";
+import { IStorageLayoutBase } from "./IStorageLayoutBase.sol";
+
+/// @title SuperfluidPoolStorageLayoutMock
+/// @notice A mock SuperfluidPool contract for testing storage layout.
+/// @dev This contract *MUST* have the same storage layout as SuperfluidPool.
+contract SuperfluidPoolStorageLayoutMock is SuperfluidPool, IStorageLayoutBase {
+ constructor(GeneralDistributionAgreementV1 gda_) SuperfluidPool(gda_) { }
+
+ function validateStorageLayout() public pure {
+ uint256 slot;
+ uint256 offset;
+
+ // offset of 2 is taken by the following variables:
+ // Initializable._initialized (uint8) 1byte
+ // Initializable._initializing (bool) 1byte
+
+ assembly { slot := superToken.slot offset := superToken.offset }
+ if (slot != 0 || offset != 2) revert STORAGE_LOCATION_CHANGED("superToken");
+
+ assembly { slot := admin.slot offset := admin.offset }
+ if (slot != 1 || offset != 0) revert STORAGE_LOCATION_CHANGED("admin");
+
+ assembly { slot := _index.slot offset := _index.offset }
+ if (slot != 2 || offset != 0) revert STORAGE_LOCATION_CHANGED("_index");
+ // slot 2: uint128 total units | uint32 wrappedSettledAt | int96 wrappedFlowRate
+ // slot 3: int256 wrappedSettledValue
+
+ assembly { slot := _membersData.slot offset := _membersData.offset }
+ if (slot != 4 || offset != 0) revert STORAGE_LOCATION_CHANGED("_membersData");
+
+ assembly { slot := _disconnectedMembers.slot offset := _disconnectedMembers.offset }
+ if (slot != 5 || offset != 0) revert STORAGE_LOCATION_CHANGED("_disconnectedMembers");
+ // slot 5: uint128 ownedUnits | uint32 syncedSettledAt | int96 syncedFlowRate
+ // slot 6: int256 syncedSettledValue
+ // slot 7: int256 settledValue
+ // slot 8: int256 claimedValue
+
+ assembly { slot := _allowances.slot offset := _allowances.offset }
+ if (slot != 9 || offset != 0) revert STORAGE_LOCATION_CHANGED("_allowances");
+
+ assembly { slot := transferabilityForUnitsOwner.slot offset := transferabilityForUnitsOwner.offset }
+ if (slot != 10 || offset != 0) revert STORAGE_LOCATION_CHANGED("transferabilityForUnitsOwner");
+
+ assembly { slot := distributionFromAnyAddress.slot offset := distributionFromAnyAddress.offset }
+ if (slot != 10 || offset != 1) revert STORAGE_LOCATION_CHANGED("distributionFromAnyAddress");
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/superfluid/ConstantInflowNFT.sol b/packages/ethereum-contracts/contracts/superfluid/ConstantInflowNFT.sol
index b8ff39e181..5603aa8fca 100644
--- a/packages/ethereum-contracts/contracts/superfluid/ConstantInflowNFT.sol
+++ b/packages/ethereum-contracts/contracts/superfluid/ConstantInflowNFT.sol
@@ -15,18 +15,12 @@ contract ConstantInflowNFT is FlowNFTBase, IConstantInflowNFT {
IConstantOutflowNFT public immutable CONSTANT_OUTFLOW_NFT;
// solhint-disable-next-line no-empty-blocks
- constructor(
- ISuperfluid host,
- IConstantOutflowNFT constantOutflowNFT
- ) FlowNFTBase(host) {
+ constructor(ISuperfluid host, IConstantOutflowNFT constantOutflowNFT) FlowNFTBase(host) {
CONSTANT_OUTFLOW_NFT = constantOutflowNFT;
}
function proxiableUUID() public pure override returns (bytes32) {
- return
- keccak256(
- "org.superfluid-finance.contracts.ConstantInflowNFT.implementation"
- );
+ return keccak256("org.superfluid-finance.contracts.ConstantInflowNFT.implementation");
}
/// @notice The mint function emits the "mint" `Transfer` event.
@@ -35,10 +29,7 @@ contract ConstantInflowNFT is FlowNFTBase, IConstantInflowNFT {
/// Only callable by ConstantOutflowNFT
/// @param to the receiver of the inflow nft and desired flow receiver
/// @param newTokenId the new token id
- function mint(
- address to,
- uint256 newTokenId
- ) external onlyConstantOutflowNFT {
+ function mint(address to, uint256 newTokenId) external onlyConstantOutflowNFT {
_mint(to, newTokenId);
}
@@ -51,9 +42,7 @@ contract ConstantInflowNFT is FlowNFTBase, IConstantInflowNFT {
_burn(tokenId);
}
- function flowDataByTokenId(
- uint256 tokenId
- )
+ function flowDataByTokenId(uint256 tokenId)
public
view
override(FlowNFTBase, IFlowNFTBase)
@@ -62,21 +51,12 @@ contract ConstantInflowNFT is FlowNFTBase, IConstantInflowNFT {
flowData = CONSTANT_OUTFLOW_NFT.flowDataByTokenId(tokenId);
}
- function tokenURI(
- uint256 tokenId
- )
- external
- view
- override(FlowNFTBase, IERC721Metadata)
- returns (string memory)
- {
+ function tokenURI(uint256 tokenId) external view override(FlowNFTBase, IERC721Metadata) returns (string memory) {
return _tokenURI(tokenId, true);
}
/// @inheritdoc FlowNFTBase
- function _ownerOf(
- uint256 tokenId
- ) internal view virtual override returns (address) {
+ function _ownerOf(uint256 tokenId) internal view override returns (address) {
FlowNFTData memory flowData = flowDataByTokenId(tokenId);
return flowData.flowReceiver;
}
@@ -87,7 +67,7 @@ contract ConstantInflowNFT is FlowNFTBase, IConstantInflowNFT {
address, // from,
address, // to,
uint256 // tokenId
- ) internal virtual override {
+ ) internal pure override {
revert CFA_NFT_TRANSFER_IS_NOT_ALLOWED();
}
diff --git a/packages/ethereum-contracts/contracts/superfluid/ConstantOutflowNFT.sol b/packages/ethereum-contracts/contracts/superfluid/ConstantOutflowNFT.sol
index 01409d3d3d..63e311d42d 100644
--- a/packages/ethereum-contracts/contracts/superfluid/ConstantOutflowNFT.sol
+++ b/packages/ethereum-contracts/contracts/superfluid/ConstantOutflowNFT.sol
@@ -22,27 +22,19 @@ contract ConstantOutflowNFT is FlowNFTBase, IConstantOutflowNFT {
mapping(uint256 => FlowNFTData) internal _flowDataByTokenId;
// solhint-disable-next-line no-empty-blocks
- constructor(
- ISuperfluid host,
- IConstantInflowNFT constantInflowNFT
- ) FlowNFTBase(host) {
+ constructor(ISuperfluid host, IConstantInflowNFT constantInflowNFT) FlowNFTBase(host) {
CONSTANT_INFLOW_NFT = constantInflowNFT;
}
// note that this is used so we don't upgrade to wrong logic contract
function proxiableUUID() public pure override returns (bytes32) {
- return
- keccak256(
- "org.superfluid-finance.contracts.ConstantOutflowNFT.implementation"
- );
+ return keccak256("org.superfluid-finance.contracts.ConstantOutflowNFT.implementation");
}
/// @notice An external function for querying flow data by `tokenId``
/// @param tokenId the token id
/// @return flowData the flow data associated with `tokenId`
- function flowDataByTokenId(
- uint256 tokenId
- )
+ function flowDataByTokenId(uint256 tokenId)
public
view
override(FlowNFTBase, IFlowNFTBase)
@@ -51,14 +43,7 @@ contract ConstantOutflowNFT is FlowNFTBase, IConstantOutflowNFT {
flowData = _flowDataByTokenId[tokenId];
}
- function tokenURI(
- uint256 tokenId
- )
- external
- view
- override(FlowNFTBase, IERC721Metadata)
- returns (string memory)
- {
+ function tokenURI(uint256 tokenId) external view override(FlowNFTBase, IERC721Metadata) returns (string memory) {
return _tokenURI(tokenId, false);
}
@@ -68,18 +53,13 @@ contract ConstantOutflowNFT is FlowNFTBase, IConstantOutflowNFT {
/// @param flowSender the flow sender
/// @param flowReceiver the flow receiver
/// NOTE: We do an existence check in here to determine whether or not to execute the hook
- function onCreate(
- ISuperfluidToken superToken,
- address flowSender,
- address flowReceiver
- ) external onlyFlowAgreements {
+ function onCreate(ISuperfluidToken superToken, address flowSender, address flowReceiver)
+ external
+ onlyFlowAgreements
+ {
// we don't check matching super token because the nft token id
// is generated based on the superToken
- uint256 newTokenId = _getTokenId(
- address(superToken),
- flowSender,
- flowReceiver
- );
+ uint256 newTokenId = _getTokenId(address(superToken), flowSender, flowReceiver);
if (_flowDataByTokenId[newTokenId].flowSender == address(0)) {
_mint(address(superToken), flowSender, flowReceiver, newTokenId);
@@ -93,16 +73,11 @@ contract ConstantOutflowNFT is FlowNFTBase, IConstantOutflowNFT {
/// @param flowSender the flow sender
/// @param flowReceiver the flow receiver
/// NOTE: We do an existence check in here to determine whether or not to execute the hook
- function onUpdate(
- ISuperfluidToken superToken,
- address flowSender,
- address flowReceiver
- ) external onlyFlowAgreements {
- uint256 tokenId = _getTokenId(
- address(superToken),
- flowSender,
- flowReceiver
- );
+ function onUpdate(ISuperfluidToken superToken, address flowSender, address flowReceiver)
+ external
+ onlyFlowAgreements
+ {
+ uint256 tokenId = _getTokenId(address(superToken), flowSender, flowReceiver);
if (_flowDataByTokenId[tokenId].flowSender != address(0)) {
_triggerMetadataUpdate(tokenId);
@@ -116,16 +91,11 @@ contract ConstantOutflowNFT is FlowNFTBase, IConstantOutflowNFT {
/// @param flowSender the flow sender
/// @param flowReceiver the flow receiver
/// NOTE: We do an existence check in here to determine whether or not to execute the hook
- function onDelete(
- ISuperfluidToken superToken,
- address flowSender,
- address flowReceiver
- ) external onlyFlowAgreements {
- uint256 tokenId = _getTokenId(
- address(superToken),
- flowSender,
- flowReceiver
- );
+ function onDelete(ISuperfluidToken superToken, address flowSender, address flowReceiver)
+ external
+ onlyFlowAgreements
+ {
+ uint256 tokenId = _getTokenId(address(superToken), flowSender, flowReceiver);
if (_flowDataByTokenId[tokenId].flowSender != address(0)) {
// must "burn" inflow NFT first because we clear storage when burning outflow NFT
@@ -136,9 +106,7 @@ contract ConstantOutflowNFT is FlowNFTBase, IConstantOutflowNFT {
}
/// @inheritdoc FlowNFTBase
- function _ownerOf(
- uint256 tokenId
- ) internal view virtual override returns (address) {
+ function _ownerOf(uint256 tokenId) internal view override returns (address) {
return _flowDataByTokenId[tokenId].flowSender;
}
@@ -148,7 +116,7 @@ contract ConstantOutflowNFT is FlowNFTBase, IConstantOutflowNFT {
address, // from,
address, // to,
uint256 // tokenId
- ) internal virtual override {
+ ) internal pure override {
revert CFA_NFT_TRANSFER_IS_NOT_ALLOWED();
}
@@ -159,12 +127,7 @@ contract ConstantOutflowNFT is FlowNFTBase, IConstantOutflowNFT {
/// @param flowSender the receiver of the newly minted outflow nft (to)
/// @param flowReceiver the flow receiver (owner of the InflowNFT)
/// @param newTokenId the new token id to be minted
- function _mint(
- address superToken,
- address flowSender,
- address flowReceiver,
- uint256 newTokenId
- ) internal {
+ function _mint(address superToken, address flowSender, address flowReceiver, uint256 newTokenId) internal {
assert(flowSender != address(0));
assert(flowSender != flowReceiver);
assert(!_exists(newTokenId));
@@ -197,7 +160,10 @@ contract ConstantOutflowNFT is FlowNFTBase, IConstantOutflowNFT {
}
modifier onlyFlowAgreements() {
- if (msg.sender != address(CONSTANT_FLOW_AGREEMENT_V1)) {
+ if (
+ msg.sender != address(CONSTANT_FLOW_AGREEMENT_V1)
+ && msg.sender != address(GENERAL_DISTRIBUTION_AGREEMENT_V1)
+ ) {
revert COF_NFT_ONLY_FLOW_AGREEMENTS();
}
_;
diff --git a/packages/ethereum-contracts/contracts/superfluid/FlowNFTBase.sol b/packages/ethereum-contracts/contracts/superfluid/FlowNFTBase.sol
index 58a3c8faee..a67ca5c55c 100644
--- a/packages/ethereum-contracts/contracts/superfluid/FlowNFTBase.sol
+++ b/packages/ethereum-contracts/contracts/superfluid/FlowNFTBase.sol
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: AGPLv3
pragma solidity 0.8.19;
-// We use reserved slots for upgradable contracts.
// solhint-disable max-states-count
+// Notes: We use reserved slots for upgradable contracts.
// They are used in solidity docs.
import {
@@ -13,7 +13,8 @@ import {
import { UUPSProxiable } from "../upgradability/UUPSProxiable.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import {
- ISuperfluid, ISuperToken, ISuperTokenFactory, IFlowNFTBase, IConstantFlowAgreementV1
+ ISuperfluid, ISuperToken, ISuperTokenFactory, IFlowNFTBase,
+ IConstantFlowAgreementV1, IGeneralDistributionAgreementV1
} from "../interfaces/superfluid/ISuperfluid.sol";
/// @title FlowNFTBase abstract contract
@@ -37,6 +38,12 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
// solhint-disable-next-line var-name-mixedcase
IConstantFlowAgreementV1 public immutable CONSTANT_FLOW_AGREEMENT_V1;
+ /// @notice GeneralDistributionAgreementV1 contract address
+ /// @dev This is the address of the GDAv1 contract cached so we don't have to
+ /// do an external call for every flow created.
+ // solhint-disable-next-line var-name-mixedcase
+ IGeneralDistributionAgreementV1 public immutable GENERAL_DISTRIBUTION_AGREEMENT_V1;
+
/// @notice Superfluid host contract address
ISuperfluid public immutable HOST;
@@ -66,7 +73,7 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
/// without having to worry about messing up the storage layout that exists in COFNFT or CIFNFT.
/// @dev This empty reserved space is put in place to allow future versions to add new
/// variables without shifting down storage in the inheritance chain.
- /// Slots 6-21 are reserved for future use.
+ /// Slots 5-21 are reserved for future use.
/// We use this pattern in SuperToken.sol and favor this over the OpenZeppelin pattern
/// as this prevents silly footgunning.
/// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
@@ -97,12 +104,16 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
)
)
);
+ GENERAL_DISTRIBUTION_AGREEMENT_V1 = IGeneralDistributionAgreementV1(
+ address(
+ ISuperfluid(host).getAgreementClass(
+ keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1")
+ )
+ )
+ );
}
- function initialize(
- string memory nftName,
- string memory nftSymbol
- )
+ function initialize(string memory nftName, string memory nftSymbol)
external
override
initializer // OpenZeppelin Initializable
@@ -132,19 +143,14 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
/// @param interfaceId the XOR of all function selectors in the interface
/// @return boolean true if the interface is supported
/// @inheritdoc IERC165
- function supportsInterface(
- bytes4 interfaceId
- ) external pure virtual override returns (bool) {
- return
- interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
- interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
- interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
+ function supportsInterface(bytes4 interfaceId) external pure virtual override returns (bool) {
+ return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165
+ || interfaceId == 0x80ac58cd // ERC165 Interface ID for ERC721
+ || interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
}
/// @inheritdoc IERC721
- function ownerOf(
- uint256 tokenId
- ) public view virtual override returns (address) {
+ function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _ownerOf(tokenId);
if (owner == address(0)) {
revert CFA_NFT_INVALID_TOKEN_ID();
@@ -178,23 +184,14 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
/// @notice This returns the Uniform Resource Identifier (URI), where the metadata for the NFT lives.
/// @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
/// @return the token URI
- function tokenURI(
- uint256 tokenId
- ) external view virtual returns (string memory);
+ function tokenURI(uint256 tokenId) external view virtual returns (string memory);
- function _tokenURI(
- uint256 tokenId,
- bool isInflow
- ) internal view virtual returns (string memory) {
+ function _tokenURI(uint256 tokenId, bool isInflow) internal view virtual returns (string memory) {
FlowNFTData memory flowData = flowDataByTokenId(tokenId);
ISuperToken token = ISuperToken(flowData.superToken);
- (, int96 flowRate, , ) = CONSTANT_FLOW_AGREEMENT_V1.getFlow(
- token,
- flowData.flowSender,
- flowData.flowReceiver
- );
+ (, int96 flowRate,,) = CONSTANT_FLOW_AGREEMENT_V1.getFlow(token, flowData.flowSender, flowData.flowReceiver);
return
string(
@@ -209,44 +206,31 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
);
}
- function _flowDataString(
- uint256 tokenId
- ) internal view returns (string memory) {
+ function _flowDataString(uint256 tokenId) internal view returns (string memory) {
FlowNFTData memory flowData = flowDataByTokenId(tokenId);
// @note taking this out to deal with the stack too deep issue
// which occurs when you are attempting to abi.encodePacked
// too many elements
- return
- string(
- abi.encodePacked(
- "&token_address=",
- Strings.toHexString(
- uint256(uint160(flowData.superToken)),
- 20
- ),
- "&chain_id=",
- block.chainid.toString(),
- "&token_symbol=",
- ISuperToken(flowData.superToken).symbol(),
- "&sender=",
- Strings.toHexString(
- uint256(uint160(flowData.flowSender)),
- 20
- ),
- "&receiver=",
- Strings.toHexString(
- uint256(uint160(flowData.flowReceiver)),
- 20
- ),
- "&token_decimals=",
- uint256(ISuperToken(flowData.superToken).decimals())
- .toString(),
- "&start_date=",
- // @note upcasting is safe
- uint256(flowData.flowStartDate).toString()
- )
- );
+ return string(
+ abi.encodePacked(
+ "&token_address=",
+ Strings.toHexString(uint256(uint160(flowData.superToken)), 20),
+ "&chain_id=",
+ block.chainid.toString(),
+ "&token_symbol=",
+ ISuperToken(flowData.superToken).symbol(),
+ "&sender=",
+ Strings.toHexString(uint256(uint160(flowData.flowSender)), 20),
+ "&receiver=",
+ Strings.toHexString(uint256(uint160(flowData.flowReceiver)), 20),
+ "&token_decimals=",
+ uint256(ISuperToken(flowData.superToken).decimals()).toString(),
+ "&start_date=",
+ // @note upcasting is safe
+ uint256(flowData.flowStartDate).toString()
+ )
+ );
}
/// @inheritdoc IERC721
@@ -264,55 +248,37 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
}
/// @inheritdoc IFlowNFTBase
- function getTokenId(
- address superToken,
- address sender,
- address receiver
- ) external view returns (uint256 tokenId) {
+ function getTokenId(address superToken, address sender, address receiver) external view returns (uint256 tokenId) {
tokenId = _getTokenId(superToken, sender, receiver);
}
- function _getTokenId(
- address superToken,
- address sender,
- address receiver
- ) internal view returns (uint256 tokenId) {
- tokenId = uint256(
- keccak256(abi.encode(block.chainid, superToken, sender, receiver))
- );
+ function _getTokenId(address superToken, address sender, address receiver)
+ internal
+ view
+ returns (uint256 tokenId)
+ {
+ tokenId = uint256(keccak256(abi.encode(block.chainid, superToken, sender, receiver)));
}
/// @inheritdoc IERC721
- function getApproved(
- uint256 tokenId
- ) public view virtual override returns (address) {
+ function getApproved(uint256 tokenId) public view virtual override returns (address) {
_requireMinted(tokenId);
return _tokenApprovals[tokenId];
}
/// @inheritdoc IERC721
- function setApprovalForAll(
- address operator,
- bool approved
- ) external virtual override {
+ function setApprovalForAll(address operator, bool approved) external virtual override {
_setApprovalForAll(msg.sender, operator, approved);
}
/// @inheritdoc IERC721
- function isApprovedForAll(
- address owner,
- address operator
- ) public view virtual override returns (bool) {
+ function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
/// @inheritdoc IERC721
- function transferFrom(
- address from,
- address to,
- uint256 tokenId
- ) external virtual override {
+ function transferFrom(address from, address to, uint256 tokenId) external virtual override {
if (!_isApprovedOrOwner(msg.sender, tokenId)) {
revert CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL();
}
@@ -321,21 +287,12 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
}
/// @inheritdoc IERC721
- function safeTransferFrom(
- address from,
- address to,
- uint256 tokenId
- ) external virtual override {
+ function safeTransferFrom(address from, address to, uint256 tokenId) external virtual override {
safeTransferFrom(from, to, tokenId, "");
}
/// @inheritdoc IERC721
- function safeTransferFrom(
- address from,
- address to,
- uint256 tokenId,
- bytes memory data
- ) public virtual override {
+ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
if (!_isApprovedOrOwner(msg.sender, tokenId)) {
revert CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL();
}
@@ -348,14 +305,9 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
/// @param spender the spender of the token
/// @param tokenId the id of the token to be spent
/// @return whether `tokenId` can be spent by `spender`
- function _isApprovedOrOwner(
- address spender,
- uint256 tokenId
- ) internal view returns (bool) {
+ function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
address owner = FlowNFTBase.ownerOf(tokenId);
- return (spender == owner ||
- isApprovedForAll(owner, spender) ||
- getApproved(tokenId) == spender);
+ return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}
/// @notice Reverts if `tokenId` doesn't exist
@@ -365,8 +317,7 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
}
/// @notice Returns whether `tokenId` exists
- /// @dev Explain to a developer any extra details
- /// Tokens can be managed by their owner or approved accounts via `approve` or `setApprovalForAll`.
+ /// @dev Tokens can be managed by their owner or approved accounts via `approve` or `setApprovalForAll`.
/// Tokens start existing when they are minted (`_mint`),
/// and stop existing when they are burned (`_burn`).
/// @param tokenId the token id we're interested in seeing if exists
@@ -385,11 +336,7 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
emit Approval(_ownerOf(tokenId), to, tokenId);
}
- function _setApprovalForAll(
- address owner,
- address operator,
- bool approved
- ) internal {
+ function _setApprovalForAll(address owner, address operator, bool approved) internal {
if (owner == operator) revert CFA_NFT_APPROVE_TO_CALLER();
_operatorApprovals[owner][operator] = approved;
@@ -400,20 +347,14 @@ abstract contract FlowNFTBase is UUPSProxiable, IFlowNFTBase {
/// @dev Returns the flow data of the `tokenId`. Does NOT revert if token doesn't exist.
/// @param tokenId the token id whose existence we're checking
/// @return flowData the FlowNFTData struct for `tokenId`
- function flowDataByTokenId(
- uint256 tokenId
- ) public view virtual returns (FlowNFTData memory flowData);
+ function flowDataByTokenId(uint256 tokenId) public view virtual returns (FlowNFTData memory flowData);
/// @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist.
/// @param tokenId the token id whose existence we're checking
/// @return address the address of the owner of `tokenId`
function _ownerOf(uint256 tokenId) internal view virtual returns (address);
- function _transfer(
- address from,
- address to,
- uint256 tokenId
- ) internal virtual;
+ function _transfer(address from, address to, uint256 tokenId) internal virtual;
function _safeTransfer(
address from,
diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol
index 00a4e3f950..2e253d62a9 100644
--- a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol
+++ b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol
@@ -10,7 +10,9 @@ import {
ISuperToken,
IERC20,
IConstantOutflowNFT,
- IConstantInflowNFT
+ IConstantInflowNFT,
+ IPoolAdminNFT,
+ IPoolMemberNFT
} from "../interfaces/superfluid/ISuperfluid.sol";
import { SuperfluidToken } from "./SuperfluidToken.sol";
import { ERC777Helper } from "../libs/ERC777Helper.sol";
@@ -49,6 +51,12 @@ contract SuperToken is
// solhint-disable-next-line var-name-mixedcase
IConstantInflowNFT immutable public CONSTANT_INFLOW_NFT;
+ // solhint-disable-next-line var-name-mixedcase
+ IPoolMemberNFT immutable public POOL_MEMBER_NFT;
+
+ // solhint-disable-next-line var-name-mixedcase
+ IPoolAdminNFT immutable public POOL_ADMIN_NFT;
+
/* WARNING: NEVER RE-ORDER VARIABLES! Including the base contracts.
Always double-check that new
variables are added APPEND-ONLY. Re-ordering variables can
@@ -96,7 +104,9 @@ contract SuperToken is
constructor(
ISuperfluid host,
IConstantOutflowNFT constantOutflowNFT,
- IConstantInflowNFT constantInflowNFT
+ IConstantInflowNFT constantInflowNFT,
+ IPoolAdminNFT poolAdminNFT,
+ IPoolMemberNFT poolMemberNFT
)
SuperfluidToken(host)
// solhint-disable-next-line no-empty-blocks
@@ -107,9 +117,14 @@ contract SuperToken is
// set the immutable canonical NFT proxy addresses
CONSTANT_OUTFLOW_NFT = constantOutflowNFT;
CONSTANT_INFLOW_NFT = constantInflowNFT;
+ POOL_ADMIN_NFT = poolAdminNFT;
+ POOL_MEMBER_NFT = poolMemberNFT;
emit ConstantOutflowNFTCreated(constantOutflowNFT);
emit ConstantInflowNFTCreated(constantInflowNFT);
+
+ emit PoolAdminNFTCreated(poolAdminNFT);
+ emit PoolMemberNFTCreated(poolMemberNFT);
}
/// @dev Initialize the Super Token proxy
diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol b/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol
index 9d4c09464e..7030064d4d 100644
--- a/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol
+++ b/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol
@@ -7,7 +7,9 @@ import {
ISuperTokenFactory,
ISuperToken
} from "../interfaces/superfluid/ISuperTokenFactory.sol";
-import { ISuperfluid, IConstantOutflowNFT, IConstantInflowNFT } from "../interfaces/superfluid/ISuperfluid.sol";
+import {
+ ISuperfluid, IConstantOutflowNFT, IConstantInflowNFT, IPoolAdminNFT, IPoolMemberNFT
+} from "../interfaces/superfluid/ISuperfluid.sol";
import { UUPSProxy } from "../upgradability/UUPSProxy.sol";
import { UUPSProxiable } from "../upgradability/UUPSProxiable.sol";
import { FullUpgradableSuperTokenProxy } from "./FullUpgradableSuperTokenProxy.sol";
@@ -36,6 +38,12 @@ abstract contract SuperTokenFactoryBase is
// solhint-disable-next-line var-name-mixedcase
IConstantInflowNFT immutable public CONSTANT_INFLOW_NFT_LOGIC;
+ // solhint-disable-next-line var-name-mixedcase
+ IPoolAdminNFT immutable public POOL_ADMIN_NFT_LOGIC;
+
+ // solhint-disable-next-line var-name-mixedcase
+ IPoolMemberNFT immutable public POOL_MEMBER_NFT_LOGIC;
+
/**************************************************************************
* Storage Variables
**************************************************************************/
@@ -66,7 +74,9 @@ abstract contract SuperTokenFactoryBase is
ISuperfluid host,
ISuperToken superTokenLogic,
IConstantOutflowNFT constantOutflowNFTLogic,
- IConstantInflowNFT constantInflowNFTLogic
+ IConstantInflowNFT constantInflowNFTLogic,
+ IPoolAdminNFT poolAdminNFTLogic,
+ IPoolMemberNFT poolMemberNFTLogic
) {
_host = host;
@@ -84,6 +94,10 @@ abstract contract SuperTokenFactoryBase is
CONSTANT_INFLOW_NFT_LOGIC = constantInflowNFTLogic;
+ POOL_ADMIN_NFT_LOGIC = poolAdminNFTLogic;
+
+ POOL_MEMBER_NFT_LOGIC = poolMemberNFTLogic;
+
// emit SuperTokenLogicCreated event
// note that creation here means the setting of the super token logic contract
// as the canonical super token logic for the Superfluid framework and not the
@@ -140,6 +154,18 @@ abstract contract SuperTokenFactoryBase is
if (address(CONSTANT_INFLOW_NFT_LOGIC) != newConstantInflowLogic) {
UUPSProxiable(address(_SUPER_TOKEN_LOGIC.CONSTANT_INFLOW_NFT())).updateCode(newConstantInflowLogic);
}
+
+ if (address(POOL_ADMIN_NFT_LOGIC) != address(newFactory.POOL_ADMIN_NFT_LOGIC())) {
+ UUPSProxiable(address(_SUPER_TOKEN_LOGIC.POOL_ADMIN_NFT())).updateCode(
+ address(newFactory.POOL_ADMIN_NFT_LOGIC())
+ );
+ }
+
+ if (address(POOL_MEMBER_NFT_LOGIC) != address(newFactory.POOL_MEMBER_NFT_LOGIC())) {
+ UUPSProxiable(address(_SUPER_TOKEN_LOGIC.POOL_MEMBER_NFT())).updateCode(
+ address(newFactory.POOL_MEMBER_NFT_LOGIC())
+ );
+ }
}
/**************************************************************************
@@ -397,13 +423,17 @@ contract SuperTokenFactory is SuperTokenFactoryBase
ISuperfluid host,
ISuperToken superTokenLogic,
IConstantOutflowNFT constantOutflowNFTLogic,
- IConstantInflowNFT constantInflowNFTLogic
+ IConstantInflowNFT constantInflowNFTLogic,
+ IPoolAdminNFT poolAdminNFTLogic,
+ IPoolMemberNFT poolMemberNFTLogic
)
SuperTokenFactoryBase(
host,
superTokenLogic,
constantOutflowNFTLogic,
- constantInflowNFTLogic
+ constantInflowNFTLogic,
+ poolAdminNFTLogic,
+ poolMemberNFTLogic
)
// solhint-disable-next-line no-empty-blocks
{}
diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol
index 4c32a8c965..2e0925450e 100644
--- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol
+++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol
@@ -19,7 +19,8 @@ import {
ISuperToken,
ISuperTokenFactory
} from "../interfaces/superfluid/ISuperfluid.sol";
-
+import { GeneralDistributionAgreementV1 } from "../agreements/gdav1/GeneralDistributionAgreementV1.sol";
+import { SuperfluidUpgradeableBeacon } from "../upgradability/SuperfluidUpgradeableBeacon.sol";
import { CallUtils } from "../libs/CallUtils.sol";
import { BaseRelayRecipient } from "../libs/BaseRelayRecipient.sol";
@@ -308,6 +309,23 @@ contract Superfluid is
token.changeAdmin(newAdmin);
}
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Superfluid Upgradeable Beacon
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /// @inheritdoc ISuperfluid
+ function updatePoolBeaconLogic(address newLogic) external override onlyGovernance {
+ GeneralDistributionAgreementV1 gda = GeneralDistributionAgreementV1(
+ address(
+ this.getAgreementClass(keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1"))
+ )
+ );
+ SuperfluidUpgradeableBeacon beacon = SuperfluidUpgradeableBeacon(address(gda.superfluidPoolBeacon()));
+ beacon.upgradeTo(newLogic);
+
+ emit PoolBeaconLogicUpdated(address(beacon), newLogic);
+ }
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// App Registry
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/packages/ethereum-contracts/contracts/upgradability/BeaconProxiable.sol b/packages/ethereum-contracts/contracts/upgradability/BeaconProxiable.sol
new file mode 100644
index 0000000000..ba692cdaad
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/upgradability/BeaconProxiable.sol
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
+
+abstract contract BeaconProxiable is Initializable {
+
+ // allows to mark logic contracts as initialized
+ // solhint-disable-next-line no-empty-blocks
+ function castrate() external initializer { }
+
+ /**
+ * @dev Proxiable UUID marker function, this would help to avoid wrong logic
+ * contract to be used for upgrading.
+ */
+ function proxiableUUID() public pure virtual returns (bytes32);
+}
\ No newline at end of file
diff --git a/packages/ethereum-contracts/contracts/upgradability/SuperfluidUpgradeableBeacon.sol b/packages/ethereum-contracts/contracts/upgradability/SuperfluidUpgradeableBeacon.sol
new file mode 100644
index 0000000000..126fc11e95
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/upgradability/SuperfluidUpgradeableBeacon.sol
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import {
+ UpgradeableBeacon
+} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
+import { BeaconProxiable } from "./BeaconProxiable.sol";
+
+contract SuperfluidUpgradeableBeacon is UpgradeableBeacon {
+ error ZERO_ADDRESS_IMPLEMENTATION(); // 0x80883162
+ error INCOMPATIBLE_LOGIC(); // 0x5af2144c
+ error NO_PROXY_LOOP(); // 0z66750bca
+
+ constructor(address implementation_) UpgradeableBeacon(implementation_) {}
+
+ function upgradeTo(address newImplementation) public override onlyOwner {
+ if (newImplementation == address(0)) {
+ revert ZERO_ADDRESS_IMPLEMENTATION();
+ }
+
+ if (newImplementation == address(this)) {
+ revert NO_PROXY_LOOP();
+ }
+
+ if (BeaconProxiable(newImplementation).proxiableUUID() != BeaconProxiable(implementation()).proxiableUUID()) {
+ revert INCOMPATIBLE_LOGIC();
+ }
+
+ super.upgradeTo(newImplementation);
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/upgradability/UUPSProxiable.sol b/packages/ethereum-contracts/contracts/upgradability/UUPSProxiable.sol
index 7a8f04dd10..1cc982cce6 100644
--- a/packages/ethereum-contracts/contracts/upgradability/UUPSProxiable.sol
+++ b/packages/ethereum-contracts/contracts/upgradability/UUPSProxiable.sol
@@ -19,7 +19,7 @@ abstract contract UUPSProxiable is Initializable {
function updateCode(address newAddress) external virtual;
- // allows to mark logic contracts as initialized in order to reduce the attack surface
+ // allows to mark logic contracts as initialized
// solhint-disable-next-line no-empty-blocks
function castrate() external initializer { }
diff --git a/packages/ethereum-contracts/contracts/utils/BatchLiquidator.sol b/packages/ethereum-contracts/contracts/utils/BatchLiquidator.sol
index 3c6b063e1f..77603cdc72 100644
--- a/packages/ethereum-contracts/contracts/utils/BatchLiquidator.sol
+++ b/packages/ethereum-contracts/contracts/utils/BatchLiquidator.sol
@@ -2,7 +2,8 @@
pragma solidity 0.8.19;
import {
- ISuperfluid, ISuperAgreement, ISuperToken, IConstantFlowAgreementV1
+ ISuperfluid, ISuperAgreement, ISuperToken, ISuperfluidPool,
+ IConstantFlowAgreementV1, IGeneralDistributionAgreementV1
} from "../interfaces/superfluid/ISuperfluid.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
@@ -14,54 +15,47 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
*/
contract BatchLiquidator {
+ enum FlowType {
+ ConstantFlowAgreement,
+ GeneralDistributionAgreement
+ }
- error ARRAY_SIZES_DIFFERENT();
+ struct FlowLiquidationData {
+ FlowType agreementOperation;
+ address sender;
+ address receiver;
+ }
address public immutable host;
address public immutable cfa;
+ address public immutable gda;
- constructor(address host_, address cfa_) {
+ constructor(address host_) {
host = host_;
- cfa = cfa_;
+ cfa = address(
+ ISuperfluid(host).getAgreementClass(keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1"))
+ );
+ gda = address(
+ ISuperfluid(host).getAgreementClass(
+ keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1")
+ )
+ );
}
/**
* @dev Delete flows in batch
* @param superToken - The super token the flows belong to.
- * @param senders - List of senders.
- * @param receivers - Corresponding list of receivers.
- * @return nSuccess - Number of succeeded deletions.
+ * @param data - The array of flow data to be deleted.
*/
- function deleteFlows(
- address superToken,
- address[] calldata senders, address[] calldata receivers
- ) external returns (uint nSuccess) {
- uint256 length = senders.length;
- if(length != receivers.length) revert ARRAY_SIZES_DIFFERENT();
- for (uint256 i; i < length;) {
+ function deleteFlows(address superToken, FlowLiquidationData[] memory data) external {
+ for (uint256 i; i < data.length;) {
// We tolerate any errors occured during liquidations.
// It could be due to flow had been liquidated by others.
- // solhint-disable-next-line avoid-low-level-calls
- (bool success,) = address(host).call(
- abi.encodeCall(
- ISuperfluid(host).callAgreement,
- (
- ISuperAgreement(cfa),
- abi.encodeCall(
- IConstantFlowAgreementV1(cfa).deleteFlow,
- (
- ISuperToken(superToken),
- senders[i],
- receivers[i],
- new bytes(0)
- )
- ),
- new bytes(0)
- )
- )
- );
- if (success) ++nSuccess;
- unchecked { i++; }
+ _deleteFlow(superToken, data[i]);
+
+ unchecked {
+ i++;
+ }
}
// If the liquidation(s) resulted in any super token
@@ -78,27 +72,14 @@ contract BatchLiquidator {
}
}
- // single flow delete with check for success
- function deleteFlow(address superToken, address sender, address receiver) external {
+ /**
+ * @dev Delete a single flow
+ * @param superToken - The super token the flow belongs to.
+ * @param data - The flow data to be deleted.
+ */
+ function deleteFlow(address superToken, FlowLiquidationData memory data) external {
/* solhint-disable */
- (bool success, bytes memory returndata) = address(host).call(
- abi.encodeCall(
- ISuperfluid(host).callAgreement,
- (
- ISuperAgreement(cfa),
- abi.encodeCall(
- IConstantFlowAgreementV1(cfa).deleteFlow,
- (
- ISuperToken(superToken),
- sender,
- receiver,
- new bytes(0)
- )
- ),
- new bytes(0)
- )
- )
- );
+ (bool success, bytes memory returndata) = _deleteFlow(superToken, data);
if (!success) {
if (returndata.length == 0) revert();
// solhint-disable
@@ -119,4 +100,41 @@ contract BatchLiquidator {
}
}
}
+
+ function _deleteFlow(address superToken, FlowLiquidationData memory data)
+ internal
+ returns (bool success, bytes memory returndata)
+ {
+ if (data.agreementOperation == FlowType.ConstantFlowAgreement) {
+ // solhint-disable-next-line avoid-low-level-calls
+ (success, returndata) = address(host).call(
+ abi.encodeCall(
+ ISuperfluid(host).callAgreement,
+ (
+ ISuperAgreement(cfa),
+ abi.encodeCall(
+ IConstantFlowAgreementV1(cfa).deleteFlow,
+ (ISuperToken(superToken), data.sender, data.receiver, new bytes(0))
+ ),
+ new bytes(0)
+ )
+ )
+ );
+ } else {
+ // solhint-disable-next-line avoid-low-level-calls
+ (success, returndata) = address(host).call(
+ abi.encodeCall(
+ ISuperfluid(host).callAgreement,
+ (
+ ISuperAgreement(gda),
+ abi.encodeCall(
+ IGeneralDistributionAgreementV1(gda).distributeFlow,
+ (ISuperToken(superToken), data.sender, ISuperfluidPool(data.receiver), 0, new bytes(0))
+ ),
+ new bytes(0)
+ )
+ )
+ );
+ }
+ }
}
diff --git a/packages/ethereum-contracts/contracts/utils/GDAv1Forwarder.sol b/packages/ethereum-contracts/contracts/utils/GDAv1Forwarder.sol
new file mode 100644
index 0000000000..dd5458d8f3
--- /dev/null
+++ b/packages/ethereum-contracts/contracts/utils/GDAv1Forwarder.sol
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { ISuperfluid, ISuperfluidToken } from "../interfaces/superfluid/ISuperfluid.sol";
+import { ISuperfluidPool } from "../agreements/gdav1/SuperfluidPool.sol";
+import {
+ IGeneralDistributionAgreementV1,
+ PoolConfig
+} from "../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
+import { ForwarderBase } from "./ForwarderBase.sol";
+
+/**
+ * @title GDAv1Forwarder
+ * @author Superfluid
+ * The GDAv1Forwarder contract provides an easy to use interface to
+ * GeneralDistributionAgreementV1 specific functionality of Super Tokens.
+ * Instances of this contract can operate on the protocol only if configured as "trusted forwarder"
+ * by protocol governance.
+ */
+contract GDAv1Forwarder is ForwarderBase {
+ IGeneralDistributionAgreementV1 internal immutable _gda;
+
+ // is tied to a specific instance of host and agreement contracts at deploy time
+ constructor(ISuperfluid host) ForwarderBase(host) {
+ _gda = IGeneralDistributionAgreementV1(
+ address(
+ _host.getAgreementClass(keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1"))
+ )
+ );
+ }
+
+ /**
+ * @dev Creates a new Superfluid Pool.
+ * @param token The Super Token address.
+ * @param admin The pool admin address.
+ * @param config The pool configuration (see PoolConfig in IGeneralDistributionAgreementV1.sol)
+ * @return success A boolean value indicating whether the pool was created successfully.
+ * @return pool The address of the deployed Superfluid Pool
+ */
+ function createPool(ISuperfluidToken token, address admin, PoolConfig memory config)
+ external
+ returns (bool success, ISuperfluidPool pool)
+ {
+ pool = _gda.createPool(token, admin, config);
+ success = true;
+ }
+
+ /**
+ * @dev Updates the units of a pool member.
+ * @param pool The Superfluid Pool to update.
+ * @param memberAddress The address of the member to update.
+ * @param newUnits The new units of the member.
+ * @param userData User-specific data.
+ */
+ function updateMemberUnits(ISuperfluidPool pool, address memberAddress, uint128 newUnits, bytes memory userData)
+ external
+ returns (bool success)
+ {
+ bytes memory callData = abi.encodeCall(_gda.updateMemberUnits, (pool, memberAddress, newUnits, new bytes(0)));
+
+ return _forwardBatchCall(address(_gda), callData, userData);
+ }
+
+ /**
+ * @dev Claims all tokens from the pool.
+ * @param pool The Superfluid Pool to claim from.
+ * @param memberAddress The address of the member to claim for.
+ * @param userData User-specific data.
+ */
+ function claimAll(ISuperfluidPool pool, address memberAddress, bytes memory userData)
+ external
+ returns (bool success)
+ {
+ bytes memory callData = abi.encodeCall(_gda.claimAll, (pool, memberAddress, new bytes(0)));
+
+ return _forwardBatchCall(address(_gda), callData, userData);
+ }
+
+ /**
+ * @dev Connects a pool member to `pool`.
+ * @param pool The Superfluid Pool to connect.
+ * @param userData User-specific data.
+ * @return A boolean value indicating whether the connection was successful.
+ */
+ function connectPool(ISuperfluidPool pool, bytes memory userData) external returns (bool) {
+ bytes memory callData = abi.encodeCall(_gda.connectPool, (pool, new bytes(0)));
+
+ return _forwardBatchCall(address(_gda), callData, userData);
+ }
+
+ /**
+ * @dev Disconnects a pool member from `pool`.
+ * @param pool The Superfluid Pool to disconnect.
+ * @param userData User-specific data.
+ * @return A boolean value indicating whether the disconnection was successful.
+ */
+ function disconnectPool(ISuperfluidPool pool, bytes memory userData) external returns (bool) {
+ bytes memory callData = abi.encodeCall(_gda.disconnectPool, (pool, new bytes(0)));
+
+ return _forwardBatchCall(address(_gda), callData, userData);
+ }
+
+ /**
+ * @dev Tries to distribute `requestedAmount` amount of `token` from `from` to `pool`.
+ * @param token The Super Token address.
+ * @param from The address from which to distribute tokens.
+ * @param pool The Superfluid Pool address.
+ * @param requestedAmount The amount of tokens to distribute.
+ * @param userData User-specific data.
+ * @return A boolean value indicating whether the distribution was successful.
+ */
+ function distribute(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool pool,
+ uint256 requestedAmount,
+ bytes memory userData
+ ) external returns (bool) {
+ bytes memory callData = abi.encodeCall(_gda.distribute, (token, from, pool, requestedAmount, new bytes(0)));
+
+ return _forwardBatchCall(address(_gda), callData, userData);
+ }
+
+ /**
+ * @dev Tries to distribute flow at `requestedFlowRate` of `token` from `from` to `pool`.
+ * @param token The Super Token address.
+ * @param from The address from which to distribute tokens.
+ * @param pool The Superfluid Pool address.
+ * @param requestedFlowRate The flow rate of tokens to distribute.
+ * @param userData User-specific data.
+ * @return A boolean value indicating whether the distribution was successful.
+ */
+ function distributeFlow(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool pool,
+ int96 requestedFlowRate,
+ bytes memory userData
+ ) external returns (bool) {
+ bytes memory callData =
+ abi.encodeCall(_gda.distributeFlow, (token, from, pool, requestedFlowRate, new bytes(0)));
+
+ return _forwardBatchCall(address(_gda), callData, userData);
+ }
+
+ /**
+ * @dev Checks if the specified account is a pool.
+ * @param token The Super Token address.
+ * @param account The account address to check.
+ * @return A boolean value indicating whether the account is a pool.
+ */
+ function isPool(ISuperfluidToken token, address account) external view virtual returns (bool) {
+ return _gda.isPool(token, account);
+ }
+
+ /**
+ * @dev Gets the GDA net flow rate for the specified account.
+ * @param token The Super Token address.
+ * @param account The account address.
+ * @return The gda net flow rate for the account.
+ */
+ function getNetFlow(ISuperfluidToken token, address account) external view returns (int96) {
+ return _gda.getNetFlow(token, account);
+ }
+
+ /**
+ * @dev Gets the flow rate of tokens between the specified accounts.
+ * @param token The Super Token address.
+ * @param from The sender address.
+ * @param to The receiver address (the pool address).
+ * @return The flow distribution flow rate
+ */
+ function getFlowDistributionFlowRate(ISuperfluidToken token, address from, ISuperfluidPool to)
+ external
+ view
+ returns (int96)
+ {
+ return _gda.getFlowRate(token, from, to);
+ }
+
+ /**
+ * @dev Gets the pool adjustment flow rate for the specified pool.
+ * @param pool The pool address.
+ * @return The pool adjustment flow rate.
+ */
+ function getPoolAdjustmentFlowRate(address pool) external view virtual returns (int96) {
+ return _gda.getPoolAdjustmentFlowRate(pool);
+ }
+
+ /**
+ * @dev Estimates the actual flow rate for flow distribution to the specified pool.
+ * @param token The Super Token address.
+ * @param from The sender address.
+ * @param to The pool address.
+ * @param requestedFlowRate The requested flow rate.
+ * @return actualFlowRate
+ * @return totalDistributionFlowRate
+ */
+ function estimateFlowDistributionActualFlowRate(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool to,
+ int96 requestedFlowRate
+ ) external view returns (int96 actualFlowRate, int96 totalDistributionFlowRate) {
+ return _gda.estimateFlowDistributionActualFlowRate(token, from, to, requestedFlowRate);
+ }
+
+ /**
+ * @dev Estimates the actual amount for distribution to the specified pool.
+ * @param token The Super Token address.
+ * @param from The sender address.
+ * @param to The pool address.
+ * @param requestedAmount The requested amount.
+ * @return actualAmount The actual amount for distribution.
+ */
+ function estimateDistributionActualAmount(
+ ISuperfluidToken token,
+ address from,
+ ISuperfluidPool to,
+ uint256 requestedAmount
+ ) external view returns (uint256 actualAmount) {
+ return _gda.estimateDistributionActualAmount(token, from, to, requestedAmount);
+ }
+
+ /**
+ * @dev Checks if the specified member is connected to the pool.
+ * @param pool The Superfluid Pool address.
+ * @param member The member address.
+ * @return A boolean value indicating whether the member is connected to the pool.
+ */
+ function isMemberConnected(ISuperfluidPool pool, address member) external view returns (bool) {
+ return _gda.isMemberConnected(pool, member);
+ }
+
+ /**
+ * @dev Gets the pool adjustment flow information for the specified pool.
+ * @param pool The pool address.
+ * @return The pool admin, pool ID, and pool adjustment flow rate.
+ */
+ function getPoolAdjustmentFlowInfo(ISuperfluidPool pool) external view virtual returns (address, bytes32, int96) {
+ return _gda.getPoolAdjustmentFlowInfo(pool);
+ }
+}
diff --git a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol
index 45a876d1d2..f96adc7883 100644
--- a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol
+++ b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol
@@ -41,118 +41,14 @@ contract SuperfluidFrameworkDeployer is SuperfluidFrameworkDeploymentSteps {
uint256 minBondDuration;
}
- error DEPLOY_AGREEMENTS_REQUIRES_DEPLOY_CORE();
- error DEPLOY_PERIPHERALS_REQUIRES_DEPLOY_CORE();
- error DEPLOY_PERIPHERALS_REQUIRES_DEPLOY_AGREEMENTS();
- error DEPLOY_SUPER_TOKEN_CONTRACTS_REQUIRES_DEPLOY_CORE();
- error DEPLOY_SUPER_TOKEN_REQUIRES_1820();
- error DEPLOY_SUPER_TOKEN_REQUIRES_DEPLOY_SUPER_TOKEN_CONTRACTS();
- error DEPLOY_TOGA_REQUIRES_1820();
- error RESOLVER_LIST_REQUIRES_DEPLOY_PERIPHERALS();
-
/// @notice Deploys the Superfluid Framework (Test)
/// @dev This uses default configurations for the framework.
/// NOTE: ERC1820 must be deployed as a prerequisite before calling this function.
function deployTestFramework() external {
// Default Configs
- TestFrameworkConfigs memory configs = TestFrameworkConfigs({
- nonUpgradeable: DEFAULT_NON_UPGRADEABLE,
- appWhitelistingEnabled: DEFAULT_APP_WHITELISTING_ENABLED,
- trustedForwarders: DEFAULT_TRUSTED_FORWARDERS,
- defaultRewardAddress: DEFAULT_REWARD_ADDRESS,
- liquidationPeriod: DEFAULT_LIQUIDATION_PERIOD,
- patricianPeriod: DEFAULT_PATRICIAN_PERIOD,
- minBondDuration: DEFAULT_TOGA_MIN_BOND_DURATION
- });
-
- _deployTestFramework(configs);
- }
-
- function _deployTestFramework(TestFrameworkConfigs memory configs) internal {
- // Deploy Host and Governance
- _deployCoreContracts(configs);
-
- // Initialize Host with Governance address
- _initializeHost();
-
- // Initialize Governance with Host address and Configs
- _initializeGovernance(
- configs.defaultRewardAddress, configs.liquidationPeriod, configs.patricianPeriod, configs.trustedForwarders
- );
-
- // Deploy CFAv1 and IDAv1
- _deployAgreementContracts();
-
- // Register the agreements with governance
- _registerAgreements();
-
- // Deploy NFT Proxy and Logic, SuperToken Logic, SuperTokenFactory Proxy and Logic contracts
- _deploySuperTokenContracts();
-
- // Set SuperTokenFactory as the canonical contract
- _setSuperTokenFactoryInHost();
-
- // Deploy Resolver, SuperfluidLoaderV1, CFAv1Forwarder, TOGA, BatchLiquidator contracts
- _deployPeripheralContracts(configs);
-
- // Enable the CFAv1Forwarder as a trusted forwarder via Governance
- _enableCFAv1ForwarderAsTrustedForwarder();
-
- // Enable the IDAv1Forwarder as a trusted forwarder via Governance
- _enableIDAv1ForwarderAsTrustedForwarder();
-
- // Set TestGovernance, Superfluid, SuperfluidLoader and CFAv1Forwarder addresses in Resolver
- _setAddressesInResolver();
- }
-
- /// @notice Deploys the core Superfluid contracts
- /// @dev Host and Governance
- function deployCoreContracts() public {
- TestFrameworkConfigs memory configs;
- configs.nonUpgradeable = true;
- configs.appWhitelistingEnabled = false;
-
- _deployCoreContracts(configs);
- }
-
- /// @notice Deploys the core Superfluid contracts w/ Configs
- /// @dev Host and Governance
- /// @param configs the configurations for the framework
- function deployCoreContracts(TestFrameworkConfigs memory configs) public {
- _deployCoreContracts(configs);
- }
-
- function _deployCoreContracts(TestFrameworkConfigs memory configs) internal {
- _deployGovernance(address(this));
- _deployHost(configs.nonUpgradeable, configs.appWhitelistingEnabled);
- }
-
- /// @notice Deploys the Superfluid agreement contracts
- /// @dev Deploys Superfluid agreement contracts
- /// NOTE: This requires the core contracts to be deployed first.
- function deployAgreementContracts() public {
- _deployAgreementContracts();
- }
-
- function _deployAgreementContracts() internal {
- if (address(host) == address(0)) revert DEPLOY_AGREEMENTS_REQUIRES_DEPLOY_CORE();
-
- _deployCFAv1();
- _deployIDAv1();
- }
-
- /// @notice Deploys all SuperToken-related contracts
- /// @dev Deploys NFT Proxy and Logic, SuperToken Logic, SuperTokenFactory Proxy and Logic contracts
- function deploySuperTokenContracts() public {
- _deploySuperTokenContracts();
- }
-
- function _deploySuperTokenContracts() internal {
- if (address(host) == address(0)) revert DEPLOY_SUPER_TOKEN_CONTRACTS_REQUIRES_DEPLOY_CORE();
-
- _deployNFTProxyAndLogicAndInitialize();
- _deploySuperTokenLogic();
- _deploySuperTokenFactory();
+ for (uint256 i = 0; i < getNumSteps(); ++i) {
+ executeStep(uint8(i));
+ }
}
/// @notice Deploys an ERC20 and a Wrapper Super Token for the ERC20 and lists both in the resolver
@@ -178,27 +74,6 @@ contract SuperfluidFrameworkDeployer is SuperfluidFrameworkDeploymentSteps {
return _deployWrapperSuperToken(_underlyingName, _underlyingSymbol, _decimals, _mintLimit, _admin);
}
- /// @notice Deploys an ERC20 and a Wrapper Super Token for the ERC20 and lists both in the resolver
- /// @dev SuperToken name and symbol format: `Super ${_underlyingSymbol}` and `${_underlyingSymbol}x`, respectively
- /// @param _underlyingName The underlying token name
- /// @param _underlyingSymbol The token symbol
- /// @param _decimals The token decimals
- /// @param _mintLimit The mint limit of the underlying token
- /// @return underlyingToken and superToken
- function deployWrapperSuperToken(
- string calldata _underlyingName,
- string calldata _underlyingSymbol,
- uint8 _decimals,
- uint256 _mintLimit
- )
- external
- requiresSuperTokenFactory
- deploySuperTokenRequires1820
- returns (TestToken underlyingToken, SuperToken superToken)
- {
- return _deployWrapperSuperToken(_underlyingName, _underlyingSymbol, _decimals, _mintLimit, address(0));
- }
-
/// @notice Deploys a Native Asset Super Token and lists it in the resolver
/// @dev e.g. ETHx, MATICx, AVAXx, etc. The underlying is the Native Asset.
/// @param _name The token name
@@ -245,8 +120,8 @@ contract SuperfluidFrameworkDeployer is SuperfluidFrameworkDeploymentSteps {
function _handleResolverList(bool _listOnResolver, string memory _resolverKey, address _superTokenAddress)
internal
- requiresResolver
{
+ if (address(testResolver) == address(0)) revert RESOLVER_LIST_REQUIRES_DEPLOY_PERIPHERALS();
if (_listOnResolver) {
testResolver.set(_resolverKey, address(_superTokenAddress));
}
@@ -286,65 +161,6 @@ contract SuperfluidFrameworkDeployer is SuperfluidFrameworkDeploymentSteps {
_handleResolverList(true, superTokenKey, address(superToken));
}
- /// @notice Deploys all peripheral Superfluid contracts
- /// @dev Deploys Resolver, SuperfluidLoaderV1, CFAv1Forwarder, TOGA, BatchLiquidator contracts
- function deployPeripheralContracts() public {
- TestFrameworkConfigs memory configs;
- configs.minBondDuration = DEFAULT_TOGA_MIN_BOND_DURATION;
-
- _deployPeripheralContracts(configs);
- }
-
- /// @notice Deploys all peripheral Superfluid contracts with configs
- /// @dev Deploys Resolver, SuperfluidLoaderV1, CFAv1Forwarder, TOGA, BatchLiquidator contracts
- function deployPeripheralContracts(TestFrameworkConfigs memory configs) public {
- _deployPeripheralContracts(configs);
- }
-
- function _deployPeripheralContracts(TestFrameworkConfigs memory configs) internal {
- if (address(host) == address(0)) revert DEPLOY_PERIPHERALS_REQUIRES_DEPLOY_CORE();
-
- _deployTestResolver(address(this));
- _deploySuperfluidLoader();
-
- // Set the deployer of this contract as an admin of the resolver
- // So that they can add other admins and set addresses
- testResolver.addAdmin(msg.sender);
-
- _deployCFAv1Forwarder();
- _deployIDAv1Forwarder();
- _deployTOGA(configs.minBondDuration);
-
- if (address(cfaV1) == address(0)) revert DEPLOY_PERIPHERALS_REQUIRES_DEPLOY_AGREEMENTS();
- _deployBatchLiquidator();
- }
-
- function _deployTOGA(uint256 _minBondDuration) internal override deployTogaRequires1820 {
- super._deployTOGA(_minBondDuration);
- }
-
- //// JS-Specific Functions ////
- function getNumSteps() external pure returns (uint8) {
- return _getNumSteps();
- }
-
- function executeStep(uint8 step) external {
- _executeStep(step);
- }
-
- function _is1820Deployed() internal view returns (bool) {
- uint256 codeSize;
- assembly {
- codeSize := extcodesize(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24)
- }
- return codeSize != 0;
- }
-
- modifier requiresResolver() {
- if (address(testResolver) == address(0)) revert RESOLVER_LIST_REQUIRES_DEPLOY_PERIPHERALS();
- _;
- }
-
modifier requiresSuperTokenFactory() {
if (address(superTokenFactory) == address(0)) revert DEPLOY_SUPER_TOKEN_REQUIRES_DEPLOY_SUPER_TOKEN_CONTRACTS();
_;
@@ -354,9 +170,4 @@ contract SuperfluidFrameworkDeployer is SuperfluidFrameworkDeploymentSteps {
if (!_is1820Deployed()) revert DEPLOY_SUPER_TOKEN_REQUIRES_1820();
_;
}
-
- modifier deployTogaRequires1820() {
- if (!_is1820Deployed()) revert DEPLOY_TOGA_REQUIRES_1820();
- _;
- }
}
diff --git a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol
index 1b3e899245..b207720f50 100644
--- a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol
+++ b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol
@@ -6,12 +6,16 @@ pragma solidity >=0.8.11;
import { CFAv1Forwarder } from "./CFAv1Forwarder.sol";
import { IDAv1Forwarder } from "./IDAv1Forwarder.sol";
+import { GDAv1Forwarder } from "./GDAv1Forwarder.sol";
import { ISuperfluid, ISuperfluidToken, Superfluid } from "../superfluid/Superfluid.sol";
import { TestGovernance } from "./TestGovernance.sol";
import { ConstantFlowAgreementV1 } from "../agreements/ConstantFlowAgreementV1.sol";
import { ConstantOutflowNFT, IConstantOutflowNFT } from "../superfluid/ConstantOutflowNFT.sol";
import { ConstantInflowNFT, IConstantInflowNFT } from "../superfluid/ConstantInflowNFT.sol";
+import { PoolAdminNFT, IPoolAdminNFT } from "../agreements/gdav1/PoolAdminNFT.sol";
+import { PoolMemberNFT, IPoolMemberNFT } from "../agreements/gdav1/PoolMemberNFT.sol";
import { InstantDistributionAgreementV1 } from "../agreements/InstantDistributionAgreementV1.sol";
+import { GeneralDistributionAgreementV1 } from "../agreements/gdav1/GeneralDistributionAgreementV1.sol";
import { SuperTokenFactory } from "../superfluid/SuperTokenFactory.sol";
import { TestToken } from "./TestToken.sol";
import { PureSuperToken } from "../tokens/PureSuperToken.sol";
@@ -19,6 +23,8 @@ import { SETHProxy } from "../tokens/SETH.sol";
import { ISuperToken, SuperToken } from "../superfluid/SuperToken.sol";
import { TestResolver } from "./TestResolver.sol";
import { SuperfluidLoader } from "./SuperfluidLoader.sol";
+import { SuperfluidPool } from "../agreements/gdav1/SuperfluidPool.sol";
+import { SuperfluidUpgradeableBeacon } from "../upgradability/SuperfluidUpgradeableBeacon.sol";
import { UUPSProxy } from "../upgradability/UUPSProxy.sol";
import { BatchLiquidator } from "./BatchLiquidator.sol";
import { TOGA } from "./TOGA.sol";
@@ -52,6 +58,7 @@ contract SuperfluidFrameworkDeploymentSteps {
ConstantFlowAgreementV1 cfa;
CFAv1Library.InitData cfaLib;
InstantDistributionAgreementV1 ida;
+ GeneralDistributionAgreementV1 gda;
IDAv1Library.InitData idaLib;
SuperTokenFactory superTokenFactory;
ISuperToken superTokenLogic;
@@ -61,6 +68,8 @@ contract SuperfluidFrameworkDeploymentSteps {
SuperfluidLoader superfluidLoader;
CFAv1Forwarder cfaV1Forwarder;
IDAv1Forwarder idaV1Forwarder;
+ GDAv1Forwarder gdaV1Forwarder;
+ BatchLiquidator batchLiquidator;
TOGA toga;
}
@@ -75,12 +84,18 @@ contract SuperfluidFrameworkDeploymentSteps {
ConstantFlowAgreementV1 internal cfaV1Logic;
InstantDistributionAgreementV1 internal idaV1;
InstantDistributionAgreementV1 internal idaV1Logic;
+ GeneralDistributionAgreementV1 internal gdaV1;
+ GeneralDistributionAgreementV1 internal gdaV1Logic;
// SuperToken-related Contracts
ConstantOutflowNFT internal constantOutflowNFTLogic;
ConstantInflowNFT internal constantInflowNFTLogic;
ConstantOutflowNFT internal constantOutflowNFT;
ConstantInflowNFT internal constantInflowNFT;
+ PoolAdminNFT internal poolAdminNFTLogic;
+ PoolMemberNFT internal poolMemberNFTLogic;
+ PoolAdminNFT internal poolAdminNFT;
+ PoolMemberNFT internal poolMemberNFT;
ISuperToken internal superTokenLogic;
SuperTokenFactory internal superTokenFactory;
SuperTokenFactory internal superTokenFactoryLogic;
@@ -90,213 +105,18 @@ contract SuperfluidFrameworkDeploymentSteps {
SuperfluidLoader internal superfluidLoader;
CFAv1Forwarder internal cfaV1Forwarder;
IDAv1Forwarder internal idaV1Forwarder;
+ GDAv1Forwarder internal gdaV1Forwarder;
BatchLiquidator internal batchLiquidator;
TOGA internal toga;
- function _deployGovernance(address newOwner) internal {
- // Deploy TestGovernance. Needs initialization later.
- testGovernance = SuperfluidGovDeployerLibrary.deployTestGovernance();
-
- SuperfluidGovDeployerLibrary.transferOwnership(testGovernance, newOwner);
- }
-
- function _deployHost(bool nonUpgradable, bool appWhiteListingEnabled) internal {
- host = SuperfluidHostDeployerLibrary.deploySuperfluidHost(nonUpgradable, appWhiteListingEnabled);
- }
-
- function _initializeHost() internal {
- host.initialize(testGovernance);
- }
-
- function _initializeGovernance(
- address defaultRewardAddress,
- uint256 defaultLiquidationPeriod,
- uint256 defaultPatricianPeriod,
- address[] memory defaultTrustedForwarders
- ) internal {
- testGovernance.initialize(
- host, defaultRewardAddress, defaultLiquidationPeriod, defaultPatricianPeriod, defaultTrustedForwarders
- );
- }
-
- function _deployHostAndInitializeHostAndGovernance(bool nonUpgradable, bool appWhiteListingEnabled) internal {
- // Deploy Host
- _deployHost(nonUpgradable, appWhiteListingEnabled);
-
- _initializeHost();
-
- _initializeGovernance(
- DEFAULT_REWARD_ADDRESS, DEFAULT_LIQUIDATION_PERIOD, DEFAULT_PATRICIAN_PERIOD, DEFAULT_TRUSTED_FORWARDERS
- );
- }
-
- function _deployCFAv1() internal {
- cfaV1Logic = SuperfluidCFAv1DeployerLibrary.deployConstantFlowAgreementV1(host);
- }
-
- function _deployIDAv1() internal {
- idaV1Logic = SuperfluidIDAv1DeployerLibrary.deployInstantDistributionAgreementV1(host);
- }
-
- function _deployAgreements() internal {
- _deployCFAv1();
- _deployIDAv1();
- }
-
- function _deployAgreementsAndRegister() internal {
- _deployAgreements();
- _registerAgreements();
- }
-
- function _registerAgreements() internal {
- // we set the canonical address based on host.getAgreementClass() because
- // in the upgradeable case, we create a new proxy contract in the function
- // and set it as the canonical agreement.
- testGovernance.registerAgreementClass(host, address(cfaV1Logic));
- cfaV1 = ConstantFlowAgreementV1(address(host.getAgreementClass(cfaV1Logic.agreementType())));
- testGovernance.registerAgreementClass(host, address(idaV1Logic));
- idaV1 = InstantDistributionAgreementV1(address(host.getAgreementClass(idaV1Logic.agreementType())));
- }
-
- function _deployCFAv1Forwarder() internal {
- cfaV1Forwarder = CFAv1ForwarderDeployerLibrary.deployCFAv1Forwarder(host);
- }
-
- function _enableCFAv1ForwarderAsTrustedForwarder() internal {
- testGovernance.enableTrustedForwarder(host, ISuperfluidToken(address(0)), address(cfaV1Forwarder));
- }
-
- function _deployCFAv1ForwarderAndEnable() internal {
- _deployCFAv1Forwarder();
- _enableCFAv1ForwarderAsTrustedForwarder();
- }
-
- function _deployIDAv1Forwarder() internal {
- idaV1Forwarder = IDAv1ForwarderDeployerLibrary.deployIDAv1Forwarder(host);
- }
-
- function _enableIDAv1ForwarderAsTrustedForwarder() internal {
- testGovernance.enableTrustedForwarder(host, ISuperfluidToken(address(0)), address(idaV1Forwarder));
- }
-
- function _deployIDAv1ForwarderAndEnable() internal {
- _deployIDAv1Forwarder();
- _enableIDAv1ForwarderAsTrustedForwarder();
- }
-
- function _deployNFTProxyAndLogicAndInitialize() internal {
- // Deploy canonical Constant Outflow NFT proxy contract
- UUPSProxy constantOutflowNFTProxy = ProxyDeployerLibrary.deployUUPSProxy();
-
- // Deploy canonical Constant Outflow NFT proxy contract
- UUPSProxy constantInflowNFTProxy = ProxyDeployerLibrary.deployUUPSProxy();
-
- // Deploy canonical Constant Outflow NFT logic contract
- constantOutflowNFTLogic = SuperfluidNFTLogicDeployerLibrary.deployConstantOutflowNFT(
- host, IConstantInflowNFT(address(constantInflowNFTProxy))
- );
-
- // Initialize Constant Outflow NFT logic contract
- constantOutflowNFTLogic.castrate();
-
- // Deploy canonical Constant Inflow NFT logic contract
- constantInflowNFTLogic = SuperfluidNFTLogicDeployerLibrary.deployConstantInflowNFT(
- host, IConstantOutflowNFT(address(constantOutflowNFTProxy))
- );
-
- // Initialize Constant Inflow NFT logic contract
- constantInflowNFTLogic.castrate();
-
- // Initialize COFNFT proxy contract
- constantOutflowNFTProxy.initializeProxy(address(constantOutflowNFTLogic));
-
- // Initialize CIFNFT proxy contract
- constantInflowNFTProxy.initializeProxy(address(constantInflowNFTLogic));
-
- // // Initialize COFNFT proxy contract
- IConstantOutflowNFT(address(constantOutflowNFTProxy)).initialize("Constant Outflow NFT", "COF");
-
- // // Initialize CIFNFT proxy contract
- IConstantInflowNFT(address(constantInflowNFTProxy)).initialize("Constant Inflow NFT", "CIF");
-
- constantOutflowNFT = ConstantOutflowNFT(address(constantOutflowNFTProxy));
- constantInflowNFT = ConstantInflowNFT(address(constantInflowNFTProxy));
- }
-
- function _deploySuperTokenLogicAndSuperTokenFactoryAndUpdateContracts() internal {
- _deploySuperTokenLogicAndSuperTokenFactory();
- _setSuperTokenFactoryInHost();
- }
-
- function _deploySuperTokenLogicAndSuperTokenFactory() internal {
- _deploySuperTokenLogic();
- _deploySuperTokenFactory();
- }
-
- function _deploySuperTokenLogic() internal {
- // Deploy canonical SuperToken logic contract
- superTokenLogic = SuperToken(
- SuperTokenDeployerLibrary.deploySuperTokenLogic(
- host, IConstantOutflowNFT(address(constantOutflowNFT)), IConstantInflowNFT(address(constantInflowNFT))
- )
- );
- }
-
- function _deploySuperTokenFactory() internal {
- superTokenFactoryLogic = SuperfluidPeripheryDeployerLibrary.deploySuperTokenFactory(
- host, superTokenLogic, constantOutflowNFTLogic, constantInflowNFTLogic
- );
- }
-
- function _setSuperTokenFactoryInHost() internal {
- // 'Update' code with Governance and register SuperTokenFactory with Superfluid
- testGovernance.updateContracts(host, address(0), new address[](0), address(superTokenFactoryLogic));
-
- // we set the canonical address based on host.getSuperTokenFactory() because
- // in the upgradeable case, we create a new proxy contract in the function
- // and set it as the canonical supertokenfactory.
- superTokenFactory = SuperTokenFactory(address(host.getSuperTokenFactory()));
- }
-
- function _deployTestResolver(address resolverAdmin) internal {
- testResolver = SuperfluidPeripheryDeployerLibrary.deployTestResolver(resolverAdmin);
- }
-
- function _deploySuperfluidLoader() internal {
- superfluidLoader = SuperfluidLoaderDeployerLibrary.deploySuperfluidLoader(testResolver);
- }
-
- function _deployTestResolverAndSuperfluidLoaderAndSet(address resolverAdmin) internal {
- _deployTestResolver(resolverAdmin);
- _deploySuperfluidLoader();
-
- _setAddressesInResolver();
- }
-
- function _setAddressesInResolver() internal {
- // Register Governance with Resolver
- testResolver.set("TestGovernance.test", address(testGovernance));
-
- // Register Superfluid with Resolver
- testResolver.set("Superfluid.test", address(host));
-
- // Register SuperfluidLoader with Resolver
- testResolver.set("SuperfluidLoader-v1", address(superfluidLoader));
-
- // Register CFAv1Forwarder with Resolver
- testResolver.set("CFAv1Forwarder", address(cfaV1Forwarder));
-
- // Register IDAv1Forwarder with Resolver
- testResolver.set("IDAv1Forwarder", address(idaV1Forwarder));
- }
-
- function _deployBatchLiquidator() internal {
- batchLiquidator = new BatchLiquidator(address(host), address(cfaV1));
- }
-
- function _deployTOGA(uint256 minBondDuration) internal virtual {
- toga = new TOGA(host, minBondDuration);
- }
+ error DEPLOY_AGREEMENTS_REQUIRES_DEPLOY_CORE();
+ error DEPLOY_PERIPHERALS_REQUIRES_DEPLOY_CORE();
+ error DEPLOY_PERIPHERALS_REQUIRES_DEPLOY_AGREEMENTS();
+ error DEPLOY_TOGA_REQUIRES_1820();
+ error DEPLOY_SUPER_TOKEN_CONTRACTS_REQUIRES_DEPLOY_CORE();
+ error DEPLOY_SUPER_TOKEN_REQUIRES_1820();
+ error DEPLOY_SUPER_TOKEN_REQUIRES_DEPLOY_SUPER_TOKEN_CONTRACTS();
+ error RESOLVER_LIST_REQUIRES_DEPLOY_PERIPHERALS();
/// @notice Fetches the framework contracts
function getFramework() external view returns (Framework memory sf) {
@@ -307,6 +127,7 @@ contract SuperfluidFrameworkDeploymentSteps {
cfaLib: CFAv1Library.InitData(host, cfaV1),
ida: idaV1,
idaLib: IDAv1Library.InitData(host, idaV1),
+ gda: gdaV1,
superTokenFactory: superTokenFactory,
superTokenLogic: superTokenLogic,
constantOutflowNFT: constantOutflowNFT,
@@ -315,6 +136,8 @@ contract SuperfluidFrameworkDeploymentSteps {
superfluidLoader: superfluidLoader,
cfaV1Forwarder: cfaV1Forwarder,
idaV1Forwarder: idaV1Forwarder,
+ gdaV1Forwarder: gdaV1Forwarder,
+ batchLiquidator: batchLiquidator,
toga: toga
});
return sf;
@@ -327,55 +150,270 @@ contract SuperfluidFrameworkDeploymentSteps {
testGovernance.transferOwnership(newOwner);
}
- function _getNumSteps() internal pure returns (uint8) {
+ function getNumSteps() public pure returns (uint8) {
return 8;
}
- function _executeStep(uint8 step) internal {
+ function executeStep(uint8 step) public {
if (step != currentStep) revert("Incorrect step");
+ // CORE CONTRACTS
if (step == 0) {
// Deploy Superfluid Governance
- _deployGovernance(address(this));
+ // Deploy TestGovernance. Needs initialization later.
+ testGovernance = SuperfluidGovDeployerLibrary.deployTestGovernance();
+
+ SuperfluidGovDeployerLibrary.transferOwnership(testGovernance, address(this));
} else if (step == 1) {
- // Deploy Superfluid Host
- _deployHostAndInitializeHostAndGovernance(true, false);
+ // Deploy Host
+ // _deployHost(nonUpgradable, appWhiteListingEnabled);
+ host = SuperfluidHostDeployerLibrary.deploySuperfluidHost(true, false);
+
+ // _initializeHost();
+ host.initialize(testGovernance);
+
+ // _initializeGovernance(
+ // DEFAULT_REWARD_ADDRESS, DEFAULT_LIQUIDATION_PERIOD, DEFAULT_PATRICIAN_PERIOD,
+ // DEFAULT_TRUSTED_FORWARDERS
+ // );
+ testGovernance.initialize(
+ host,
+ DEFAULT_REWARD_ADDRESS,
+ DEFAULT_LIQUIDATION_PERIOD,
+ DEFAULT_PATRICIAN_PERIOD,
+ DEFAULT_TRUSTED_FORWARDERS
+ );
} else if (step == 2) {
+ // AGREEMENT CONTRACTS
// Deploy Superfluid CFA, IDA, GDA
- _deployAgreementsAndRegister();
+
+ if (address(host) == address(0)) revert DEPLOY_AGREEMENTS_REQUIRES_DEPLOY_CORE();
+
+ // _deployAgreementContracts();
+ // _deployCFAv1();
+ cfaV1Logic = SuperfluidCFAv1DeployerLibrary.deployConstantFlowAgreementV1(host);
+
+ // _deployIDAv1();
+ idaV1Logic = SuperfluidIDAv1DeployerLibrary.deployInstantDistributionAgreementV1(host);
+
+ // _deployGDAv1();
+ gdaV1Logic = SuperfluidGDAv1DeployerLibrary.deployGeneralDistributionAgreementV1(host);
+
+ // _registerAgreements();
+ // we set the canonical address based on host.getAgreementClass() because
+ // in the upgradeable case, we create a new proxy contract in the function
+ // and set it as the canonical agreement.
+ testGovernance.registerAgreementClass(host, address(cfaV1Logic));
+ cfaV1 = ConstantFlowAgreementV1(address(host.getAgreementClass(cfaV1Logic.agreementType())));
+ testGovernance.registerAgreementClass(host, address(idaV1Logic));
+ idaV1 = InstantDistributionAgreementV1(address(host.getAgreementClass(idaV1Logic.agreementType())));
+ testGovernance.registerAgreementClass(host, address(gdaV1Logic));
+ gdaV1 = GeneralDistributionAgreementV1(address(host.getAgreementClass(gdaV1Logic.agreementType())));
} else if (step == 3) {
+ // PERIPHERAL CONTRACTS: FORWARDERS
// Deploy CFAv1Forwarder
- _deployCFAv1ForwarderAndEnable();
+ // _deployCFAv1Forwarder()
+ cfaV1Forwarder = CFAv1ForwarderDeployerLibrary.deployCFAv1Forwarder(host);
+ // _enableCFAv1ForwarderAsTrustedForwarder()
+ testGovernance.enableTrustedForwarder(host, ISuperfluidToken(address(0)), address(cfaV1Forwarder));
// Deploy IDAv1Forwarder
- _deployIDAv1ForwarderAndEnable();
+ // _deployIDAv1Forwarder();
+ idaV1Forwarder = IDAv1ForwarderDeployerLibrary.deployIDAv1Forwarder(host);
+ // _enableIDAv1ForwarderAsTrustedForwarder();
+ testGovernance.enableTrustedForwarder(host, ISuperfluidToken(address(0)), address(idaV1Forwarder));
// Deploy GDAv1Forwarder
- // TODO
- // solhint-disable-next-line no-empty-blocks
+ // _deployGDAv1Forwarder();
+ gdaV1Forwarder = GDAv1ForwarderDeployerLibrary.deployGDAv1Forwarder(host);
+ // _enableGDAv1ForwarderAsTrustedForwarder();
+ testGovernance.enableTrustedForwarder(host, ISuperfluidToken(address(0)), address(gdaV1Forwarder));
} else if (step == 4) {
+ // PERIPHERAL CONTRACTS: SuperfluidPool Logic
// Deploy SuperfluidPool
// Initialize GDA with SuperfluidPool beacon
+ // _deploySuperfluidPoolLogicAndInitializeGDA();
+
+ /// Deploy SuperfluidPool logic contract
+ SuperfluidPool superfluidPoolLogic = SuperfluidPoolLogicDeployerLibrary.deploySuperfluidPool(gdaV1);
+
+ // Initialize the logic contract
+ superfluidPoolLogic.castrate();
+
+ // Deploy SuperfluidPool beacon
+ SuperfluidUpgradeableBeacon superfluidPoolBeacon =
+ ProxyDeployerLibrary.deploySuperfluidUpgradeableBeacon(address(superfluidPoolLogic));
+ gdaV1.initialize(superfluidPoolBeacon);
+
+ superfluidPoolBeacon.transferOwnership(address(host));
} else if (step == 5) {
+ // PERIPHERAL CONTRACTS: NFT Proxy and Logic
// Deploy Superfluid NFTs (Proxy and Logic contracts)
- _deployNFTProxyAndLogicAndInitialize();
+
+ if (address(host) == address(0)) revert DEPLOY_SUPER_TOKEN_CONTRACTS_REQUIRES_DEPLOY_CORE();
+ // Deploy canonical Constant Outflow NFT proxy contract
+ UUPSProxy constantOutflowNFTProxy = ProxyDeployerLibrary.deployUUPSProxy();
+
+ // Deploy canonical Constant Outflow NFT proxy contract
+ UUPSProxy constantInflowNFTProxy = ProxyDeployerLibrary.deployUUPSProxy();
+
+ // Deploy canonical Pool Admin NFT proxy contract
+ UUPSProxy poolAdminNFTProxy = ProxyDeployerLibrary.deployUUPSProxy();
+
+ // Deploy canonical Pool Member NFT proxy contract
+ UUPSProxy poolMemberNFTProxy = ProxyDeployerLibrary.deployUUPSProxy();
+
+ // Deploy canonical Constant Outflow NFT logic contract
+ constantOutflowNFTLogic = SuperfluidFlowNFTLogicDeployerLibrary.deployConstantOutflowNFT(
+ host, IConstantInflowNFT(address(constantInflowNFTProxy))
+ );
+
+ // Initialize Constant Outflow NFT logic contract
+ constantOutflowNFTLogic.castrate();
+
+ // Deploy canonical Constant Inflow NFT logic contract
+ constantInflowNFTLogic = SuperfluidFlowNFTLogicDeployerLibrary.deployConstantInflowNFT(
+ host, IConstantOutflowNFT(address(constantOutflowNFTProxy))
+ );
+
+ // Initialize Constant Inflow NFT logic contract
+ constantInflowNFTLogic.castrate();
+
+ // Deploy canonical Pool Admin NFT logic contract
+ poolAdminNFTLogic = SuperfluidPoolNFTLogicDeployerLibrary.deployPoolAdminNFT(host);
+
+ // Initialize Pool Admin NFT logic contract
+ poolAdminNFTLogic.castrate();
+
+ // Deploy canonical Pool Member NFT logic contract
+ poolMemberNFTLogic = SuperfluidPoolNFTLogicDeployerLibrary.deployPoolMemberNFT(host);
+
+ // Initialize Pool Member NFT logic contract
+ poolMemberNFTLogic.castrate();
+
+ // Initialize COFNFT proxy contract
+ constantOutflowNFTProxy.initializeProxy(address(constantOutflowNFTLogic));
+
+ // Initialize CIFNFT proxy contract
+ constantInflowNFTProxy.initializeProxy(address(constantInflowNFTLogic));
+
+ // Initialize Pool Admin NFT proxy contract
+ poolAdminNFTProxy.initializeProxy(address(poolAdminNFTLogic));
+
+ // Initialize Pool Member NFT proxy contract
+ poolMemberNFTProxy.initializeProxy(address(poolMemberNFTLogic));
+
+ // // Initialize COFNFT proxy contract
+ IConstantOutflowNFT(address(constantOutflowNFTProxy)).initialize("Constant Outflow NFT", "COF");
+
+ // // Initialize CIFNFT proxy contract
+ IConstantInflowNFT(address(constantInflowNFTProxy)).initialize("Constant Inflow NFT", "CIF");
+
+ // // Initialize Pool Admin NFT proxy contract
+ IPoolAdminNFT(address(poolAdminNFTProxy)).initialize("Pool Admin NFT", "PA");
+
+ // // Initialize Pool Member NFT proxy contract
+ IPoolMemberNFT(address(poolMemberNFTProxy)).initialize("Pool Member NFT", "PM");
+
+ constantOutflowNFT = ConstantOutflowNFT(address(constantOutflowNFTProxy));
+ constantInflowNFT = ConstantInflowNFT(address(constantInflowNFTProxy));
+ poolAdminNFT = PoolAdminNFT(address(poolAdminNFTProxy));
+ poolMemberNFT = PoolMemberNFT(address(poolMemberNFTProxy));
} else if (step == 6) {
+ // PERIPHERAL CONTRACTS: SuperToken Logic and SuperTokenFactory Logic
// Deploy SuperToken Logic
// Deploy SuperToken Factory
- _deploySuperTokenLogicAndSuperTokenFactoryAndUpdateContracts();
+
+ // _deploySuperTokenLogic();
+ // Deploy canonical SuperToken logic contract
+ superTokenLogic = SuperToken(
+ SuperTokenDeployerLibrary.deploySuperTokenLogic(
+ host,
+ IConstantOutflowNFT(address(constantOutflowNFT)),
+ IConstantInflowNFT(address(constantInflowNFT)),
+ IPoolAdminNFT(address(poolAdminNFT)),
+ IPoolMemberNFT(address(poolMemberNFT))
+ )
+ );
+
+ // _deploySuperTokenFactory();
+ superTokenFactoryLogic = SuperfluidPeripheryDeployerLibrary.deploySuperTokenFactory(
+ host,
+ superTokenLogic,
+ constantOutflowNFTLogic,
+ constantInflowNFTLogic,
+ poolAdminNFTLogic,
+ poolMemberNFTLogic
+ );
+
+ // _setSuperTokenFactoryInHost();
+ // 'Update' code with Governance and register SuperTokenFactory with Superfluid
+ testGovernance.updateContracts(
+ host, address(0), new address[](0), address(superTokenFactoryLogic), address(0)
+ );
+
+ // we set the canonical address based on host.getSuperTokenFactory() because
+ // in the upgradeable case, we create a new proxy contract in the function
+ // and set it as the canonical supertokenfactory.
+ superTokenFactory = SuperTokenFactory(address(host.getSuperTokenFactory()));
} else if (step == 7) {
+ // PERIPHERAL CONTRACTS: Resolver, SuperfluidLoader, TOGA, BatchLiquidator
// Deploy TestResolver
- // Deploy SuperfluidLoader and make SuperfluidFrameworkDpeloyer an admin for the TestResolver
+ // Deploy SuperfluidLoader and make SuperfluidFrameworkDeployer an admin for the TestResolver
// Set TestGovernance, Superfluid, SuperfluidLoader and CFAv1Forwarder in TestResolver
- _deployTestResolverAndSuperfluidLoaderAndSet(address(this));
+
+ // _deployTestResolver(resolverAdmin);
+ if (address(host) == address(0)) revert DEPLOY_PERIPHERALS_REQUIRES_DEPLOY_CORE();
+ testResolver = SuperfluidPeripheryDeployerLibrary.deployTestResolver(address(this));
+
+ // _deploySuperfluidLoader();
+ superfluidLoader = SuperfluidLoaderDeployerLibrary.deploySuperfluidLoader(testResolver);
+
+ // _setAddressesInResolver();
+ // Register Governance with Resolver
+ testResolver.set("TestGovernance.test", address(testGovernance));
+
+ // Register Superfluid with Resolver
+ testResolver.set("Superfluid.test", address(host));
+
+ // Register SuperfluidLoader with Resolver
+ testResolver.set("SuperfluidLoader-v1", address(superfluidLoader));
+
+ // Register CFAv1Forwarder with Resolver
+ testResolver.set("CFAv1Forwarder", address(cfaV1Forwarder));
+
+ // Register IDAv1Forwarder with Resolver
+ testResolver.set("IDAv1Forwarder", address(idaV1Forwarder));
+
+ // Register GDAv1Forwarder with Resolver
+ testResolver.set("GDAv1Forwarder", address(gdaV1Forwarder));
+
// Make SuperfluidFrameworkDeployer deployer an admin for the TestResolver as well
testResolver.addAdmin(msg.sender);
+
+ // _deployTOGA();
+ if (!_is1820Deployed()) revert DEPLOY_TOGA_REQUIRES_1820();
+ toga = new TOGA(host, DEFAULT_TOGA_MIN_BOND_DURATION);
+ testGovernance.setRewardAddress(host, ISuperfluidToken(address(0)), address(toga));
+
+ // _deployBatchLiquidator();
+ if (address(cfaV1) == address(0)) revert DEPLOY_PERIPHERALS_REQUIRES_DEPLOY_CORE();
+ if (address(cfaV1) == address(0)) revert DEPLOY_PERIPHERALS_REQUIRES_DEPLOY_AGREEMENTS();
+ batchLiquidator = new BatchLiquidator(address(host));
} else {
revert("Invalid step");
}
currentStep++;
}
+
+ function _is1820Deployed() internal view returns (bool) {
+ uint256 codeSize;
+ assembly {
+ codeSize := extcodesize(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24)
+ }
+ return codeSize != 0;
+ }
}
//// External Libraries ////
@@ -430,6 +468,22 @@ library SuperfluidIDAv1DeployerLibrary {
}
}
+/// @title SuperfluidGDAv1DeployerLibrary
+/// @author Superfluid
+/// @notice An external library that deploys Superfluid GeneralDistributionAgreementV1 contract
+/// @dev This library is used for testing purposes only, not deployments to test OR production networks
+library SuperfluidGDAv1DeployerLibrary {
+ /// @notice deploys the Superfluid GeneralDistributionAgreementV1 Contract
+ /// @param _host Superfluid host address
+ /// @return newly deployed GeneralDistributionAgreementV1 contract
+ function deployGeneralDistributionAgreementV1(ISuperfluid _host)
+ external
+ returns (GeneralDistributionAgreementV1)
+ {
+ return new GeneralDistributionAgreementV1(_host);
+ }
+}
+
/// @title SuperfluidCFAv1DeployerLibrary
/// @author Superfluid
/// @notice An external library that deploys Superfluid ConstantFlowAgreementV1 contract
@@ -449,12 +503,19 @@ library SuperfluidCFAv1DeployerLibrary {
library SuperTokenDeployerLibrary {
/// @notice Deploy a SuperToken logic contract
/// @param host the address of the host contract
+ /// @param constantOutflowNFT the address of the ConstantOutflowNFT contract
+ /// @param constantInflowNFT the address of the ConstantInflowNFT contract
+ /// @param poolAdminNFT the address of the PoolAdminNFT contract
+ /// @param poolMemberNFT the address of the PoolMemberNFT contract
+ /// @return the address of the newly deployed SuperToken logic contract
function deploySuperTokenLogic(
ISuperfluid host,
IConstantOutflowNFT constantOutflowNFT,
- IConstantInflowNFT constantInflowNFT
+ IConstantInflowNFT constantInflowNFT,
+ IPoolAdminNFT poolAdminNFT,
+ IPoolMemberNFT poolMemberNFT
) external returns (address) {
- return address(new SuperToken(host, constantOutflowNFT, constantInflowNFT));
+ return address(new SuperToken(host, constantOutflowNFT, constantInflowNFT, poolAdminNFT, poolMemberNFT));
}
}
@@ -466,18 +527,26 @@ library SuperfluidPeripheryDeployerLibrary {
/// @dev deploys Super Token Factory contract
/// @param _host address of the Superfluid contract
/// @param _superTokenLogic address of the Super Token logic contract
+ /// @param constantOutflowNFTLogic address of the Constant Outflow NFT logic contract
+ /// @param constantInflowNFTLogic address of the Constant Inflow NFT logic contract
+ /// @param poolAdminNFTLogic address of the Pool Admin NFT logic contract
+ /// @param poolMemberNFTLogic address of the Pool Member NFT logic contract
/// @return newly deployed SuperTokenFactory contract
function deploySuperTokenFactory(
ISuperfluid _host,
ISuperToken _superTokenLogic,
- IConstantOutflowNFT constantOutflowNFT,
- IConstantInflowNFT constantInflowNFT
+ IConstantOutflowNFT constantOutflowNFTLogic,
+ IConstantInflowNFT constantInflowNFTLogic,
+ IPoolAdminNFT poolAdminNFTLogic,
+ IPoolMemberNFT poolMemberNFTLogic
) external returns (SuperTokenFactory) {
return new SuperTokenFactory(
_host,
_superTokenLogic,
- constantOutflowNFT,
- constantInflowNFT
+ constantOutflowNFTLogic,
+ constantInflowNFTLogic,
+ poolAdminNFTLogic,
+ poolMemberNFTLogic
);
}
@@ -507,6 +576,15 @@ library IDAv1ForwarderDeployerLibrary {
}
}
+library GDAv1ForwarderDeployerLibrary {
+ /// @notice deploys the Superfluid GDAv1Forwarder contract
+ /// @param _host Superfluid host address
+ /// @return newly deployed GDAv1Forwarder contract
+ function deployGDAv1Forwarder(ISuperfluid _host) external returns (GDAv1Forwarder) {
+ return new GDAv1Forwarder(_host);
+ }
+}
+
library SuperfluidLoaderDeployerLibrary {
/// @notice deploys the Superfluid SuperfluidLoader contract
/// @param _resolver Superfluid resolver address
@@ -516,7 +594,15 @@ library SuperfluidLoaderDeployerLibrary {
}
}
-library SuperfluidNFTLogicDeployerLibrary {
+library SuperfluidPoolLogicDeployerLibrary {
+ /// @notice deploys the Superfluid SuperfluidPool contract
+ /// @return newly deployed SuperfluidPool contract
+ function deploySuperfluidPool(GeneralDistributionAgreementV1 _gda) external returns (SuperfluidPool) {
+ return new SuperfluidPool(_gda);
+ }
+}
+
+library SuperfluidFlowNFTLogicDeployerLibrary {
/// @notice deploys the Superfluid ConstantOutflowNFT contract
/// @param _host Superfluid host address
/// @param _constantInflowNFTProxy address of the ConstantInflowNFT proxy contract
@@ -540,10 +626,30 @@ library SuperfluidNFTLogicDeployerLibrary {
}
}
+library SuperfluidPoolNFTLogicDeployerLibrary {
+ /// @notice deploys the Superfluid PoolAdminNFT contract
+ /// @param _host Superfluid host address
+ /// @return newly deployed PoolAdminNFT contract
+ function deployPoolAdminNFT(ISuperfluid _host) external returns (PoolAdminNFT) {
+ return new PoolAdminNFT(_host);
+ }
+
+ /// @notice deploys the Superfluid PoolMemberNFT contract
+ /// @param _host Superfluid host address
+ /// @return newly deployed PoolMemberNFT contract
+ function deployPoolMemberNFT(ISuperfluid _host) external returns (PoolMemberNFT) {
+ return new PoolMemberNFT(_host);
+ }
+}
+
library ProxyDeployerLibrary {
function deployUUPSProxy() external returns (UUPSProxy) {
return new UUPSProxy();
}
+
+ function deploySuperfluidUpgradeableBeacon(address logicContract) external returns (SuperfluidUpgradeableBeacon) {
+ return new SuperfluidUpgradeableBeacon(logicContract);
+ }
}
library TokenDeployerLibrary {
diff --git a/packages/ethereum-contracts/contracts/utils/SuperfluidLoader.sol b/packages/ethereum-contracts/contracts/utils/SuperfluidLoader.sol
index 1051b919ee..9776250b83 100644
--- a/packages/ethereum-contracts/contracts/utils/SuperfluidLoader.sol
+++ b/packages/ethereum-contracts/contracts/utils/SuperfluidLoader.sol
@@ -26,6 +26,7 @@ contract SuperfluidLoader {
ISuperTokenFactory superTokenFactory;
ISuperAgreement agreementCFAv1;
ISuperAgreement agreementIDAv1;
+ ISuperAgreement agreementGDAv1;
}
constructor(IResolver resolver) {
@@ -51,5 +52,8 @@ contract SuperfluidLoader {
result.agreementIDAv1 = result.superfluid.getAgreementClass(
keccak256("org.superfluid-finance.agreements.InstantDistributionAgreement.v1")
);
+ result.agreementGDAv1 = result.superfluid.getAgreementClass(
+ keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1")
+ );
}
}
diff --git a/packages/ethereum-contracts/dev-scripts/deploy-contracts-and-token.js b/packages/ethereum-contracts/dev-scripts/deploy-contracts-and-token.js
index a350f665eb..34bf331116 100644
--- a/packages/ethereum-contracts/dev-scripts/deploy-contracts-and-token.js
+++ b/packages/ethereum-contracts/dev-scripts/deploy-contracts-and-token.js
@@ -19,11 +19,12 @@ async function deployContractsAndToken() {
await deployer
.connect(Deployer)
- ["deployWrapperSuperToken(string,string,uint8,uint256)"](
+ .deployWrapperSuperToken(
"Fake DAI",
"fDAI",
18,
- ethers.utils.parseUnits("1000000000000")
+ ethers.utils.parseUnits("1000000000000"),
+ ethers.constants.AddressZero
);
await deployer
diff --git a/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js b/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js
index 348b5b631a..7f07237592 100644
--- a/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js
+++ b/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js
@@ -1,16 +1,21 @@
const {ethers} = require("hardhat");
const {JsonRpcProvider} = require("@ethersproject/providers");
+const SuperfluidPoolDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol/SuperfluidPoolDeployerLibrary.json");
const SuperfluidGovDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidGovDeployerLibrary.json");
const SuperfluidHostDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidHostDeployerLibrary.json");
const SuperfluidCFAv1DeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidCFAv1DeployerLibrary.json");
const SuperfluidIDAv1DeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidIDAv1DeployerLibrary.json");
+const SuperfluidGDAv1DeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidGDAv1DeployerLibrary.json");
const SuperTokenDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperTokenDeployerLibrary.json");
const SuperfluidPeripheryDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidPeripheryDeployerLibrary.json");
-const SuperfluidNFTLogicDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidNFTLogicDeployerLibrary.json");
+const SuperfluidPoolLogicDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidPoolLogicDeployerLibrary.json");
+const SuperfluidFlowNFTLogicDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidFlowNFTLogicDeployerLibrary.json");
+const SuperfluidPoolNFTLogicDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidPoolNFTLogicDeployerLibrary.json");
const ProxyDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/ProxyDeployerLibrary.json");
const CFAv1ForwarderDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/CFAv1ForwarderDeployerLibrary.json");
const IDAv1ForwarderDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/IDAv1ForwarderDeployerLibrary.json");
+const GDAv1ForwarderDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/GDAv1ForwarderDeployerLibrary.json");
const SuperfluidLoaderDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidLoaderDeployerLibrary.json");
const SuperfluidFrameworkDeployerArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeployer.sol/SuperfluidFrameworkDeployer.json");
const SlotsBitmapLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/libs/SlotsBitmapLibrary.sol/SlotsBitmapLibrary.json");
@@ -145,6 +150,27 @@ const _deployTestFramework = async (provider, signer) => {
},
}
);
+
+ const SuperfluidPoolDeployerLibrary =
+ await _getFactoryAndReturnDeployedContract(
+ "SuperfluidPoolDeployerLibrary",
+ SuperfluidPoolDeployerLibraryArtifact,
+ signer
+ );
+
+ const SuperfluidGDAv1DeployerLibrary =
+ await _getFactoryAndReturnDeployedContract(
+ "SuperfluidGDAv1DeployerLibrary",
+ SuperfluidGDAv1DeployerLibraryArtifact,
+ {
+ signer,
+ libraries: {
+ SuperfluidPoolDeployerLibrary:
+ SuperfluidPoolDeployerLibrary.address,
+ SlotsBitmapLibrary: SlotsBitmapLibrary.address,
+ },
+ }
+ );
const SuperTokenDeployerLibrary =
await _getFactoryAndReturnDeployedContract(
"SuperTokenDeployerLibrary",
@@ -159,10 +185,23 @@ const _deployTestFramework = async (provider, signer) => {
SuperfluidPeripheryDeployerLibraryArtifact,
signer
);
- const SuperfluidNFTLogicDeployerLibrary =
+
+ const SuperfluidPoolLogicDeployerLibrary =
+ await _getFactoryAndReturnDeployedContract(
+ "SuperfluidPoolLogicDeployerLibrary",
+ SuperfluidPoolLogicDeployerLibraryArtifact,
+ signer
+ );
+ const SuperfluidFlowNFTLogicDeployerLibrary =
await _getFactoryAndReturnDeployedContract(
- "SuperfluidNFTLogicDeployerLibrary",
- SuperfluidNFTLogicDeployerLibraryArtifact,
+ "SuperfluidFlowNFTLogicDeployerLibrary",
+ SuperfluidFlowNFTLogicDeployerLibraryArtifact,
+ signer
+ );
+ const SuperfluidPoolNFTLogicDeployerLibrary =
+ await _getFactoryAndReturnDeployedContract(
+ "SuperfluidPoolNFTLogicDeployerLibrary",
+ SuperfluidPoolNFTLogicDeployerLibraryArtifact,
signer
);
const ProxyDeployerLibrary = await _getFactoryAndReturnDeployedContract(
@@ -182,6 +221,12 @@ const _deployTestFramework = async (provider, signer) => {
IDAv1ForwarderDeployerLibraryArtifact,
signer
);
+ const GDAv1ForwarderDeployerLibrary =
+ await _getFactoryAndReturnDeployedContract(
+ "GDAv1ForwarderDeployerLibrary",
+ GDAv1ForwarderDeployerLibraryArtifact,
+ signer
+ );
const SuperfluidLoaderDeployerLibrary =
await _getFactoryAndReturnDeployedContract(
"SuperfluidLoaderDeployerLibrary",
@@ -212,14 +257,23 @@ const _deployTestFramework = async (provider, signer) => {
SuperfluidIDAv1DeployerLibrary: getContractAddress(
SuperfluidIDAv1DeployerLibrary
),
+ SuperfluidGDAv1DeployerLibrary: getContractAddress(
+ SuperfluidGDAv1DeployerLibrary
+ ),
SuperfluidPeripheryDeployerLibrary: getContractAddress(
SuperfluidPeripheryDeployerLibrary
),
SuperTokenDeployerLibrary: getContractAddress(
SuperTokenDeployerLibrary
),
- SuperfluidNFTLogicDeployerLibrary: getContractAddress(
- SuperfluidNFTLogicDeployerLibrary
+ SuperfluidPoolLogicDeployerLibrary: getContractAddress(
+ SuperfluidPoolLogicDeployerLibrary
+ ),
+ SuperfluidFlowNFTLogicDeployerLibrary: getContractAddress(
+ SuperfluidFlowNFTLogicDeployerLibrary
+ ),
+ SuperfluidPoolNFTLogicDeployerLibrary: getContractAddress(
+ SuperfluidPoolNFTLogicDeployerLibrary
),
ProxyDeployerLibrary: getContractAddress(ProxyDeployerLibrary),
CFAv1ForwarderDeployerLibrary: getContractAddress(
@@ -228,6 +282,9 @@ const _deployTestFramework = async (provider, signer) => {
IDAv1ForwarderDeployerLibrary: getContractAddress(
IDAv1ForwarderDeployerLibrary
),
+ GDAv1ForwarderDeployerLibrary: getContractAddress(
+ GDAv1ForwarderDeployerLibrary
+ ),
SuperfluidLoaderDeployerLibrary: getContractAddress(
SuperfluidLoaderDeployerLibrary
),
diff --git a/packages/ethereum-contracts/dev-scripts/run-deploy-contracts-and-token.js b/packages/ethereum-contracts/dev-scripts/run-deploy-contracts-and-token.js
index 3cd8f0505e..e81ebc157e 100644
--- a/packages/ethereum-contracts/dev-scripts/run-deploy-contracts-and-token.js
+++ b/packages/ethereum-contracts/dev-scripts/run-deploy-contracts-and-token.js
@@ -12,6 +12,7 @@ deployContractsAndToken()
hostAddress: frameworkAddresses.host,
cfaAddress: frameworkAddresses.cfa,
idaAddress: frameworkAddresses.ida,
+ gdaAddress: frameworkAddresses.gda,
superTokenFactoryAddress: frameworkAddresses.superTokenFactory,
resolverV1Address: frameworkAddresses.resolver,
nativeAssetSuperTokenAddress:
@@ -44,4 +45,4 @@ deployContractsAndToken()
.catch((err) => {
console.error(err);
process.exit(1);
- });
+ });
\ No newline at end of file
diff --git a/packages/ethereum-contracts/foundry.toml b/packages/ethereum-contracts/foundry.toml
index 36101b5039..04e9527012 100644
--- a/packages/ethereum-contracts/foundry.toml
+++ b/packages/ethereum-contracts/foundry.toml
@@ -9,6 +9,7 @@ ignored_error_codes = [5159] # selfdestruct in contracts/mocks/SuperfluidDestruc
evm_version = 'paris'
remappings = [
'@superfluid-finance/ethereum-contracts/contracts/=packages/ethereum-contracts/contracts/',
+ '@superfluid-finance/solidity-semantic-money/src/=packages/solidity-semantic-money/src/',
'@openzeppelin/=node_modules/@openzeppelin/',
'ds-test/=lib/forge-std/lib/ds-test/src/',
'forge-std/=lib/forge-std/src/']
diff --git a/packages/ethereum-contracts/ops-scripts/deploy-aux-contracts.js b/packages/ethereum-contracts/ops-scripts/deploy-aux-contracts.js
index 0f12471c79..41c68afb2c 100644
--- a/packages/ethereum-contracts/ops-scripts/deploy-aux-contracts.js
+++ b/packages/ethereum-contracts/ops-scripts/deploy-aux-contracts.js
@@ -80,10 +80,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
console.log("deploying solvency related contracts");
const minBondDuration = process.env.TOGA_MIN_BOND_DURATION || 604800;
- const toga = await TOGA.new(
- sf.host.address,
- minBondDuration
- );
+ const toga = await TOGA.new(sf.host.address, minBondDuration);
console.log("TOGA deployed at:", toga.address);
await gov.setRewardAddress(
@@ -93,10 +90,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
);
console.log("reward address set to TOGA");
- const batchLiquidator = await BatchLiquidator.new(
- sf.host.address,
- sf.agreements.cfa.address,
- );
+ const batchLiquidator = await BatchLiquidator.new(sf.host.address);
console.log("BatchLiquidator deployed at:", batchLiquidator.address);
await oldGov.replaceGovernance(sf.host.address, govProxy.address);
diff --git a/packages/ethereum-contracts/ops-scripts/deploy-deterministically.js b/packages/ethereum-contracts/ops-scripts/deploy-deterministically.js
index 59127e6ae4..9b1da6c5bc 100644
--- a/packages/ethereum-contracts/ops-scripts/deploy-deterministically.js
+++ b/packages/ethereum-contracts/ops-scripts/deploy-deterministically.js
@@ -5,6 +5,7 @@ const Resolver = artifacts.require("Resolver");
const SuperfluidLoader = artifacts.require("SuperfluidLoader");
const CFAv1Forwarder = artifacts.require("CFAv1Forwarder");
+const GDAv1Forwarder = artifacts.require("GDAv1Forwarder");
/**
* @dev Deploy specified contract at a deterministic address (defined by sender, nonce)
@@ -80,6 +81,12 @@ module.exports = eval(`(${S.toString()})()`)(async function (
console.log(
`setting up CFAv1Forwarder for chainId ${chainId}, host ${hostAddr}`
);
+ } else if (contractName === "GDAv1Forwarder") {
+ ContractArtifact = GDAv1Forwarder;
+ deployArgs = [hostAddr];
+ console.log(
+ `setting up GDAv1Forwarder for chainId ${chainId}, host ${hostAddr}`
+ );
} else {
throw new Error("Contract unknown / not supported");
}
@@ -165,5 +172,7 @@ module.exports = eval(`(${S.toString()})()`)(async function (
const deployTxReceipt = await web3.eth.sendSignedTransaction(
signedTx.rawTransaction
);
- console.log("contract deployed at:", deployTxReceipt.contractAddress);
+ // make it easy to get the deployed address with `tail -n 1`
+ console.log("contract deployed at:");
+ console.log(deployTxReceipt.contractAddress);
});
diff --git a/packages/ethereum-contracts/ops-scripts/deploy-framework.js b/packages/ethereum-contracts/ops-scripts/deploy-framework.js
index 9f04525a41..e41c8d4e2a 100644
--- a/packages/ethereum-contracts/ops-scripts/deploy-framework.js
+++ b/packages/ethereum-contracts/ops-scripts/deploy-framework.js
@@ -102,6 +102,8 @@ async function deployContractIfCodeChanged(
* (overriding env: RELEASE_VERSION)
* @param {string} options.outputFile Name of file where to log addresses of newly deployed contracts
* (overriding env: OUTPUT_FILE)
+ * @param {boolean} options.newSuperfluidLoader Deploy a new superfluid loader contract
+ * (overriding env: NEW_SUPERFLUID_LOADER)
*
* Usage: npx truffle exec ops-scripts/deploy-framework.js
*/
@@ -118,6 +120,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
appWhiteListing,
protocolReleaseVersion,
outputFile,
+ newSuperfluidLoader,
} = options;
resetSuperfluidFramework = options.resetSuperfluidFramework;
@@ -159,6 +162,9 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
const IDAv1_TYPE = web3.utils.sha3(
"org.superfluid-finance.agreements.InstantDistributionAgreement.v1"
);
+ const GDAv1_TYPE = web3.utils.sha3(
+ "org.superfluid-finance.agreements.GeneralDistributionAgreement.v1"
+ );
newTestResolver = newTestResolver || !!process.env.CREATE_NEW_RESOLVER;
useMocks = useMocks || !!process.env.USE_MOCKS;
@@ -167,6 +173,8 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
appWhiteListing ||
config.gov_enableAppWhiteListing ||
!!process.env.ENABLE_APP_WHITELISTING;
+ newSuperfluidLoader = newSuperfluidLoader || !!process.env.NEW_SUPERFLUID_LOADER;
+
console.log("app whitelisting enabled:", appWhiteListing);
if (newTestResolver) {
console.log("**** !ATTN! CREATING NEW RESOLVER ****");
@@ -177,8 +185,8 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
if (nonUpgradable) {
console.log("**** !ATTN! DISABLED UPGRADABILITY ****");
}
- if (appWhiteListing) {
- console.log("**** !ATTN! ENABLING APP WHITELISTING ****");
+ if (newSuperfluidLoader) {
+ console.log("**** !ATTN! DEPLOYING NEW SUPERFLUID LOADER ****");
}
await deployERC1820((err) => {
@@ -188,6 +196,8 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
const contracts = [
"Ownable",
"CFAv1Forwarder",
+ "IDAv1Forwarder",
+ "GDAv1Forwarder",
"IMultiSigWallet",
"ISafe",
"SuperfluidGovernanceBase",
@@ -203,8 +213,14 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
"SlotsBitmapLibrary",
"ConstantFlowAgreementV1",
"InstantDistributionAgreementV1",
+ "GeneralDistributionAgreementV1",
+ "SuperfluidUpgradeableBeacon",
+ "SuperfluidPool",
+ "SuperfluidPoolDeployerLibrary",
"ConstantOutflowNFT",
"ConstantInflowNFT",
+ "PoolAdminNFT",
+ "PoolMemberNFT",
"IAccessControlEnumerable",
];
const mockContracts = [
@@ -217,6 +233,8 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
IMultiSigWallet,
ISafe,
CFAv1Forwarder,
+ IDAv1Forwarder,
+ GDAv1Forwarder,
SuperfluidGovernanceBase,
Resolver,
SuperfluidLoader,
@@ -233,8 +251,14 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
SlotsBitmapLibrary,
ConstantFlowAgreementV1,
InstantDistributionAgreementV1,
+ GeneralDistributionAgreementV1,
+ SuperfluidUpgradeableBeacon,
+ SuperfluidPool,
+ SuperfluidPoolDeployerLibrary,
ConstantOutflowNFT,
ConstantInflowNFT,
+ PoolAdminNFT,
+ PoolMemberNFT,
IAccessControlEnumerable,
} = await SuperfluidSDK.loadContracts({
...extractWeb3Options(options),
@@ -283,7 +307,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
await deployAndRegisterContractIf(
SuperfluidLoader,
"SuperfluidLoader-v1",
- async (contractAddress) => contractAddress === ZERO_ADDRESS,
+ async (contractAddress) => newSuperfluidLoader === true || contractAddress === ZERO_ADDRESS,
async () => {
const c = await web3tx(
SuperfluidLoader.new,
@@ -348,7 +372,9 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
);
// this is needed later on
const superfluidConstructorParam = superfluid.address
- .toLowerCase().slice(2).padStart(64, "0");
+ .toLowerCase()
+ .slice(2)
+ .padStart(64, "0");
// load existing governance if needed
if (!governance) {
@@ -368,6 +394,9 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
if (config.cfaFwd) {
trustedForwarders.push(config.cfaFwd);
}
+ if (config.gdaFwd) {
+ trustedForwarders.push(config.gdaFwd);
+ }
console.log(`initializing governance with config: ${JSON.stringify({
liquidationPeriod: config.liquidationPeriod,
patricianPeriod: config.patricityPeriod,
@@ -449,16 +478,18 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
return externalLibrary;
};
+ let slotsBitmapLibraryAddress = ZERO_ADDRESS;
// list IDA v1
const deployIDAv1 = async () => {
// small inefficiency: this may be re-deployed even if not changed
// deploySlotsBitmapLibrary
- await deployExternalLibraryAndLink(
+ const slotsBitmapLibrary = await deployExternalLibraryAndLink(
SlotsBitmapLibrary,
"SlotsBitmapLibrary",
- "SLOTS_BITMAP_LIBRARY_ADDRESS",
+ "SLOTS_BITMAP_LIBRARY",
InstantDistributionAgreementV1
);
+ slotsBitmapLibraryAddress = slotsBitmapLibrary.address;
const agreement = await web3tx(
InstantDistributionAgreementV1.new,
"InstantDistributionAgreementV1.new"
@@ -482,7 +513,6 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
// here as an optimization, this assumes that we do not change the
// library code.
// link library in order to avoid spurious code change detections
- let slotsBitmapLibraryAddress = ZERO_ADDRESS;
try {
const IDAv1 = await InstantDistributionAgreementV1.at(
await superfluid.getAgreementClass.call(IDAv1_TYPE)
@@ -507,6 +537,94 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
}
}
+ // @note GDA deployment is commented out until we plan on releasing it
+ const deployGDAv1 = async () => {
+ try {
+ // deploy and link SuperfluidPoolDeployerLibrary
+ await deployExternalLibraryAndLink(
+ SuperfluidPoolDeployerLibrary,
+ "SuperfluidPoolDeployerLibrary",
+ "SUPERFLUID_POOL_DEPLOYER",
+ GeneralDistributionAgreementV1
+ );
+
+ if (process.env.IS_HARDHAT) {
+ if (slotsBitmapLibraryAddress !== ZERO_ADDRESS) {
+ const lib = await SlotsBitmapLibrary.at(
+ slotsBitmapLibraryAddress
+ );
+ GeneralDistributionAgreementV1.link(lib);
+ }
+ } else {
+ GeneralDistributionAgreementV1.link(
+ "SlotsBitmapLibrary",
+ slotsBitmapLibraryAddress
+ );
+ }
+ } catch (err) {
+ console.error(err);
+ }
+ const agreement = await web3tx(
+ GeneralDistributionAgreementV1.new,
+ "GeneralDistributionAgreementV1.new"
+ )(superfluid.address);
+
+ console.log(
+ "New GeneralDistributionAgreementV1 address",
+ agreement.address
+ );
+ output += `GDA_LOGIC=${agreement.address}\n`;
+ return agreement;
+ };
+
+ if (!(await superfluid.isAgreementTypeListed.call(GDAv1_TYPE))) {
+ const gda = await deployGDAv1();
+ await web3tx(
+ governance.registerAgreementClass,
+ "Governance registers GDA"
+ )(superfluid.address, gda.address);
+ } else {
+ // NOTE that we are reusing the existing deployed external library
+ // here as an optimization, this assumes that we do not change the
+ // library code.
+ // link library in order to avoid spurious code change detections
+ try {
+ const GDAv1 = await GeneralDistributionAgreementV1.at(
+ await superfluid.getAgreementClass.call(GDAv1_TYPE)
+ );
+ slotsBitmapLibraryAddress =
+ await GDAv1.SLOTS_BITMAP_LIBRARY_ADDRESS.call();
+ let superfluidPoolDeployerLibraryAddress =
+ await GDAv1.SUPERFLUID_POOL_DEPLOYER_ADDRESS.call();
+ if (process.env.IS_HARDHAT) {
+ if (slotsBitmapLibraryAddress !== ZERO_ADDRESS) {
+ const lib = await SlotsBitmapLibrary.at(
+ slotsBitmapLibraryAddress
+ );
+ GeneralDistributionAgreementV1.link(lib);
+ }
+ if (superfluidPoolDeployerLibraryAddress !== ZERO_ADDRESS) {
+ const lib = await SuperfluidPoolDeployerLibrary.at(
+ superfluidPoolDeployerLibraryAddress
+ );
+ GeneralDistributionAgreementV1.link(lib);
+ }
+ } else {
+ GeneralDistributionAgreementV1.link(
+ "SlotsBitmapLibrary",
+ slotsBitmapLibraryAddress
+ );
+ GeneralDistributionAgreementV1.link(
+ "SuperfluidPoolDeployerLibrary",
+ superfluidPoolDeployerLibraryAddress
+ );
+ }
+ } catch (e) {
+ console.warn("Cannot get slotsBitmapLibrary address", e.toString());
+ }
+ }
+ // @note GDA deployment is commented out until we plan on releasing it
+
if (protocolReleaseVersion === "test") {
// deploy CFAv1Forwarder for test deployments
// for other (permanent) deployments, it's not handled by this script
@@ -519,7 +637,41 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
output += `CFA_V1_FORWARDER=${forwarder.address}\n`;
await web3tx(
governance.enableTrustedForwarder,
- "Governance set CFAv1Forwarder"
+ `Governance set CFAv1Forwarder`
+ )(superfluid.address, ZERO_ADDRESS, forwarder.address);
+ return forwarder;
+ }
+ );
+
+ // deploy IDAv1Forwarder for test deployments
+ // for other (permanent) deployments, it's not handled by this script
+ await deployAndRegisterContractIf(
+ IDAv1Forwarder,
+ "IDAv1Forwarder",
+ async (contractAddress) => contractAddress === ZERO_ADDRESS,
+ async () => {
+ const forwarder = await IDAv1Forwarder.new(superfluid.address);
+ output += `IDA_V1_FORWARDER=${forwarder.address}\n`;
+ await web3tx(
+ governance.enableTrustedForwarder,
+ `Governance set IDAv1Forwarder`
+ )(superfluid.address, ZERO_ADDRESS, forwarder.address);
+ return forwarder;
+ }
+ );
+
+ // deploy GDAv1Forwarder for test deployments
+ // for other (permanent) deployments, it's not handled by this script
+ await deployAndRegisterContractIf(
+ GDAv1Forwarder,
+ "GDAv1Forwarder",
+ async (contractAddress) => contractAddress === ZERO_ADDRESS,
+ async () => {
+ const forwarder = await GDAv1Forwarder.new(superfluid.address);
+ output += `GDA_V1_FORWARDER=${forwarder.address}\n`;
+ await web3tx(
+ governance.enableTrustedForwarder,
+ `Governance set GDAv1Forwarder`
)(superfluid.address, ZERO_ADDRESS, forwarder.address);
return forwarder;
}
@@ -567,9 +719,9 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
ZERO_ADDRESS.toLowerCase().slice(2).padStart(64, "0"),
]
);
- if (cfaNewLogicAddress !== ZERO_ADDRESS)
+ if (cfaNewLogicAddress !== ZERO_ADDRESS) {
agreementsToUpdate.push(cfaNewLogicAddress);
-
+ }
// deploy new IDA logic
const idaNewLogicAddress = await deployContractIfCodeChanged(
web3,
@@ -585,8 +737,28 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
superfluidConstructorParam,
]
);
- if (idaNewLogicAddress !== ZERO_ADDRESS)
+ if (idaNewLogicAddress !== ZERO_ADDRESS) {
agreementsToUpdate.push(idaNewLogicAddress);
+ }
+ // @note commented out: deploy new GDA logic
+ const gdaNewLogicAddress = await deployContractIfCodeChanged(
+ web3,
+ GeneralDistributionAgreementV1,
+ await (
+ await UUPSProxiable.at(
+ await superfluid.getAgreementClass.call(GDAv1_TYPE)
+ )
+ ).getCodeAddress(),
+ async () => (await deployGDAv1()).address,
+ [
+ // See SuperToken constructor parameter
+ superfluidConstructorParam,
+ ]
+ );
+ if (gdaNewLogicAddress !== ZERO_ADDRESS) {
+ agreementsToUpdate.push(gdaNewLogicAddress);
+ }
+ // @note GDA deployment is commented out until we plan on releasing it
}
// deploy new super token factory logic (depends on SuperToken logic, which links to nft deployer library)
@@ -600,23 +772,17 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
let constantOutflowNFTLogicChanged = false;
let constantInflowNFTLogicChanged = false;
+ let poolAdminNFTLogicChanged = false;
+ let poolMemberNFTLogicChanged = false;
+
+ const deployNFTContract = async (artifact, nftType, nftTypeCaps, args) => {
+ const nftLogic = await web3tx(artifact.new, `${nftType}.new`)(...args);
+ console.log(`${nftType} Logic address`, nftLogic.address);
+ output += `${nftTypeCaps}=${nftLogic.address}\n`;
- const deployNFTContract = async (isOutflow, args) => {
- const artifact = isOutflow ? ConstantOutflowNFT : ConstantInflowNFT;
- const nftType = isOutflow ? "ConstantOutflowNFT" : "ConstantInflowNFT";
- const nftTypeCaps = isOutflow
- ? "CONSTANT_OUTFLOW_NFT"
- : "CONSTANT_INFLOW_NFT";
- const flowNFTLogic = await web3tx(
- artifact.new,
- `${nftType}.new`
- )(...args);
- console.log(`${nftType} Logic address`, flowNFTLogic.address);
- output += `${nftTypeCaps}=${flowNFTLogic.address}\n`;
-
- await flowNFTLogic.castrate();
-
- return flowNFTLogic;
+ await nftLogic.castrate();
+
+ return nftLogic;
};
const superTokenFactoryNewLogicAddress = await deployContractIf(
@@ -643,13 +809,21 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
const cofNFTLAddr = await cofNFTContract.getCodeAddress();
const cifNFTLAddr = await cifNFTContract.getCodeAddress();
+ const poolAdminNFTPAddr = await superTokenLogic.POOL_ADMIN_NFT();
+ const poolMemberNFTPAddr = await superTokenLogic.POOL_MEMBER_NFT();
+ const poolAdminNFTContract = await PoolAdminNFT.at(poolAdminNFTPAddr);
+ const poolMemberNFTContract = await PoolMemberNFT.at(poolMemberNFTPAddr);
+ const poolAdminNFTLAddr = await poolAdminNFTContract.getCodeAddress();
+ const poolMemberNFTLAddr = await poolMemberNFTContract.getCodeAddress();
+
const cfaPAddr = await superfluid.getAgreementClass.call(CFAv1_TYPE);
+ const gdaPAddr = await superfluid.getAgreementClass.call(GDAv1_TYPE);
constantOutflowNFTLogicChanged = await codeChanged(
web3,
ConstantOutflowNFT,
cofNFTLAddr,
- [superfluidConstructorParam, ap(cifNFTPAddr), ap(cfaPAddr)]
+ [superfluidConstructorParam, ap(cifNFTPAddr), ap(cfaPAddr), ap(gdaPAddr)]
);
console.log(" constantOutflowNFTLogicChanged:", constantOutflowNFTLogicChanged);
@@ -657,15 +831,32 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
web3,
ConstantInflowNFT,
cifNFTLAddr,
- [superfluidConstructorParam, ap(cofNFTPAddr), ap(cfaPAddr)]
+ [superfluidConstructorParam, ap(cofNFTPAddr), ap(cfaPAddr), ap(gdaPAddr)]
);
console.log(" constantInflowNFTLogicChanged:", constantInflowNFTLogicChanged);
+ poolAdminNFTLogicChanged = await codeChanged(
+ web3,
+ PoolAdminNFT,
+ poolAdminNFTLAddr,
+ [superfluidConstructorParam, ap(gdaPAddr)]
+ );
+ console.log(" poolAdminNFTLogicChanged:", poolAdminNFTLogicChanged);
+
+ poolMemberNFTLogicChanged = await codeChanged(
+ web3,
+ PoolMemberNFT,
+ poolMemberNFTLAddr,
+ [superfluidConstructorParam, ap(gdaPAddr)]
+ );
+ console.log(" poolMemberNFTLogicChanged:", poolMemberNFTLogicChanged);
+
const superTokenFactoryCodeChanged = await codeChanged(
web3,
SuperTokenFactoryLogic,
await superfluid.getSuperTokenFactoryLogic.call(),
- [superfluidConstructorParam, ap(superTokenLogicAddress), ap(cofNFTLAddr), ap(cifNFTLAddr)]
+ [superfluidConstructorParam, ap(superTokenLogicAddress), ap(cofNFTLAddr), ap(cifNFTLAddr),
+ ap(poolAdminNFTLAddr), ap(poolMemberNFTLAddr)]
);
console.log(" superTokenFactoryCodeChanged:", superTokenFactoryCodeChanged);
@@ -674,7 +865,10 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
SuperTokenLogic,
await factory.getSuperTokenLogic.call(),
// this replacement does not support SuperTokenMock
- [superfluidConstructorParam, ap(cofNFTPAddr), ap(cifNFTPAddr)]
+ [
+ superfluidConstructorParam, ap(cofNFTPAddr), ap(cifNFTPAddr),
+ ap(poolAdminNFTPAddr), ap(poolMemberNFTPAddr)
+ ]
);
console.log(" superTokenLogicCodeChanged:", superTokenLogicCodeChanged);
return (
@@ -685,7 +879,9 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
superTokenFactoryCodeChanged ||
superTokenLogicCodeChanged ||
constantOutflowNFTLogicChanged ||
- constantInflowNFTLogicChanged
+ constantInflowNFTLogicChanged ||
+ poolAdminNFTLogicChanged ||
+ poolMemberNFTLogicChanged
);
} catch (e) {
console.log(
@@ -704,6 +900,10 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
let cifNFTProxyAddress = ZERO_ADDRESS;
let cofNFTLogicAddress = ZERO_ADDRESS;
let cifNFTLogicAddress = ZERO_ADDRESS;
+ let poolAdminNFTProxyAddress = ZERO_ADDRESS;
+ let poolAdminNFTLogicAddress = ZERO_ADDRESS;
+ let poolMemberNFTProxyAddress = ZERO_ADDRESS;
+ let poolMemberNFTLogicAddress = ZERO_ADDRESS;
// try to get NFT proxy addresses from canonical Super Token logic
if (factoryAddress !== ZERO_ADDRESS) {
@@ -717,6 +917,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
const superTokenLogic = await SuperTokenLogic.at(
superTokenLogicAddress
);
+ // Flow NFTs
cofNFTProxyAddress =
await superTokenLogic.CONSTANT_OUTFLOW_NFT.call();
cifNFTProxyAddress =
@@ -727,6 +928,18 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
cifNFTLogicAddress = await (
await UUPSProxiable.at(cifNFTProxyAddress)
).getCodeAddress();
+
+ // Pool NFTs
+ poolAdminNFTProxyAddress =
+ await superTokenLogic.POOL_ADMIN_NFT.call();
+ poolMemberNFTProxyAddress =
+ await superTokenLogic.POOL_MEMBER_NFT.call();
+ poolAdminNFTLogicAddress = await (
+ await UUPSProxiable.at(poolAdminNFTProxyAddress)
+ ).getCodeAddress();
+ poolMemberNFTLogicAddress = await (
+ await UUPSProxiable.at(poolMemberNFTProxyAddress)
+ ).getCodeAddress();
} catch (err) {
console.error("Unable to get nft proxy addresses");
}
@@ -735,57 +948,146 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
// if the super token logic does not have the proxies, we must deploy
// new nft logic and proxies.
if (
- cofNFTProxyAddress === ZERO_ADDRESS &&
- cifNFTProxyAddress === ZERO_ADDRESS
+ cofNFTProxyAddress === ZERO_ADDRESS ||
+ cifNFTProxyAddress === ZERO_ADDRESS ||
+ poolAdminNFTProxyAddress === ZERO_ADDRESS ||
+ poolMemberNFTProxyAddress === ZERO_ADDRESS
) {
- const constantOutflowNFTProxy = await web3tx(
- UUPSProxy.new,
- `Create ConstantOutflowNFT proxy`
- )();
- console.log("ConstantOutflowNFT Proxy address", constantOutflowNFTProxy.address);
- output += `CONSTANT_OUTFLOW_NFT_PROXY=${constantOutflowNFTProxy.address}\n`;
+ if (
+ cofNFTProxyAddress === ZERO_ADDRESS ||
+ cifNFTProxyAddress === ZERO_ADDRESS
+ ) {
+ const constantOutflowNFTProxy = await web3tx(
+ UUPSProxy.new,
+ `Create ConstantOutflowNFT proxy`
+ )();
+ console.log(
+ "ConstantOutflowNFT Proxy address",
+ constantOutflowNFTProxy.address
+ );
+ output += `CONSTANT_OUTFLOW_NFT_PROXY=${constantOutflowNFTProxy.address}\n`;
+
+ const constantInflowNFTProxy = await web3tx(
+ UUPSProxy.new,
+ `Create ConstantInflowNFT proxy`
+ )();
+ console.log(
+ "ConstantInflowNFT Proxy address",
+ constantInflowNFTProxy.address
+ );
+ output += `CONSTANT_INFLOW_NFT_PROXY=${constantInflowNFTProxy.address}\n`;
- const constantInflowNFTProxy = await web3tx(
- UUPSProxy.new,
- `Create ConstantInflowNFT proxy`
- )();
- console.log("ConstantInflowNFT Proxy address", constantInflowNFTProxy.address);
- output += `CONSTANT_INFLOW_NFT_PROXY=${constantInflowNFTProxy.address}\n`;
+ const constantOutflowNFTLogic = await deployNFTContract(
+ ConstantOutflowNFT,
+ "ConstantOutflowNFT",
+ "CONSTANT_OUTFLOW_NFT",
+ [superfluid.address, constantInflowNFTProxy.address]
+ );
+ const constantInflowNFTLogic = await deployNFTContract(
+ ConstantInflowNFT,
+ "ConstantInflowNFT",
+ "CONSTANT_INFLOW_NFT",
+ [superfluid.address, constantOutflowNFTProxy.address]
+ );
- const constantOutflowNFTLogic = await deployNFTContract(true, [
- superfluid.address,
- constantInflowNFTProxy.address,
- ]);
- const constantInflowNFTLogic = await deployNFTContract(false, [
- superfluid.address,
- constantOutflowNFTProxy.address,
- ]);
+ // set the nft logic addresses (to be consumed by the super token factory logic constructor)
+ cofNFTLogicAddress = constantOutflowNFTLogic.address;
+ cifNFTLogicAddress = constantInflowNFTLogic.address;
- // set the nft logic addresses (to be consumed by the super token factory logic constructor)
- cofNFTLogicAddress = constantOutflowNFTLogic.address;
- cifNFTLogicAddress = constantInflowNFTLogic.address;
+ // initialize the nft proxy with the nft logic
+ await constantOutflowNFTProxy.initializeProxy(
+ constantOutflowNFTLogic.address
+ );
+ await constantInflowNFTProxy.initializeProxy(
+ constantInflowNFTLogic.address
+ );
+ const constantOutflowNFT = await ConstantOutflowNFT.at(
+ constantOutflowNFTProxy.address
+ );
+ const constantInflowNFT = await ConstantInflowNFT.at(
+ constantInflowNFTProxy.address
+ );
- // initialize the nft proxy with the nft logic
- await constantOutflowNFTProxy.initializeProxy(
- constantOutflowNFTLogic.address
- );
- await constantInflowNFTProxy.initializeProxy(
- constantInflowNFTLogic.address
- );
- const constantOutflowNFT = await ConstantOutflowNFT.at(
- constantOutflowNFTProxy.address
- );
- const constantInflowNFT = await ConstantInflowNFT.at(
- constantInflowNFTProxy.address
- );
+ // initialize the proxy contracts with the nft names
+ await constantOutflowNFT.initialize(
+ "Constant Outflow NFT",
+ "COF"
+ );
+ await constantInflowNFT.initialize(
+ "Constant Inflow NFT",
+ "CIF"
+ );
- // initialize the proxy contracts with the nft names
- await constantOutflowNFT.initialize("Constant Outflow NFT", "COF");
- await constantInflowNFT.initialize("Constant Inflow NFT", "CIF");
+ // set the nft proxy addresses (to be consumed by the super token logic constructor)
+ cofNFTProxyAddress = constantOutflowNFTProxy.address;
+ cifNFTProxyAddress = constantInflowNFTProxy.address;
+ }
+ if (
+ poolAdminNFTProxyAddress === ZERO_ADDRESS ||
+ poolMemberNFTProxyAddress === ZERO_ADDRESS
+ ) {
+ const poolAdminNFTProxy = await web3tx(
+ UUPSProxy.new,
+ `Create PoolAdminNFT proxy`
+ )();
+ console.log(
+ "PoolAdminNFT Proxy address",
+ poolAdminNFTProxy.address
+ );
+ output += `POOL_ADMIN_NFT_PROXY=${poolAdminNFTProxy.address}\n`;
+
+ const poolMemberNFTProxy = await web3tx(
+ UUPSProxy.new,
+ `Create PoolMemberNFT proxy`
+ )();
+ console.log(
+ "PoolMemberNFT Proxy address",
+ poolMemberNFTProxy.address
+ );
+ output += `POOL_MEMBER_NFT_PROXY=${poolMemberNFTProxy.address}\n`;
+
+ const poolAdminNFTLogic = await deployNFTContract(
+ PoolAdminNFT,
+ "PoolAdminNFT",
+ "POOL_ADMIN_NFT",
+ [superfluid.address]
+ );
+ const poolMemberNFTLogic = await deployNFTContract(
+ PoolMemberNFT,
+ "PoolMemberNFT",
+ "POOL_MEMBER_NFT",
+ [superfluid.address]
+ );
+
+ // set the nft logic addresses (to be consumed by the super token factory logic constructor)
+ poolAdminNFTLogicAddress = poolAdminNFTLogic.address;
+ poolMemberNFTLogicAddress = poolMemberNFTLogic.address;
+
+ // initialize the nft proxy with the nft logic
+ await poolAdminNFTProxy.initializeProxy(
+ poolAdminNFTLogic.address
+ );
+
+ await poolMemberNFTProxy.initializeProxy(
+ poolMemberNFTLogic.address
+ );
+
+ const poolAdminNFT = await PoolAdminNFT.at(
+ poolAdminNFTProxy.address
+ );
- // set the nft proxy addresses (to be consumed by the super token logic constructor)
- cofNFTProxyAddress = constantOutflowNFTProxy.address;
- cifNFTProxyAddress = constantInflowNFTProxy.address;
+ const poolMemberNFT = await PoolMemberNFT.at(
+ poolMemberNFTProxy.address
+ );
+
+ // initialize the proxy contracts with the nft names
+ await poolAdminNFT.initialize("Pool Admin NFT", "PA");
+ await poolMemberNFT.initialize("Pool Member NFT", "PM");
+
+ // set the nft proxy addresses (to be consumed by the super token logic constructor)
+ poolAdminNFTProxyAddress = poolAdminNFTProxy.address;
+ poolMemberNFTProxyAddress = poolMemberNFTProxy.address;
+ }
} else {
// nft proxies already exist
await deployContractIf(
@@ -795,10 +1097,12 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
return constantOutflowNFTLogicChanged;
},
async () => {
- const cofNFTLogic = await deployNFTContract(true, [
- superfluid.address,
- cifNFTProxyAddress,
- ]);
+ const cofNFTLogic = await deployNFTContract(
+ ConstantOutflowNFT,
+ "ConstantOutflowNFT",
+ "CONSTANT_OUTFLOW_NFT",
+ [superfluid.address, cifNFTProxyAddress]
+ );
// @note we set the cofNFTLogicAddress to be passed to SuperTokenFactoryLogic here
cofNFTLogicAddress = cofNFTLogic.address;
@@ -812,15 +1116,56 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
return constantInflowNFTLogicChanged;
},
async () => {
- const cifNFTLogic = await deployNFTContract(false, [
- superfluid.address,
- cofNFTProxyAddress,
- ]);
+ const cifNFTLogic = await deployNFTContract(
+ ConstantInflowNFT,
+ "ConstantInflowNFT",
+ "CONSTANT_INFLOW_NFT",
+ [
+ superfluid.address,
+ cofNFTProxyAddress,
+ ]
+ );
// @note we set the cifNFTLogicAddress to be passed to SuperTokenFactoryLogic here
cifNFTLogicAddress = cifNFTLogic.address;
return cifNFTLogic.address;
}
);
+ await deployContractIf(
+ web3,
+ PoolAdminNFT,
+ async () => {
+ return poolAdminNFTLogicChanged;
+ },
+ async () => {
+ const poolAdminNFTLogic = await deployNFTContract(
+ PoolAdminNFT,
+ "PoolAdminNFT",
+ "POOL_ADMIN_NFT",
+ [superfluid.address]
+ );
+ // @note we set the poolAdminNFTLogicAddress to be passed to SuperTokenFactoryLogic here
+ poolAdminNFTLogicAddress = poolAdminNFTLogic.address;
+ return poolAdminNFTLogic.address;
+ }
+ );
+ await deployContractIf(
+ web3,
+ PoolMemberNFT,
+ async () => {
+ return poolMemberNFTLogicChanged;
+ },
+ async () => {
+ const poolMemberNFTLogic = await deployNFTContract(
+ PoolMemberNFT,
+ "PoolMemberNFT",
+ "POOL_MEMBER_NFT",
+ [superfluid.address]
+ );
+ // @note we set the poolMemberNFTLogicAddress to be passed to SuperTokenFactoryLogic here
+ poolMemberNFTLogicAddress = poolMemberNFTLogic.address;
+ return poolMemberNFTLogic.address;
+ }
+ );
}
// deploy super token logic contract
@@ -830,18 +1175,22 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
superfluid.address,
0,
cofNFTProxyAddress,
- cifNFTProxyAddress
+ cifNFTProxyAddress,
+ poolAdminNFTProxyAddress,
+ poolMemberNFTProxyAddress
)
: await web3tx(SuperTokenLogic.new, "SuperTokenLogic.new")(
superfluid.address,
cofNFTProxyAddress,
- cifNFTProxyAddress
+ cifNFTProxyAddress,
+ poolAdminNFTProxyAddress,
+ poolMemberNFTProxyAddress
);
console.log(
`SuperToken new logic code address ${superTokenLogic.address}`
);
- output += `SUPERFLUID_SUPER_TOKEN_LOGIC=${superTokenLogic.address}\n`;
+ output += `SUPER_TOKEN_LOGIC=${superTokenLogic.address}\n`;
superTokenFactoryLogic = await web3tx(
SuperTokenFactoryLogic.new,
@@ -850,9 +1199,11 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
superfluid.address,
superTokenLogic.address,
cofNFTLogicAddress,
- cifNFTLogicAddress
+ cifNFTLogicAddress,
+ poolAdminNFTLogicAddress,
+ poolMemberNFTLogicAddress
);
- output += `SUPERFLUID_SUPER_TOKEN_FACTORY_LOGIC=${superTokenFactoryLogic.address}\n`;
+ output += `SUPER_TOKEN_FACTORY_LOGIC=${superTokenFactoryLogic.address}\n`;
return superTokenFactoryLogic.address;
}
);
@@ -878,11 +1229,109 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function (
superfluid.address,
superfluidNewLogicAddress,
agreementsToUpdate,
- superTokenFactoryNewLogicAddress
+ superTokenFactoryNewLogicAddress,
+ ZERO_ADDRESS
)
);
}
+ // Superfluid Pool Beacon deployment
+ const gdaV1Contract = await GeneralDistributionAgreementV1.at(
+ await superfluid.getAgreementClass.call(GDAv1_TYPE)
+ );
+ const superfluidPoolBeaconAddress =
+ await gdaV1Contract.superfluidPoolBeacon();
+
+ const getPoolLogicAddress = async () => {
+ if (superfluidPoolBeaconAddress === ZERO_ADDRESS) {
+ return ZERO_ADDRESS;
+ }
+
+ try {
+ return await (
+ await SuperfluidUpgradeableBeacon.at(
+ superfluidPoolBeaconAddress
+ )
+ ).implementation();
+ } catch (e) {
+ return ZERO_ADDRESS;
+ }
+ };
+ const superfluidPoolLogicAddress = await deployContractIfCodeChanged(
+ web3,
+ SuperfluidPool,
+ await getPoolLogicAddress(),
+ async () => {
+ // Deploy new SuperfluidPool logic contract
+ const superfluidPoolLogic = await web3tx(
+ SuperfluidPool.new,
+ "SuperfluidPool.new"
+ )(gdaV1Contract.address);
+ await superfluidPoolLogic.castrate();
+ console.log(
+ "New SuperfluidPoolLogic address",
+ superfluidPoolLogic.address
+ );
+ output += `SUPERFLUID_POOL_LOGIC=${superfluidPoolLogic.address}\n`;
+
+ return superfluidPoolLogic.address;
+ },
+ [
+ // See SuperToken constructor parameter
+ gdaV1Contract.address.toLowerCase().slice(2).padStart(64, "0"),
+ ]
+ );
+
+ // if beacon doesn't exist, we deploy a new one
+ if (superfluidPoolBeaconAddress === ZERO_ADDRESS) {
+ console.log(
+ "SuperfluidPool Beacon doesn't exist, creating a new one..."
+ );
+ const superfluidPoolBeaconContract = await web3tx(
+ SuperfluidUpgradeableBeacon.new,
+ "SuperfluidUpgradeableBeacon.new"
+ )(superfluidPoolLogicAddress);
+ console.log(
+ "New SuperfluidPoolBeacon address",
+ superfluidPoolBeaconContract.address
+ );
+ output += `SUPERFLUID_POOL_BEACON=${superfluidPoolBeaconContract.address}\n`;
+
+ console.log("Transferring ownership of beacon contract to Superfluid Host...");
+ await superfluidPoolBeaconContract.transferOwnership(superfluid.address);
+
+ console.log("Initializing GDA w/ beacon contract...");
+ await gdaV1Contract.initialize(superfluidPoolBeaconContract.address);
+ } else {
+ console.log("Superfluid Pool Beacon exists...");
+ // if the beacon exists AND we deployed a new SuperfluidPool logic contract
+ if (superfluidPoolLogicAddress !== ZERO_ADDRESS) {
+ console.log(
+ "superfluidPoolLogicAddress updated, upgrading logic contract..."
+ );
+ // update beacon implementation
+ const superfluidPoolBeacon = await SuperfluidUpgradeableBeacon.at(
+ superfluidPoolBeaconAddress
+ );
+ await superfluidPoolBeacon.upgradeTo(superfluidPoolLogicAddress);
+ }
+ }
+
+ // finally, set the version string in resolver
+ if (previousVersionString !== versionString) {
+ const sfObjForResolver = {
+ contracts: {
+ Resolver,
+ IAccessControlEnumerable,
+ },
+ resolver: {
+ address: resolver.address
+ }
+ };
+ const encodedVersionString = versionStringToPseudoAddress(versionString);
+ await setResolver(sfObjForResolver, `versionString.${protocolReleaseVersion}`, encodedVersionString);
+ }
+
// finally, set the version string in resolver
if (previousVersionString !== versionString) {
diff --git a/packages/ethereum-contracts/ops-scripts/info-print-contract-addresses.js b/packages/ethereum-contracts/ops-scripts/info-print-contract-addresses.js
index 5342a0f8e1..c74400b43f 100644
--- a/packages/ethereum-contracts/ops-scripts/info-print-contract-addresses.js
+++ b/packages/ethereum-contracts/ops-scripts/info-print-contract-addresses.js
@@ -56,10 +56,17 @@ module.exports = eval(`(${S.toString()})()`)(async function (
const SuperToken = artifacts.require("SuperToken");
const UUPSProxiable = artifacts.require("UUPSProxiable");
const ISuperTokenFactory = artifacts.require("ISuperTokenFactory");
+ const GeneralDistributionAgreementV1 = artifacts.require(
+ "GeneralDistributionAgreementV1"
+ );
+ const SuperfluidUpgradeableBeacon = artifacts.require(
+ "SuperfluidUpgradeableBeacon"
+ );
if (config.isTestnet) {
output += "IS_TESTNET=1\n";
}
+
output += `NETWORK_ID=${networkId}\n`;
output += `RESOLVER=${sf.resolver.address}\n`;
output += `SUPERFLUID_LOADER=${sf.loader.address}\n`;
@@ -80,20 +87,20 @@ module.exports = eval(`(${S.toString()})()`)(async function (
// ignore
}
- output += `SUPERFLUID_SUPER_TOKEN_FACTORY_PROXY=${await sf.host.getSuperTokenFactory()}\n`;
- output += `SUPERFLUID_SUPER_TOKEN_FACTORY_LOGIC=${await sf.host.getSuperTokenFactoryLogic()}\n`;
+ output += `SUPER_TOKEN_FACTORY_PROXY=${await sf.host.getSuperTokenFactory()}\n`;
+ output += `SUPER_TOKEN_FACTORY_LOGIC=${await sf.host.getSuperTokenFactoryLogic()}\n`;
output += `CFA_PROXY=${sf.agreements.cfa.address}\n`;
output += `CFA_LOGIC=${await getCodeAddress(
UUPSProxiable,
sf.agreements.cfa.address
)}\n`;
output += `IDA_PROXY=${sf.agreements.ida.address}\n`;
- output += `SLOTS_BITMAP_LIBRARY_ADDRESS=${
+ output += `SLOTS_BITMAP_LIBRARY=${
"0x" +
(
await web3.eth.call({
to: sf.agreements.ida.address,
- data: "0x3fd4176a", //SLOTS_BITMAP_LIBRARY_ADDRESS()
+ data: "0x3fd4176a", //SLOTS_BITMAP_LIBRARY()
})
).slice(-40)
}\n`;
@@ -101,10 +108,35 @@ module.exports = eval(`(${S.toString()})()`)(async function (
UUPSProxiable,
sf.agreements.ida.address
)}\n`;
+ const gdaProxy = await sf.host.getAgreementClass(
+ web3.utils.sha3(
+ "org.superfluid-finance.agreements.GeneralDistributionAgreement.v1"
+ )
+ );
+ output += `GDA_PROXY=${gdaProxy}\n`;
+ output += `GDA_SLOTS_BITMAP_LIBRARY=${
+ "0x" +
+ (
+ await web3.eth.call({
+ to: gdaProxy,
+ data: "0x3fd4176a", //SLOTS_BITMAP_LIBRARY()
+ })
+ ).slice(-40)
+ }\n`;
+ output += `GDA_LOGIC=${await getCodeAddress(UUPSProxiable, gdaProxy)}\n`;
+
+ const gdaContract = await GeneralDistributionAgreementV1.at(gdaProxy);
+ const superfluidPoolBeaconContract = await SuperfluidUpgradeableBeacon.at(
+ await gdaContract.superfluidPoolBeacon()
+ );
+ output += `SUPERFLUID_POOL_DEPLOYER=${await gdaContract.SUPERFLUID_POOL_DEPLOYER_ADDRESS()}\n`;
+ output += `SUPERFLUID_POOL_BEACON=${superfluidPoolBeaconContract.address}\n`;
+ output += `SUPERFLUID_POOL_LOGIC=${await superfluidPoolBeaconContract.implementation()}\n`;
+
const superTokenLogicAddress = await (
await ISuperTokenFactory.at(await sf.host.getSuperTokenFactory())
).getSuperTokenLogic();
- output += `SUPERFLUID_SUPER_TOKEN_LOGIC=${superTokenLogicAddress}\n`;
+ output += `SUPER_TOKEN_LOGIC=${superTokenLogicAddress}\n`;
const superTokenLogicContract = await SuperToken.at(superTokenLogicAddress);
@@ -125,6 +157,24 @@ module.exports = eval(`(${S.toString()})()`)(async function (
await UUPSProxiable.at(constantInflowNFTProxyAddress)
).getCodeAddress();
output += `CONSTANT_INFLOW_NFT_LOGIC=${constantInflowNFTLogicAddress}\n`;
+
+ const poolAdminNFTProxyAddress =
+ await superTokenLogicContract.POOL_ADMIN_NFT();
+ output += `POOL_ADMIN_NFT_PROXY=${poolAdminNFTProxyAddress}\n`;
+
+ const poolAdminNFTLogicAddress = await (
+ await UUPSProxiable.at(poolAdminNFTProxyAddress)
+ ).getCodeAddress();
+ output += `POOL_ADMIN_NFT_LOGIC=${poolAdminNFTLogicAddress}\n`;
+
+ const poolMemberNFTProxyAddress =
+ await superTokenLogicContract.POOL_MEMBER_NFT();
+ output += `POOL_MEMBER_NFT_PROXY=${poolMemberNFTProxyAddress}\n`;
+
+ const poolMemberNFTLogicAddress = await (
+ await UUPSProxiable.at(poolMemberNFTProxyAddress)
+ ).getCodeAddress();
+ output += `POOL_MEMBER_NFT_LOGIC=${poolMemberNFTLogicAddress}\n`;
if (! skipTokens) {
await Promise.all(
diff --git a/packages/ethereum-contracts/ops-scripts/libs/common.js b/packages/ethereum-contracts/ops-scripts/libs/common.js
index 448f312991..7cc7fdf92c 100644
--- a/packages/ethereum-contracts/ops-scripts/libs/common.js
+++ b/packages/ethereum-contracts/ops-scripts/libs/common.js
@@ -80,6 +80,7 @@ async function hasCode(web3, address) {
/**
* @dev Check if the code at the address differs from the contract object provided
+ * @param replacements should contain all immutable contract fields, encoded as words
* TODO: this isn't always working as intended, see https://github.com/superfluid-finance/protocol-monorepo/issues/1448
*/
async function codeChanged(
diff --git a/packages/ethereum-contracts/ops-scripts/libs/getConfig.js b/packages/ethereum-contracts/ops-scripts/libs/getConfig.js
index cb0b2380c9..98bb0cb0da 100644
--- a/packages/ethereum-contracts/ops-scripts/libs/getConfig.js
+++ b/packages/ethereum-contracts/ops-scripts/libs/getConfig.js
@@ -46,6 +46,7 @@ module.exports = function getConfig(chainId) {
getLogsRange: sfNw?.logsQueryRange || 5000,
},
cfaFwd: sfNw?.contractsV1?.cfaV1Forwarder || "0xcfA132E353cB4E398080B9700609bb008eceB125",
+ gdaFwd: sfNw?.contractsV1?.gdaV1Forwarder || "0x6dA170169d5Fca20F902b7E5755346a97c94B07c",
nativeTokenSymbol: sfNw?.nativeTokenSymbol || "ETH",
metadata: sfNw,
resolverAddress: global?.process.env.RESOLVER_ADDRESS || sfNw?.contractsV1?.resolver,
diff --git a/packages/ethereum-contracts/ops-scripts/validate-deployment.ts b/packages/ethereum-contracts/ops-scripts/validate-deployment.ts
new file mode 100644
index 0000000000..0495f223b7
--- /dev/null
+++ b/packages/ethereum-contracts/ops-scripts/validate-deployment.ts
@@ -0,0 +1,143 @@
+import { assert, ethers } from "hardhat";
+import metadata from "@superfluid-finance/metadata";
+
+const cfaAgreementType = ethers.utils.solidityKeccak256(["string"], ["org.superfluid-finance.agreements.ConstantFlowAgreement.v1"]);
+const idaAgreementType = ethers.utils.solidityKeccak256(["string"], ["org.superfluid-finance.agreements.InstantDistributionAgreement.v1"]);
+const gdaAgreementType = ethers.utils.solidityKeccak256(["string"], ["org.superfluid-finance.agreements.GeneralDistributionAgreement.v1"]);
+
+const superTokenFactoryUuid = ethers.utils.solidityKeccak256(["string"], ["org.superfluid-finance.contracts.SuperTokenFactory.implementation"]);
+const superTokenUUID = ethers.utils.solidityKeccak256(["string"], ["org.superfluid-finance.contracts.SuperToken.implementation"]);
+const constantOutflowNftUuid = ethers.utils.solidityKeccak256(["string"], ["org.superfluid-finance.contracts.ConstantOutflowNFT.implementation"]);
+const constantInflowNftUuid = ethers.utils.solidityKeccak256(["string"], ["org.superfluid-finance.contracts.ConstantInflowNFT.implementation"]);
+const superfluidPoolUUID = ethers.utils.solidityKeccak256(["string"], ["org.superfluid-finance.contracts.SuperfluidPool.implementation"]);
+
+function assertLog(condition: boolean, message: string) {
+ console.log("ASSERTING:", message);
+ assert(condition, "[ASSERTION ERROR]: " + message);
+ console.log("ASSERTION PASSED!", "\n")
+}
+
+async function main() {
+ const networkId = (await ethers.provider.getNetwork()).chainId;
+ const networkMetadata = metadata.getNetworkByChainId(networkId);
+
+ if (networkMetadata === undefined) {
+ throw new Error("Network not supported");
+ }
+
+ const RESOLVER_ADDRESS = networkMetadata.contractsV1.resolver;
+
+ const resolver = await ethers.getContractAt("Resolver", RESOLVER_ADDRESS || "");
+
+ const hostAddress = await resolver.get("Superfluid.v1");
+ const hostContract = await ethers.getContractAt("Superfluid", hostAddress);
+
+ const superTokenFactoryAddress = await hostContract.getSuperTokenFactory();
+ const superTokenFactoryContract = await ethers.getContractAt("SuperTokenFactory", superTokenFactoryAddress);
+ console.log("SuperTokenFactory Address:", superTokenFactoryAddress, "\n");
+ const superTokenFactoryLiveUUID = await superTokenFactoryContract.proxiableUUID();
+ assertLog(superTokenFactoryUuid === superTokenFactoryLiveUUID, "SuperTokenFactory Deployed UUID matches live UUID");
+
+ const isCFAv1ForwarderATrustedForwarder = await hostContract.isTrustedForwarder(networkMetadata.contractsV1.cfaV1Forwarder);
+ assertLog(isCFAv1ForwarderATrustedForwarder, "CFAv1 Forwarder is set as trusted forwarder");
+
+ const superTokenFactoryLogicAddress = await hostContract.getSuperTokenFactoryLogic();
+ console.log("SuperTokenFactory Logic Address:", superTokenFactoryLogicAddress, "\n");
+
+ assertLog(superTokenFactoryLogicAddress === await superTokenFactoryContract.getCodeAddress(), "Canonical Factory Logic Address matches Factory Proxy Logic Address");
+
+ const superTokenLogicAddress = await superTokenFactoryContract.getSuperTokenLogic();
+ console.log("SuperToken Logic Address:", superTokenLogicAddress, "\n");
+
+ const superTokenLogicContract = await ethers.getContractAt("SuperToken", superTokenLogicAddress);
+ const superTokenLiveUUID = await superTokenLogicContract.proxiableUUID();
+ assertLog(superTokenUUID === superTokenLiveUUID, "SuperTokenFactory Deployed UUID matches live UUID");
+
+ // validate flow NFTs
+ const constantOutflowNFTCanonicalLogic = await superTokenFactoryContract.CONSTANT_OUTFLOW_NFT_LOGIC();
+ console.log("ConstantOutflowNFT Canonical Logic (on Factory):", constantOutflowNFTCanonicalLogic);
+ const constantInflowNFTCanonicalLogic = await superTokenFactoryContract.CONSTANT_INFLOW_NFT_LOGIC();
+ console.log("ConstantInflowNFT Canonical Logic (on Factory):", constantInflowNFTCanonicalLogic, "\n");
+
+ const constantOutflowNFProxy = await superTokenLogicContract.CONSTANT_OUTFLOW_NFT();
+ const cofNFTContract = await ethers.getContractAt("ConstantOutflowNFT", constantOutflowNFProxy);
+ const cofNFTContractLiveUUID = await cofNFTContract.proxiableUUID();
+ assertLog(constantOutflowNftUuid === cofNFTContractLiveUUID, "ConstantOutflowNFT Deployed UUID matches live UUID");
+ console.log("ConstantOutflowNFT:", constantOutflowNFProxy);
+
+ const outflowProxyLogic = await cofNFTContract.getCodeAddress();
+ console.log("ConstantOutflow NFT Logic (on Proxy):", outflowProxyLogic, "\n");
+ assertLog(await cofNFTContract.baseURI() === "https://nft.superfluid.finance/cfa/v2/getmeta", "ConstantOutflowNFT baseURI is equal to https://nft.superfluid.finance/cfa/v2/getmeta");
+
+ const constantInflowNFProxy = await superTokenLogicContract.CONSTANT_INFLOW_NFT();
+ const cifNFTContract = await ethers.getContractAt("ConstantInflowNFT", constantInflowNFProxy);
+ const cifNFTContractLiveUUID = await cifNFTContract.proxiableUUID();
+ assertLog(constantInflowNftUuid === cifNFTContractLiveUUID, "ConstantInflowNFT Deployed UUID matches live UUID");
+ console.log("ConstantInflowNFT:", constantInflowNFProxy);
+ assertLog(await cifNFTContract.baseURI() === "https://nft.superfluid.finance/cfa/v2/getmeta", "ConstantInflowNFT baseURI is equal to https://nft.superfluid.finance/cfa/v2/getmeta");
+
+ const inflowProxyLogic = await cifNFTContract.getCodeAddress();
+ console.log("ConstantInflow NFT Logic (on Proxy):", inflowProxyLogic);
+
+ assertLog(await cofNFTContract.proxiableUUID() !== await cifNFTContract.proxiableUUID(), "NFT proxies have different implementation.");
+
+ assertLog(outflowProxyLogic === constantOutflowNFTCanonicalLogic, "Outflow proxy logic is equal to canonical outflow logic");
+ assertLog(inflowProxyLogic === constantInflowNFTCanonicalLogic, "Inflow proxy logic is equal to canonical inflow logic");
+
+ // validate pool NFTs
+ const poolAdminNFTCanonicalLogic = await superTokenFactoryContract.POOL_ADMIN_NFT_LOGIC();
+ console.log("PoolAdminNFT Canonical Logic (on Factory):", poolAdminNFTCanonicalLogic);
+ const poolMemberNFTCanonicalLogic = await superTokenFactoryContract.POOL_MEMBER_NFT_LOGIC();
+ console.log("PoolMemberNFT Canonical Logic (on Factory):", poolMemberNFTCanonicalLogic, "\n");
+
+ const poolAdminNFProxy = await superTokenLogicContract.POOL_ADMIN_NFT();
+ const paNFTContract = await ethers.getContractAt("PoolAdminNFT", poolAdminNFProxy);
+ console.log("PoolAdminNFT:", poolAdminNFProxy);
+ const poolAdminProxyLogic = await paNFTContract.getCodeAddress();
+ console.log("PoolAdmin NFT Logic (on Proxy):", poolAdminProxyLogic, "\n");
+ assertLog(await paNFTContract.baseURI() === "https://nft.superfluid.finance/pool/v2/getmeta", "PoolAdminNFT baseURI is equal to https://nft.superfluid.finance/pool/v2/getmeta");
+
+ const poolMemberNFProxy = await superTokenLogicContract.POOL_MEMBER_NFT();
+ console.log("PoolMemberNFT:", poolMemberNFProxy);
+ const pmNFTContract = await ethers.getContractAt("PoolMemberNFT", poolMemberNFProxy);
+ assertLog(await pmNFTContract.baseURI() === "https://nft.superfluid.finance/pool/v2/getmeta", "PoolMemberNFT baseURI is equal to https://nft.superfluid.finance/pool/v2/getmeta");
+
+ const poolMemberProxyLogic = await pmNFTContract.getCodeAddress();
+ console.log("ConstantInflow NFT Logic (on Proxy):", poolMemberProxyLogic);
+
+ assertLog(await paNFTContract.proxiableUUID() !== await pmNFTContract.proxiableUUID(), "NFT proxies have different implementation.");
+
+ assertLog(poolAdminProxyLogic === poolAdminNFTCanonicalLogic, "Pool admin proxy logic is equal to canonical pool admin logic");
+ assertLog(poolMemberProxyLogic === poolMemberNFTCanonicalLogic, "Pool member proxy logic is equal to canonical pool member logic");
+
+ const cfaAddress = await hostContract.getAgreementClass(cfaAgreementType);
+ const idaAddress = await hostContract.getAgreementClass(idaAgreementType);
+ const gdaAddress = await hostContract.getAgreementClass(gdaAgreementType);
+
+ assertLog(cfaAddress !== ethers.constants.AddressZero, "CFA Address is not zero address");
+ assertLog(idaAddress !== ethers.constants.AddressZero, "IDA Address is not zero address");
+ assertLog(gdaAddress !== ethers.constants.AddressZero, "GDA Address is not zero address");
+
+ const cfaContract = await ethers.getContractAt("ConstantFlowAgreementV1", cfaAddress);
+ const idaContract = await ethers.getContractAt("InstantDistributionAgreementV1", idaAddress);
+ const gdaContract = await ethers.getContractAt("GeneralDistributionAgreementV1", gdaAddress);
+
+ assertLog(await cfaContract.agreementType() === cfaAgreementType, "CFA AgreementType is equal to expected agreementType")
+ assertLog(await idaContract.agreementType() === idaAgreementType, "IDA AgreementType is equal to expected agreementType")
+ assertLog(await gdaContract.agreementType() === gdaAgreementType, "GDA AgreementType is equal to expected agreementType")
+
+ // GDA specific validation
+ const superfluidPoolBeaconAddress = await gdaContract.superfluidPoolBeacon();
+ assertLog(superfluidPoolBeaconAddress !== ethers.constants.AddressZero, "SuperfluidPoolBeaconAddress is not zero address")
+
+ const beaconContract = await ethers.getContractAt("IBeacon", superfluidPoolBeaconAddress);
+ const sfPoolBeaconImplementationAddress = await beaconContract.implementation();
+ assertLog(sfPoolBeaconImplementationAddress !== ethers.constants.AddressZero, "SFPool beacon implementation is not zero address");
+
+ const superfluidPoolContract = await ethers.getContractAt("SuperfluidPool", sfPoolBeaconImplementationAddress);
+ const sfPoolLiveUUID = await superfluidPoolContract.proxiableUUID();
+
+ assertLog(sfPoolLiveUUID === superfluidPoolUUID, "SFPool Deployed UUID is equal to expected SFPool UUID");
+}
+
+main();
\ No newline at end of file
diff --git a/packages/ethereum-contracts/ops-scripts/validate-nft-addresses.ts b/packages/ethereum-contracts/ops-scripts/validate-nft-addresses.ts
deleted file mode 100644
index 6ff3177e0a..0000000000
--- a/packages/ethereum-contracts/ops-scripts/validate-nft-addresses.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { ethers } from "hardhat";
-import metadata from "@superfluid-finance/metadata";
-
-async function main() {
- const networkId = (await ethers.provider.getNetwork()).chainId;
- const RESOLVER_ADDRESS = metadata.getNetworkByChainId(networkId)?.contractsV1.resolver;
- const resolver = await ethers.getContractAt("Resolver", RESOLVER_ADDRESS || "");
- const hostAddress = await resolver.get("Superfluid.v1");
- const hostContract = await ethers.getContractAt("Superfluid", hostAddress);
- const superTokenFactoryAddress = await hostContract.getSuperTokenFactory();
- const superTokenFactoryContract = await ethers.getContractAt("SuperTokenFactory", superTokenFactoryAddress);
- console.log("superTokenFactoryAddress", superTokenFactoryAddress);
-
- const constantOutflowNFTCanonicalLogic = await superTokenFactoryContract.CONSTANT_OUTFLOW_NFT_LOGIC();
- console.log("constantOutflowNFTCanonicalLogic", constantOutflowNFTCanonicalLogic);
- const constantInflowNFTCanonicalLogic = await superTokenFactoryContract.CONSTANT_INFLOW_NFT_LOGIC();
- console.log("constantInflowNFTCanonicalLogic", constantInflowNFTCanonicalLogic);
-
- const superTokenFactoryLogicAddress = await hostContract.getSuperTokenFactoryLogic();
- console.log("superTokenFactoryLogicAddress", superTokenFactoryLogicAddress);
- const superTokenLogicAddress = await superTokenFactoryContract.getSuperTokenLogic();
- const superTokenLogicContract = await ethers.getContractAt("SuperToken", superTokenLogicAddress);
-
- const constantOutflowNFProxy = await superTokenLogicContract.CONSTANT_OUTFLOW_NFT();
- const cofNFTContract = await ethers.getContractAt("ConstantOutflowNFT", constantOutflowNFProxy);
- console.log("constantOutflowNFProxy", constantOutflowNFProxy);
- const outflowProxyLogic = await cofNFTContract.getCodeAddress();
- console.log("outflowProxyLogic", outflowProxyLogic);
- console.log("cof baseURI", await cofNFTContract.baseURI());
-
- const constantInflowNFProxy = await superTokenLogicContract.CONSTANT_INFLOW_NFT();
- console.log("constantInflowNFProxy", constantInflowNFProxy);
- const cifNFTContract = await ethers.getContractAt("ConstantInflowNFT", constantInflowNFProxy);
- const inflowProxyLogic = await cifNFTContract.getCodeAddress();
- console.log("inflowProxyLogic", inflowProxyLogic);
- const differentImplementations = await cofNFTContract.proxiableUUID() !== await cifNFTContract.proxiableUUID();
- console.log("nft's have different implementations:", differentImplementations);
- console.log("cif baseURI", await cofNFTContract.baseURI());
-
- if (!differentImplementations) throw new Error("nft's have the same implementation");
-
- console.log("outflow proxy logic equal canonical logic", outflowProxyLogic === constantOutflowNFTCanonicalLogic);
- console.log("inflow proxy logic equal canonical logic", inflowProxyLogic === constantInflowNFTCanonicalLogic);
-
- if (outflowProxyLogic !== constantOutflowNFTCanonicalLogic) throw new Error("outflow proxy logic not equal canonical logic");
- if (inflowProxyLogic !== constantInflowNFTCanonicalLogic) throw new Error("inflow proxy logic not equal canonical logic");
-}
-
-main();
\ No newline at end of file
diff --git a/packages/ethereum-contracts/package.json b/packages/ethereum-contracts/package.json
index ea051bbc8e..5735394014 100644
--- a/packages/ethereum-contracts/package.json
+++ b/packages/ethereum-contracts/package.json
@@ -1,6 +1,6 @@
{
"name": "@superfluid-finance/ethereum-contracts",
- "version": "1.8.1",
+ "version": "1.9.0",
"description": " Ethereum contracts implementation for the Superfluid Protocol",
"homepage": "https://github.com/superfluid-finance/protocol-monorepo/tree/dev/packages/ethereum-contracts#readme",
"repository": {
@@ -43,7 +43,7 @@
"build:post-contracts": "run-p -l build:post-contracts:*",
"build:post-contracts:abi-bundle": "tasks/build-bundled-abi.sh",
"build:post-contracts:dev-scripts-typings": "rm -rf dev-scripts/*.d.ts dev-scripts/*.d.ts.map; tsc -p tsconfig.scripts.json",
- "build:post-contracts:contracts-size": "forge build --sizes > build/contracts-sizes.txt",
+ "build:post-contracts:contracts-size": "forge build --sizes > build/contracts-sizes.txt > /dev/null&",
"verify-framework": "tasks/etherscan-verify-framework.sh",
"testenv:start": "test/testenv-ctl.sh start",
"testenv:stop": "test/testenv-ctl.sh stop",
diff --git a/packages/ethereum-contracts/tasks/bundled-abi-contracts-list.json b/packages/ethereum-contracts/tasks/bundled-abi-contracts-list.json
index 86155c49bb..b531b112c8 100644
--- a/packages/ethereum-contracts/tasks/bundled-abi-contracts-list.json
+++ b/packages/ethereum-contracts/tasks/bundled-abi-contracts-list.json
@@ -13,11 +13,12 @@
"IFlowNFTBase", "FlowNFTBase",
"IConstantInflowNFT", "ConstantInflowNFT",
"IConstantOutflowNFT", "ConstantOutflowNFT",
- "IPoolAdminNFT",
- "IPoolMemberNFT",
+ "IPoolAdminNFT", "PoolAdminNFT",
+ "IPoolMemberNFT", "PoolMemberNFT",
"ISuperAgreement",
"IConstantFlowAgreementV1", "ConstantFlowAgreementV1",
"IInstantDistributionAgreementV1", "InstantDistributionAgreementV1",
+ "IGeneralDistributionAgreementV1", "GeneralDistributionAgreementV1",
"ISuperfluidGovernance", "SuperfluidGovernanceBase", "SuperfluidGovernanceII", "TestGovernance",
"TestToken",
"IPureSuperToken",
diff --git a/packages/ethereum-contracts/tasks/coverage-cleanup.sh b/packages/ethereum-contracts/tasks/coverage-cleanup.sh
index b371764641..0ff548eac5 100755
--- a/packages/ethereum-contracts/tasks/coverage-cleanup.sh
+++ b/packages/ethereum-contracts/tasks/coverage-cleanup.sh
@@ -4,12 +4,12 @@ set -ex
cd "$(dirname "$0")"/..
-# extract coverage for NFT contracts from forge coverage
+# extract coverage for Superfluid contracts from forge coverage
lcov -e ../../lcov.info \
"packages/ethereum-contracts/contracts/*" \
-o lcov.info
-# remove mocks, base super app, test and deployer contracts (see .solcover.js)
+# remove contracts whose coverage we don't care about (see .solcover.js)
lcov -r lcov.info \
"packages/ethereum-contracts/contracts/mocks/*" \
"packages/ethereum-contracts/contracts/apps/*Base*" \
diff --git a/packages/ethereum-contracts/tasks/deploy-cfa-forwarder.sh b/packages/ethereum-contracts/tasks/deploy-cfa-forwarder.sh
index a9135444a0..0e6151c198 100755
--- a/packages/ethereum-contracts/tasks/deploy-cfa-forwarder.sh
+++ b/packages/ethereum-contracts/tasks/deploy-cfa-forwarder.sh
@@ -1,42 +1,54 @@
#!/usr/bin/env bash
-set -eux
+set -eu
# Usage:
-# tasks/deploy-cfa-forwarder.sh []
+# tasks/deploy-cfa-forwarder.sh
#
# Example:
-# tasks/deploy-cfa-forwarder.sh optimism-goerli 0xcfa132e353cb4e398080b9700609bb008eceb125
+# tasks/deploy-cfa-forwarder.sh optimism-goerli
#
# The invoking account needs to be (co-)owner of the resolver and governance
#
# important ENV vars:
-# RELEASE_VERSION, DETERMINISTIC_DEPLOYER_PK, RESOLVER_ADMIN_TYPE, GOVERNANCE_ADMIN_TYPE
+# RELEASE_VERSION, CFAFWD_DEPLOYER_PK
#
# You can use the npm package vanity-eth to get a deployer account for a given contract address:
# Example use: npx vanityeth -i cfa1 --contract
#
-# Note that the value of DETERMINISTIC_DEPLOYER_PK needs to match the given contract-addr.
-# The script will not check this, but fail (at contract verification) if not matching.
-#
-# For optimism the gas estimation doesn't work, requires setting EST_TX_COST.
-# For polygon-mainnet, GAS_PRICE usually needs to be set.
+# For optimism the gas estimation doesn't work, requires setting EST_TX_COST
+# (the value auto-detected for arbitrum should work).
#
# On some networks you may need to use override ENV vars for the deployment to succeed
+# shellcheck source=/dev/null
+source .env
+
+set -x
+
network=$1
-cfaFwdAddr=${2:-0xcfA132E353cB4E398080B9700609bb008eceB125}
+expectedContractAddr="0xcfA132E353cB4E398080B9700609bb008eceB125"
+deployerPk=$CFAFWD_DEPLOYER_PK
+tmpfile="/tmp/deploy-cfa-forwarder.sh"
# deploy
-npx truffle exec --network "$network" ops-scripts/deploy-deterministically.js : CFAv1Forwarder
+DETERMINISTIC_DEPLOYER_PK=$deployerPk npx truffle exec --network "$network" ops-scripts/deploy-deterministically.js : CFAv1Forwarder | tee $tmpfile
+contractAddr=$(cat $tmpfile | tail -n 1)
+rm $tmpfile
+
+echo "deployed to $contractAddr"
+if [[ $contractAddr != "$expectedContractAddr" ]]; then
+ echo "oh no!"
+ exit
+fi
# verify (give it a few seconds to pick up the code)
sleep 5
-npx truffle run --network "$network" verify CFAv1Forwarder@"$cfaFwdAddr"
+npx truffle run --network "$network" verify CFAv1Forwarder@"$contractAddr"
# set resolver
-ALLOW_UPDATE=1 npx truffle exec --network "$network" ops-scripts/resolver-set-key-value.js : CFAv1Forwarder "$cfaFwdAddr"
+ALLOW_UPDATE=1 npx truffle exec --network "$network" ops-scripts/resolver-set-key-value.js : CFAv1Forwarder "$contractAddr"
# create gov action
-npx truffle exec --network "$network" ops-scripts/gov-set-trusted-forwarder.js : 0x0000000000000000000000000000000000000000 "$cfaFwdAddr" 1
+npx truffle exec --network "$network" ops-scripts/gov-set-trusted-forwarder.js : 0x0000000000000000000000000000000000000000 "$contractAddr" 1
# TODO: on mainnets, the resolver entry should be set only after the gov action was signed & executed
diff --git a/packages/ethereum-contracts/tasks/deploy-gda-forwarder.sh b/packages/ethereum-contracts/tasks/deploy-gda-forwarder.sh
new file mode 100755
index 0000000000..08da123704
--- /dev/null
+++ b/packages/ethereum-contracts/tasks/deploy-gda-forwarder.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+set -eu
+
+# Usage:
+# tasks/deploy-gda-forwarder.sh
+#
+# Example:
+# tasks/deploy-gda-forwarder.sh optimism-goerli
+#
+# The invoking account needs to be (co-)owner of the resolver and governance
+#
+# important ENV vars:
+# RELEASE_VERSION, GDAFWD_DEPLOYER_PK
+#
+# You can use the npm package vanity-eth to get a deployer account for a given contract address:
+# Example use: npx vanityeth -i 6da1 --contract
+#
+# For optimism the gas estimation doesn't work, requires setting EST_TX_COST
+# (the value auto-detected for arbitrum should work).
+#
+# On some networks you may need to use override ENV vars for the deployment to succeed
+
+# shellcheck source=/dev/null
+source .env
+
+set -x
+
+network=$1
+expectedContractAddr="0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08"
+deployerPk=$GDAFWD_DEPLOYER_PK
+
+tmpfile="/tmp/deploy-gda-forwarder.sh"
+# deploy
+DETERMINISTIC_DEPLOYER_PK=$deployerPk npx truffle exec --network "$network" ops-scripts/deploy-deterministically.js : GDAv1Forwarder | tee $tmpfile
+contractAddr=$(cat $tmpfile | tail -n 1)
+rm $tmpfile
+
+echo "deployed to $contractAddr"
+if [[ $contractAddr != "$expectedContractAddr" ]]; then
+ echo "oh no!"
+ exit
+fi
+
+# verify (give it a few seconds to pick up the code)
+sleep 5
+npx truffle run --network "$network" verify GDAv1Forwarder@"$contractAddr"
+
+# set resolver
+ALLOW_UPDATE=1 npx truffle exec --network "$network" ops-scripts/resolver-set-key-value.js : GDAv1Forwarder "$contractAddr"
+
+# create gov action
+npx truffle exec --network "$network" ops-scripts/gov-set-trusted-forwarder.js : 0x0000000000000000000000000000000000000000 "$contractAddr" 1
+
+# TODO: on mainnets, the resolver entry should be set only after the gov action was signed & executed
diff --git a/packages/ethereum-contracts/tasks/etherscan-verify-framework.sh b/packages/ethereum-contracts/tasks/etherscan-verify-framework.sh
index 3044c23f6d..880d40dcdf 100755
--- a/packages/ethereum-contracts/tasks/etherscan-verify-framework.sh
+++ b/packages/ethereum-contracts/tasks/etherscan-verify-framework.sh
@@ -2,6 +2,7 @@
# verification script for etherscan-like explorers.
# takes 2 arguments: the canonical network name and a file with a list of contract addresses to verify.
+# If additional arguments are provided, they will be added to individual verification commands.
# tries to verify the (sub)set of contracts listed in the file.
# if proxy addresses are provided, verification against up-to-date logic contracts will only succeed
# once they point to those (after gov upgrade execution)
@@ -12,6 +13,8 @@ CONTRACTS_DIR=build/truffle
TRUFFLE_NETWORK=$1
ADDRESSES_VARS=$2
+shift 2
+EXTRA_ARGS="$*"
if [ -z "$ADDRESSES_VARS" ]; then
echo "no addresses provided, fetching myself..."
@@ -25,7 +28,7 @@ source "$ADDRESSES_VARS"
FAILED_VERIFICATIONS=()
function try_verify() {
echo # newline for better readability
- npx truffle run --network "$TRUFFLE_NETWORK" verify "$@" ||
+ npx truffle run --network "$TRUFFLE_NETWORK" verify "$@" ${EXTRA_ARGS:+$EXTRA_ARGS} ||
FAILED_VERIFICATIONS[${#FAILED_VERIFICATIONS[@]}]="$*"
# NOTE: append using length so that having spaces in the element is not a problem
# TODO: version 0.6.5 of the plugin seems to not reliably return non-zero if verification fails
@@ -57,6 +60,14 @@ if [ -n "$RESOLVER" ]; then
try_verify Resolver@"${RESOLVER}"
fi
+if [ -n "$POOL_ADMIN_NFT_LOGIC" ]; then
+ try_verify PoolAdminNFT@"${POOL_ADMIN_NFT_LOGIC}"
+fi
+
+if [ -n "$POOL_MEMBER_NFT_LOGIC" ]; then
+ try_verify PoolMemberNFT@"${POOL_MEMBER_NFT_LOGIC}"
+fi
+
if [ -n "$SUPERFLUID_HOST_LOGIC" ]; then
# verify the logic contract. May or may not be already set as a proxy implementation
try_verify Superfluid@"${SUPERFLUID_HOST_LOGIC}"
@@ -81,11 +92,11 @@ if [ -n "$SUPERFLUID_LOADER" ]; then
try_verify SuperfluidLoader@"${SUPERFLUID_LOADER}"
fi
-if [ -n "$SUPERFLUID_SUPER_TOKEN_FACTORY_LOGIC" ]; then
- try_verify SuperTokenFactory@"${SUPERFLUID_SUPER_TOKEN_FACTORY_LOGIC}"
+if [ -n "$SUPER_TOKEN_FACTORY_LOGIC" ]; then
+ try_verify SuperTokenFactory@"${SUPER_TOKEN_FACTORY_LOGIC}"
fi
-if [ -n "$SUPERFLUID_SUPER_TOKEN_FACTORY_PROXY" ]; then
- try_verify SuperTokenFactory@"${SUPERFLUID_SUPER_TOKEN_FACTORY_PROXY}" --custom-proxy UUPSProxy
+if [ -n "$SUPER_TOKEN_FACTORY_PROXY" ]; then
+ try_verify SuperTokenFactory@"${SUPER_TOKEN_FACTORY_PROXY}" --custom-proxy UUPSProxy
fi
if [ -n "$CONSTANT_OUTFLOW_NFT_LOGIC" ]; then
@@ -104,8 +115,16 @@ if [ -n "$CONSTANT_INFLOW_NFT_PROXY" ]; then
try_verify ConstantInflowNFT@"${CONSTANT_INFLOW_NFT_PROXY}" --custom-proxy UUPSProxy
fi
-if [ -n "$SUPERFLUID_SUPER_TOKEN_LOGIC" ]; then
- try_verify SuperToken@"${SUPERFLUID_SUPER_TOKEN_LOGIC}"
+if [ -n "$POOL_ADMIN_NFT_PROXY" ]; then
+ try_verify PoolAdminNFT@"${POOL_ADMIN_NFT_PROXY}" --custom-proxy UUPSProxy
+fi
+
+if [ -n "$POOL_MEMBER_NFT_PROXY" ]; then
+ try_verify PoolMemberNFT@"${POOL_MEMBER_NFT_PROXY}" --custom-proxy UUPSProxy
+fi
+
+if [ -n "$SUPER_TOKEN_LOGIC" ]; then
+ try_verify SuperToken@"${SUPER_TOKEN_LOGIC}"
fi
if [ -n "$CFA_LOGIC" ]; then
@@ -115,17 +134,40 @@ if [ -n "$CFA_PROXY" ]; then
try_verify ConstantFlowAgreementV1@"${CFA_PROXY}" --custom-proxy UUPSProxy
fi
-if [ -n "$SLOTS_BITMAP_LIBRARY_ADDRESS" ]; then
- try_verify SlotsBitmapLibrary@"${SLOTS_BITMAP_LIBRARY_ADDRESS}"
+if [ -n "$SLOTS_BITMAP_LIBRARY" ]; then
+ try_verify SlotsBitmapLibrary@"${SLOTS_BITMAP_LIBRARY}"
fi
-link_library "InstantDistributionAgreementV1" "SlotsBitmapLibrary" "${SLOTS_BITMAP_LIBRARY_ADDRESS}"
+link_library "InstantDistributionAgreementV1" "SlotsBitmapLibrary" "${SLOTS_BITMAP_LIBRARY}"
if [ -n "$IDA_LOGIC" ]; then
try_verify InstantDistributionAgreementV1@"${IDA_LOGIC}"
fi
if [ -n "$IDA_PROXY" ]; then
try_verify InstantDistributionAgreementV1@"${IDA_PROXY}" --custom-proxy UUPSProxy
fi
+
+if [ -n "$SUPERFLUID_POOL_DEPLOYER" ]; then
+ try_verify SuperfluidPoolDeployerLibrary@"${SUPERFLUID_POOL_DEPLOYER}"
+fi
+
+link_library "GeneralDistributionAgreementV1" "SlotsBitmapLibrary" "${GDA_SLOTS_BITMAP_LIBRARY}"
+link_library "GeneralDistributionAgreementV1" "SuperfluidPoolDeployerLibrary" "${SUPERFLUID_POOL_DEPLOYER}"
+if [ -n "$GDA_LOGIC" ]; then
+ try_verify GeneralDistributionAgreementV1@"${GDA_LOGIC}"
+fi
+
+if [ -n "$GDA_PROXY" ]; then
+ try_verify GeneralDistributionAgreementV1@"${GDA_PROXY}" --custom-proxy UUPSProxy
+fi
+
+if [ -n "$SUPERFLUID_POOL_BEACON" ]; then
+ try_verify SuperfluidUpgradeableBeacon@"${SUPERFLUID_POOL_BEACON}"
+fi
+
+if [ -n "$SUPERFLUID_POOL_LOGIC" ]; then
+ try_verify SuperfluidPool@"${SUPERFLUID_POOL_LOGIC}"
+fi
+
mv -f $CONTRACTS_DIR/InstantDistributionAgreementV1.json.bak $CONTRACTS_DIR/InstantDistributionAgreementV1.json
if [ -n "$SUPER_TOKEN_NATIVE_COIN" ];then
diff --git a/packages/ethereum-contracts/tasks/etherscan-verify-proxies.sh b/packages/ethereum-contracts/tasks/etherscan-verify-proxies.sh
index 2c8b6e19c6..1367c0fc25 100644
--- a/packages/ethereum-contracts/tasks/etherscan-verify-proxies.sh
+++ b/packages/ethereum-contracts/tasks/etherscan-verify-proxies.sh
@@ -13,8 +13,8 @@ echo TRUFFLE_RUN_VERIFY="npx truffle run --network $TRUFFLE_NETWORK verify"
echo SUPERFLUID_HOST UUPSProxy
$TRUFFLE_RUN_VERIFY --verify-proxy UUPSProxy@"${SUPERFLUID_HOST_PROXY}"
-echo SUPERFLUID_SUPER_TOKEN_FACTORY UUPSProxy
-$TRUFFLE_RUN_VERIFY --verify-proxy UUPSProxy@"${SUPERFLUID_SUPER_TOKEN_FACTORY_PROXY}"
+echo SUPER_TOKEN_FACTORY UUPSProxy
+$TRUFFLE_RUN_VERIFY --verify-proxy UUPSProxy@"${SUPER_TOKEN_FACTORY_PROXY}"
echo CFA UUPSProxy
$TRUFFLE_RUN_VERIFY --verify-proxy UUPSProxy@"${CFA_PROXY}"
diff --git a/packages/ethereum-contracts/test/TestEnvironment.ts b/packages/ethereum-contracts/test/TestEnvironment.ts
index f2f0a2f3e9..1985ef56c3 100644
--- a/packages/ethereum-contracts/test/TestEnvironment.ts
+++ b/packages/ethereum-contracts/test/TestEnvironment.ts
@@ -13,6 +13,10 @@ import {
ConstantOutflowNFT__factory,
ISuperToken,
ISuperToken__factory,
+ PoolAdminNFT,
+ PoolAdminNFT__factory,
+ PoolMemberNFT,
+ PoolMemberNFT__factory,
SuperTokenMock,
TestToken,
UUPSProxiableMock__factory,
@@ -306,6 +310,17 @@ export default class TestEnvironment {
"InstantDistributionAgreementV1",
this.sf.agreements.ida.address
)),
+ (this.contracts.gda = await ethers.getContractAt(
+ "GeneralDistributionAgreementV1",
+ await this.contracts.superfluid.getAgreementClass(
+ ethers.utils.solidityKeccak256(
+ ["string"],
+ [
+ "org.superfluid-finance.agreements.GeneralDistributionAgreement.v1",
+ ]
+ )
+ )
+ )),
// load governance contract
(this.contracts.governance = await ethers.getContractAt(
"TestGovernance",
@@ -571,9 +586,14 @@ export default class TestEnvironment {
deployNFTContracts = async () => {
let constantOutflowNFT;
- let constantInflowNFTProxy;
+ let constantInflowNFT;
let cofNFTLogicAddress;
let cifNFTLogicAddress;
+ let paNFTLogicAddress;
+ let poolAdminNFT;
+ let pmNFTLogicAddress;
+ let poolMemberNFT;
+
const superTokenFactoryLogicAddress =
await this.contracts.superfluid.getSuperTokenFactoryLogic();
const superTokenFactory = await ethers.getContractAt(
@@ -591,13 +611,22 @@ export default class TestEnvironment {
const constantInflowNFTProxyAddress =
await superTokenLogic.CONSTANT_INFLOW_NFT();
+ const poolAdminNFTProxyAddress = await superTokenLogic.POOL_ADMIN_NFT();
+ const poolMemberNFTProxyAddress =
+ await superTokenLogic.POOL_MEMBER_NFT();
+
if (
constantOutflowNFTProxyAddress === ethers.constants.AddressZero ||
- constantInflowNFTProxyAddress === ethers.constants.AddressZero
+ constantInflowNFTProxyAddress === ethers.constants.AddressZero ||
+ poolAdminNFTProxyAddress === ethers.constants.AddressZero ||
+ poolMemberNFTProxyAddress === ethers.constants.AddressZero
) {
const cofProxy = await this.deployContract("UUPSProxy");
const cifProxy = await this.deployContract("UUPSProxy");
+ const paProxy = await this.deployContract("UUPSProxy");
+ const pmProxy = await this.deployContract("UUPSProxy");
+
const constantOutflowNFTLogic =
await this.deployContract(
"ConstantOutflowNFT",
@@ -605,6 +634,7 @@ export default class TestEnvironment {
cifProxy.address
);
cofNFTLogicAddress = constantOutflowNFTLogic.address;
+
const constantInflowNFTLogic =
await this.deployContract(
"ConstantInflowNFT",
@@ -612,6 +642,19 @@ export default class TestEnvironment {
cofProxy.address
);
cifNFTLogicAddress = constantInflowNFTLogic.address;
+
+ const poolAdminNFTLogic = await this.deployContract(
+ "PoolAdminNFT",
+ this.contracts.superfluid.address
+ );
+ paNFTLogicAddress = poolAdminNFTLogic.address;
+
+ const poolMemberNFTLogic = await this.deployContract(
+ "PoolMemberNFT",
+ this.contracts.superfluid.address
+ );
+ pmNFTLogicAddress = poolMemberNFTLogic.address;
+
const signer = await ethers.getSigner(this.aliases.admin);
const proxiableCofLogic = UUPSProxiableMock__factory.connect(
constantOutflowNFTLogic.address,
@@ -621,37 +664,71 @@ export default class TestEnvironment {
constantInflowNFTLogic.address,
signer
);
+ const proxiablePaLogic = UUPSProxiableMock__factory.connect(
+ poolAdminNFTLogic.address,
+ signer
+ );
+ const proxiablePmLogic = UUPSProxiableMock__factory.connect(
+ poolMemberNFTLogic.address,
+ signer
+ );
await proxiableCofLogic.castrate();
await proxiableCifLogic.castrate();
+ await proxiablePaLogic.castrate();
+ await proxiablePmLogic.castrate();
await cofProxy.initializeProxy(constantOutflowNFTLogic.address);
await cifProxy.initializeProxy(constantInflowNFTLogic.address);
+ await paProxy.initializeProxy(poolAdminNFTLogic.address);
+ await pmProxy.initializeProxy(poolMemberNFTLogic.address);
constantOutflowNFT = ConstantOutflowNFT__factory.connect(
cofProxy.address,
signer
);
- constantInflowNFTProxy = ConstantInflowNFT__factory.connect(
+ constantInflowNFT = ConstantInflowNFT__factory.connect(
cifProxy.address,
signer
);
+ poolAdminNFT = PoolAdminNFT__factory.connect(
+ paProxy.address,
+ signer
+ );
+ poolMemberNFT = PoolMemberNFT__factory.connect(
+ pmProxy.address,
+ signer
+ );
} else {
constantOutflowNFT = ConstantOutflowNFT__factory.connect(
constantOutflowNFTProxyAddress,
await ethers.getSigner(this.aliases.admin)
);
- constantInflowNFTProxy = ConstantInflowNFT__factory.connect(
+ constantInflowNFT = ConstantInflowNFT__factory.connect(
constantInflowNFTProxyAddress,
await ethers.getSigner(this.aliases.admin)
);
+ poolAdminNFT = PoolAdminNFT__factory.connect(
+ poolAdminNFTProxyAddress,
+ await ethers.getSigner(this.aliases.admin)
+ );
+ poolMemberNFT = PoolMemberNFT__factory.connect(
+ poolMemberNFTProxyAddress,
+ await ethers.getSigner(this.aliases.admin)
+ );
cofNFTLogicAddress = await constantOutflowNFT.getCodeAddress();
- cifNFTLogicAddress = await constantInflowNFTProxy.getCodeAddress();
+ cifNFTLogicAddress = await constantInflowNFT.getCodeAddress();
+ paNFTLogicAddress = await poolAdminNFT.getCodeAddress();
+ pmNFTLogicAddress = await poolMemberNFT.getCodeAddress();
}
return {
constantOutflowNFTProxy: constantOutflowNFT,
- constantInflowNFTProxy,
+ constantInflowNFTProxy: constantInflowNFT,
cofNFTLogicAddress,
cifNFTLogicAddress,
+ poolAdminNFTProxy: poolAdminNFT,
+ poolMemberNFTProxy: poolMemberNFT,
+ paNFTLogicAddress,
+ pmNFTLogicAddress,
};
};
diff --git a/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.CFA.test.ts b/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.CFA.test.ts
index 482fa6b69b..2d34e6a453 100644
--- a/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.CFA.test.ts
+++ b/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.CFA.test.ts
@@ -41,14 +41,20 @@ const callbackFunctionIndex = {
export const deploySuperTokenAndNFTContractsAndInitialize = async (
t: TestEnvironment
) => {
- const {constantOutflowNFTProxy, constantInflowNFTProxy} =
- await t.deployNFTContracts();
+ const {
+ constantOutflowNFTProxy,
+ constantInflowNFTProxy,
+ poolAdminNFTProxy,
+ poolMemberNFTProxy,
+ } = await t.deployNFTContracts();
const superToken = await t.deployContract(
"SuperTokenMock",
t.contracts.superfluid.address,
"69",
constantOutflowNFTProxy.address,
- constantInflowNFTProxy.address
+ constantInflowNFTProxy.address,
+ poolAdminNFTProxy.address,
+ poolMemberNFTProxy.address
);
return superToken;
diff --git a/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts b/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts
new file mode 100644
index 0000000000..68ddfff3e3
--- /dev/null
+++ b/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts
@@ -0,0 +1,446 @@
+import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers";
+import {ContractReceipt} from "ethers";
+import {ethers, expect, web3} from "hardhat";
+
+import {
+ ConstantFlowAgreementV1,
+ SuperfluidMock,
+ SuperfluidPool,
+ SuperTokenLibraryGDAMock,
+ SuperTokenLibraryGDASuperAppMock,
+ SuperTokenMock,
+} from "../../../typechain-types";
+import TestEnvironment from "../../TestEnvironment";
+import {toBN} from "../utils/helpers";
+
+import {deploySuperTokenAndNFTContractsAndInitialize} from "./SuperTokenV1Library.CFA.test";
+
+const mintAmount = "1000000000000000000000000000"; // a small loan of a billion dollars
+const flowRate = "1000000000000";
+
+const callbackFunctionIndex = {
+ UPDATE_MEMBER_UNITS: 0,
+ CONNECT_POOL: 1,
+ DISCONNECT_POOL: 2,
+ CLAIM_ALL: 3,
+ DISTRIBUTE: 4,
+ DISTRIBUTE_FLOW: 5,
+};
+
+const userData = (
+ functionIndex: number,
+ pool = ethers.constants.AddressZero,
+ member = ethers.constants.AddressZero,
+ from = ethers.constants.AddressZero,
+ units = 0,
+ requestedAmount = 0,
+ requestedFlowRate = 0
+) =>
+ web3.eth.abi.encodeParameters(
+ [
+ "uint8",
+ "address",
+ "address",
+ "address",
+ "uint128",
+ "uint256",
+ "int96",
+ ],
+ [
+ functionIndex,
+ pool,
+ member,
+ from,
+ units,
+ requestedAmount,
+ requestedFlowRate,
+ ]
+ );
+
+describe("SuperTokenV1Library.GDA", function () {
+ this.timeout(300e3);
+ const t = TestEnvironment.getSingleton();
+ let host: SuperfluidMock, cfa: ConstantFlowAgreementV1;
+ let aliceSigner: SignerWithAddress;
+ let createFlowCalldata: string;
+ let superTokenLibGDASuperAppMock: SuperTokenLibraryGDASuperAppMock;
+
+ const getPoolAddressFromReceipt = (receipt: ContractReceipt) => {
+ const POOL_CREATED_TOPIC = ethers.utils.solidityKeccak256(
+ ["string"],
+ ["PoolCreated(address,address,address)"]
+ );
+ const event = receipt.events?.find((x) =>
+ x.topics.includes(POOL_CREATED_TOPIC)
+ );
+ return ethers.utils.hexStripZeros(
+ event ? event.data : ethers.constants.AddressZero
+ );
+ };
+
+ let alice: string, bob: string;
+ let superTokenLibraryGDAMock: SuperTokenLibraryGDAMock;
+ let superToken: SuperTokenMock;
+
+ before(async () => {
+ await t.beforeTestSuite({
+ isTruffle: true,
+ nAccounts: 5,
+ });
+ ({alice, bob} = t.aliases);
+ superToken = t.tokens.SuperToken;
+
+ cfa = t.contracts.cfa;
+ host = t.contracts.superfluid;
+ aliceSigner = await ethers.getSigner(alice);
+ });
+
+ beforeEach(async function () {
+ await t.beforeEachTestCase();
+ const mockFactory = await ethers.getContractFactory(
+ "SuperTokenLibraryGDAMock"
+ );
+ superTokenLibraryGDAMock = await mockFactory.deploy();
+
+ t.beforeEachTestCaseBenchmark(this);
+ });
+
+ this.afterEach(() => {
+ t.afterEachTestCaseBenchmark();
+ });
+
+ it("#1.1 Should be able to create pool", async () => {
+ const createPoolTxn = await superTokenLibraryGDAMock.createPoolTest(
+ superToken.address,
+ alice,
+ {
+ transferabilityForUnitsOwner: true,
+ distributionFromAnyAddress: true,
+ }
+ );
+ const receipt = await createPoolTxn.wait();
+ const poolAddress = getPoolAddressFromReceipt(receipt);
+ const poolContract = await ethers.getContractAt(
+ "SuperfluidPool",
+ poolAddress
+ );
+ expect(await poolContract.admin()).to.equal(alice);
+ expect(await poolContract.superToken()).to.equal(superToken.address);
+ });
+
+ context("With a pool", () => {
+ let pool: SuperfluidPool;
+ let admin: SignerWithAddress;
+
+ beforeEach(async () => {
+ admin = await ethers.getSigner(alice);
+ const createPoolTxn = await superTokenLibraryGDAMock.createPoolTest(
+ superToken.address,
+ admin.address,
+ {
+ transferabilityForUnitsOwner: true,
+ distributionFromAnyAddress: true,
+ }
+ );
+ const receipt = await createPoolTxn.wait();
+ const poolAddress = getPoolAddressFromReceipt(receipt);
+ pool = await ethers.getContractAt("SuperfluidPool", poolAddress);
+
+ // transfer tokens to the mock gda contract
+ await t.upgradeBalance("alice", t.configs.INIT_BALANCE);
+ await superToken
+ .connect(admin)
+ .transfer(
+ superTokenLibraryGDAMock.address,
+ t.configs.INIT_BALANCE
+ );
+ });
+
+ it("#1.2 Should be able to distribute to pool", async () => {
+ expect(await pool.getUnits(bob)).to.equal("0");
+ await pool.connect(admin).updateMemberUnits(bob, "10");
+ expect(await pool.getUnits(bob)).to.equal("10");
+ const bobBalanceBefore = await superToken.balanceOf(bob);
+ const requestedDistributionAmount = "100";
+ const estimatedDistributionActualAmount =
+ await superTokenLibraryGDAMock.estimateDistributionActualAmountTest(
+ superToken.address,
+ superTokenLibraryGDAMock.address,
+ pool.address,
+ requestedDistributionAmount
+ );
+ await superTokenLibraryGDAMock.distributeToPoolTest(
+ superToken.address,
+ superTokenLibraryGDAMock.address,
+ pool.address,
+ requestedDistributionAmount
+ );
+ const bobBalanceAfter = await superToken.balanceOf(bob);
+ expect(
+ bobBalanceAfter,
+ bobBalanceBefore
+ .add(toBN(requestedDistributionAmount))
+ .toString()
+ );
+ expect(
+ estimatedDistributionActualAmount,
+ requestedDistributionAmount
+ );
+ });
+
+ it("#1.3 Should be able to distribute flow to pool", async () => {
+ expect(await pool.getUnits(bob)).to.equal("0");
+ await pool.connect(admin).updateMemberUnits(bob, "10");
+ expect(await pool.getUnits(bob)).to.equal("10");
+ const requestedFlowRate = "99";
+ const estimatedFlowDistributionActualFlowRate =
+ await superTokenLibraryGDAMock.estimateFlowDistributionActualFlowRateTest(
+ superToken.address,
+ superTokenLibraryGDAMock.address,
+ pool.address,
+ requestedFlowRate
+ );
+ await superTokenLibraryGDAMock.distributeFlowTest(
+ superToken.address,
+ superTokenLibraryGDAMock.address,
+ pool.address,
+ requestedFlowRate
+ );
+ const flowDistributionFlowRate =
+ await superTokenLibraryGDAMock.getFlowDistributionFlowRateTest(
+ superToken.address,
+ superTokenLibraryGDAMock.address,
+ pool.address
+ );
+ expect(
+ flowDistributionFlowRate.toString(),
+ estimatedFlowDistributionActualFlowRate.actualFlowRate.toString()
+ );
+ });
+
+ it("#1.4 Should be able to connect to a pool", async () => {
+ await superTokenLibraryGDAMock.connectPoolTest(
+ superToken.address,
+ pool.address
+ );
+ expect(
+ await superTokenLibraryGDAMock.isMemberConnectedTest(
+ superToken.address,
+ pool.address,
+ superTokenLibraryGDAMock.address
+ )
+ ).to.equal(true);
+ });
+
+ it("#1.4 Should be able to disconnect from a pool", async () => {
+ await superTokenLibraryGDAMock.connectPoolTest(
+ superToken.address,
+ pool.address
+ );
+ expect(
+ await superTokenLibraryGDAMock.isMemberConnectedTest(
+ superToken.address,
+ pool.address,
+ superTokenLibraryGDAMock.address
+ )
+ ).to.equal(true);
+ await superTokenLibraryGDAMock.disconnectPoolTest(
+ superToken.address,
+ pool.address
+ );
+ expect(
+ await superTokenLibraryGDAMock.isMemberConnectedTest(
+ superToken.address,
+ pool.address,
+ superTokenLibraryGDAMock.address
+ )
+ ).to.equal(false);
+ });
+
+ context("#2 - Callback GDA Operations", async function () {
+ let appCreatedPool: SuperfluidPool;
+ let appSuperToken: SuperTokenMock;
+
+ beforeEach(async () => {
+ appSuperToken =
+ await deploySuperTokenAndNFTContractsAndInitialize(t);
+
+ await appSuperToken.mintInternal(alice, mintAmount, "0x", "0x");
+
+ const superTokenLibGDASuperAppMockFactory =
+ await ethers.getContractFactory(
+ "SuperTokenLibraryGDASuperAppMock"
+ );
+ superTokenLibGDASuperAppMock =
+ await superTokenLibGDASuperAppMockFactory.deploy(
+ host.address
+ );
+
+ const createPoolTxn =
+ await superTokenLibGDASuperAppMock.createPoolTest(
+ appSuperToken.address,
+ superTokenLibGDASuperAppMock.address,
+ {
+ transferabilityForUnitsOwner: true,
+ distributionFromAnyAddress: true,
+ }
+ );
+ const receipt = await createPoolTxn.wait();
+ const poolAddress = getPoolAddressFromReceipt(receipt);
+ appCreatedPool = await ethers.getContractAt(
+ "SuperfluidPool",
+ poolAddress
+ );
+ createFlowCalldata =
+ t.agreementHelper.cfaInterface.encodeFunctionData(
+ "createFlow",
+ [
+ appSuperToken.address,
+ superTokenLibGDASuperAppMock.address,
+ flowRate,
+ "0x",
+ ]
+ );
+ });
+
+ it("#2.1 should updateMemberUnits in callback", async () => {
+ await host
+ .connect(aliceSigner)
+ .callAgreement(
+ cfa.address,
+ createFlowCalldata,
+ userData(
+ callbackFunctionIndex.UPDATE_MEMBER_UNITS,
+ appCreatedPool.address,
+ bob,
+ ethers.constants.AddressZero,
+ 10
+ )
+ );
+ expect(await appCreatedPool.getUnits(bob)).to.equal("10");
+ });
+
+ it("#2.2 should connectPool in callback", async () => {
+ await host
+ .connect(aliceSigner)
+ .callAgreement(
+ cfa.address,
+ createFlowCalldata,
+ userData(
+ callbackFunctionIndex.CONNECT_POOL,
+ appCreatedPool.address
+ )
+ );
+ expect(
+ await t.contracts.gda["isMemberConnected(address,address)"](
+ appCreatedPool.address,
+ superTokenLibGDASuperAppMock.address
+ )
+ ).to.equal(true);
+ });
+
+ it("#2.3 should call disconnectPool in callback without revert", async () => {
+ await host
+ .connect(aliceSigner)
+ .callAgreement(
+ cfa.address,
+ createFlowCalldata,
+ userData(
+ callbackFunctionIndex.DISCONNECT_POOL,
+ appCreatedPool.address
+ )
+ );
+ });
+
+ it("#2.4 should claimAll in callback", async () => {
+ await expect(
+ host
+ .connect(aliceSigner)
+ .callAgreement(
+ cfa.address,
+ createFlowCalldata,
+ userData(
+ callbackFunctionIndex.CLAIM_ALL,
+ appCreatedPool.address,
+ bob
+ )
+ )
+ )
+ .to.emit(appCreatedPool, "DistributionClaimed")
+ .withArgs(appSuperToken.address, bob, 0, 0);
+ });
+
+ it("#2.5 should distribute in callback", async () => {
+ await expect(
+ host
+ .connect(aliceSigner)
+ .callAgreement(
+ cfa.address,
+ createFlowCalldata,
+ userData(
+ callbackFunctionIndex.DISTRIBUTE,
+ appCreatedPool.address,
+ ethers.constants.AddressZero,
+ superTokenLibGDASuperAppMock.address,
+ 0,
+ 100
+ )
+ )
+ )
+ .to.emit(t.contracts.gda, "InstantDistributionUpdated")
+ .withArgs(
+ ethers.utils.getAddress(appSuperToken.address),
+ ethers.utils.getAddress(appCreatedPool.address),
+ ethers.utils.getAddress(
+ superTokenLibGDASuperAppMock.address
+ ),
+ ethers.utils.getAddress(
+ superTokenLibGDASuperAppMock.address
+ ),
+ 100,
+ 0,
+ "0x3078"
+ );
+ });
+
+ it("#2.6 should distributeFlow in callback", async () => {
+ await expect(
+ host
+ .connect(aliceSigner)
+ .callAgreement(
+ cfa.address,
+ createFlowCalldata,
+ userData(
+ callbackFunctionIndex.DISTRIBUTE_FLOW,
+ appCreatedPool.address,
+ ethers.constants.AddressZero,
+ superTokenLibGDASuperAppMock.address,
+ 0,
+ 0,
+ 100
+ )
+ )
+ )
+ .to.emit(t.contracts.gda, "FlowDistributionUpdated")
+ .withArgs(
+ ethers.utils.getAddress(appSuperToken.address),
+ ethers.utils.getAddress(appCreatedPool.address),
+ ethers.utils.getAddress(
+ superTokenLibGDASuperAppMock.address
+ ),
+ ethers.utils.getAddress(
+ superTokenLibGDASuperAppMock.address
+ ),
+ 0,
+ 0,
+ 0,
+ ethers.utils.getAddress(
+ superTokenLibGDASuperAppMock.address
+ ),
+ 0,
+ "0x3078"
+ );
+ });
+ });
+ });
+});
diff --git a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts
index 28f9f974d8..8e06678697 100644
--- a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts
+++ b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts
@@ -79,6 +79,7 @@ describe("Superfluid Ownable Governance Contract", function () {
superfluid.address,
ZERO_ADDRESS,
[],
+ ZERO_ADDRESS,
ZERO_ADDRESS
),
governance,
diff --git a/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts
index 7f03b825aa..bbbeb74be4 100644
--- a/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts
+++ b/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts
@@ -67,14 +67,20 @@ describe("SuperToken's Non Standard Functions", function () {
describe("#1 upgradability", () => {
it("#1.1 storage layout", async () => {
- const {constantOutflowNFTProxy, constantInflowNFTProxy} =
- await t.deployNFTContracts();
+ const {
+ constantOutflowNFTProxy,
+ constantInflowNFTProxy,
+ poolAdminNFTProxy,
+ poolMemberNFTProxy,
+ } = await t.deployNFTContracts();
const superTokenLogic =
await t.deployContract(
"SuperTokenStorageLayoutTester",
superfluid.address,
constantOutflowNFTProxy.address,
- constantInflowNFTProxy.address
+ constantInflowNFTProxy.address,
+ poolAdminNFTProxy.address,
+ poolMemberNFTProxy.address
);
await superTokenLogic.validateStorageLayout();
});
@@ -704,14 +710,20 @@ describe("SuperToken's Non Standard Functions", function () {
});
it("#3.1 Custom token storage should not overlap with super token", async () => {
- const {constantOutflowNFTProxy, constantInflowNFTProxy} =
- await t.deployNFTContracts();
+ const {
+ constantOutflowNFTProxy,
+ constantInflowNFTProxy,
+ poolAdminNFTProxy,
+ poolMemberNFTProxy,
+ } = await t.deployNFTContracts();
const superTokenLogic =
await t.deployContract(
"SuperTokenStorageLayoutTester",
superfluid.address,
constantOutflowNFTProxy.address,
- constantInflowNFTProxy.address
+ constantInflowNFTProxy.address,
+ poolAdminNFTProxy.address,
+ poolMemberNFTProxy.address
);
const a = await superTokenLogic.getLastSuperTokenStorageSlot();
const b = await customToken.getFirstCustomTokenStorageSlot();
diff --git a/packages/ethereum-contracts/test/contracts/superfluid/SuperTokenFactory.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/SuperTokenFactory.test.ts
index bda66083ff..0e19b464c4 100644
--- a/packages/ethereum-contracts/test/contracts/superfluid/SuperTokenFactory.test.ts
+++ b/packages/ethereum-contracts/test/contracts/superfluid/SuperTokenFactory.test.ts
@@ -70,13 +70,19 @@ describe("SuperTokenFactory Contract", function () {
constantInflowNFTProxy,
cofNFTLogicAddress,
cifNFTLogicAddress,
+ poolAdminNFTProxy,
+ poolMemberNFTProxy,
+ paNFTLogicAddress,
+ pmNFTLogicAddress,
} = await t.deployNFTContracts();
const superTokenLogic = await t.deployContract(
"SuperTokenMock",
superfluid.address,
"0",
constantOutflowNFTProxy.address,
- constantInflowNFTProxy.address
+ constantInflowNFTProxy.address,
+ poolAdminNFTProxy.address,
+ poolMemberNFTProxy.address
);
const tester =
await t.deployContract(
@@ -84,7 +90,9 @@ describe("SuperTokenFactory Contract", function () {
superfluid.address,
superTokenLogic.address,
cofNFTLogicAddress,
- cifNFTLogicAddress
+ cifNFTLogicAddress,
+ paNFTLogicAddress,
+ pmNFTLogicAddress
);
await tester.validateStorageLayout();
});
@@ -149,13 +157,19 @@ describe("SuperTokenFactory Contract", function () {
constantInflowNFTProxy,
cofNFTLogicAddress,
cifNFTLogicAddress,
+ poolAdminNFTProxy,
+ poolMemberNFTProxy,
+ paNFTLogicAddress,
+ pmNFTLogicAddress,
} = await t.deployNFTContracts();
const superTokenLogic = await t.deployContract(
"SuperTokenMock",
superfluid.address,
42,
constantOutflowNFTProxy.address,
- constantInflowNFTProxy.address
+ constantInflowNFTProxy.address,
+ poolAdminNFTProxy.address,
+ poolMemberNFTProxy.address
);
const factory2Logic =
await t.deployContract(
@@ -163,13 +177,16 @@ describe("SuperTokenFactory Contract", function () {
superfluid.address,
superTokenLogic.address,
cofNFTLogicAddress,
- cifNFTLogicAddress
+ cifNFTLogicAddress,
+ paNFTLogicAddress,
+ pmNFTLogicAddress
);
await governance.updateContracts(
superfluid.address,
ZERO_ADDRESS,
[],
- factory2Logic.address
+ factory2Logic.address,
+ ZERO_ADDRESS
);
await superfluid.getSuperTokenFactoryLogic();
}
@@ -270,14 +287,20 @@ describe("SuperTokenFactory Contract", function () {
await updateSuperTokenFactory();
assert.equal((await superToken1.waterMark()).toString(), "0");
- const {constantOutflowNFTProxy, constantInflowNFTProxy} =
- await t.deployNFTContracts();
+ const {
+ constantOutflowNFTProxy,
+ constantInflowNFTProxy,
+ poolAdminNFTProxy,
+ poolMemberNFTProxy,
+ } = await t.deployNFTContracts();
const superTokenLogic = await t.deployContract(
"SuperTokenMock",
superfluid.address,
69,
constantOutflowNFTProxy.address,
- constantInflowNFTProxy.address
+ constantInflowNFTProxy.address,
+ poolAdminNFTProxy.address,
+ poolMemberNFTProxy.address
);
await governance[
@@ -298,12 +321,18 @@ describe("SuperTokenFactory Contract", function () {
constantInflowNFTProxy,
cofNFTLogicAddress,
cifNFTLogicAddress,
+ poolAdminNFTProxy,
+ poolMemberNFTProxy,
+ paNFTLogicAddress,
+ pmNFTLogicAddress,
} = await t.deployNFTContracts();
const superTokenLogic = await t.deployContract(
"SuperToken",
superfluid.address,
constantOutflowNFTProxy.address,
- constantInflowNFTProxy.address
+ constantInflowNFTProxy.address,
+ poolAdminNFTProxy.address,
+ poolMemberNFTProxy.address
);
const factory2Logic =
await t.deployContract(
@@ -311,13 +340,16 @@ describe("SuperTokenFactory Contract", function () {
superfluid.address,
superTokenLogic.address,
cofNFTLogicAddress,
- cifNFTLogicAddress
+ cifNFTLogicAddress,
+ paNFTLogicAddress,
+ pmNFTLogicAddress
);
await governance.updateContracts(
superfluid.address,
ZERO_ADDRESS,
[],
- factory2Logic.address
+ factory2Logic.address,
+ ZERO_ADDRESS
);
await expectCustomError(
diff --git a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts
index a3c55df496..98fe04baf7 100644
--- a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts
+++ b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts
@@ -122,6 +122,7 @@ describe("Superfluid Host Contract", function () {
superfluid.address,
mock1.address,
[],
+ ZERO_ADDRESS,
ZERO_ADDRESS
);
@@ -137,6 +138,7 @@ describe("Superfluid Host Contract", function () {
superfluid.address,
mock2.address,
[],
+ ZERO_ADDRESS,
ZERO_ADDRESS
),
superfluid,
@@ -240,6 +242,7 @@ describe("Superfluid Host Contract", function () {
superfluid.address,
ZERO_ADDRESS,
[mockA2.address],
+ ZERO_ADDRESS,
ZERO_ADDRESS
);
console.debug(
@@ -349,6 +352,7 @@ describe("Superfluid Host Contract", function () {
superfluid.address,
ZERO_ADDRESS,
[mockA.address],
+ ZERO_ADDRESS,
ZERO_ADDRESS
),
superfluid,
@@ -403,12 +407,18 @@ describe("Superfluid Host Contract", function () {
constantInflowNFTProxy,
cofNFTLogicAddress,
cifNFTLogicAddress,
+ poolAdminNFTProxy,
+ poolMemberNFTProxy,
+ paNFTLogicAddress,
+ pmNFTLogicAddress,
} = await t.deployNFTContracts();
const superTokenLogic = await t.deployContract(
"SuperToken",
superfluid.address,
constantOutflowNFTProxy.address,
- constantInflowNFTProxy.address
+ constantInflowNFTProxy.address,
+ poolAdminNFTProxy.address,
+ poolMemberNFTProxy.address
);
const factory2LogicFactory =
await ethers.getContractFactory("SuperTokenFactory");
@@ -416,13 +426,16 @@ describe("Superfluid Host Contract", function () {
superfluid.address,
superTokenLogic.address,
cofNFTLogicAddress,
- cifNFTLogicAddress
+ cifNFTLogicAddress,
+ paNFTLogicAddress,
+ pmNFTLogicAddress
);
await governance.updateContracts(
superfluid.address,
ZERO_ADDRESS,
[],
- factory2Logic.address
+ factory2Logic.address,
+ ZERO_ADDRESS
);
assert.equal(
await superfluid.getSuperTokenFactory(),
@@ -443,12 +456,18 @@ describe("Superfluid Host Contract", function () {
constantInflowNFTProxy,
cofNFTLogicAddress,
cifNFTLogicAddress,
+ poolAdminNFTProxy,
+ poolMemberNFTProxy,
+ paNFTLogicAddress,
+ pmNFTLogicAddress,
} = await t.deployNFTContracts();
const superTokenLogic = await t.deployContract(
"SuperToken",
superfluid.address,
constantOutflowNFTProxy.address,
- constantInflowNFTProxy.address
+ constantInflowNFTProxy.address,
+ poolAdminNFTProxy.address,
+ poolMemberNFTProxy.address
);
const factory2LogicFactory = await ethers.getContractFactory(
"SuperTokenFactoryUpdateLogicContractsTester"
@@ -457,13 +476,16 @@ describe("Superfluid Host Contract", function () {
superfluid.address,
superTokenLogic.address,
cofNFTLogicAddress,
- cifNFTLogicAddress
+ cifNFTLogicAddress,
+ paNFTLogicAddress,
+ pmNFTLogicAddress
);
await governance.updateContracts(
superfluid.address,
ZERO_ADDRESS,
[],
- factory2Logic.address
+ factory2Logic.address,
+ ZERO_ADDRESS
);
assert.equal(
await superfluid.getSuperTokenFactory(),
@@ -2617,6 +2639,7 @@ describe("Superfluid Host Contract", function () {
superfluid.address,
ZERO_ADDRESS,
[t.contracts.ida.address],
+ ZERO_ADDRESS,
ZERO_ADDRESS
),
superfluid,
@@ -2634,26 +2657,35 @@ describe("Superfluid Host Contract", function () {
constantInflowNFTProxy,
cofNFTLogicAddress,
cifNFTLogicAddress,
+ poolAdminNFTProxy,
+ poolMemberNFTProxy,
+ paNFTLogicAddress,
+ pmNFTLogicAddress,
} = await t.deployNFTContracts();
const superTokenLogic = await t.deployContract(
"SuperToken",
superfluid.address,
constantOutflowNFTProxy.address,
- constantInflowNFTProxy.address
+ constantInflowNFTProxy.address,
+ poolAdminNFTProxy.address,
+ poolMemberNFTProxy.address
);
const factory2Logic = await t.deployContract(
"SuperTokenFactory",
superfluid.address,
superTokenLogic.address,
cofNFTLogicAddress,
- cifNFTLogicAddress
+ cifNFTLogicAddress,
+ paNFTLogicAddress,
+ pmNFTLogicAddress
);
await expectCustomError(
governance.updateContracts(
superfluid.address,
ZERO_ADDRESS,
[],
- factory2Logic.address
+ factory2Logic.address,
+ ZERO_ADDRESS
),
superfluid,
"HOST_NON_UPGRADEABLE"
@@ -2672,6 +2704,7 @@ describe("Superfluid Host Contract", function () {
superfluid.address,
mock1.address,
[],
+ ZERO_ADDRESS,
ZERO_ADDRESS
),
superfluid,
diff --git a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol
index 6c4e805903..e31dedfeb2 100644
--- a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol
+++ b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol
@@ -5,9 +5,21 @@ import "forge-std/Test.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
-
import { ERC1820RegistryCompiled } from "../../contracts/libs/ERC1820RegistryCompiled.sol";
import { SuperfluidFrameworkDeployer } from "../../contracts/utils/SuperfluidFrameworkDeployer.sol";
+import { Superfluid } from "../../contracts/superfluid/Superfluid.sol";
+import { ISuperfluidPool, SuperfluidPool } from "../../contracts/agreements/gdav1/SuperfluidPool.sol";
+import { IFlowNFTBase } from "../../contracts/interfaces/superfluid/IFlowNFTBase.sol";
+import {
+ IGeneralDistributionAgreementV1,
+ PoolConfig
+} from "../../contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
+import { IPoolNFTBase } from "../../contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol";
+import { IPoolAdminNFT } from "../../contracts/interfaces/agreements/gdav1/IPoolAdminNFT.sol";
+import { IPoolMemberNFT } from "../../contracts/interfaces/agreements/gdav1/IPoolMemberNFT.sol";
+import { IConstantOutflowNFT } from "../../contracts/interfaces/superfluid/IConstantOutflowNFT.sol";
+import { IConstantInflowNFT } from "../../contracts/interfaces/superfluid/IConstantInflowNFT.sol";
+import { ISuperfluidToken } from "../../contracts/interfaces/superfluid/ISuperfluidToken.sol";
import { ISETH } from "../../contracts/interfaces/tokens/ISETH.sol";
import { UUPSProxy } from "../../contracts/upgradability/UUPSProxy.sol";
import { ConstantFlowAgreementV1 } from "../../contracts/agreements/ConstantFlowAgreementV1.sol";
@@ -35,6 +47,7 @@ import { TestToken } from "../../contracts/utils/TestToken.sol";
contract FoundrySuperfluidTester is Test {
using SuperTokenV1Library for ISuperToken;
using EnumerableSet for EnumerableSet.Bytes32Set;
+ using EnumerableSet for EnumerableSet.AddressSet;
using SafeCast for uint256;
using SafeCast for int256;
@@ -47,6 +60,11 @@ contract FoundrySuperfluidTester is Test {
UNSUPPORTED_TOKEN_TYPE
}
+ struct _StackVars_UseBools {
+ bool useForwarder;
+ bool useGDA;
+ }
+
struct RealtimeBalance {
int256 availableBalance;
uint256 deposit;
@@ -61,6 +79,34 @@ contract FoundrySuperfluidTester is Test {
uint32 indexId;
}
+ struct ExpectedSuperfluidPoolData {
+ int128 totalUnits;
+ int128 connectedUnits;
+ int128 disconnectedUnits;
+ int96 connectedFlowRate;
+ int96 disconnectedFlowRate;
+ int256 disconnectedBalance;
+ }
+
+ struct ExpectedPoolMemberData {
+ bool isConnected;
+ uint128 ownedUnits;
+ int96 flowRate;
+ int96 netFlowRate;
+ }
+
+ struct PoolUnitData {
+ uint128 totalUnits;
+ uint128 connectedUnits;
+ uint128 disconnectedUnits;
+ }
+
+ struct PoolFlowRateData {
+ int96 totalFlowRate;
+ int96 totalConnectedFlowRate;
+ int96 totalDisconnectedFlowRate;
+ }
+
error INVALID_TEST_SUPER_TOKEN_TYPE();
SuperfluidFrameworkDeployer internal immutable sfDeployer;
@@ -68,7 +114,7 @@ contract FoundrySuperfluidTester is Test {
uint256 internal constant DEFAULT_WARP_TIME = 1 days;
uint256 internal constant INIT_TOKEN_BALANCE = type(uint128).max;
- uint256 internal constant INIT_SUPER_TOKEN_BALANCE = type(uint64).max;
+ uint256 internal constant INIT_SUPER_TOKEN_BALANCE = type(uint88).max;
string internal constant DEFAULT_TEST_TOKEN_TYPE = "WRAPPER_SUPER_TOKEN";
string internal constant TOKEN_TYPE_ENV_KEY = "TOKEN_TYPE";
@@ -85,7 +131,7 @@ contract FoundrySuperfluidTester is Test {
address[] internal TEST_ACCOUNTS = [admin, alice, bob, carol, dan, eve, frank, grace, heidi, ivan];
/// @dev Other account addresses added that aren't testers (pools, super apps, smart contracts)
- address[] internal OTHER_ACCOUNTS;
+ EnumerableSet.AddressSet internal OTHER_ACCOUNTS;
uint256 internal immutable N_TESTERS;
@@ -116,6 +162,14 @@ contract FoundrySuperfluidTester is Test {
/// @notice A mapping from super token to subId to sub.indexValue for the IDA
mapping(ISuperToken => mapping(bytes32 subId => uint128 indexValue)) internal _lastUpdatedSubIndexValues;
+ /// @notice A mapping from pool to
+ mapping(address pool => EnumerableSet.AddressSet members) internal _poolMembers;
+ mapping(address pool => mapping(address member => ExpectedPoolMemberData expectedData)) internal
+ _poolToExpectedMemberData;
+
+ /// @notice The default poolConfig (true, true)
+ PoolConfig public poolConfig;
+
constructor(uint8 nTesters) {
// deploy ERC1820 registry
vm.etch(ERC1820RegistryCompiled.at, ERC1820RegistryCompiled.bin);
@@ -140,12 +194,16 @@ contract FoundrySuperfluidTester is Test {
require(nTesters <= TEST_ACCOUNTS.length, "too many testers");
N_TESTERS = nTesters;
+ _addAccount(address(sf.gda));
+
// Set the token type being tested
string memory tokenType = vm.envOr(TOKEN_TYPE_ENV_KEY, DEFAULT_TEST_TOKEN_TYPE);
bytes32 hashedTokenType = keccak256(abi.encode(tokenType));
_addAccount(address(sf.toga));
+ poolConfig = PoolConfig({ transferabilityForUnitsOwner: true, distributionFromAnyAddress: true });
+
// @note we must use a ternary expression because immutable variables cannot be initialized
// in an if statement
testSuperTokenType = hashedTokenType == keccak256(abi.encode("WRAPPER_SUPER_TOKEN"))
@@ -172,7 +230,7 @@ contract FoundrySuperfluidTester is Test {
/// @notice Deploys a Wrapper SuperToken with an underlying test token and gives tokens to the test accounts
function _setUpWrapperSuperToken() internal {
- (token, superToken) = sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max);
+ (token, superToken) = sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, address(0));
address[] memory accounts = _listAccounts();
for (uint256 i = 0; i < accounts.length; ++i) {
@@ -192,6 +250,7 @@ contract FoundrySuperfluidTester is Test {
/// @dev We use vm.deal to give each account a starting amount of ether
function _setUpNativeAssetSuperToken() internal {
(superToken) = sfDeployer.deployNativeAssetSuperToken("Super ETH", "ETHx");
+
address[] memory accounts = _listAccounts();
for (uint256 i = 0; i < accounts.length; ++i) {
address account = accounts[i];
@@ -200,7 +259,7 @@ contract FoundrySuperfluidTester is Test {
ISETH(address(superToken)).upgradeByETH{ value: INIT_SUPER_TOKEN_BALANCE }();
_expectedTotalSupply += INIT_SUPER_TOKEN_BALANCE;
vm.stopPrank();
- _helperTakeBalanceSnapshot(superToken, TEST_ACCOUNTS[i]);
+ _helperTakeBalanceSnapshot(superToken, account);
}
}
@@ -212,6 +271,7 @@ contract FoundrySuperfluidTester is Test {
uint256 initialSupply = INIT_SUPER_TOKEN_BALANCE * accounts.length;
(superToken) = sfDeployer.deployPureSuperToken("Super MR", "MRx", initialSupply);
_expectedTotalSupply = initialSupply;
+
for (uint256 i = 0; i < accounts.length; ++i) {
address account = accounts[i];
superToken.transfer(account, INIT_SUPER_TOKEN_BALANCE);
@@ -240,16 +300,22 @@ contract FoundrySuperfluidTester is Test {
/// @notice Adds an account to the testing mix
function _addAccount(address account) internal {
- OTHER_ACCOUNTS.push(account);
+ if (OTHER_ACCOUNTS.contains(account)) return;
+
+ for (uint i = 0; i < TEST_ACCOUNTS.length; ++i) {
+ if (TEST_ACCOUNTS[i] == account) return;
+ }
+
+ OTHER_ACCOUNTS.add(account);
}
function _listAccounts() internal view returns (address[] memory accounts) {
- accounts = new address[](N_TESTERS + OTHER_ACCOUNTS.length);
+ accounts = new address[](N_TESTERS + OTHER_ACCOUNTS.values().length);
for (uint i = 0; i < N_TESTERS; ++i) {
accounts[i] = address(TEST_ACCOUNTS[i]);
}
- for (uint i = 0; i < OTHER_ACCOUNTS.length; ++i) {
- accounts[i + N_TESTERS] = OTHER_ACCOUNTS[i];
+ for (uint i = 0; i < OTHER_ACCOUNTS.values().length; ++i) {
+ accounts[i + N_TESTERS] = OTHER_ACCOUNTS.values()[i];
}
}
@@ -297,20 +363,6 @@ contract FoundrySuperfluidTester is Test {
return netFlowRateSum == 0;
}
- /// @notice Warps forwards 1 day and asserts balances of all testers and global invariants
- function _warpAndAssertAll(ISuperToken superToken_) internal virtual {
- vm.warp(block.timestamp + DEFAULT_WARP_TIME);
- _assertRealTimeBalances(superToken_);
- _assertGlobalInvariants();
- }
-
- /// @notice Warps forwards `time` seconds and asserts balances of all testers and global invariants
- function _warpAndAssertAll(ISuperToken superToken_, uint256 time) internal virtual {
- vm.warp(block.timestamp + time);
- _assertRealTimeBalances(superToken_);
- _assertGlobalInvariants();
- }
-
/// @notice Asserts that the global invariants hold true
function _assertGlobalInvariants() internal virtual {
_assertInvariantLiquiditySum();
@@ -335,6 +387,20 @@ contract FoundrySuperfluidTester is Test {
assertTrue(_defintionAumGtEqSuperTokenTotalSupplyInvariant(), "Invariant: AUM > SuperToken Total Supply");
}
+ /// @notice Warps forwards 1 day and asserts balances of all testers and global invariants
+ function _warpAndAssertAll(ISuperToken superToken_) internal virtual {
+ vm.warp(block.timestamp + DEFAULT_WARP_TIME);
+ _assertRealTimeBalances(superToken_);
+ _assertGlobalInvariants();
+ }
+
+ /// @notice Warps forwards `time` seconds and asserts balances of all testers and global invariants
+ function _warpAndAssertAll(ISuperToken superToken_, uint256 time) internal virtual {
+ vm.warp(block.timestamp + time);
+ _assertRealTimeBalances(superToken_);
+ _assertGlobalInvariants();
+ }
+
/*//////////////////////////////////////////////////////////////////////////
Assume Helpers
//////////////////////////////////////////////////////////////////////////*/
@@ -342,8 +408,8 @@ contract FoundrySuperfluidTester is Test {
/// @dev Flow rate must be greater than 0 and less than or equal to int32.max
function _assumeValidFlowRate(int96 desiredFlowRate) internal pure returns (int96 flowRate) {
vm.assume(desiredFlowRate > 0);
- vm.assume(desiredFlowRate <= int96(type(int32).max));
- flowRate = int96(int32(desiredFlowRate));
+ vm.assume(desiredFlowRate <= int96(uint96(uint256(type(uint64).max))));
+ flowRate = desiredFlowRate;
}
/*//////////////////////////////////////////////////////////////////////////
@@ -398,7 +464,11 @@ contract FoundrySuperfluidTester is Test {
(int256 availableBalance, uint256 deposit, uint256 owedDeposit,) =
superToken_.realtimeBalanceOfNow(accounts[i]);
- liquiditySum += availableBalance + int256(deposit) - int256(owedDeposit);
+ // FIXME: correct formula
+ // liquiditySum += availableBalance + int256(deposit) - int256(owedDeposit);
+ // current faulty one
+ liquiditySum +=
+ availableBalance + (deposit > owedDeposit ? int256(deposit) - int256(owedDeposit) : int256(0));
}
}
@@ -494,7 +564,7 @@ contract FoundrySuperfluidTester is Test {
assertTrue(netFlowRate < 0, "_helperWarpToCritical: netFlowRate must be less than 0 to reach critical");
assertTrue(secondsCritical > 0, "_helperWarpToCritical: secondsCritical must be > 0 to reach critical");
(int256 ab,,) = superToken_.realtimeBalanceOf(account, block.timestamp);
- int256 timeToZero = ab / netFlowRate;
+ int256 timeToZero = ab / netFlowRate < 0 ? (ab / netFlowRate) * -1 : ab / netFlowRate;
uint256 amountToWarp = timeToZero.toUint256() + secondsCritical;
vm.warp(block.timestamp + amountToWarp);
assertTrue(superToken_.isAccountCriticalNow(account), "_helperWarpToCritical: account is not critical");
@@ -515,7 +585,7 @@ contract FoundrySuperfluidTester is Test {
assertTrue(netFlowRate < 0, "_helperWarpToCritical: netFlowRate must be less than 0 to reach critical");
assertTrue(secondsInsolvent > 0, "_helperWarpToInsolvency: secondsInsolvent must be > 0 to reach insolvency");
(int256 ab,,) = superToken_.realtimeBalanceOf(account, block.timestamp);
- int256 timeToZero = ab / netFlowRate;
+ int256 timeToZero = ab / netFlowRate < 0 ? (ab / netFlowRate) * -1 : ab / netFlowRate;
uint256 amountToWarp = timeToZero.toUint256() + liquidationPeriod + secondsInsolvent;
vm.warp(block.timestamp + amountToWarp);
assertFalse(superToken_.isAccountSolventNow(account), "_helperWarpToInsolvency: account is still solvent");
@@ -603,6 +673,14 @@ contract FoundrySuperfluidTester is Test {
}
// Write Helpers - SuperToken
+ function _helperTransferAll(ISuperToken superToken_, address sender, address receiver) internal {
+ vm.startPrank(sender);
+ superToken_.transferAll(receiver);
+ vm.stopPrank();
+
+ _helperTakeBalanceSnapshot(superToken_, sender);
+ _helperTakeBalanceSnapshot(superToken_, receiver);
+ }
function _helperDeploySuperTokenAndInitialize(
ISuperToken previousSuperToken,
@@ -610,14 +688,16 @@ contract FoundrySuperfluidTester is Test {
uint8 underlyingDecimals,
string memory name,
string memory symbol,
- address admin
+ address _admin
) internal returns (SuperToken localSuperToken) {
localSuperToken = new SuperToken(
sf.host,
previousSuperToken.CONSTANT_OUTFLOW_NFT(),
- previousSuperToken.CONSTANT_INFLOW_NFT()
+ previousSuperToken.CONSTANT_INFLOW_NFT(),
+ previousSuperToken.POOL_ADMIN_NFT(),
+ previousSuperToken.POOL_MEMBER_NFT()
);
- localSuperToken.initializeWithAdmin(underlyingToken, underlyingDecimals, name, symbol, admin);
+ localSuperToken.initializeWithAdmin(underlyingToken, underlyingDecimals, name, symbol, _admin);
}
// Write Helpers - ConstantFlowAgreementV1
@@ -664,8 +744,6 @@ contract FoundrySuperfluidTester is Test {
// Assert RTB for all users
_assertRealTimeBalances(superToken_);
-
- // Assert Global Invariants
_assertGlobalInvariants();
}
@@ -710,8 +788,6 @@ contract FoundrySuperfluidTester is Test {
// Assert RTB for all users
_assertRealTimeBalances(superToken_);
-
- // Assert Global Invariants
_assertGlobalInvariants();
}
@@ -743,6 +819,15 @@ contract FoundrySuperfluidTester is Test {
_helperTakeBalanceSnapshot(superToken_, sender);
_helperTakeBalanceSnapshot(superToken_, receiver);
+
+ if (caller != sender && caller != receiver) {
+ _helperTakeBalanceSnapshot(superToken_, caller);
+ }
+
+ // Get the default reward address for the token and update their snapshot too in the
+ // liquidation case
+ address rewardAddress = sf.governance.getRewardAddress(sf.host, superToken_);
+ _helperTakeBalanceSnapshot(superToken_, rewardAddress);
}
// Assert Flow Data + Account Flow Info for sender/receiver
@@ -755,8 +840,6 @@ contract FoundrySuperfluidTester is Test {
// Assert RTB for all users
_assertRealTimeBalances(superToken_);
-
- // Assert Global Invariants
_assertGlobalInvariants();
}
@@ -828,8 +911,6 @@ contract FoundrySuperfluidTester is Test {
// Assert RTB for all users
_assertRealTimeBalances(superToken_);
-
- // Assert Global Invariants
_assertGlobalInvariants();
}
@@ -899,6 +980,7 @@ contract FoundrySuperfluidTester is Test {
// Assert RTB for all users
_assertRealTimeBalances(superToken_);
+ _assertGlobalInvariants();
// Assert Global Invariants
_assertGlobalInvariants();
@@ -960,8 +1042,6 @@ contract FoundrySuperfluidTester is Test {
// Assert RTB for all users
_assertRealTimeBalances(superToken_);
-
- // Assert Global Invariants
_assertGlobalInvariants();
}
@@ -1036,7 +1116,7 @@ contract FoundrySuperfluidTester is Test {
_assertGlobalInvariants();
}
- /// @notice Distributes tokens to subscribers
+ /// @notice Executes an IDA distribution of tokens to subscribers
/// @dev We assert:
/// - The index data has been updated as expected
/// - the publisher's balance and deposit has been updated as expected
@@ -1044,7 +1124,9 @@ contract FoundrySuperfluidTester is Test {
/// @param publisher The publisher of the index
/// @param indexId The indexId to update
/// @param amount The new index value to update to
- function _helperDistribute(ISuperToken superToken_, address publisher, uint32 indexId, uint256 amount) internal {
+ function _helperDistributeViaIDA(ISuperToken superToken_, address publisher, uint32 indexId, uint256 amount)
+ internal
+ {
// Get Index Data and Publisher Balance Before
(, uint128 indexValueBefore, uint128 totalUnitsApprovedBefore, uint128 totalUnitsPendingBefore) =
superToken_.getIndex(publisher, indexId);
@@ -1363,7 +1445,7 @@ contract FoundrySuperfluidTester is Test {
/// @param publisher The publisher of the subscription
/// @param indexId The index ID of the index
/// @param subscriber The subscriber of the subscription
- function _helperClaim(
+ function _helperClaimViaIDA(
ISuperToken superToken_,
address caller,
address publisher,
@@ -1414,6 +1496,616 @@ contract FoundrySuperfluidTester is Test {
_assertGlobalInvariants();
}
+ // Write Helpers - GeneralDistributionAgreementV1/SuperfluidPool
+
+ function _helperCreatePool(
+ ISuperToken _superToken,
+ address _caller,
+ address _poolAdmin,
+ bool _useForwarder,
+ PoolConfig memory _poolConfig
+ ) internal returns (ISuperfluidPool) {
+ ISuperfluidPool localPool;
+
+ vm.startPrank(_caller);
+ if (!_useForwarder) {
+ localPool = SuperfluidPool(address(sf.gda.createPool(_superToken, _poolAdmin, _poolConfig)));
+ } else {
+ (, localPool) = sf.gdaV1Forwarder.createPool(_superToken, _poolAdmin, _poolConfig);
+ }
+ vm.stopPrank();
+ _addAccount(address(localPool));
+
+ // Assert Pool Creation was properly handled
+ address poolAdmin = localPool.admin();
+ {
+ bool isPool = _useForwarder
+ ? sf.gdaV1Forwarder.isPool(_superToken, address(localPool))
+ : sf.gda.isPool(_superToken, address(localPool));
+ assertTrue(isPool, "GDAv1.t: Created pool is not pool");
+ assertEq(poolAdmin, _poolAdmin, "GDAv1.t: Pool admin is incorrect");
+ assertEq(address(localPool.superToken()), address(_superToken), "GDAv1.t: Pool super token is incorrect");
+ }
+
+ IPoolAdminNFT poolAdminNft = SuperToken(address(_superToken)).POOL_ADMIN_NFT();
+ uint256 tokenId = poolAdminNft.getTokenId(address(localPool), _poolAdmin);
+
+ // Assert PoolAdminNFT Owner is expected
+ assertEq(
+ poolAdminNft.ownerOf(tokenId), _poolAdmin, "_helperCreatePool: Pool Admin NFT is not owned by pool admin"
+ );
+
+ // Assert PoolAdminNFTData is expected
+ {
+ IPoolAdminNFT.PoolAdminNFTData memory poolAdminData = poolAdminNft.poolAdminDataByTokenId(tokenId);
+ assertEq(poolAdminData.pool, address(localPool), "_helperCreatePool: Pool Admin NFT pool mismatch");
+ assertEq(poolAdminData.admin, _poolAdmin, "_helperCreatePool: Pool Admin NFT admin mismatch");
+ }
+
+ // Assert Admin is PoolAdjustment Flow receiver
+ {
+ (address adjustmentFlowRecipient,,) = _useForwarder
+ ? sf.gdaV1Forwarder.getPoolAdjustmentFlowInfo(localPool)
+ : sf.gda.getPoolAdjustmentFlowInfo(localPool);
+ assertEq(poolAdmin, adjustmentFlowRecipient, "_helperCreatePool: Incorrect pool adjustment flow receiver");
+ }
+
+ return localPool;
+ }
+
+ function _helperCreatePool(ISuperToken _superToken, address _caller, address _poolAdmin)
+ internal
+ returns (ISuperfluidPool)
+ {
+ return _helperCreatePool(_superToken, _caller, _poolAdmin, false, poolConfig);
+ }
+
+ function _helperUpdateMemberUnits(ISuperfluidPool pool_, address caller_, address member_, uint128 newUnits_)
+ internal
+ {
+ _StackVars_UseBools memory useBools_;
+ _helperUpdateMemberUnits(pool_, caller_, member_, newUnits_, useBools_);
+ }
+
+ function _updateMemberUnits(
+ ISuperfluidPool pool_,
+ ISuperToken poolSuperToken,
+ address caller_,
+ address member_,
+ uint128 newUnits_,
+ _StackVars_UseBools memory useBools_
+ ) internal {
+ vm.startPrank(caller_);
+ if (useBools_.useGDA) {
+ if (useBools_.useForwarder) {
+ sf.gdaV1Forwarder.updateMemberUnits(pool_, member_, newUnits_, new bytes(0));
+ } else {
+ poolSuperToken.updateMemberUnits(pool_, member_, newUnits_);
+ }
+ } else {
+ pool_.updateMemberUnits(member_, newUnits_);
+ }
+ vm.stopPrank();
+ }
+
+ function _helperUpdateMemberUnits(
+ ISuperfluidPool pool_,
+ address caller_,
+ address member_,
+ uint128 newUnits_,
+ _StackVars_UseBools memory useBools_
+ ) internal {
+ // there is a hard restriction in which total units must never exceed type(int96).max
+ vm.assume(newUnits_ < type(uint72).max);
+ ISuperToken poolSuperToken = ISuperToken(address(pool_.superToken()));
+ if (caller_ == address(0) || member_ == address(0) || sf.gda.isPool(poolSuperToken, member_)) return;
+
+ (bool isConnected, int256 oldUnits,) = _helperGetMemberPoolState(pool_, member_);
+
+ PoolUnitData memory poolUnitDataBefore = _helperGetPoolUnitsData(pool_);
+
+ (int256 claimableBalance,) = pool_.getClaimableNow(member_);
+ (int256 balanceBefore,,,) = poolSuperToken.realtimeBalanceOfNow(member_);
+ {
+ _updateMemberUnits(pool_, poolSuperToken, caller_, member_, newUnits_, useBools_);
+ }
+ PoolUnitData memory poolUnitDataAfter = _helperGetPoolUnitsData(pool_);
+
+ {
+ _helperTakeBalanceSnapshot(ISuperToken(address(poolSuperToken)), member_);
+ }
+
+ assertEq(pool_.getUnits(member_), newUnits_, "GDAv1.t: Members' units incorrectly set");
+
+ // Assert that pending balance is claimed if user is disconnected
+ if (!isConnected) {
+ (int256 balanceAfter,,,) = poolSuperToken.realtimeBalanceOfNow(member_);
+ assertEq(
+ balanceAfter, balanceBefore + claimableBalance, "_helperUpdateMemberUnits: Pending balance not claimed"
+ );
+ }
+
+ // Assert that the flow rate for a member is updated accordingly
+ {
+ uint128 totalUnits = pool_.getTotalUnits();
+ uint128 flowRatePerUnit = totalUnits == 0 ? 0 : uint128(uint96(pool_.getTotalFlowRate())) / totalUnits;
+ assertEq(
+ flowRatePerUnit * newUnits_,
+ uint128(uint96(pool_.getMemberFlowRate(member_))),
+ "_helperUpdateMemberUnits: Member flow rate incorrect"
+ );
+ }
+
+ // Update Expected Member Data
+ if (newUnits_ > 0) {
+ // @note You are only considered a member if you are given units
+ _poolMembers[address(pool_)].add(member_);
+ }
+
+ // Assert Pool Total, Connected and Disconnect Units are correct
+ {
+ int256 unitsDelta = uint256(newUnits_).toInt256() - oldUnits;
+ assertEq(
+ uint256(uint256(poolUnitDataBefore.totalUnits).toInt256() + unitsDelta),
+ poolUnitDataAfter.totalUnits,
+ "_helperUpdateMemberUnits: Pool total units incorrect"
+ );
+ assertEq(
+ uint256(uint256(poolUnitDataBefore.connectedUnits).toInt256() + (isConnected ? unitsDelta : int128(0))),
+ poolUnitDataAfter.connectedUnits,
+ "_helperUpdateMemberUnits: Pool connected units incorrect"
+ );
+ assertEq(
+ uint256(
+ uint256(poolUnitDataBefore.disconnectedUnits).toInt256() + (isConnected ? int128(0) : unitsDelta)
+ ),
+ poolUnitDataAfter.disconnectedUnits,
+ "_helperUpdateMemberUnits: Pool disconnected units incorrect"
+ );
+ }
+
+ // Assert Pool Member NFT is minted/burned
+ _assertPoolMemberNFT(poolSuperToken, pool_, member_, newUnits_);
+
+ // Assert RTB for all users
+ // _assertRealTimeBalances(ISuperToken(address(poolSuperToken)));
+ }
+
+ function _helperConnectPool(address caller_, ISuperToken superToken_, ISuperfluidPool pool_, bool useForwarder_)
+ internal
+ {
+ (bool isConnectedBefore, int256 oldUnits, int96 oldFlowRate) = _helperGetMemberPoolState(pool_, caller_);
+
+ PoolUnitData memory poolUnitDataBefore = _helperGetPoolUnitsData(pool_);
+ PoolFlowRateData memory poolFlowRateDataBefore = _helperGetPoolFlowRatesData(pool_);
+
+ vm.startPrank(caller_);
+ if (useForwarder_) {
+ sf.gdaV1Forwarder.connectPool(pool_, "");
+ } else {
+ sf.host.callAgreement(
+ sf.gda,
+ abi.encodeWithSelector(IGeneralDistributionAgreementV1.connectPool.selector, pool_, ""),
+ new bytes(0)
+ );
+ }
+ vm.stopPrank();
+
+ PoolUnitData memory poolUnitDataAfter = _helperGetPoolUnitsData(pool_);
+ PoolFlowRateData memory poolFlowRateDataAfter = _helperGetPoolFlowRatesData(pool_);
+
+ {
+ _helperTakeBalanceSnapshot(superToken_, caller_);
+ }
+
+ bool isMemberConnected = useForwarder_
+ ? sf.gdaV1Forwarder.isMemberConnected(pool_, caller_)
+ : sf.gda.isMemberConnected(pool_, caller_);
+ assertEq(isMemberConnected, true, "GDAv1.t: Member not connected");
+
+ // Assert connected units delta for the pool
+ {
+ assertEq(
+ isConnectedBefore ? 0 : uint256(oldUnits),
+ poolUnitDataAfter.connectedUnits - poolUnitDataBefore.connectedUnits,
+ "_helperConnectPool: Pool connected units incorrect"
+ );
+ }
+
+ // Assert connected and disconnected flow rate for the pool
+ {
+ assertEq(
+ poolFlowRateDataBefore.totalConnectedFlowRate + (isConnectedBefore ? int96(0) : oldFlowRate),
+ poolFlowRateDataAfter.totalConnectedFlowRate,
+ "_helperConnectPool: Pool connected flow rate incorrect"
+ );
+ assertEq(
+ poolFlowRateDataBefore.totalDisconnectedFlowRate - (isConnectedBefore ? int96(0) : oldFlowRate),
+ poolFlowRateDataAfter.totalDisconnectedFlowRate,
+ "_helperConnectPool: Pool disconnected flow rate incorrect"
+ );
+ }
+ // Assert RTB for all users
+ // _assertRealTimeBalances(superToken_);
+ _assertGlobalInvariants();
+ }
+
+ function _helperConnectPool(address caller_, ISuperToken superToken_, ISuperfluidPool pool_) internal {
+ _helperConnectPool(caller_, superToken_, pool_, false);
+ }
+
+ function _helperDisconnectPool(address caller_, ISuperToken superToken_, ISuperfluidPool pool_, bool useForwarder_)
+ internal
+ {
+ (bool isConnectedBefore, int256 oldUnits, int96 oldFlowRate) = _helperGetMemberPoolState(pool_, caller_);
+
+ PoolUnitData memory poolUnitDataBefore = _helperGetPoolUnitsData(pool_);
+ PoolFlowRateData memory poolFlowRateDataBefore = _helperGetPoolFlowRatesData(pool_);
+
+ vm.startPrank(caller_);
+ if (useForwarder_) {
+ sf.gdaV1Forwarder.disconnectPool(pool_, "");
+ } else {
+ sf.host.callAgreement(sf.gda, abi.encodeCall(sf.gda.disconnectPool, (pool_, new bytes(0))), new bytes(0));
+ }
+ vm.stopPrank();
+
+ PoolUnitData memory poolUnitDataAfter = _helperGetPoolUnitsData(pool_);
+ PoolFlowRateData memory poolFlowRateDataAfter = _helperGetPoolFlowRatesData(pool_);
+
+ {
+ _helperTakeBalanceSnapshot(superToken_, caller_);
+ }
+
+ assertEq(
+ sf.gda.isMemberConnected(pool_, caller_),
+ false,
+ "GDAv1.t D/C: Member not disconnected"
+ );
+
+ // Assert disconnected units delta for the pool
+ {
+ assertEq(
+ isConnectedBefore ? uint256(oldUnits) : 0,
+ poolUnitDataAfter.disconnectedUnits - poolUnitDataBefore.disconnectedUnits,
+ "_helperDisconnectPool: Pool disconnected units incorrect"
+ );
+ }
+ {
+ assertEq(
+ poolFlowRateDataBefore.totalConnectedFlowRate - (isConnectedBefore ? oldFlowRate : int96(0)),
+ poolFlowRateDataAfter.totalConnectedFlowRate,
+ "_helperDisconnectPool: Pool connected flow rate incorrect"
+ );
+ assertEq(
+ poolFlowRateDataBefore.totalDisconnectedFlowRate + (isConnectedBefore ? oldFlowRate : int96(0)),
+ poolFlowRateDataAfter.totalDisconnectedFlowRate,
+ "_helperDisconnectPool: Pool disconnected flow rate incorrect"
+ );
+ }
+
+ // Assert RTB for all users
+ // _assertRealTimeBalances(superToken_);
+ _assertGlobalInvariants();
+ }
+
+ function _helperDisconnectPool(address caller_, ISuperToken superToken_, ISuperfluidPool pool_) internal {
+ _helperDisconnectPool(caller_, superToken_, pool_, false);
+ }
+
+ function _helperDistributeViaGDA(
+ ISuperToken superToken_,
+ address caller_,
+ address from_,
+ ISuperfluidPool pool_,
+ uint256 requestedAmount,
+ bool useForwarder
+ ) internal {
+ (int256 fromRTBBefore,,,) = superToken.realtimeBalanceOfNow(from_);
+
+ uint256 actualAmountDistributed = useForwarder
+ ? sf.gdaV1Forwarder.estimateDistributionActualAmount(superToken, from_, pool_, requestedAmount)
+ : sf.gda.estimateDistributionActualAmount(superToken, from_, pool_, requestedAmount);
+
+ address[] memory members = _poolMembers[address(pool_)].values();
+ uint256[] memory memberBalancesBefore = new uint256[](members.length);
+ uint256[] memory memberClaimableBefore = new uint256[](members.length);
+
+ for (uint256 i = 0; i < members.length; ++i) {
+ (int256 memberRTB,,,) = superToken.realtimeBalanceOfNow(members[i]);
+ memberBalancesBefore[i] = uint256(memberRTB);
+ (int256 claimable,) = pool_.getClaimableNow(members[i]);
+ memberClaimableBefore[i] = uint256(claimable);
+ }
+
+ {
+ vm.startPrank(caller_);
+ if (useForwarder) {
+ sf.gdaV1Forwarder.distribute(superToken_, from_, pool_, requestedAmount, new bytes(0));
+ } else {
+ superToken_.distributeToPool(from_, pool_, requestedAmount);
+ }
+ vm.stopPrank();
+ }
+
+ {
+ _helperTakeBalanceSnapshot(superToken_, from_);
+ }
+
+ uint256 amountPerUnit = pool_.getTotalUnits() > 0 ? actualAmountDistributed / pool_.getTotalUnits() : 0;
+
+ // Assert Distributor RTB
+ {
+ (int256 fromRTBAfter,,,) = superToken.realtimeBalanceOfNow(from_);
+ // If the distributor is a connected member themselves, they will receive the units
+ // they have just distributed
+ uint256 amountReceivedInitial = sf.gda.isMemberConnected(pool_, from_)
+ ? uint256(pool_.getUnits(from_)) * amountPerUnit
+ : 0;
+ assertEq(
+ fromRTBAfter,
+ fromRTBBefore - int256(actualAmountDistributed) + int256(amountReceivedInitial),
+ "GDAv1.t D: Distributor RTB incorrect"
+ );
+ }
+
+ if (members.length == 0) return;
+
+ // Assert Members RTB
+ for (uint256 i; i < members.length; ++i) {
+ (int256 memberRTB,,,) = superToken.realtimeBalanceOfNow(members[i]);
+ bool memberConnected = sf.gda.isMemberConnected(pool_, members[i]);
+
+ uint256 amountReceived = uint256(pool_.getUnits(members[i])) * amountPerUnit;
+ if (memberConnected) {
+ if (members[i] == from_) {
+ assertEq(
+ memberRTB,
+ int256(memberBalancesBefore[i]) - int256(actualAmountDistributed) + int256(amountReceived),
+ "GDAv1.t D: Distributor who is Member RTB incorrect"
+ );
+ } else {
+ assertEq(
+ uint256(memberRTB), memberBalancesBefore[i] + amountReceived, "GDAv1.t D: Member RTB incorrect"
+ );
+ }
+ } else {
+ (int256 claimable,) = pool_.getClaimableNow(members[i]);
+ assertEq(
+ uint256(claimable),
+ amountReceived + uint256(memberClaimableBefore[i]),
+ "GDAv1.t D: Member claimable incorrect"
+ );
+ }
+ }
+
+ // Assert RTB for all users
+ // _assertRealTimeBalances(superToken_);
+ _assertGlobalInvariants();
+ }
+
+ function _helperDistributeViaGDA(
+ ISuperToken superToken_,
+ address caller_,
+ address from_,
+ ISuperfluidPool pool_,
+ uint256 requestedAmount
+ ) internal {
+ _helperDistributeViaGDA(superToken_, caller_, from_, pool_, requestedAmount, false);
+ }
+
+ function _helperDistributeFlow(
+ ISuperToken superToken_,
+ address caller,
+ address from,
+ ISuperfluidPool pool_,
+ int96 requestedFlowRate,
+ bool useForwarder
+ ) internal {
+ (int96 actualFlowRate, int96 totalDistributionFlowRate) = useForwarder
+ ? sf.gdaV1Forwarder.estimateFlowDistributionActualFlowRate(superToken_, from, pool_, requestedFlowRate)
+ : sf.gda.estimateFlowDistributionActualFlowRate(superToken_, from, pool_, requestedFlowRate);
+
+ address[] memory members = _poolMembers[address(pool_)].values();
+ int96[] memory memberFlowRatesBefore = new int96[](members.length);
+
+ for (uint256 i = 0; i < members.length; ++i) {
+ int96 memberFlowRate = pool_.getMemberFlowRate(members[i]);
+ memberFlowRatesBefore[i] = memberFlowRate;
+ }
+
+ vm.startPrank(caller);
+ if (useForwarder) {
+ sf.gdaV1Forwarder.distributeFlow(superToken_, from, pool_, requestedFlowRate, new bytes(0));
+ } else {
+ superToken_.distributeFlow(from, pool_, requestedFlowRate);
+ }
+ vm.stopPrank();
+
+ {
+ _helperTakeBalanceSnapshot(superToken_, from);
+ }
+
+ int96 poolTotalFlowRateAfter = pool_.getTotalFlowRate();
+ {
+ // Assert distributor flow rate
+ int96 fromToPoolFlowRateAfter = useForwarder
+ ? sf.gdaV1Forwarder.getFlowDistributionFlowRate(superToken_, from, pool_)
+ : sf.gda.getFlowRate(superToken_, from, pool_);
+ assertEq(
+ fromToPoolFlowRateAfter,
+ actualFlowRate,
+ "_helperDistributeFlow: from flow rate should be actual flow rate"
+ );
+
+ // Assert pool total flow rate
+ assertEq(
+ poolTotalFlowRateAfter,
+ totalDistributionFlowRate,
+ "_helperDistributeFlow: pool total flow rate != total distribution flow rate"
+ );
+ }
+
+ // Assert Outflow NFT is minted to distributor
+ // Assert Inflow NFT is minted to pool
+ _assertFlowNftOnDistributeFlow(superToken_, pool_, from, requestedFlowRate);
+
+ {
+ if (members.length == 0) return;
+ uint128 poolTotalUnitsAfter = pool_.getTotalUnits();
+ int96 flowRatePerUnit = poolTotalUnitsAfter == 0
+ ? int96(0)
+ : poolTotalFlowRateAfter / uint256(poolTotalUnitsAfter).toInt256().toInt96();
+
+ for (uint256 i; i < members.length; ++i) {
+ int96 memberFlowRate = pool_.getMemberFlowRate(members[i]);
+ uint128 memberUnits = pool_.getUnits(members[i]);
+ int96 expectedMemberFlowRate = flowRatePerUnit * uint256(memberUnits).toInt256().toInt96();
+ assertEq(
+ expectedMemberFlowRate,
+ memberFlowRate,
+ "_helperDistributeFlow: member flow rate != expected member flow rate"
+ );
+ }
+ }
+ // Assert RTB for all users
+ // _assertRealTimeBalances(superToken_);
+ _assertGlobalInvariants();
+ }
+
+ function _helperDistributeFlow(
+ ISuperToken superToken_,
+ address caller,
+ address from,
+ ISuperfluidPool pool_,
+ int96 requestedFlowRate
+ ) internal {
+ _helperDistributeFlow(superToken_, caller, from, pool_, requestedFlowRate, false);
+ }
+
+ // Write Helpers - SuperfluidPool ERC20 Functionality
+
+ function _helperSuperfluidPoolApprove(ISuperfluidPool _pool, address owner, address spender, uint256 amount)
+ internal
+ {
+ vm.startPrank(owner);
+ _pool.approve(spender, amount);
+ vm.stopPrank();
+
+ _assertPoolAllowance(_pool, owner, spender, amount);
+ }
+
+ function _helperSuperfluidPoolIncreaseAllowance(
+ ISuperfluidPool _pool,
+ address owner,
+ address spender,
+ uint256 addedValue
+ ) internal {
+ uint256 allowanceBefore = _pool.allowance(owner, spender);
+
+ vm.startPrank(owner);
+ _pool.increaseAllowance(spender, addedValue);
+ vm.stopPrank();
+
+ _assertPoolAllowance(_pool, owner, spender, allowanceBefore + addedValue);
+ }
+
+ function _helperSuperfluidPoolDecreaseAllowance(
+ ISuperfluidPool _pool,
+ address owner,
+ address spender,
+ uint256 subtractedValue
+ ) internal {
+ uint256 allowanceBefore = _pool.allowance(owner, spender);
+
+ vm.startPrank(owner);
+ _pool.decreaseAllowance(spender, subtractedValue);
+ vm.stopPrank();
+
+ _assertPoolAllowance(_pool, owner, spender, allowanceBefore - subtractedValue);
+ }
+
+ function _helperSuperfluidPoolUnitsTransfer(ISuperfluidPool _pool, address from, address to, uint256 amount)
+ internal
+ {
+ uint256 fromBalanceOfBefore = _pool.balanceOf(from);
+ uint256 toBalanceOfBefore = _pool.balanceOf(to);
+
+ vm.startPrank(from);
+ _pool.transfer(to, amount);
+ vm.stopPrank();
+
+ uint256 fromBalanceOfAfter = _pool.balanceOf(from);
+ uint256 toBalanceOfAfter = _pool.balanceOf(to);
+ assertEq(
+ fromBalanceOfBefore - amount,
+ fromBalanceOfAfter,
+ "_helperSuperfluidPoolUnitsTransfer: from balance mismatch"
+ );
+ assertEq(
+ toBalanceOfBefore + amount, toBalanceOfAfter, "_helperSuperfluidPoolUnitsTransfer: to balance mismatch"
+ );
+ }
+
+ function _helperSuperfluidPoolUnitsTransferFrom(
+ ISuperfluidPool _pool,
+ address caller,
+ address from,
+ address to,
+ uint256 amount
+ ) internal {
+ uint256 fromBalanceOfBefore = _pool.balanceOf(from);
+ uint256 toBalanceOfBefore = _pool.balanceOf(to);
+ uint256 allowanceBefore = _pool.allowance(from, caller);
+
+ vm.startPrank(caller);
+ _pool.transferFrom(from, to, amount);
+ vm.stopPrank();
+
+ uint256 fromBalanceOfAfter = _pool.balanceOf(from);
+ uint256 toBalanceOfAfter = _pool.balanceOf(to);
+ uint256 allowanceAfter = _pool.allowance(from, caller);
+ assertEq(
+ fromBalanceOfBefore - amount,
+ fromBalanceOfAfter,
+ "_helperSuperfluidPoolUnitsTransferFrom: from balance mismatch"
+ );
+ assertEq(
+ toBalanceOfBefore + amount, toBalanceOfAfter, "_helperSuperfluidPoolUnitsTransferFrom: to balance mismatch"
+ );
+ assertEq(allowanceBefore - amount, allowanceAfter, "_helperSuperfluidPoolUnitsTransferFrom: allowance mismatch");
+ }
+
+ function _helperGetMemberPoolState(ISuperfluidPool pool_, address member_)
+ internal
+ view
+ returns (bool isConnected, int256 units, int96 flowRate)
+ {
+ units = uint256(pool_.getUnits(member_)).toInt256();
+ isConnected = sf.gda.isMemberConnected(pool_, member_);
+ flowRate = pool_.getMemberFlowRate(member_);
+ }
+
+ function _helperGetPoolUnitsData(ISuperfluidPool pool_) internal view returns (PoolUnitData memory poolUnitData) {
+ poolUnitData = PoolUnitData({
+ totalUnits: pool_.getTotalUnits(),
+ connectedUnits: pool_.getTotalConnectedUnits(),
+ disconnectedUnits: pool_.getTotalDisconnectedUnits()
+ });
+ }
+
+ function _helperGetPoolFlowRatesData(ISuperfluidPool pool_)
+ internal
+ view
+ returns (PoolFlowRateData memory poolFlowRateData)
+ {
+ poolFlowRateData = PoolFlowRateData({
+ totalFlowRate: pool_.getTotalFlowRate(),
+ totalConnectedFlowRate: pool_.getTotalConnectedFlowRate(),
+ totalDisconnectedFlowRate: pool_.getTotalDisconnectedFlowRate()
+ });
+ }
+
/*//////////////////////////////////////////////////////////////////////////
Assertion Helpers
//////////////////////////////////////////////////////////////////////////*/
@@ -1482,7 +2174,6 @@ contract FoundrySuperfluidTester is Test {
) internal {
(uint256 lastUpdated, int96 netFlowRate, uint256 deposit, uint256 owedDeposit) =
sf.cfa.getAccountFlowInfo(superToken, account);
-
int96 expectedNetFlowRate = flowInfoBefore.flowRate + (isSender ? -flowRateDelta : flowRateDelta);
int256 depositDelta =
superToken.getBufferAmountByFlowRate(flowRateDelta < 0 ? -flowRateDelta : flowRateDelta).toInt256();
@@ -1497,6 +2188,8 @@ contract FoundrySuperfluidTester is Test {
assertEq(owedDeposit, 0, "AccountFlowInfo: owed deposit");
}
+ // InstantDistributionAgreement Assertions
+
/// @dev Asserts that the index data has been updated as expected
/// @param superToken_ The SuperToken to check
/// @param publisher The publisher of the index
@@ -1552,8 +2245,13 @@ contract FoundrySuperfluidTester is Test {
RealtimeBalance memory balanceSnapshot = _balanceSnapshots[superToken_][account];
(int256 avb, uint256 deposit, uint256 owedDeposit, uint256 currentTime) =
superToken_.realtimeBalanceOfNow(account);
+ int96 cfaNetFlowRate = superToken_.getCFANetFlowRate(account);
- int96 netFlowRate = superToken_.getNetFlowRate(account);
+ // GDA Net Flow Rate is 0 for pools because this is not accounted for in the pools' RTB
+ // however it is the disconnected flow rate for that pool
+ int96 gdaNetFlowRate =
+ sf.gda.isPool(superToken_, account) ? int96(0) : superToken_.getGDANetFlowRate(account);
+ int96 netFlowRate = cfaNetFlowRate + gdaNetFlowRate;
int256 amountFlowedSinceSnapshot = (currentTime - balanceSnapshot.timestamp).toInt256() * netFlowRate;
int256 expectedAvb = balanceSnapshot.availableBalance + amountFlowedSinceSnapshot;
@@ -1564,4 +2262,64 @@ contract FoundrySuperfluidTester is Test {
_helperTakeBalanceSnapshot(superToken_, account);
}
}
+
+ // GeneralDistributionAgreement Assertions
+
+ function _assertPoolAllowance(ISuperfluidPool _pool, address owner, address spender, uint256 expectedAllowance)
+ internal
+ {
+ assertEq(_pool.allowance(owner, spender), expectedAllowance, "_assertPoolAllowance: allowance mismatch");
+ }
+
+ function _assertPoolMemberNFT(
+ ISuperfluidToken _superToken,
+ ISuperfluidPool _pool,
+ address _member,
+ uint128 _newUnits
+ ) internal {
+ IPoolMemberNFT poolMemberNFT = SuperToken(address(_superToken)).POOL_MEMBER_NFT();
+ uint256 tokenId = poolMemberNFT.getTokenId(address(_pool), address(_member));
+ if (_newUnits > 0) {
+ // Assert Pool Member NFT owner
+ assertEq(poolMemberNFT.ownerOf(tokenId), _member, "_assertPoolMemberNFT: member doesn't own NFT");
+
+ // Assert Pool Member NFT data
+ IPoolMemberNFT.PoolMemberNFTData memory poolMemberData = poolMemberNFT.poolMemberDataByTokenId(tokenId);
+ assertEq(poolMemberData.pool, address(_pool), "_assertPoolMemberNFT: Pool Member NFT pool mismatch");
+ assertEq(poolMemberData.member, _member, "_assertPoolMemberNFT: Pool Member NFT member mismatch");
+ assertEq(poolMemberData.units, _newUnits, "_assertPoolMemberNFT: Pool Member NFT units mismatch");
+ } else {
+ vm.expectRevert(IPoolNFTBase.POOL_NFT_INVALID_TOKEN_ID.selector);
+ poolMemberNFT.ownerOf(tokenId);
+ }
+ }
+
+ function _assertFlowNftOnDistributeFlow(
+ ISuperfluidToken _superToken,
+ ISuperfluidPool _pool,
+ address _distributor,
+ int96 _newFlowRate
+ ) internal {
+ IConstantOutflowNFT constantOutflowNFT = SuperToken(address(_superToken)).CONSTANT_OUTFLOW_NFT();
+ IConstantInflowNFT constantInflowNFT = SuperToken(address(_superToken)).CONSTANT_INFLOW_NFT();
+ uint256 tokenId = constantOutflowNFT.getTokenId(address(_superToken), address(_distributor), address(_pool));
+ if (_newFlowRate > 0) {
+ assertEq(
+ constantOutflowNFT.ownerOf(tokenId),
+ _distributor,
+ "_assertFlowNftOnDistributeFlow: distributor doesn't own outflow NFT"
+ );
+ assertEq(
+ constantInflowNFT.ownerOf(tokenId),
+ address(_pool),
+ "_assertFlowNftOnDistributeFlow: distributor doesn't own inflow NFT"
+ );
+ } else {
+ vm.expectRevert(IFlowNFTBase.CFA_NFT_INVALID_TOKEN_ID.selector);
+ constantOutflowNFT.ownerOf(tokenId);
+
+ vm.expectRevert(IFlowNFTBase.CFA_NFT_INVALID_TOKEN_ID.selector);
+ constantInflowNFT.ownerOf(tokenId);
+ }
+ }
}
diff --git a/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol b/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol
index 4df2f141d7..08123e9936 100644
--- a/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol
+++ b/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol
@@ -13,6 +13,7 @@ contract SuperfluidFrameworkDeployerTest is FoundrySuperfluidTester {
assertTrue(address(sf.host) != address(0), "SFDeployer: host not deployed");
assertTrue(address(sf.cfa) != address(0), "SFDeployer: cfa not deployed");
assertTrue(address(sf.ida) != address(0), "SFDeployer: ida not deployed");
+ assertTrue(address(sf.gda) != address(0), "SFDeployer: gda not deployed");
assertTrue(address(sf.superTokenFactory) != address(0), "SFDeployer: superTokenFactory not deployed");
assertTrue(address(sf.superTokenLogic) != address(0), "SFDeployer: superTokenLogic not deployed");
assertTrue(address(sf.constantOutflowNFT) != address(0), "SFDeployer: constantOutflowNFT not deployed");
@@ -20,6 +21,9 @@ contract SuperfluidFrameworkDeployerTest is FoundrySuperfluidTester {
assertTrue(address(sf.resolver) != address(0), "SFDeployer: resolver not deployed");
assertTrue(address(sf.superfluidLoader) != address(0), "SFDeployer: superfluidLoader not deployed");
assertTrue(address(sf.cfaV1Forwarder) != address(0), "SFDeployer: cfaV1Forwarder not deployed");
+ assertTrue(address(sf.idaV1Forwarder) != address(0), "SFDeployer: idaV1Forwarder not deployed");
+ assertTrue(address(sf.gdaV1Forwarder) != address(0), "SFDeployer: gdaV1Forwarder not deployed");
+ assertTrue(address(sf.batchLiquidator) != address(0), "SFDeployer: batchLiquidator not deployed");
}
function testResolverGetsGovernance() public {
@@ -63,7 +67,7 @@ contract SuperfluidFrameworkDeployerTest is FoundrySuperfluidTester {
uint256 _mintLimit
) public {
(TestToken underlyingToken, SuperToken _superToken) =
- sfDeployer.deployWrapperSuperToken(_name, _symbol, _decimals, _mintLimit);
+ sfDeployer.deployWrapperSuperToken(_name, _symbol, _decimals, _mintLimit, address(0));
// assert underlying erc20 name/symbol properly set
assertEq(underlyingToken.name(), _name, "SFDeployer: Underlying token name not properly set");
diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol
new file mode 100644
index 0000000000..9d9b80e727
--- /dev/null
+++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol
@@ -0,0 +1,933 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
+import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
+import { IBeacon } from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
+import "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol";
+import "../../FoundrySuperfluidTester.sol";
+import {
+ GeneralDistributionAgreementV1,
+ IGeneralDistributionAgreementV1
+} from "../../../../contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol";
+import { SuperTokenV1Library } from "../../../../contracts/apps/SuperTokenV1Library.sol";
+import { ISuperToken, SuperToken } from "../../../../contracts/superfluid/SuperToken.sol";
+import { ISuperfluidToken } from "../../../../contracts/interfaces/superfluid/ISuperfluidToken.sol";
+import { ISuperfluidPool, SuperfluidPool } from "../../../../contracts/agreements/gdav1/SuperfluidPool.sol";
+import { SuperfluidPoolStorageLayoutMock } from "../../../../contracts/mocks/SuperfluidPoolUpgradabilityMock.sol";
+import { IPoolNFTBase } from "../../../../contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol";
+import { IPoolAdminNFT } from "../../../../contracts/interfaces/agreements/gdav1/IPoolAdminNFT.sol";
+import { IPoolMemberNFT } from "../../../../contracts/interfaces/agreements/gdav1/IPoolMemberNFT.sol";
+import { IFlowNFTBase } from "../../../../contracts/interfaces/superfluid/IFlowNFTBase.sol";
+import { IConstantOutflowNFT } from "../../../../contracts/interfaces/superfluid/IConstantOutflowNFT.sol";
+import { IConstantInflowNFT } from "../../../../contracts/interfaces/superfluid/IConstantInflowNFT.sol";
+
+/// @title GeneralDistributionAgreementV1 Integration Tests
+/// @author Superfluid
+/// @notice This is a contract that runs integrations tests for the GDAv1
+/// It tests interactions between contracts and more complicated interactions
+/// with a range of values when applicable and it aims to ensure that the
+/// these interactions work as expected.
+contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTester {
+ using SuperTokenV1Library for ISuperToken;
+ using EnumerableSet for EnumerableSet.AddressSet;
+ using SafeCast for uint256;
+ using SafeCast for int256;
+
+ struct UpdateMemberData {
+ address member;
+ uint64 newUnits;
+ }
+
+ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
+
+ /// @dev The freePool uses `poolConfig` where both transfer and distributeFromAnyAddress is true
+ SuperfluidPool public freePool;
+ uint256 public liquidationPeriod;
+
+ constructor() FoundrySuperfluidTester(7) { }
+
+ function setUp() public override {
+ super.setUp();
+ vm.startPrank(alice);
+ freePool = SuperfluidPool(address(superToken.createPool(alice, poolConfig)));
+ _addAccount(address(freePool));
+ vm.stopPrank();
+ (liquidationPeriod,) = sf.governance.getPPPConfig(sf.host, superToken);
+ }
+
+ function _getMembers(uint8 length) internal view returns (address[] memory) {
+ if (length > TEST_ACCOUNTS.length - 2) revert("Too many members");
+ address[] memory members = new address[](length);
+ for (uint8 i = 0; i < length; ++i) {
+ // do not use Admin and Alice
+ members[i] = TEST_ACCOUNTS[i + 2];
+ }
+ return members;
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ GDA Integration Tests
+ //////////////////////////////////////////////////////////////////////////*/
+
+ function testInitializeGDA(IBeacon beacon) public {
+ GeneralDistributionAgreementV1 gdaV1 = new GeneralDistributionAgreementV1(sf.host);
+ assertEq(address(gdaV1.superfluidPoolBeacon()), address(0), "GDAv1.t: Beacon address not address(0)");
+ gdaV1.initialize(beacon);
+
+ assertEq(address(gdaV1.superfluidPoolBeacon()), address(beacon), "GDAv1.t: Beacon address not equal");
+ }
+
+ function testRevertReinitializeGDA(IBeacon beacon) public {
+ vm.expectRevert("Initializable: contract is already initialized");
+ sf.gda.initialize(beacon);
+ }
+
+ function testRevertAppendIndexUpdateByPoolByNonPool(BasicParticle memory p, Time t) public {
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_ONLY_SUPER_TOKEN_POOL.selector);
+ sf.gda.appendIndexUpdateByPool(superToken, p, t);
+ }
+
+ function testRevertPoolSettleClaimByNonPool(address claimRecipient, int256 amount) public {
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_ONLY_SUPER_TOKEN_POOL.selector);
+ sf.gda.poolSettleClaim(superToken, claimRecipient, amount);
+ }
+
+ function testProxiableUUIDIsExpectedValue(PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ assertEq(
+ SuperfluidPool(address(pool)).proxiableUUID(),
+ keccak256("org.superfluid-finance.contracts.SuperfluidPool.implementation")
+ );
+ }
+
+ function testPositiveBalanceIsPatricianPeriodNow(address account) public {
+ (bool isPatricianPeriod,) = sf.gda.isPatricianPeriodNow(superToken, account);
+ assertEq(isPatricianPeriod, true);
+ }
+
+ function testNegativeBalanceIsPatricianPeriodNowIsTrue(PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ uint256 balance = superToken.balanceOf(alice);
+ int96 flowRate = balance.toInt256().toInt96() / type(int32).max;
+ int96 requestedDistributionFlowRate = int96(flowRate);
+
+ _helperConnectPool(bob, superToken, pool);
+ _helperUpdateMemberUnits(pool, alice, bob, 1);
+
+ _helperDistributeFlow(superToken, alice, alice, pool, requestedDistributionFlowRate);
+
+ _helperWarpToCritical(superToken, alice, 1);
+
+ (bool isPatricianPeriod,) = sf.gda.isPatricianPeriodNow(superToken, alice);
+ assertEq(isPatricianPeriod, true);
+ }
+
+ function testNegativeBalanceIsPatricianPeriodNowIsFalse(PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ uint256 balance = superToken.balanceOf(alice);
+ int96 flowRate = balance.toInt256().toInt96() / type(int32).max;
+ int96 requestedDistributionFlowRate = int96(flowRate);
+
+ _helperConnectPool(bob, superToken, pool);
+ _helperUpdateMemberUnits(pool, alice, bob, 1);
+
+ (int96 actualDistributionFlowRate,) =
+ sf.gda.estimateFlowDistributionActualFlowRate(superToken, alice, pool, requestedDistributionFlowRate);
+
+ _helperDistributeFlow(superToken, alice, alice, pool, requestedDistributionFlowRate);
+
+ if (actualDistributionFlowRate > 0) {
+ _helperWarpToInsolvency(superToken, alice, liquidationPeriod, 1);
+ }
+
+ (bool isPatricianPeriod,) = sf.gda.isPatricianPeriodNow(superToken, alice);
+ assertEq(isPatricianPeriod, false);
+ }
+
+ function testNegativeBalanceIsPatricianPeriodNowIsFalseWithZeroDeposit(PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ uint256 aliceBalance = superToken.balanceOf(alice);
+ int96 flowRate = aliceBalance.toInt256().toInt96() / type(int32).max;
+ int96 requestedDistributionFlowRate = int96(flowRate);
+
+ vm.startPrank(sf.governance.owner());
+ sf.governance.setRewardAddress(sf.host, ISuperfluidToken(address(0)), alice);
+ vm.stopPrank();
+
+ _helperConnectPool(bob, superToken, pool);
+ _helperUpdateMemberUnits(pool, alice, bob, 1);
+
+ (int256 aliceRTB, uint256 deposit,,) = superToken.realtimeBalanceOfNow(alice);
+
+ _helperDistributeFlow(superToken, alice, alice, pool, requestedDistributionFlowRate);
+ int96 fr = sf.gda.getFlowRate(superToken, alice, pool);
+
+ vm.warp(block.timestamp + (INIT_SUPER_TOKEN_BALANCE / uint256(uint96(fr))) + 1);
+
+ (aliceRTB, deposit,,) = superToken.realtimeBalanceOfNow(alice);
+
+ _helperDistributeFlow(superToken, bob, alice, pool, 0);
+
+ (bool isPatricianPeriod,) = sf.gda.isPatricianPeriodNow(superToken, alice);
+ assertEq(isPatricianPeriod, false, "false patrician period");
+ }
+
+ function testCreatePool(bool useForwarder, PoolConfig memory config) public {
+ _helperCreatePool(superToken, alice, alice, useForwarder, config);
+ }
+
+ function testRevertConnectPoolByNonHost(address notHost, PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ vm.assume(notHost != address(sf.host));
+ vm.startPrank(notHost);
+ vm.expectRevert("unauthorized host");
+ sf.gda.connectPool(pool, "0x");
+ vm.stopPrank();
+ }
+
+ function testRevertNonHostDisconnectPool(address notHost, PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ vm.assume(notHost != address(sf.host));
+ vm.startPrank(notHost);
+ vm.expectRevert("unauthorized host");
+ sf.gda.disconnectPool(pool, "0x");
+ vm.stopPrank();
+ }
+
+ function testConnectPool(address caller, bool useForwarder, PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, caller, alice, useForwarder, config);
+ _helperConnectPool(caller, superToken, pool, useForwarder);
+ }
+
+ function testDisconnectPool(address caller, bool useForwarder, PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, caller, alice, useForwarder, config);
+ _helperConnectPool(caller, superToken, pool, useForwarder);
+ _helperDisconnectPool(caller, superToken, pool, useForwarder);
+ }
+
+ function testRevertDistributeFlowToNonPool(int96 requestedFlowRate) public {
+ vm.assume(requestedFlowRate >= 0);
+ vm.assume(requestedFlowRate < int96(type(int64).max));
+ vm.startPrank(alice);
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_ONLY_SUPER_TOKEN_POOL.selector);
+ superToken.distributeFlow(alice, ISuperfluidPool(bob), requestedFlowRate);
+ vm.stopPrank();
+ }
+
+ function testRevertDistributeFromAnyAddressWhenNotAllowed(bool useForwarder) public {
+ PoolConfig memory config = PoolConfig({ transferabilityForUnitsOwner: true, distributionFromAnyAddress: false });
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, useForwarder, config);
+
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_DISTRIBUTE_FROM_ANY_ADDRESS_NOT_ALLOWED.selector);
+ vm.startPrank(bob);
+ superToken.distributeToPool(bob, pool, 1);
+ vm.stopPrank();
+ }
+
+ function testRevertDistributeFlowFromAnyAddressWhenNotAllowed(bool useForwarder) public {
+ PoolConfig memory config = PoolConfig({ transferabilityForUnitsOwner: true, distributionFromAnyAddress: false });
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, useForwarder, config);
+
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_DISTRIBUTE_FROM_ANY_ADDRESS_NOT_ALLOWED.selector);
+ vm.startPrank(bob);
+ superToken.distributeFlow(bob, pool, 1);
+ vm.stopPrank();
+ }
+
+ function testRevertIfNotAdminUpdatesMemberUnitsViaGDA(bool useForwarder, PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, useForwarder, config);
+ vm.startPrank(bob);
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_NOT_POOL_ADMIN.selector);
+ superToken.updateMemberUnits(pool, bob, 69);
+ vm.stopPrank();
+ }
+
+ function testRevertIfNotAdminOrGDAUpdatesMemberUnitsViaPool(address caller) public {
+ vm.assume(caller != alice);
+ vm.startPrank(caller);
+ vm.expectRevert(ISuperfluidPool.SUPERFLUID_POOL_NOT_POOL_ADMIN_OR_GDA.selector);
+ freePool.updateMemberUnits(caller, 69);
+ vm.stopPrank();
+ }
+
+ function testRevertDistributeFlowWithNegativeFlowRate(int96 requestedFlowRate, PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ vm.assume(requestedFlowRate < 0);
+
+ vm.startPrank(alice);
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_NO_NEGATIVE_FLOW_RATE.selector);
+ superToken.distributeFlow(alice, pool, requestedFlowRate);
+ vm.stopPrank();
+ }
+
+ function testRevertDistributeToNonPool(uint256 requestedAmount) public {
+ vm.assume(requestedAmount < uint256(type(uint128).max));
+
+ vm.startPrank(alice);
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_ONLY_SUPER_TOKEN_POOL.selector);
+ superToken.distributeToPool(alice, ISuperfluidPool(bob), requestedAmount);
+ vm.stopPrank();
+ }
+
+ function testRevertDistributeForOthers(address signer, uint256 requestedAmount) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, poolConfig);
+ vm.assume(requestedAmount < uint256(type(uint128).max));
+ vm.assume(signer != alice);
+
+ vm.startPrank(signer);
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_DISTRIBUTE_FOR_OTHERS_NOT_ALLOWED.selector);
+ sf.host.callAgreement(
+ sf.gda,
+ abi.encodeCall(sf.gda.distribute, (superToken, alice, pool, requestedAmount, new bytes(0))),
+ new bytes(0)
+ );
+ vm.stopPrank();
+ }
+
+ function testRevertDistributeFlowForOthers(address signer, int32 requestedFlowRate) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, poolConfig);
+ vm.assume(requestedFlowRate > 0);
+ vm.assume(signer != alice);
+
+ vm.startPrank(signer);
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_DISTRIBUTE_FOR_OTHERS_NOT_ALLOWED.selector);
+ sf.host.callAgreement(
+ sf.gda,
+ abi.encodeCall(sf.gda.distributeFlow, (superToken, alice, pool, requestedFlowRate, new bytes(0))),
+ new bytes(0)
+ );
+ vm.stopPrank();
+ }
+
+ function testRevertDistributeFlowInsufficientBalance(PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ uint256 balance = superToken.balanceOf(alice);
+ balance /= 4 hours;
+ int96 tooBigFlowRate = int96(int256(balance)) + 1;
+
+ _helperConnectPool(bob, superToken, pool);
+
+ _helperUpdateMemberUnits(pool, alice, bob, 1);
+ vm.startPrank(alice);
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_INSUFFICIENT_BALANCE.selector);
+ sf.host.callAgreement(
+ sf.gda,
+ abi.encodeCall(sf.gda.distributeFlow, (superToken, alice, pool, tooBigFlowRate, new bytes(0))),
+ new bytes(0)
+ );
+ vm.stopPrank();
+ }
+
+ function testRevertLiquidateNonCriticalDistributor(int32 flowRate, int96 units, PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ vm.assume(flowRate > 0);
+ _helperConnectPool(bob, superToken, pool);
+
+ _helperUpdateMemberUnits(pool, alice, bob, uint96(units));
+
+ _helperDistributeFlow(superToken, alice, alice, pool, flowRate);
+
+ vm.startPrank(bob);
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_NON_CRITICAL_SENDER.selector);
+ superToken.distributeFlow(alice, pool, 0);
+ vm.stopPrank();
+ }
+
+ function testRevertDistributeInsufficientBalance(PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ uint256 balance = superToken.balanceOf(alice);
+
+ _helperConnectPool(bob, superToken, pool);
+
+ vm.startPrank(alice);
+ sf.gdaV1Forwarder.updateMemberUnits(pool, bob, 1, new bytes(0));
+ vm.stopPrank();
+
+ vm.startPrank(alice);
+ vm.expectRevert(IGeneralDistributionAgreementV1.GDA_INSUFFICIENT_BALANCE.selector);
+ sf.host.callAgreement(
+ sf.gda,
+ abi.encodeCall(sf.gda.distribute, (superToken, alice, pool, balance + 1, new bytes(0))),
+ new bytes(0)
+ );
+ vm.stopPrank();
+ }
+
+ function testRevertPoolOperatorConnectMember(
+ address notOperator,
+ address member,
+ bool doConnect,
+ uint32 time,
+ PoolConfig memory config
+ ) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ vm.assume(notOperator != address(sf.gda));
+ vm.startPrank(notOperator);
+ vm.expectRevert(ISuperfluidPool.SUPERFLUID_POOL_NOT_GDA.selector);
+ SuperfluidPool(address(pool)).operatorConnectMember(member, doConnect, time);
+ vm.stopPrank();
+ }
+
+ function testRevertPoolUpdateMemberThatIsPool(uint128 units, PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ vm.assume(units < uint128(type(int128).max));
+
+ vm.expectRevert(ISuperfluidPool.SUPERFLUID_POOL_NO_POOL_MEMBERS.selector);
+ vm.startPrank(alice);
+ pool.updateMemberUnits(address(pool), units);
+ vm.stopPrank();
+ }
+
+ function testSuperfluidPoolStorageLayout() public {
+ SuperfluidPoolStorageLayoutMock mock = new SuperfluidPoolStorageLayoutMock(sf.gda);
+ mock.validateStorageLayout();
+ }
+
+ function testDistributeFlowUsesMinDeposit(
+ uint64 distributionFlowRate,
+ uint32 minDepositMultiplier,
+ address member,
+ FoundrySuperfluidTester._StackVars_UseBools memory useBools_,
+ PoolConfig memory config
+ ) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ vm.assume(distributionFlowRate < minDepositMultiplier);
+ vm.assume(distributionFlowRate > 0);
+ vm.assume(member != address(pool));
+ vm.assume(member != address(0));
+
+ _addAccount(member);
+
+ vm.startPrank(address(sf.governance.owner()));
+ uint256 minimumDeposit = 4 hours * uint256(minDepositMultiplier);
+ sf.governance.setSuperTokenMinimumDeposit(sf.host, superToken, minimumDeposit);
+ vm.stopPrank();
+
+ _helperConnectPool(member, superToken, pool);
+ _helperUpdateMemberUnits(pool, alice, member, 1, useBools_);
+ _helperDistributeFlow(superToken, alice, alice, pool, int96(int64(distributionFlowRate)));
+ (, uint256 buffer,,) = superToken.realtimeBalanceOfNow(alice);
+ assertEq(buffer, minimumDeposit, "GDAv1.t: Min buffer should be used");
+ }
+
+ function testDistributeFlowIgnoresMinDeposit(
+ int32 distributionFlowRate,
+ uint32 minDepositMultiplier,
+ address member,
+ FoundrySuperfluidTester._StackVars_UseBools memory useBools_,
+ PoolConfig memory config
+ ) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+ vm.assume(uint32(distributionFlowRate) >= minDepositMultiplier);
+ vm.assume(distributionFlowRate > 0);
+ vm.assume(member != address(0));
+ vm.assume(member != address(freePool));
+
+ _addAccount(member);
+
+ vm.startPrank(address(sf.governance.owner()));
+
+ uint256 minimumDeposit = 4 hours * uint256(minDepositMultiplier);
+ sf.governance.setSuperTokenMinimumDeposit(sf.host, superToken, minimumDeposit);
+ vm.stopPrank();
+
+ _helperConnectPool(member, superToken, pool);
+ _helperUpdateMemberUnits(pool, alice, member, 1, useBools_);
+ _helperDistributeFlow(superToken, alice, alice, pool, int96(distributionFlowRate));
+ (, uint256 buffer,,) = superToken.realtimeBalanceOfNow(alice);
+ assertTrue(buffer >= minimumDeposit, "GDAv1.t: Buffer should be >= minDeposit");
+ }
+
+ function testDistributeFlowToConnectedMemberSendingToCFA(
+ int32 flowRate,
+ uint64 units,
+ FoundrySuperfluidTester._StackVars_UseBools memory useBools_,
+ PoolConfig memory config
+ ) public {
+ vm.assume(flowRate > 0);
+
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+
+ int96 requestedDistributionFlowRate = int96(flowRate);
+
+ uint128 memberUnits = uint128(units);
+
+ _helperUpdateMemberUnits(pool, alice, bob, memberUnits, useBools_);
+
+ _helperDistributeFlow(superToken, alice, alice, pool, requestedDistributionFlowRate);
+
+ _helperConnectPool(bob, superToken, pool);
+ // bob sends a flow of 1 to alice
+ vm.startPrank(bob);
+ superToken.createFlow(alice, requestedDistributionFlowRate * 10);
+ vm.stopPrank();
+
+ int96 aliceGDANetFlowRate = sf.gda.getNetFlow(superToken, alice);
+ int96 bobGDANetFlowRate = sf.gda.getNetFlow(superToken, bob);
+ int96 aliceCFANetFlowRate = sf.cfa.getNetFlow(superToken, alice);
+ int96 bobCFANetFlowRate = sf.cfa.getNetFlow(superToken, bob);
+ assertEq(
+ aliceGDANetFlowRate + bobGDANetFlowRate + aliceCFANetFlowRate + bobCFANetFlowRate,
+ 0,
+ "alice and bob GDA net flow rates !="
+ );
+ }
+
+ function testDistributeToEmptyPool(uint64 distributionAmount, bool useForwarder, PoolConfig memory config) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, useForwarder, config);
+ _helperDistributeViaGDA(superToken, alice, alice, pool, distributionAmount, useForwarder);
+ }
+
+ function testDistributeFlowToEmptyPool(int32 flowRate, bool useForwarder, PoolConfig memory config) public {
+ vm.assume(flowRate >= 0);
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, useForwarder, config);
+ _helperDistributeFlow(superToken, alice, alice, pool, flowRate, useForwarder);
+ assertEq(sf.gda.getFlowRate(superToken, alice, pool), 0, "GDAv1.t: distributionFlowRate should be 0");
+ }
+
+ function testDistributeFlowCriticalLiquidation(
+ uint64 units,
+ _StackVars_UseBools memory useBools_,
+ PoolConfig memory config
+ ) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+
+ uint256 balance = superToken.balanceOf(alice);
+ int96 flowRate = balance.toInt256().toInt96() / type(int32).max;
+ int96 requestedDistributionFlowRate = int96(flowRate);
+
+ uint128 memberUnits = uint128(units);
+
+ _helperConnectPool(bob, superToken, pool);
+ _helperUpdateMemberUnits(pool, alice, bob, memberUnits, useBools_);
+
+ (int96 actualDistributionFlowRate,) =
+ sf.gda.estimateFlowDistributionActualFlowRate(superToken, alice, pool, requestedDistributionFlowRate);
+
+ _helperDistributeFlow(superToken, alice, alice, pool, requestedDistributionFlowRate);
+
+ if (actualDistributionFlowRate > 0) {
+ _helperWarpToCritical(superToken, alice, 1);
+ _helperDistributeFlow(superToken, bob, alice, pool, 0);
+ }
+ }
+
+ function testDistributeFlowInsolventLiquidation(
+ uint64 units,
+ _StackVars_UseBools memory useBools_,
+ PoolConfig memory config
+ ) public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+
+ uint256 balance = superToken.balanceOf(alice);
+ int96 flowRate = balance.toInt256().toInt96() / type(int32).max;
+ int96 requestedDistributionFlowRate = int96(flowRate);
+
+ uint128 memberUnits = uint128(units);
+
+ _helperConnectPool(bob, superToken, pool);
+ _helperUpdateMemberUnits(pool, alice, bob, memberUnits, useBools_);
+ _helperDistributeFlow(superToken, alice, alice, pool, requestedDistributionFlowRate);
+
+ (int96 actualDistributionFlowRate,) =
+ sf.gda.estimateFlowDistributionActualFlowRate(superToken, alice, pool, requestedDistributionFlowRate);
+
+ _helperDistributeFlow(superToken, alice, alice, pool, requestedDistributionFlowRate);
+
+ if (actualDistributionFlowRate > 0) {
+ _helperWarpToInsolvency(superToken, alice, liquidationPeriod, 1);
+ _helperDistributeFlow(superToken, bob, alice, pool, 0);
+ }
+ }
+
+ function testDistributeToDisconnectedMembers(
+ uint64[5] memory memberUnits,
+ uint256 distributionAmount,
+ _StackVars_UseBools memory useBools_,
+ PoolConfig memory config
+ ) public {
+ address distributor = alice;
+ uint256 distributorBalance = superToken.balanceOf(distributor);
+
+ vm.assume(distributionAmount < distributorBalance);
+
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+
+ address[] memory members = _getMembers(5);
+
+ for (uint256 i = 0; i < members.length; ++i) {
+ if (sf.gda.isPool(superToken, members[i]) || members[i] == address(0)) continue;
+
+ _helperUpdateMemberUnits(pool, alice, members[i], memberUnits[i], useBools_);
+ }
+ _helperDistributeViaGDA(superToken, alice, alice, pool, distributionAmount, useBools_.useForwarder);
+ }
+
+ function testDistributeToConnectedMembers(
+ uint64[5] memory memberUnits,
+ uint256 distributionAmount,
+ _StackVars_UseBools memory useBools_,
+ PoolConfig memory config
+ ) public {
+ address distributor = alice;
+ uint256 distributorBalance = superToken.balanceOf(distributor);
+
+ vm.assume(distributionAmount < distributorBalance);
+
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+
+ address[] memory members = _getMembers(5);
+
+ for (uint256 i = 0; i < members.length; ++i) {
+ if (sf.gda.isPool(superToken, members[i]) || members[i] == address(0)) continue;
+
+ _helperConnectPool(members[i], superToken, pool, useBools_.useForwarder);
+ _helperUpdateMemberUnits(pool, alice, members[i], memberUnits[i], useBools_);
+ _addAccount(members[i]);
+ }
+ _helperDistributeViaGDA(superToken, alice, alice, pool, distributionAmount, useBools_.useForwarder);
+ }
+
+ function testDistributeFlowToConnectedMembers(
+ uint64[5] memory memberUnits,
+ int32 flowRate,
+ _StackVars_UseBools memory useBools_,
+ PoolConfig memory config
+ ) public {
+ vm.assume(flowRate > 0);
+
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+
+ address[] memory members = _getMembers(5);
+
+ for (uint256 i = 0; i < members.length; ++i) {
+ if (sf.gda.isPool(superToken, members[i]) || members[i] == address(0)) continue;
+
+ _helperConnectPool(members[i], superToken, pool, useBools_.useForwarder);
+ _helperUpdateMemberUnits(pool, alice, members[i], memberUnits[i], useBools_);
+ _addAccount(members[i]);
+ }
+
+ _helperDistributeFlow(superToken, alice, alice, pool, 100, useBools_.useForwarder);
+ int96 poolAdjustmentFlowRate = useBools_.useForwarder
+ ? sf.gdaV1Forwarder.getPoolAdjustmentFlowRate(address(pool))
+ : sf.gda.getPoolAdjustmentFlowRate(address(pool));
+ assertEq(poolAdjustmentFlowRate, 0, "GDAv1.t: Pool adjustment rate is non-zero");
+ }
+
+ function testDistributeFlowToUnconnectedMembers(
+ uint64[5] memory memberUnits,
+ int32 flowRate,
+ uint16 warpTime,
+ _StackVars_UseBools memory useBools_,
+ PoolConfig memory config
+ ) public {
+ vm.assume(flowRate > 0);
+
+ ISuperfluidPool pool;
+ {
+ pool = _helperCreatePool(superToken, alice, alice, false, config);
+ }
+
+ address[] memory members = _getMembers(5);
+
+ for (uint256 i = 0; i < members.length; ++i) {
+ if (sf.gda.isPool(superToken, members[i]) || members[i] == address(0)) continue;
+ _helperUpdateMemberUnits(pool, alice, members[i], memberUnits[i], useBools_);
+ }
+
+ int96 actualDistributionFlowRate;
+ {
+ int96 requestedFlowRate = flowRate;
+ _helperDistributeFlow(superToken, alice, alice, pool, requestedFlowRate, useBools_.useForwarder);
+ (actualDistributionFlowRate,) =
+ sf.gda.estimateFlowDistributionActualFlowRate(superToken, alice, pool, requestedFlowRate);
+
+ vm.warp(block.timestamp + warpTime);
+ }
+ uint128 totalUnits = pool.getTotalUnits();
+
+ for (uint256 i; i < members.length; ++i) {
+ if (members[i] != address(0)) {
+ // @note we test realtimeBalanceOfNow here as well
+ (int256 memberRTB,,) = sf.gda.realtimeBalanceOf(superToken, members[i], block.timestamp);
+ (int256 rtbNow,,,) = sf.gda.realtimeBalanceOfNow(superToken, members[i]);
+ assertEq(memberRTB, rtbNow, "testDistributeFlowToUnconnectedMembers: rtb != rtbNow");
+
+ assertEq(
+ pool.getTotalDisconnectedFlowRate(),
+ actualDistributionFlowRate,
+ "testDistributeFlowToUnconnectedMembers: pendingDistributionFlowRate != actualDistributionFlowRate"
+ );
+ (int256 memberClaimable,) = pool.getClaimableNow(members[i]);
+
+ assertEq(
+ memberClaimable,
+ totalUnits > 0
+ ? (actualDistributionFlowRate * int96(int256(uint256(warpTime)))) * int96(uint96(memberUnits[i]))
+ / uint256(totalUnits).toInt256()
+ : int256(0),
+ "testDistributeFlowToUnconnectedMembers: memberClaimable != (actualDistributionFlowRate * warpTime) / totalUnits"
+ );
+ assertEq(memberRTB, 0, "testDistributeFlowToUnconnectedMembers: memberRTB != 0");
+
+ vm.startPrank(members[i]);
+ if (useBools_.useGDA) {
+ if (useBools_.useForwarder) {
+ sf.gdaV1Forwarder.claimAll(pool, members[i], new bytes(0));
+ } else {
+ superToken.claimAll(pool, members[i]);
+ }
+ } else {
+ pool.claimAll();
+ }
+ vm.stopPrank();
+
+ (memberRTB,,) = sf.gda.realtimeBalanceOf(superToken, members[i], block.timestamp);
+ assertEq(
+ memberRTB, memberClaimable, "testDistributeFlowToUnconnectedMembers: memberRTB != memberClaimable"
+ );
+ }
+ }
+ }
+
+ // Pool ERC20 functions
+
+ function testApproveOnly(address owner, address spender, uint256 amount, PoolConfig memory config) public {
+ vm.assume(owner != address(0));
+ vm.assume(spender != address(0));
+
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+
+ _helperSuperfluidPoolApprove(pool, owner, spender, amount);
+ }
+
+ function testIncreaseAllowance(address owner, address spender, uint256 addedValue, PoolConfig memory config)
+ public
+ {
+ vm.assume(owner != address(0));
+ vm.assume(spender != address(0));
+
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+
+ _helperSuperfluidPoolIncreaseAllowance(pool, owner, spender, addedValue);
+ }
+
+ function testDecreaseAllowance(
+ address owner,
+ address spender,
+ uint256 addedValue,
+ uint256 subtractedValue,
+ PoolConfig memory config
+ ) public {
+ vm.assume(owner != address(0));
+ vm.assume(spender != address(0));
+ vm.assume(addedValue >= subtractedValue);
+
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
+
+ _helperSuperfluidPoolIncreaseAllowance(pool, owner, spender, addedValue);
+ _helperSuperfluidPoolDecreaseAllowance(pool, owner, spender, subtractedValue);
+ }
+
+ function testRevertIfUnitsTransferReceiverIsPool(address from, address to, int96 unitsAmount, int128 transferAmount)
+ public
+ {
+ // @note we use int96 because overflow will happen otherwise
+ vm.assume(unitsAmount >= 0);
+ vm.assume(transferAmount > 0);
+ vm.assume(from != address(0));
+ vm.assume(to != address(0));
+ vm.assume(from != to);
+ vm.assume(transferAmount <= unitsAmount);
+ _helperUpdateMemberUnits(freePool, alice, from, uint128(int128(unitsAmount)));
+
+ vm.startPrank(from);
+ vm.expectRevert(ISuperfluidPool.SUPERFLUID_POOL_NO_POOL_MEMBERS.selector);
+ freePool.transfer(address(freePool), uint256(uint128(transferAmount)));
+ vm.stopPrank();
+ }
+
+ function testRevertIfTransferNotAllowed(bool useForwarder) public {
+ PoolConfig memory config = PoolConfig({ transferabilityForUnitsOwner: false, distributionFromAnyAddress: true });
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, useForwarder, config);
+
+ _helperUpdateMemberUnits(pool, alice, bob, 1000);
+
+ vm.startPrank(bob);
+ vm.expectRevert(ISuperfluidPool.SUPERFLUID_POOL_TRANSFER_UNITS_NOT_ALLOWED.selector);
+ pool.transfer(alice, 1000);
+ vm.stopPrank();
+ }
+
+ function testRevertIfTransferFromNotAllowed(bool useForwarder) public {
+ PoolConfig memory config = PoolConfig({ transferabilityForUnitsOwner: false, distributionFromAnyAddress: true });
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, useForwarder, config);
+
+ _helperUpdateMemberUnits(freePool, alice, bob, 1000);
+
+ vm.startPrank(bob);
+ pool.approve(carol, 1000);
+ vm.stopPrank();
+
+ vm.startPrank(carol);
+ vm.expectRevert(ISuperfluidPool.SUPERFLUID_POOL_TRANSFER_UNITS_NOT_ALLOWED.selector);
+ pool.transferFrom(bob, carol, 1000);
+ vm.stopPrank();
+ }
+
+ function testBasicTransfer(
+ address from,
+ address to,
+ int96 unitsAmount,
+ int128 transferAmount,
+ FoundrySuperfluidTester._StackVars_UseBools memory useBools_
+ ) public {
+ // @note we use int96 because overflow will happen otherwise
+ vm.assume(unitsAmount >= 0);
+ vm.assume(transferAmount > 0);
+ vm.assume(from != address(0));
+ vm.assume(to != address(0));
+ vm.assume(from != to);
+ vm.assume(transferAmount <= unitsAmount);
+ _helperUpdateMemberUnits(freePool, alice, from, uint128(int128(unitsAmount)), useBools_);
+
+ _helperSuperfluidPoolUnitsTransfer(freePool, from, to, uint256(uint128(transferAmount)));
+ }
+
+ function testApproveAndTransferFrom(
+ address owner,
+ address spender,
+ int128 transferAmount,
+ FoundrySuperfluidTester._StackVars_UseBools memory useBools_
+ ) public {
+ vm.assume(transferAmount > 0);
+ vm.assume(spender != address(0));
+ vm.assume(owner != address(0));
+ vm.assume(spender != owner);
+ _helperUpdateMemberUnits(freePool, alice, owner, uint128(int128(transferAmount)), useBools_);
+ _helperSuperfluidPoolApprove(freePool, owner, spender, uint256(uint128(transferAmount)));
+ _helperSuperfluidPoolUnitsTransferFrom(freePool, spender, owner, spender, uint256(uint128(transferAmount)));
+ }
+
+ function testIncreaseAllowanceAndTransferFrom(
+ address owner,
+ address spender,
+ int128 transferAmount,
+ FoundrySuperfluidTester._StackVars_UseBools memory useBools_
+ ) public {
+ vm.assume(transferAmount > 0);
+ vm.assume(spender != address(0));
+ vm.assume(owner != address(0));
+ vm.assume(spender != owner);
+ _helperUpdateMemberUnits(freePool, alice, owner, uint128(int128(transferAmount)), useBools_);
+ _helperSuperfluidPoolIncreaseAllowance(freePool, owner, spender, uint256(uint128(transferAmount)));
+ _helperSuperfluidPoolUnitsTransferFrom(freePool, spender, owner, spender, uint256(uint128(transferAmount)));
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ Assertion Functions
+ //////////////////////////////////////////////////////////////////////////*/
+
+ struct PoolUpdateStep {
+ uint8 u; // which user
+ uint8 a; // action types: 0 update units, 1 distribute flow, 2 freePool connection, 3 freePool claim for,
+ // 4 distribute
+ uint32 v; // action param
+ uint16 dt; // time delta
+ }
+
+ function testPoolRandomSeqs(PoolUpdateStep[20] memory steps, _StackVars_UseBools memory useBools_) external {
+ uint256 N_MEMBERS = 5;
+
+ for (uint256 i = 0; i < steps.length; ++i) {
+ emit log_named_string("", "");
+ emit log_named_uint(">>> STEP", i);
+ PoolUpdateStep memory s = steps[i];
+ uint256 action = s.a % 5;
+ uint256 u = 1 + s.u % N_MEMBERS;
+ address user = TEST_ACCOUNTS[u];
+
+ emit log_named_uint("user", u);
+ emit log_named_uint("time delta", s.dt);
+ emit log_named_uint("> timestamp", block.timestamp);
+ emit log_named_address("tester", user);
+
+ if (action == 0) {
+ emit log_named_string("action", "updateMember");
+ emit log_named_uint("units", s.v);
+ _helperUpdateMemberUnits(freePool, freePool.admin(), user, s.v, useBools_);
+ } else if (action == 1) {
+ emit log_named_string("action", "distributeFlow");
+ emit log_named_uint("flow rate", s.v);
+ if (sf.gda.getFlowRate(superToken, user, freePool) == 0) {
+ vm.assume(s.v > 0);
+ }
+ _helperDistributeFlow(superToken, user, user, freePool, int96(uint96(s.v)), useBools_.useForwarder);
+ } else if (action == 2) {
+ address u4 = TEST_ACCOUNTS[1 + (s.v % N_MEMBERS)];
+ emit log_named_string("action", "claimAll");
+ emit log_named_address("claim for", u4);
+ vm.startPrank(user);
+ assert(freePool.claimAll(u4));
+ vm.stopPrank();
+ } else if (action == 3) {
+ bool doConnect = s.v % 2 == 0 ? false : true;
+ emit log_named_string("action", "doConnectPool");
+ emit log_named_string("doConnect", doConnect ? "true" : "false");
+ doConnect
+ ? _helperConnectPool(user, superToken, freePool, useBools_.useForwarder)
+ : _helperDisconnectPool(user, superToken, freePool, useBools_.useForwarder);
+ } else if (action == 4) {
+ emit log_named_string("action", "distribute");
+ emit log_named_uint("distributionAmount", s.v);
+ _helperDistributeViaGDA(superToken, user, user, freePool, uint256(s.v), useBools_.useForwarder);
+ } else {
+ assert(false);
+ }
+
+ {
+ (int256 rtb, uint256 buffer, uint256 owedBuffer) =
+ sf.gda.realtimeBalanceOf(superToken, address(freePool), block.timestamp);
+ int96 nr = useBools_.useForwarder
+ ? sf.gdaV1Forwarder.getNetFlow(superToken, address(freePool))
+ : sf.gda.getNetFlow(superToken, address(freePool));
+ emit log_string("> freePool before time warp");
+ emit log_named_int("rtb", rtb);
+ emit log_named_uint("buffer", buffer);
+ emit log_named_uint("owedBuffer", owedBuffer);
+ emit log_named_int("freePool net flow rate", nr);
+ }
+
+ emit log_named_uint("> dt", s.dt);
+ vm.warp(block.timestamp + s.dt);
+
+ {
+ (int256 rtb, uint256 buffer, uint256 owedBuffer) =
+ sf.gda.realtimeBalanceOf(superToken, address(freePool), block.timestamp);
+ int96 nr = useBools_.useForwarder
+ ? sf.gdaV1Forwarder.getNetFlow(superToken, address(freePool))
+ : sf.gda.getNetFlow(superToken, address(freePool));
+ emit log_string("> freePool before time warp");
+ emit log_named_int("rtb", rtb);
+ emit log_named_uint("buffer", buffer);
+ emit log_named_uint("owedBuffer", owedBuffer);
+ emit log_named_int("freePool net flow rate", nr);
+ }
+ }
+
+ int96 flowRatesSum;
+ {
+ int96 poolNetFlowRate = sf.gda.getNetFlow(superToken, address(freePool));
+ flowRatesSum = flowRatesSum + poolNetFlowRate;
+ }
+
+ for (uint256 i = 1; i <= N_MEMBERS; ++i) {
+ int96 flowRate = sf.gda.getNetFlow(superToken, TEST_ACCOUNTS[i]);
+ flowRatesSum = flowRatesSum + flowRate;
+ }
+
+ assertEq(flowRatesSum, 0, "GDAv1.t: flowRatesSum != 0");
+ }
+}
diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.sol
new file mode 100644
index 0000000000..487e50ef95
--- /dev/null
+++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.sol
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import "forge-std/Test.sol";
+import { IBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
+import "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol";
+
+import {
+ ProxyDeployerLibrary,
+ SuperfluidPoolLogicDeployerLibrary,
+ SuperfluidUpgradeableBeacon
+} from "../../../../contracts/utils/SuperfluidFrameworkDeploymentSteps.sol";
+import { ERC1820RegistryCompiled } from "../../../../contracts/libs/ERC1820RegistryCompiled.sol";
+import { SuperfluidFrameworkDeployer } from "../../../../contracts/utils/SuperfluidFrameworkDeployer.sol";
+import { TestToken } from "../../../../contracts/utils/TestToken.sol";
+import { ISuperToken, SuperToken } from "../../../../contracts/superfluid/SuperToken.sol";
+import { ISuperAgreement } from "../../../../contracts/interfaces/superfluid/ISuperAgreement.sol";
+import {
+ GeneralDistributionAgreementV1,
+ ISuperfluid,
+ ISuperfluidPool
+} from "../../../../contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol";
+import {
+ IGeneralDistributionAgreementV1,
+ PoolConfig
+} from "../../../../contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.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
+/// It involves testing the pure functions of the GDAv1 to ensure that we get
+/// the expected output for a range of inputs.
+contract GeneralDistributionAgreementV1Properties is GeneralDistributionAgreementV1, Test {
+ using SuperTokenV1Library for ISuperToken;
+
+ SuperfluidFrameworkDeployer internal immutable sfDeployer;
+ SuperfluidFrameworkDeployer.Framework internal sf;
+
+ SuperfluidPool public currentPool;
+ uint256 public liquidationPeriod;
+
+ /// @dev The current underlying token being tested (applies only to wrapper super tokens)
+ TestToken internal token;
+
+ /// @dev The current super token being tested
+ ISuperToken internal superToken;
+
+ address public constant alice = address(0x420);
+
+ constructor() GeneralDistributionAgreementV1(ISuperfluid(address(0))) {
+ // deploy ERC1820 registry
+ vm.etch(ERC1820RegistryCompiled.at, ERC1820RegistryCompiled.bin);
+ sfDeployer = new SuperfluidFrameworkDeployer();
+ sfDeployer.deployTestFramework();
+ sf = sfDeployer.getFramework();
+
+ (token, superToken) = sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, address(0));
+
+ // /// Deploy SuperfluidPool logic contract
+ // SuperfluidPool superfluidPoolLogic =
+ // SuperfluidPoolLogicDeployerLibrary.deploySuperfluidPool(GeneralDistributionAgreementV1(address(this)));
+
+ // // Initialize the logic contract
+ // superfluidPoolLogic.castrate();
+
+ // SuperfluidUpgradeableBeacon superfluidPoolBeacon =
+ // ProxyDeployerLibrary.deploySuperfluidUpgradeableBeacon(address(superfluidPoolLogic));
+ // this.initialize(superfluidPoolBeacon);
+
+ PoolConfig memory poolConfig =
+ PoolConfig({ transferabilityForUnitsOwner: true, distributionFromAnyAddress: true });
+
+ vm.startPrank(alice);
+ currentPool = SuperfluidPool(address(sf.gda.createPool(superToken, alice, poolConfig)));
+ vm.stopPrank();
+
+ (liquidationPeriod,) = sf.governance.getPPPConfig(sf.host, superToken);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ GDA Setters/Getters Tests
+ //////////////////////////////////////////////////////////////////////////*/
+ // Universal Index Setters/Getters
+ function testSetGetUIndex(address owner, uint32 settledAt, int96 flowRate, int256 settledValue) public {
+ bytes memory eff = abi.encode(superToken);
+ BasicParticle memory p = BasicParticle({
+ _settled_at: Time.wrap(settledAt),
+ _flow_rate: FlowRate.wrap(flowRate),
+ _settled_value: Value.wrap(settledValue)
+ });
+ _setUIndex(eff, owner, p);
+ BasicParticle memory setP = _getUIndex(eff, owner);
+
+ assertEq(Time.unwrap(p._settled_at), Time.unwrap(setP._settled_at), "settledAt not equal");
+ assertEq(FlowRate.unwrap(p._flow_rate), FlowRate.unwrap(setP._flow_rate), "flowRate not equal");
+ assertEq(Value.unwrap(p._settled_value), Value.unwrap(setP._settled_value), "settledValue not equal");
+ }
+
+ function testSetGetUIndexData(address owner, uint32 settledAt, int96 flowRate, int256 settledValue) public {
+ vm.assume(owner != address(currentPool));
+
+ bytes memory eff = abi.encode(superToken);
+ BasicParticle memory p = BasicParticle({
+ _settled_at: Time.wrap(settledAt),
+ _flow_rate: FlowRate.wrap(flowRate),
+ _settled_value: Value.wrap(settledValue)
+ });
+ _setUIndex(eff, owner, p);
+ GeneralDistributionAgreementV1.UniversalIndexData memory setUIndexData = _getUIndexData(eff, 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");
+ }
+
+ // Flow Distribution Data Setters/Getters
+ function testSetGetFlowDistributionData(
+ address from,
+ ISuperfluidPool to,
+ uint32 newFlowRate,
+ uint96 newFlowRateDelta
+ ) public {
+ uint256 lastUpdated = block.timestamp;
+
+ bytes32 flowHash = _getFlowDistributionHash(from, to);
+
+ _setFlowInfo(
+ abi.encode(superToken),
+ flowHash,
+ from,
+ address(to),
+ FlowRate.wrap(int128(uint128(newFlowRate))),
+ FlowRate.wrap(int128(uint128(newFlowRateDelta)))
+ );
+
+ vm.warp(1000);
+
+ (bool exist, IGeneralDistributionAgreementV1.FlowDistributionData memory setFlowDistributionData) =
+ _getFlowDistributionData(superToken, flowHash);
+
+ assertEq(true, exist, "flow distribution data does not exist");
+
+ assertEq(int96(uint96(newFlowRate)), setFlowDistributionData.flowRate, "flowRate not equal");
+
+ assertEq(lastUpdated, setFlowDistributionData.lastUpdated, "lastUpdated not equal");
+
+ assertEq(0, setFlowDistributionData.buffer, "buffer not equal");
+ assertEq(
+ int96(FlowRate.unwrap(_getFlowRate(abi.encode(superToken), flowHash))),
+ int96(uint96(newFlowRate)),
+ "_getFlowRate: flow rate not equal"
+ );
+ assertEq(
+ int96(FlowRate.unwrap(_getFlowRate(abi.encode(superToken), flowHash))),
+ int96(uint96(newFlowRate)),
+ "getFlowRate: flow rate not equal"
+ );
+ }
+
+ // Pool Member Data Setters/Getters
+ 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));
+ bytes32 poolMemberId = _getPoolMemberHash(poolMember, _pool);
+
+ vm.startPrank(address(this));
+ superToken.updateAgreementData(
+ poolMemberId,
+ _encodePoolMemberData(
+ IGeneralDistributionAgreementV1.PoolMemberData({ poolID: poolID, pool: address(_pool) })
+ )
+ );
+ vm.stopPrank();
+
+ (bool exist, IGeneralDistributionAgreementV1.PoolMemberData memory setPoolMemberData) =
+ _getPoolMemberData(superToken, 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");
+ }
+
+ // Proportional Distribution Pool Index Setters/Getters
+ function testSetGetPDPIndex(
+ address owner,
+ uint128 totalUnits,
+ uint32 wrappedSettledAt,
+ int96 wrappedFlowRate,
+ int256 wrappedSettledValue
+ ) public {
+ vm.assume(owner != address(0));
+ vm.assume(totalUnits < uint128(type(int128).max));
+ bytes memory eff = abi.encode(superToken);
+ PDPoolIndex memory pdpIndex = PDPoolIndex({
+ total_units: Unit.wrap(int128(totalUnits)),
+ _wrapped_particle: BasicParticle({
+ _settled_at: Time.wrap(wrappedSettledAt),
+ _flow_rate: FlowRate.wrap(wrappedFlowRate),
+ _settled_value: Value.wrap(wrappedSettledValue)
+ })
+ });
+
+ // we have to pretend to be the registered gda, not this testing contract
+ vm.startPrank(address(sf.gda));
+ _setPDPIndex(eff, address(currentPool), pdpIndex);
+ vm.stopPrank();
+
+ (PDPoolIndex memory setPdpIndex) = _getPDPIndex(new bytes(0), address(currentPool));
+
+ assertEq(Unit.unwrap(pdpIndex.total_units), Unit.unwrap(setPdpIndex.total_units), "total units not equal");
+ assertEq(
+ Time.unwrap(pdpIndex._wrapped_particle._settled_at),
+ Time.unwrap(setPdpIndex._wrapped_particle._settled_at),
+ "settled at not equal"
+ );
+ assertEq(
+ FlowRate.unwrap(pdpIndex._wrapped_particle._flow_rate),
+ FlowRate.unwrap(setPdpIndex._wrapped_particle._flow_rate),
+ "flow rate not equal"
+ );
+ assertEq(
+ Value.unwrap(pdpIndex._wrapped_particle._settled_value),
+ Value.unwrap(setPdpIndex._wrapped_particle._settled_value),
+ "settled value not equal"
+ );
+ }
+
+ // // 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(
+ int96 flowRate,
+ uint32 settledAt,
+ int256 settledValue,
+ uint96 totalBuffer,
+ bool isPool_
+ ) public {
+ 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);
+
+ 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");
+ }
+
+ function testEncodeDecodeUIDataInputeUniversalIndexData(
+ int96 flowRate,
+ uint32 settledAt,
+ int256 settledValue,
+ uint96 totalBuffer,
+ bool isPool_
+ ) public {
+ UniversalIndexData memory data = UniversalIndexData({
+ flowRate: flowRate,
+ settledAt: settledAt,
+ settledValue: settledValue,
+ totalBuffer: totalBuffer,
+ isPool: isPool_
+ });
+
+ bytes32[] memory encoded = _encodeUniversalIndexData(data);
+ (, UniversalIndexData memory decoded) = _decodeUniversalIndexData(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");
+ }
+
+ function testGetBasicParticleFromUIndex(UniversalIndexData memory data) public {
+ BasicParticle memory particle = _getBasicParticleFromUIndex(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 {
+ vm.assume(flowRate >= 0);
+ vm.assume(buffer >= 0);
+ IGeneralDistributionAgreementV1.FlowDistributionData memory original = IGeneralDistributionAgreementV1
+ .FlowDistributionData({ flowRate: flowRate, lastUpdated: uint32(block.timestamp), buffer: buffer });
+ bytes32[] memory encoded = _encodeFlowDistributionData(original);
+ (, IGeneralDistributionAgreementV1.FlowDistributionData memory decoded) =
+ _decodeFlowDistributionData(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 {
+ vm.assume(pool != address(0));
+ IGeneralDistributionAgreementV1.PoolMemberData memory original =
+ IGeneralDistributionAgreementV1.PoolMemberData({ pool: pool, poolID: poolID });
+ bytes32[] memory encoded = _encodePoolMemberData(original);
+ (, IGeneralDistributionAgreementV1.PoolMemberData memory decoded) = _decodePoolMemberData(uint256(encoded[0]));
+
+ assertEq(original.pool, decoded.pool, "pool not equal");
+ assertEq(original.poolID, decoded.poolID, "poolID not equal");
+ }
+}
diff --git a/packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol b/packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol
index f92aa8e053..33a68b8572 100644
--- a/packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol
+++ b/packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol
@@ -30,6 +30,13 @@ contract CrossStreamSuperAppTest is FoundrySuperfluidTester {
vm.assume(flowRate > 2 ** 32 - 1);
int96 initialFlowRate = flowRate;
+ // @note transfer tokens from alice to carol so that
+ // alice has type(uint64).max balance to start
+ uint256 diff = type(uint88).max - type(uint64).max;
+ vm.startPrank(alice);
+ superToken.transfer(carol, diff);
+ vm.stopPrank();
+
uint256 balance = superToken.balanceOf(alice);
uint256 amountOfTimeTillZero = balance / uint256(uint96(initialFlowRate));
diff --git a/packages/ethereum-contracts/test/foundry/echidna/EchidnaTestCases.t.sol b/packages/ethereum-contracts/test/foundry/echidna/EchidnaTestCases.t.sol
new file mode 100644
index 0000000000..813e4487bd
--- /dev/null
+++ b/packages/ethereum-contracts/test/foundry/echidna/EchidnaTestCases.t.sol
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.sol";
+import { ISuperfluidPool, SuperfluidPool } from "../../../contracts/agreements/gdav1/SuperfluidPool.sol";
+import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol";
+import { ISuperToken, SuperToken } from "../../../contracts/superfluid/SuperToken.sol";
+
+/// @dev This contract includes test sequences discovered by echidna which broke invariants previously.
+contract EchidnaTestCases is FoundrySuperfluidTester {
+ using SuperTokenV1Library for ISuperToken;
+
+ SuperfluidPool public currentPool;
+
+ constructor() FoundrySuperfluidTester(6) { }
+
+ function setUp() public override {
+ super.setUp();
+ vm.startPrank(alice);
+ currentPool = SuperfluidPool(address(superToken.createPool(alice, poolConfig)));
+ _addAccount(address(currentPool));
+ vm.stopPrank();
+ }
+
+ function testDistributeFlowToDisconnectedMember(address member, uint64 units, int32 flowRate, bool useForwarder)
+ public
+ {
+ vm.assume(flowRate > 0);
+
+ _helperUpdateMemberUnits(currentPool, alice, member, units);
+
+ _helperDistributeFlow(superToken, alice, alice, currentPool, flowRate, useForwarder);
+ }
+
+ function testLiquidationCase() public {
+ int96 flowRate = 28880687301540251;
+ uint256 warpTime = 70;
+
+ _helperCreateFlow(superToken, alice, bob, flowRate);
+ _helperTransferAll(superToken, alice, bob);
+ vm.warp(block.timestamp + warpTime);
+ _helperDeleteFlow(superToken, carol, alice, bob);
+ }
+}
diff --git a/packages/ethereum-contracts/test/foundry/gov/SuperfluidGovernanceII.t.sol b/packages/ethereum-contracts/test/foundry/gov/SuperfluidGovernanceII.t.sol
index eefd1fba31..6305d82130 100644
--- a/packages/ethereum-contracts/test/foundry/gov/SuperfluidGovernanceII.t.sol
+++ b/packages/ethereum-contracts/test/foundry/gov/SuperfluidGovernanceII.t.sol
@@ -8,6 +8,7 @@ import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library
import { ISuperAgreement } from "../../../contracts/interfaces/superfluid/ISuperAgreement.sol";
import { ISuperfluid } from "../../../contracts/interfaces/superfluid/ISuperfluid.sol";
import { AgreementMock } from "../../../contracts/mocks/AgreementMock.sol";
+import { SuperfluidPool } from "../../../contracts/agreements/gdav1/SuperfluidPool.sol";
contract SuperfluidGovernanceIntegrationTest is FoundrySuperfluidTester {
using SuperTokenV1Library for SuperToken;
@@ -26,6 +27,7 @@ contract SuperfluidGovernanceIntegrationTest is FoundrySuperfluidTester {
function testRevertChangeSuperTokenAdminWhenCallerIsNotNotGovOwner(address newAdmin) public {
vm.assume(newAdmin != address(0));
+ vm.assume(newAdmin != sf.governance.owner());
vm.startPrank(newAdmin);
vm.expectRevert();
@@ -33,7 +35,7 @@ contract SuperfluidGovernanceIntegrationTest is FoundrySuperfluidTester {
vm.stopPrank();
}
- function testRevertWhenHostIsNotAdmin(address initialAdmin) public {
+ function testRevertChangeSuperTokenAdminWhenHostIsNotAdmin(address initialAdmin) public {
vm.assume(initialAdmin != address(0));
vm.assume(initialAdmin != address(sf.host));
@@ -47,10 +49,50 @@ contract SuperfluidGovernanceIntegrationTest is FoundrySuperfluidTester {
vm.stopPrank();
}
+ function testRevertUpgradePoolBeaconLogicWhenNotOwner() public {
+ SuperfluidPool newPoolLogic = new SuperfluidPool(sf.gda);
+
+ vm.expectRevert();
+ sf.governance.updateContracts(sf.host, address(0), new address[](0), address(0), address(newPoolLogic));
+ }
+
+ function testUpdateContractsToUpgradePoolBeaconLogic() public {
+ SuperfluidPool newPoolLogic = new SuperfluidPool(sf.gda);
+ vm.startPrank(sf.governance.owner());
+ sf.governance.updateContracts(sf.host, address(0), new address[](0), address(0), address(newPoolLogic));
+ vm.stopPrank();
+
+ assertEq(
+ sf.gda.superfluidPoolBeacon().implementation(),
+ address(newPoolLogic),
+ "testUpdateContractsToUpgradePoolBeaconLogic: pool beacon logic not upgraded"
+ );
+ }
+
+ function testRevertUpgradePoolBeaconLogicWhenNotGovernance() public {
+ SuperfluidPool newPoolLogic = new SuperfluidPool(sf.gda);
+ vm.expectRevert();
+ sf.host.updatePoolBeaconLogic(address(newPoolLogic));
+ }
+
+ function testUpgradePoolBeaconLogic() public {
+ SuperfluidPool newPoolLogic = new SuperfluidPool(sf.gda);
+ vm.startPrank(address(sf.governance));
+ sf.host.updatePoolBeaconLogic(address(newPoolLogic));
+ vm.stopPrank();
+
+ assertEq(
+ sf.gda.superfluidPoolBeacon().implementation(),
+ address(newPoolLogic),
+ "testUpgradePoolBeaconLogic: pool beacon logic not upgraded"
+ );
+ }
+
function testBatchChangeSuperTokenAdmin(address newAdmin) public {
vm.assume(newAdmin != address(0));
- (, ISuperToken localSuperToken) = sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max);
+ (, ISuperToken localSuperToken) =
+ sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, address(0));
ISuperToken[] memory superTokens = new ISuperToken[](2);
superTokens[0] = superToken; // host admin
@@ -90,7 +132,8 @@ contract SuperfluidGovernanceIntegrationTest is FoundrySuperfluidTester {
vm.assume(newAdmin != address(0));
vm.assume(newAdmin != address(sf.governance.owner()));
- (, ISuperToken localSuperToken) = sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max);
+ (, ISuperToken localSuperToken) =
+ sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, address(0));
ISuperToken[] memory superTokens = new ISuperToken[](2);
superTokens[0] = superToken; // host admin
diff --git a/packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.sol b/packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.sol
index 66e093f8e1..81a13cf044 100644
--- a/packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.sol
+++ b/packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.sol
@@ -34,7 +34,7 @@ contract SlotsBitmapLibraryPropertyTest is Test {
vm.stopPrank();
vm.startPrank(subscriber);
- (token, superToken) = sfDeployer.deployWrapperSuperToken("Test Token", "TST", 18, type(uint256).max);
+ (token, superToken) = sfDeployer.deployWrapperSuperToken("Test Token", "TST", 18, type(uint256).max, address(0));
vm.stopPrank();
}
diff --git a/packages/ethereum-contracts/test/foundry/superfluid/ConstantInflowNFT.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/ConstantInflowNFT.t.sol
index c1422b25c9..37e635dec3 100644
--- a/packages/ethereum-contracts/test/foundry/superfluid/ConstantInflowNFT.t.sol
+++ b/packages/ethereum-contracts/test/foundry/superfluid/ConstantInflowNFT.t.sol
@@ -11,148 +11,45 @@ contract ConstantInflowNFTTest is FlowNFTBaseTest {
/*//////////////////////////////////////////////////////////////////////////
Revert Tests
//////////////////////////////////////////////////////////////////////////*/
- function testRevertIfContractAlreadyInitialized() public {
- vm.expectRevert("Initializable: contract is already initialized");
- constantInflowNFTProxy.initialize(
- string.concat("henlo", INFLOW_NFT_NAME_TEMPLATE), string.concat("goodbye", INFLOW_NFT_SYMBOL_TEMPLATE)
- );
- }
-
- function testRevertIfOwnerOfCalledForNonExistentToken(uint256 _tokenId) public {
- vm.expectRevert(IFlowNFTBase.CFA_NFT_INVALID_TOKEN_ID.selector);
- constantInflowNFTProxy.ownerOf(_tokenId);
- }
-
- function testRevertIfGetApprovedCalledForNonExistentToken(uint256 _tokenId) public {
- vm.expectRevert(IFlowNFTBase.CFA_NFT_INVALID_TOKEN_ID.selector);
- constantInflowNFTProxy.getApproved(_tokenId);
- }
-
- function testRevertIfApproveToCallerWhenSetApprovalForAll(address _flowSender, address _flowReceiver) public {
- _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
-
- uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
-
- vm.expectRevert(IFlowNFTBase.CFA_NFT_APPROVE_TO_CALLER.selector);
-
- vm.prank(_flowReceiver);
- constantInflowNFTProxy.setApprovalForAll(_flowReceiver, true);
- }
-
- function testRevertIfApproveToCurrentOwner(address _flowSender, address _flowReceiver) public {
- _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
-
- uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
-
- vm.expectRevert(IFlowNFTBase.CFA_NFT_APPROVE_TO_CURRENT_OWNER.selector);
-
- vm.prank(_flowReceiver);
- constantInflowNFTProxy.approve(_flowReceiver, nftId);
+ function testRevertIfMintIsNotCalledByOutflowNFT(address caller) public {
+ _assumeCallerIsNotOtherAddress(caller, address(constantOutflowNFT));
+ vm.expectRevert(IConstantInflowNFT.CIF_NFT_ONLY_CONSTANT_OUTFLOW.selector);
+ constantInflowNFT.mint(address(0), 69);
}
- function testRevertIfApproveAsNonOwner(
- address _flowSender,
- address _flowReceiver,
- address _approver,
- address _approvedAccount
- ) public {
- _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
- /// @dev _flowReceiver is owner of inflow NFT
- vm.assume(_approver != _flowReceiver);
- vm.assume(_approvedAccount != _flowReceiver);
-
- uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
- vm.expectRevert(IFlowNFTBase.CFA_NFT_APPROVE_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector);
- vm.prank(_approver);
- constantInflowNFTProxy.approve(_approvedAccount, nftId);
+ function testRevertIfBurnIsNotCalledByOutflowNFT(address caller) public {
+ _assumeCallerIsNotOtherAddress(caller, address(constantOutflowNFT));
+ vm.expectRevert(IConstantInflowNFT.CIF_NFT_ONLY_CONSTANT_OUTFLOW.selector);
+ constantInflowNFT.burn(69);
}
function testRevertIfYouTryToTransferInflowNFT(address _flowSender, address _flowReceiver) public {
_assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
+ constantOutflowNFT.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
- _assertEventTransfer(address(constantOutflowNFTProxy), address(0), _flowSender, nftId);
-
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
- _assertNFTFlowDataStateIsExpected(
- nftId, address(superTokenMock), _flowSender, uint32(block.timestamp), _flowReceiver
- );
-
- vm.prank(_flowReceiver);
- vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_IS_NOT_ALLOWED.selector);
- constantInflowNFTProxy.transferFrom(_flowReceiver, _flowSender, nftId);
-
- vm.prank(_flowReceiver);
- vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_IS_NOT_ALLOWED.selector);
- constantInflowNFTProxy.safeTransferFrom(_flowReceiver, _flowSender, nftId);
-
- vm.prank(_flowReceiver);
+ vm.startPrank(_flowReceiver);
vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_IS_NOT_ALLOWED.selector);
- constantInflowNFTProxy.safeTransferFrom(_flowReceiver, _flowSender, nftId, "0x");
- }
-
- function testRevertIfYouAreNotTheOwnerAndTryToTransferInflowNFT(address _flowSender, address _flowReceiver)
- public
- {
- _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
-
- uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
-
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
-
- vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector);
- vm.prank(_flowSender);
- constantInflowNFTProxy.transferFrom(_flowReceiver, _flowSender, nftId);
-
- vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector);
- vm.prank(_flowSender);
- constantInflowNFTProxy.safeTransferFrom(_flowReceiver, _flowSender, nftId);
-
- vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector);
- vm.prank(_flowSender);
- constantInflowNFTProxy.safeTransferFrom(_flowReceiver, _flowSender, nftId, "0x");
- }
-
- function testRevertIfMintIsNotCalledByOutflowNFT(address caller) public {
- _assumeCallerIsNotOtherAddress(caller, address(constantOutflowNFTProxy));
- vm.expectRevert(IConstantInflowNFT.CIF_NFT_ONLY_CONSTANT_OUTFLOW.selector);
- constantInflowNFTProxy.mint(address(0), 69);
- }
-
- function testRevertIfBurnIsNotCalledByOutflowNFT(address caller) public {
- _assumeCallerIsNotOtherAddress(caller, address(constantOutflowNFTProxy));
- vm.expectRevert(IConstantInflowNFT.CIF_NFT_ONLY_CONSTANT_OUTFLOW.selector);
- constantInflowNFTProxy.burn(69);
+ constantInflowNFT.transferFrom(_flowReceiver, _flowSender, nftId);
+ vm.stopPrank();
}
/*//////////////////////////////////////////////////////////////////////////
Passing Tests
//////////////////////////////////////////////////////////////////////////*/
- function testContractSupportsExpectedInterfaces() public {
- assertEq(constantInflowNFTProxy.supportsInterface(type(IERC165).interfaceId), true);
- assertEq(constantInflowNFTProxy.supportsInterface(type(IERC721).interfaceId), true);
- assertEq(constantInflowNFTProxy.supportsInterface(type(IERC721Metadata).interfaceId), true);
- }
function testProxiableUUIDIsExpectedValue() public {
assertEq(
- constantInflowNFTProxy.proxiableUUID(),
+ constantInflowNFT.proxiableUUID(),
keccak256("org.superfluid-finance.contracts.ConstantInflowNFT.implementation")
);
}
- function testNFTBalanceOfIsAlwaysOne(address _owner) public {
- assertEq(constantInflowNFTProxy.balanceOf(_owner), 1);
- }
-
function testConstantInflowNFTIsProperlyInitialized() public {
- assertEq(constantInflowNFTProxy.name(), INFLOW_NFT_NAME_TEMPLATE);
- assertEq(constantInflowNFTProxy.symbol(), INFLOW_NFT_SYMBOL_TEMPLATE);
+ assertEq(constantInflowNFT.name(), INFLOW_NFT_NAME_TEMPLATE);
+ assertEq(constantInflowNFT.symbol(), INFLOW_NFT_SYMBOL_TEMPLATE);
}
function testFlowDataByTokenIdMint(address _flowSender, address _flowReceiver) public {
@@ -160,12 +57,12 @@ contract ConstantInflowNFTTest is FlowNFTBaseTest {
uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
+ constantOutflowNFT.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
_assertNFTFlowDataStateIsExpected(
nftId, address(superTokenMock), _flowSender, uint32(block.timestamp), _flowReceiver
);
- IFlowNFTBase.FlowNFTData memory flowData = constantInflowNFTProxy.mockFlowNFTDataByTokenId(nftId);
+ IFlowNFTBase.FlowNFTData memory flowData = constantInflowNFT.flowDataByTokenId(nftId);
assertEq(flowData.flowSender, _flowSender);
assertEq(flowData.flowReceiver, _flowReceiver);
}
@@ -175,9 +72,9 @@ contract ConstantInflowNFTTest is FlowNFTBaseTest {
uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- _assertEventTransfer(address(constantInflowNFTProxy), address(0), _flowReceiver, nftId);
+ _assertEventTransfer(address(constantInflowNFT), address(0), _flowReceiver, nftId);
- constantInflowNFTProxy.mockMint(_flowReceiver, nftId);
+ constantInflowNFT.mockMint(_flowReceiver, nftId);
_assertNFTFlowDataStateIsEmpty(nftId);
}
@@ -186,14 +83,14 @@ contract ConstantInflowNFTTest is FlowNFTBaseTest {
_assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
+ constantOutflowNFT.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
_assertNFTFlowDataStateIsExpected(
nftId, address(superTokenMock), _flowSender, uint32(block.timestamp), _flowReceiver
);
- _assertEventTransfer(address(constantInflowNFTProxy), _flowReceiver, address(0), nftId);
+ _assertEventTransfer(address(constantInflowNFT), _flowReceiver, address(0), nftId);
- constantInflowNFTProxy.mockBurn(nftId);
+ constantInflowNFT.mockBurn(nftId);
_assertNFTFlowDataStateIsExpected(
nftId, address(superTokenMock), _flowSender, uint32(block.timestamp), _flowReceiver
@@ -202,30 +99,32 @@ contract ConstantInflowNFTTest is FlowNFTBaseTest {
function testApprove(address _flowSender, address _flowReceiver, address _approvedAccount)
public
+ override
returns (uint256 nftId)
{
_assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
vm.assume(_flowReceiver != _approvedAccount);
nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
+ constantOutflowNFT.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
_assertNFTFlowDataStateIsExpected(
nftId, address(superTokenMock), _flowSender, uint32(block.timestamp), _flowReceiver
);
- _assertEventApproval(address(constantInflowNFTProxy), _flowReceiver, _approvedAccount, nftId);
+ _assertEventApproval(address(constantInflowNFT), _flowReceiver, _approvedAccount, nftId);
- vm.prank(_flowReceiver);
- constantInflowNFTProxy.approve(_approvedAccount, nftId);
+ vm.startPrank(_flowReceiver);
+ constantInflowNFT.approve(_approvedAccount, nftId);
+ vm.stopPrank();
- _assertApprovalIsExpected(constantInflowNFTProxy, nftId, _approvedAccount);
+ _assertApprovalIsExpected(constantInflowNFT, nftId, _approvedAccount);
}
function testApproveThenBurn(address _flowSender, address _flowReceiver, address _approvedAccount) public {
uint256 nftId = testApprove(_flowSender, _flowReceiver, _approvedAccount);
- constantInflowNFTProxy.mockBurn(nftId);
+ constantInflowNFT.mockBurn(nftId);
- assertEq(constantInflowNFTProxy.mockGetApproved(nftId), address(0));
+ assertEq(constantInflowNFT.mockGetApproved(nftId), address(0));
}
function testSetApprovalForAll(address _tokenOwner, address _operator, bool _approved) public {
@@ -233,10 +132,10 @@ contract ConstantInflowNFTTest is FlowNFTBaseTest {
vm.assume(_tokenOwner != _operator);
vm.startPrank(_tokenOwner);
- _assertEventApprovalForAll(address(constantInflowNFTProxy), _tokenOwner, _operator, _approved);
- constantInflowNFTProxy.setApprovalForAll(_operator, _approved);
+ _assertEventApprovalForAll(address(constantInflowNFT), _tokenOwner, _operator, _approved);
+ constantInflowNFT.setApprovalForAll(_operator, _approved);
vm.stopPrank();
- _assertOperatorApprovalIsExpected(constantInflowNFTProxy, _tokenOwner, _operator, _approved);
+ _assertOperatorApprovalIsExpected(constantInflowNFT, _tokenOwner, _operator, _approved);
}
}
diff --git a/packages/ethereum-contracts/test/foundry/superfluid/ConstantOutflowNFT.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/ConstantOutflowNFT.t.sol
index 4cb8c346b0..b543431680 100644
--- a/packages/ethereum-contracts/test/foundry/superfluid/ConstantOutflowNFT.t.sol
+++ b/packages/ethereum-contracts/test/foundry/superfluid/ConstantOutflowNFT.t.sol
@@ -4,7 +4,6 @@ pragma solidity 0.8.19;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC165, IERC721, IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
-
import { UUPSProxy } from "../../../contracts/upgradability/UUPSProxy.sol";
import {
FlowNFTBase, ConstantOutflowNFT, IConstantOutflowNFT
@@ -14,7 +13,8 @@ import { FoundrySuperfluidTester, SuperTokenV1Library } from "../FoundrySuperflu
import { IFlowNFTBase } from "../../../contracts/interfaces/superfluid/IFlowNFTBase.sol";
import { FlowNFTBaseTest } from "./FlowNFTBase.t.sol";
import { SuperToken, SuperTokenMock } from "../../../contracts/mocks/SuperTokenMock.sol";
-import { ConstantOutflowNFTMock, NoNFTSuperTokenMock } from "../../../contracts/mocks/CFAv1NFTMock.sol";
+import { ConstantOutflowNFTMock } from "../../../contracts/mocks/CFAv1NFTMock.sol";
+import { NoNFTSuperTokenMock } from "../../../contracts/mocks/SuperTokenMock.sol";
import { TestToken } from "../../../contracts/utils/TestToken.sol";
import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol";
import { ISuperToken } from "../../../contracts/superfluid/SuperToken.sol";
@@ -27,75 +27,20 @@ contract ConstantOutflowNFTTest is FlowNFTBaseTest {
/*//////////////////////////////////////////////////////////////////////////
Revert Tests
//////////////////////////////////////////////////////////////////////////*/
- function testRevertIfContractAlreadyInitialized() public {
- vm.expectRevert("Initializable: contract is already initialized");
-
- constantOutflowNFTProxy.initialize(
- string.concat("henlo", OUTFLOW_NFT_NAME_TEMPLATE), string.concat("goodbye", OUTFLOW_NFT_SYMBOL_TEMPLATE)
- );
- }
-
- function testRevertIfOwnerOfForNonExistentToken(uint256 _tokenId) public {
- vm.expectRevert(IFlowNFTBase.CFA_NFT_INVALID_TOKEN_ID.selector);
- constantOutflowNFTProxy.ownerOf(_tokenId);
- }
-
- function testRevertIfGetApprovedForNonExistentToken(uint256 _tokenId) public {
- vm.expectRevert(IFlowNFTBase.CFA_NFT_INVALID_TOKEN_ID.selector);
- constantOutflowNFTProxy.getApproved(_tokenId);
- }
-
- function testRevertIfSetApprovalForAllOperatorApproveToCaller(address _flowSender, address _flowReceiver) public {
- _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
-
- uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
- vm.expectRevert(IFlowNFTBase.CFA_NFT_APPROVE_TO_CALLER.selector);
- vm.prank(_flowSender);
- constantOutflowNFTProxy.setApprovalForAll(_flowSender, true);
- }
-
- function testRevertIfApproveToCurrentOwner(address _flowSender, address _flowReceiver) public {
- _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
-
- uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
- vm.expectRevert(IFlowNFTBase.CFA_NFT_APPROVE_TO_CURRENT_OWNER.selector);
- vm.prank(_flowSender);
- constantOutflowNFTProxy.approve(_flowSender, nftId);
- }
-
- function testRevertIfApproveAsNonOwner(
- address _flowSender,
- address _flowReceiver,
- address _approver,
- address _approvedAccount
- ) public {
- _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
- /// @dev _flowSender is owner of outflow NFT
- vm.assume(_approver != _flowSender);
- vm.assume(_approvedAccount != _flowSender);
-
- uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
- vm.expectRevert(IFlowNFTBase.CFA_NFT_APPROVE_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector);
- vm.prank(_approver);
- constantOutflowNFTProxy.approve(_approvedAccount, nftId);
- }
function testRevertIfInternalMintToZeroAddress(address _flowReceiver) public {
uint256 nftId = _helperGetNFTID(address(superTokenMock), address(0), _flowReceiver);
vm.expectRevert();
- constantOutflowNFTProxy.mockMint(address(superTokenMock), address(0), _flowReceiver, nftId);
+ constantOutflowNFT.mockMint(address(superTokenMock), address(0), _flowReceiver, nftId);
}
function testRevertIfInternalMintTokenThatExists(address _flowSender, address _flowReceiver) public {
_assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
+ constantOutflowNFT.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
vm.expectRevert();
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
+ constantOutflowNFT.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
}
function testRevertIfInternalMintSameToAndFlowReceiver(address _flowSender) public {
@@ -103,113 +48,70 @@ contract ConstantOutflowNFTTest is FlowNFTBaseTest {
uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowSender);
vm.expectRevert();
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowSender, nftId);
- }
-
- function testRevertIfYouTryToTransferOutflowNFT(address _flowSender, address _flowReceiver) public {
- _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
-
- uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
-
- _assertEventTransfer(address(constantOutflowNFTProxy), address(0), _flowSender, nftId);
-
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
- _assertNFTFlowDataStateIsExpected(
- nftId, address(superTokenMock), _flowSender, uint32(block.timestamp), _flowReceiver
- );
-
- vm.prank(_flowSender);
- vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_IS_NOT_ALLOWED.selector);
- constantOutflowNFTProxy.transferFrom(_flowSender, _flowReceiver, nftId);
-
- vm.prank(_flowSender);
- vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_IS_NOT_ALLOWED.selector);
- constantOutflowNFTProxy.safeTransferFrom(_flowSender, _flowReceiver, nftId);
-
- vm.prank(_flowSender);
- vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_IS_NOT_ALLOWED.selector);
- constantOutflowNFTProxy.safeTransferFrom(_flowSender, _flowReceiver, nftId, "0x");
+ constantOutflowNFT.mockMint(address(superTokenMock), _flowSender, _flowSender, nftId);
}
- function testRevertIfYouAreNotTheOwnerAndTryToTransferOutflowNFT(address _flowSender, address _flowReceiver)
- public
- {
- _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
-
- uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
-
- _assertEventTransfer(address(constantOutflowNFTProxy), address(0), _flowSender, nftId);
-
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
- _assertNFTFlowDataStateIsExpected(
- nftId, address(superTokenMock), _flowSender, uint32(block.timestamp), _flowReceiver
- );
-
- vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector);
- vm.prank(_flowReceiver);
- constantOutflowNFTProxy.transferFrom(_flowSender, _flowReceiver, nftId);
-
- vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector);
- vm.prank(_flowReceiver);
- constantOutflowNFTProxy.safeTransferFrom(_flowSender, _flowReceiver, nftId);
-
- vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector);
- vm.prank(_flowReceiver);
- constantOutflowNFTProxy.safeTransferFrom(_flowSender, _flowReceiver, nftId, "0x");
- }
-
- function testRevertIfOnCreateIsNotCalledByCFAv1(address caller) public {
+ function testRevertIfOnCreateIsNotCalledByFlowAgreement(address caller) public {
_assumeCallerIsNotOtherAddress(caller, address(sf.cfa));
+ _assumeCallerIsNotOtherAddress(caller, address(sf.gda));
+
vm.expectRevert(IConstantOutflowNFT.COF_NFT_ONLY_FLOW_AGREEMENTS.selector);
vm.prank(caller);
- constantOutflowNFTProxy.onCreate(superToken, address(1), address(2));
+ constantOutflowNFT.onCreate(superToken, address(1), address(2));
}
- function testRevertIfOnUpdateIsNotCalledByCFAv1(address caller) public {
+ function testRevertIfOnUpdateIsNotCalledByFlowAgreement(address caller) public {
_assumeCallerIsNotOtherAddress(caller, address(sf.cfa));
- vm.prank(caller);
+ _assumeCallerIsNotOtherAddress(caller, address(sf.gda));
+
+ vm.startPrank(caller);
vm.expectRevert(IConstantOutflowNFT.COF_NFT_ONLY_FLOW_AGREEMENTS.selector);
- constantOutflowNFTProxy.onUpdate(superToken, address(1), address(2));
+ constantOutflowNFT.onUpdate(superToken, address(1), address(2));
+ vm.stopPrank();
}
- function testRevertIfOnDeleteIsNotCalledByCFAv1(address caller) public {
+ function testRevertIfOnDeleteIsNotCalledByFlowAgreement(address caller) public {
_assumeCallerIsNotOtherAddress(caller, address(sf.cfa));
+ _assumeCallerIsNotOtherAddress(caller, address(sf.gda));
vm.prank(caller);
vm.expectRevert(IConstantOutflowNFT.COF_NFT_ONLY_FLOW_AGREEMENTS.selector);
- constantOutflowNFTProxy.onDelete(superToken, address(1), address(2));
+ constantOutflowNFT.onDelete(superToken, address(1), address(2));
}
- function testRevertGetNoFlowTokenURI() public {
+ function testRevertIfGetNoFlowTokenURI() public {
uint256 nftId = _helperGetNFTID(address(superTokenMock), alice, bob);
vm.expectRevert();
- constantOutflowNFTProxy.tokenURI(nftId);
+ constantOutflowNFT.tokenURI(nftId);
vm.expectRevert();
- constantInflowNFTProxy.tokenURI(nftId);
+ constantInflowNFT.tokenURI(nftId);
+ }
+
+ function testRevertIfYouTryToTransferOutflowNFT(address _flowSender, address _flowReceiver) public {
+ _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
+
+ uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
+ constantOutflowNFT.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
+
+ vm.startPrank(_flowSender);
+ vm.expectRevert(IFlowNFTBase.CFA_NFT_TRANSFER_IS_NOT_ALLOWED.selector);
+ constantOutflowNFT.transferFrom(_flowSender, _flowReceiver, nftId);
+ vm.stopPrank();
}
/*//////////////////////////////////////////////////////////////////////////
Passing Tests
//////////////////////////////////////////////////////////////////////////*/
- function testContractSupportsExpectedInterfaces() public {
- assertEq(constantOutflowNFTProxy.supportsInterface(type(IERC165).interfaceId), true);
- assertEq(constantOutflowNFTProxy.supportsInterface(type(IERC721).interfaceId), true);
- assertEq(constantOutflowNFTProxy.supportsInterface(type(IERC721Metadata).interfaceId), true);
- }
function testProxiableUUIDIsExpectedValue() public {
assertEq(
- constantOutflowNFTProxy.proxiableUUID(),
+ constantOutflowNFT.proxiableUUID(),
keccak256("org.superfluid-finance.contracts.ConstantOutflowNFT.implementation")
);
}
- function testNFTBalanceOfIsAlwaysOne(address _owner) public {
- assertEq(constantInflowNFTProxy.balanceOf(_owner), 1);
- }
-
function testConstantOutflowNFTIsProperlyInitialized() public {
- assertEq(constantOutflowNFTProxy.name(), OUTFLOW_NFT_NAME_TEMPLATE);
- assertEq(constantOutflowNFTProxy.symbol(), OUTFLOW_NFT_SYMBOL_TEMPLATE);
+ assertEq(constantOutflowNFT.name(), OUTFLOW_NFT_NAME_TEMPLATE);
+ assertEq(constantOutflowNFT.symbol(), OUTFLOW_NFT_SYMBOL_TEMPLATE);
}
function testInternalMintToken(address _flowSender, address _flowReceiver) public {
@@ -217,9 +119,9 @@ contract ConstantOutflowNFTTest is FlowNFTBaseTest {
uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- _assertEventTransfer(address(constantOutflowNFTProxy), address(0), _flowSender, nftId);
+ _assertEventTransfer(address(constantOutflowNFT), address(0), _flowSender, nftId);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
+ constantOutflowNFT.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
_assertNFTFlowDataStateIsExpected(
nftId, address(superTokenMock), _flowSender, uint32(block.timestamp), _flowReceiver
);
@@ -229,43 +131,42 @@ contract ConstantOutflowNFTTest is FlowNFTBaseTest {
_assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
+ constantOutflowNFT.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
_assertNFTFlowDataStateIsExpected(
nftId, address(superTokenMock), _flowSender, uint32(block.timestamp), _flowReceiver
);
- _assertEventTransfer(address(constantOutflowNFTProxy), _flowSender, address(0), nftId);
+ _assertEventTransfer(address(constantOutflowNFT), _flowSender, address(0), nftId);
- constantOutflowNFTProxy.mockBurn(nftId);
+ constantOutflowNFT.mockBurn(nftId);
_assertNFTFlowDataStateIsEmpty(nftId);
}
function testApprove(address _flowSender, address _flowReceiver, address _approvedAccount)
public
+ override
returns (uint256 nftId)
{
_assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
vm.assume(_flowSender != _approvedAccount);
nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- constantOutflowNFTProxy.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
- _assertNFTFlowDataStateIsExpected(
- nftId, address(superTokenMock), _flowSender, uint32(block.timestamp), _flowReceiver
- );
+ constantOutflowNFT.mockMint(address(superTokenMock), _flowSender, _flowReceiver, nftId);
- _assertEventApproval(address(constantOutflowNFTProxy), _flowSender, _approvedAccount, nftId);
+ _assertEventApproval(address(constantOutflowNFT), _flowSender, _approvedAccount, nftId);
- vm.prank(_flowSender);
- constantOutflowNFTProxy.approve(_approvedAccount, nftId);
+ vm.startPrank(_flowSender);
+ constantOutflowNFT.approve(_approvedAccount, nftId);
+ vm.stopPrank();
- _assertApprovalIsExpected(constantOutflowNFTProxy, nftId, _approvedAccount);
+ _assertApprovalIsExpected(constantOutflowNFT, nftId, _approvedAccount);
}
function testApproveThenBurn(address _flowSender, address _flowReceiver, address _approvedAccount) public {
uint256 nftId = testApprove(_flowSender, _flowReceiver, _approvedAccount);
- constantOutflowNFTProxy.mockBurn(nftId);
+ constantOutflowNFT.mockBurn(nftId);
- assertEq(constantOutflowNFTProxy.mockGetApproved(nftId), address(0));
+ assertEq(constantOutflowNFT.mockGetApproved(nftId), address(0));
}
function testSetApprovalForAll(address _tokenOwner, address _operator, bool _approved) public {
@@ -274,11 +175,11 @@ contract ConstantOutflowNFTTest is FlowNFTBaseTest {
vm.startPrank(_tokenOwner);
- _assertEventApprovalForAll(address(constantOutflowNFTProxy), _tokenOwner, _operator, _approved);
- constantOutflowNFTProxy.setApprovalForAll(_operator, _approved);
+ _assertEventApprovalForAll(address(constantOutflowNFT), _tokenOwner, _operator, _approved);
+ constantOutflowNFT.setApprovalForAll(_operator, _approved);
vm.stopPrank();
- _assertOperatorApprovalIsExpected(constantOutflowNFTProxy, _tokenOwner, _operator, _approved);
+ _assertOperatorApprovalIsExpected(constantOutflowNFT, _tokenOwner, _operator, _approved);
}
function testCreateFlowMintsOutflowAndInflowNFTsAndEmitsTransferEvents() public {
@@ -295,8 +196,8 @@ contract ConstantOutflowNFTTest is FlowNFTBaseTest {
_helperCreateFlowAndAssertNFTInvariants(flowSender, flowReceiver, flowRate);
uint256 nftId = _helperGetNFTID(address(superTokenMock), flowSender, flowReceiver);
- _assertEventMetadataUpdate(address(constantOutflowNFTProxy), nftId);
- _assertEventMetadataUpdate(address(constantInflowNFTProxy), nftId);
+ _assertEventMetadataUpdate(address(constantOutflowNFT), nftId);
+ _assertEventMetadataUpdate(address(constantInflowNFT), nftId);
vm.prank(flowSender);
superTokenMock.updateFlow(flowReceiver, flowRate + 333);
@@ -314,9 +215,9 @@ contract ConstantOutflowNFTTest is FlowNFTBaseTest {
uint256 nftId = _helperGetNFTID(address(superTokenMock), flowSender, flowReceiver);
- _assertEventTransfer(address(constantInflowNFTProxy), flowReceiver, address(0), nftId);
+ _assertEventTransfer(address(constantInflowNFT), flowReceiver, address(0), nftId);
- _assertEventTransfer(address(constantOutflowNFTProxy), flowSender, address(0), nftId);
+ _assertEventTransfer(address(constantOutflowNFT), flowSender, address(0), nftId);
vm.prank(flowSender);
superTokenMock.deleteFlow(flowSender, flowReceiver);
@@ -333,7 +234,7 @@ contract ConstantOutflowNFTTest is FlowNFTBaseTest {
uint256 nftId = _helperGetNFTID(address(superTokenMock), flowSender, flowReceiver);
assertEq(
- constantOutflowNFTProxy.tokenURI(nftId),
+ constantOutflowNFT.tokenURI(nftId),
string(
abi.encodePacked(
"https://nft.superfluid.finance/cfa/v2/getmeta?flowRate=",
diff --git a/packages/ethereum-contracts/test/foundry/superfluid/ERC721.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/ERC721.t.sol
new file mode 100644
index 0000000000..21951ba2a9
--- /dev/null
+++ b/packages/ethereum-contracts/test/foundry/superfluid/ERC721.t.sol
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { IERC721Metadata } from "@openzeppelin/contracts/interfaces/IERC721Metadata.sol";
+import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.sol";
+import { ConstantOutflowNFTMock, ConstantInflowNFTMock } from "../../../contracts/mocks/CFAv1NFTMock.sol";
+import { PoolAdminNFTMock, PoolMemberNFTMock } from "../../../contracts/mocks/PoolNFTMock.sol";
+import { ConstantOutflowNFT, IConstantOutflowNFT } from "../../../contracts/superfluid/ConstantOutflowNFT.sol";
+import { ConstantInflowNFT, IConstantInflowNFT } from "../../../contracts/superfluid/ConstantInflowNFT.sol";
+import { TestToken } from "../../../contracts/utils/TestToken.sol";
+import { PoolAdminNFT, IPoolAdminNFT } from "../../../contracts/agreements/gdav1/PoolAdminNFT.sol";
+import { PoolMemberNFT, IPoolMemberNFT } from "../../../contracts/agreements/gdav1/PoolMemberNFT.sol";
+import { UUPSProxy } from "../../../contracts/upgradability/UUPSProxy.sol";
+import { UUPSProxiable } from "../../../contracts/upgradability/UUPSProxiable.sol";
+import { SuperToken, SuperTokenMock } from "../../../contracts/mocks/SuperTokenMock.sol";
+
+contract ERC721IntegrationTest is FoundrySuperfluidTester {
+ string internal constant POOL_MEMBER_NFT_NAME_TEMPLATE = "Pool Member NFT";
+ string internal constant POOL_MEMBER_NFT_SYMBOL_TEMPLATE = "PMF";
+ string internal constant POOL_ADMIN_NFT_NAME_TEMPLATE = "Pool Admin NFT";
+ string internal constant POOL_ADMIN_NFT_SYMBOL_TEMPLATE = "PAF";
+ string internal constant OUTFLOW_NFT_NAME_TEMPLATE = "Constant Outflow NFT";
+ string internal constant OUTFLOW_NFT_SYMBOL_TEMPLATE = "COF";
+ string internal constant INFLOW_NFT_NAME_TEMPLATE = "Constant Inflow NFT";
+ string internal constant INFLOW_NFT_SYMBOL_TEMPLATE = "CIF";
+
+ SuperTokenMock public superTokenMock;
+
+ ConstantOutflowNFTMock public constantOutflowNFTLogic;
+ ConstantInflowNFTMock public constantInflowNFTLogic;
+
+ ConstantOutflowNFTMock public constantOutflowNFT;
+ ConstantInflowNFTMock public constantInflowNFT;
+
+ PoolMemberNFTMock public poolMemberNFTLogic;
+ PoolAdminNFTMock public poolAdminNFTLogic;
+
+ PoolMemberNFTMock public poolMemberNFT;
+ PoolAdminNFTMock public poolAdminNFT;
+
+ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
+
+ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
+
+ event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
+
+ event MetadataUpdate(uint256 _tokenId);
+
+ constructor() FoundrySuperfluidTester(5) { }
+
+ function setUp() public virtual override {
+ super.setUp();
+
+ // Deploy Flow NFTs
+
+ // deploy outflow NFT contract
+ UUPSProxy outflowProxy = new UUPSProxy();
+
+ // deploy inflow NFT contract
+ UUPSProxy inflowProxy = new UUPSProxy();
+
+ // we deploy mock NFT contracts for the tests to access internal functions
+ constantOutflowNFTLogic = new ConstantOutflowNFTMock(
+ sf.host,
+ IConstantInflowNFT(address(inflowProxy))
+ );
+ constantInflowNFTLogic = new ConstantInflowNFTMock(
+ sf.host,
+ IConstantOutflowNFT(address(outflowProxy))
+ );
+
+ constantOutflowNFTLogic.castrate();
+ constantInflowNFTLogic.castrate();
+
+ // initialize proxy to point at logic
+ outflowProxy.initializeProxy(address(constantOutflowNFTLogic));
+
+ // initialize proxy to point at logic
+ inflowProxy.initializeProxy(address(constantInflowNFTLogic));
+
+ constantOutflowNFT = ConstantOutflowNFTMock(address(outflowProxy));
+ constantInflowNFT = ConstantInflowNFTMock(address(inflowProxy));
+
+ constantOutflowNFT.initialize(OUTFLOW_NFT_NAME_TEMPLATE, OUTFLOW_NFT_SYMBOL_TEMPLATE);
+
+ constantInflowNFT.initialize(INFLOW_NFT_NAME_TEMPLATE, INFLOW_NFT_SYMBOL_TEMPLATE);
+
+ // Deploy Pool NFTs
+
+ // deploy pool member NFT contract
+ UUPSProxy poolMemberProxy = new UUPSProxy();
+
+ // deploy pool admin NFT contract
+ UUPSProxy poolAdminProxy = new UUPSProxy();
+
+ // we deploy mock NFT contracts for the tests to access internal functions
+ poolMemberNFTLogic = new PoolMemberNFTMock(sf.host);
+ poolAdminNFTLogic = new PoolAdminNFTMock(sf.host);
+
+ poolMemberNFTLogic.castrate();
+ poolAdminNFTLogic.castrate();
+
+ // initialize proxy to point at logic
+ poolMemberProxy.initializeProxy(address(poolMemberNFTLogic));
+
+ // initialize proxy to point at logic
+ poolAdminProxy.initializeProxy(address(poolAdminNFTLogic));
+
+ poolMemberNFT = PoolMemberNFTMock(address(poolMemberProxy));
+ poolAdminNFT = PoolAdminNFTMock(address(poolAdminProxy));
+
+ poolMemberNFT.initialize(POOL_MEMBER_NFT_NAME_TEMPLATE, POOL_MEMBER_NFT_SYMBOL_TEMPLATE);
+
+ poolAdminNFT.initialize(POOL_ADMIN_NFT_NAME_TEMPLATE, POOL_ADMIN_NFT_SYMBOL_TEMPLATE);
+
+ // Deploy TestToken
+ TestToken testTokenMock = new TestToken(
+ "Mock Test",
+ "MT",
+ 18,
+ 100000000
+ );
+
+ // Deploy SuperToken proxy
+ UUPSProxy superTokenMockProxy = new UUPSProxy();
+
+ // deploy super token mock for testing with mock constant outflow/inflow NFTs
+ SuperTokenMock superTokenMockLogic = new SuperTokenMock(
+ sf.host,
+ 0,
+ IConstantOutflowNFT(address(constantOutflowNFT)),
+ IConstantInflowNFT(address(constantInflowNFT)),
+ IPoolAdminNFT(address(poolAdminNFT)),
+ IPoolMemberNFT(address(poolMemberNFT))
+ );
+ superTokenMockProxy.initializeProxy(address(superTokenMockLogic));
+
+ superTokenMock = SuperTokenMock(address(superTokenMockProxy));
+ superTokenMock.initialize(testTokenMock, 18, "Super Mock Test", "MTx");
+
+ // mint tokens to test accounts
+ for (uint256 i = 0; i < N_TESTERS; i++) {
+ superTokenMock.mintInternal(TEST_ACCOUNTS[i], INIT_SUPER_TOKEN_BALANCE, "0x", "0x");
+ }
+ }
+
+ // If we properly create mock contracts for the base NFT contracts
+ // then we can just use the base NFT contracts for testing these reverts
+ // and the other functionality of ERC721 here
+ // Instead of testing each of the NFT contracts separately
+ function _helperRevertIfOwnerOf(IERC721Metadata _nftContract, uint256 _tokenId, bytes4 _errorSelector) internal {
+ vm.expectRevert(_errorSelector);
+ _nftContract.ownerOf(_tokenId);
+ }
+
+ function _helperRevertIfGetApproved(IERC721Metadata _nftContract, uint256 _tokenId, bytes4 _errorSelector)
+ internal
+ {
+ vm.expectRevert(_errorSelector);
+ _nftContract.getApproved(_tokenId);
+ }
+
+ function _helperRevertIfTransferFrom(
+ IERC721Metadata _nftContract,
+ address _caller,
+ address _from,
+ address _to,
+ uint256 _tokenId,
+ bytes4 _errorSelector
+ ) internal {
+ vm.startPrank(_caller);
+ vm.expectRevert(_errorSelector);
+ _nftContract.transferFrom(_from, _to, _tokenId);
+ vm.stopPrank();
+ }
+
+ function _helperRevertIfSafeTransferFrom(
+ IERC721Metadata _nftContract,
+ address _caller,
+ address _from,
+ address _to,
+ uint256 _tokenId,
+ bytes4 _errorSelector
+ ) internal {
+ vm.startPrank(_caller);
+ vm.expectRevert(_errorSelector);
+ _nftContract.safeTransferFrom(_from, _to, _tokenId);
+ vm.stopPrank();
+ }
+
+ function _helperRevertIfSafeTransferFrom(
+ IERC721Metadata _nftContract,
+ address _caller,
+ address _from,
+ address _to,
+ uint256 _tokenId,
+ bytes memory _data,
+ bytes4 _errorSelector
+ ) internal {
+ vm.startPrank(_caller);
+ vm.expectRevert(_errorSelector);
+ _nftContract.safeTransferFrom(_from, _to, _tokenId, _data);
+ vm.stopPrank();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ Assertion Helpers
+ //////////////////////////////////////////////////////////////////////////*/
+ function _assertOwnerOfIsExpected(
+ IERC721Metadata _nftContract,
+ uint256 _tokenId,
+ address _expectedOwner,
+ string memory _message
+ ) public {
+ // we use mockOwnerOf to overcome the CFA_NFT_INVALID_TOKEN_ID error
+ address owner = PoolAdminNFTMock(address(_nftContract)).mockOwnerOf(_tokenId);
+
+ assertEq(owner, _expectedOwner, _message);
+ }
+
+ function _assertApprovalIsExpected(IERC721Metadata _nftContract, uint256 _tokenId, address _expectedApproved)
+ public
+ {
+ address approved = _nftContract.getApproved(_tokenId);
+
+ assertEq(approved, _expectedApproved);
+ }
+
+ function _assertOperatorApprovalIsExpected(
+ IERC721Metadata _nftContract,
+ address _expectedOwner,
+ address _expectedOperator,
+ bool _expectedOperatorApproval
+ ) public {
+ bool operatorApproval = _nftContract.isApprovedForAll(_expectedOwner, _expectedOperator);
+
+ assertEq(operatorApproval, _expectedOperatorApproval);
+ }
+
+ function _assertEventTransfer(
+ address _emittingAddress,
+ address _expectedFrom,
+ address _expectedTo,
+ uint256 _expectedTokenId
+ ) public {
+ vm.expectEmit(true, true, true, false, _emittingAddress);
+
+ emit Transfer(_expectedFrom, _expectedTo, _expectedTokenId);
+ }
+
+ function _assertEventApproval(
+ address _emittingAddress,
+ address _expectedOwner,
+ address _expectedApproved,
+ uint256 _expectedTokenId
+ ) public {
+ vm.expectEmit(true, true, true, false, _emittingAddress);
+
+ emit Approval(_expectedOwner, _expectedApproved, _expectedTokenId);
+ }
+
+ function _assertEventApprovalForAll(
+ address _emittingAddress,
+ address _expectedOwner,
+ address _expectedOperator,
+ bool _expectedApproved
+ ) public {
+ vm.expectEmit(true, true, false, true, _emittingAddress);
+
+ emit ApprovalForAll(_expectedOwner, _expectedOperator, _expectedApproved);
+ }
+
+ function _assertEventMetadataUpdate(address _emittingAddress, uint256 _tokenId) public {
+ vm.expectEmit(true, false, false, false, _emittingAddress);
+
+ emit MetadataUpdate(_tokenId);
+ }
+}
diff --git a/packages/ethereum-contracts/test/foundry/superfluid/FlowNFTBase.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/FlowNFTBase.t.sol
index 3c25f69be8..051dd52ebe 100644
--- a/packages/ethereum-contracts/test/foundry/superfluid/FlowNFTBase.t.sol
+++ b/packages/ethereum-contracts/test/foundry/superfluid/FlowNFTBase.t.sol
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: AGPLv3
pragma solidity 0.8.19;
+import { IERC165, IERC721, IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
+import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { UUPSProxy } from "../../../contracts/upgradability/UUPSProxy.sol";
import { UUPSProxiable } from "../../../contracts/upgradability/UUPSProxiable.sol";
import {
@@ -10,106 +12,244 @@ import {
IConstantOutflowNFT
} from "../../../contracts/superfluid/ConstantOutflowNFT.sol";
import { ConstantInflowNFT, IConstantInflowNFT } from "../../../contracts/superfluid/ConstantInflowNFT.sol";
+import { IPoolAdminNFT } from "../../../contracts/agreements/gdav1/PoolAdminNFT.sol";
+import { IPoolMemberNFT } from "../../../contracts/agreements/gdav1/PoolMemberNFT.sol";
import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol";
import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.sol";
import { ConstantOutflowNFTMock, ConstantInflowNFTMock } from "../../../contracts/mocks/CFAv1NFTMock.sol";
import { SuperToken, SuperTokenMock } from "../../../contracts/mocks/SuperTokenMock.sol";
+import { FlowNFTBaseMock } from "../../../contracts/mocks/CFAv1NFTMock.sol";
import { TestToken } from "../../../contracts/utils/TestToken.sol";
import {
FlowNFTBaseStorageLayoutMock,
ConstantInflowNFTStorageLayoutMock,
ConstantOutflowNFTStorageLayoutMock
} from "../../../contracts/mocks/CFAv1NFTUpgradabilityMock.sol";
+import { ERC721IntegrationTest } from "./ERC721.t.sol";
-abstract contract FlowNFTBaseTest is FoundrySuperfluidTester {
+abstract contract FlowNFTBaseTest is ERC721IntegrationTest {
+ using Strings for uint256;
using SuperTokenV1Library for SuperTokenMock;
using SuperTokenV1Library for SuperToken;
- string constant internal OUTFLOW_NFT_NAME_TEMPLATE = "Constant Outflow NFT";
- string constant internal OUTFLOW_NFT_SYMBOL_TEMPLATE = "COF";
- string constant internal INFLOW_NFT_NAME_TEMPLATE = "Constant Inflow NFT";
- string constant internal INFLOW_NFT_SYMBOL_TEMPLATE = "CIF";
+ string public constant NAME = "Flow NFT Base";
+ string public constant SYMBOL = "FNFTB";
- SuperTokenMock public superTokenMock;
+ FlowNFTBaseMock public flowNFTBaseMock;
- ConstantOutflowNFTMock public constantOutflowNFTLogic;
- ConstantInflowNFTMock public constantInflowNFTLogic;
+ function setUp() public virtual override {
+ super.setUp();
+ flowNFTBaseMock = new FlowNFTBaseMock(sf.host);
+ flowNFTBaseMock.initialize(NAME, SYMBOL);
+ }
- ConstantOutflowNFTMock public constantOutflowNFTProxy;
- ConstantInflowNFTMock public constantInflowNFTProxy;
+ /*//////////////////////////////////////////////////////////////////////////
+ Revert Tests
+ //////////////////////////////////////////////////////////////////////////*/
- event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
+ function testRevertIfContractAlreadyInitialized() public {
+ vm.expectRevert("Initializable: contract is already initialized");
- event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
+ flowNFTBaseMock.initialize(NAME, SYMBOL);
+ }
- event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
+ function testRevertIfOwnerOfCalledForNonExistentToken(uint256 tokenId) public {
+ _helperRevertIfOwnerOf(flowNFTBaseMock, tokenId, IFlowNFTBase.CFA_NFT_INVALID_TOKEN_ID.selector);
+ }
- event MetadataUpdate(uint256 _tokenId);
+ function testRevertIfGetApprovedCalledForNonExistentToken(uint256 tokenId) public {
+ _helperRevertIfGetApproved(flowNFTBaseMock, tokenId, IFlowNFTBase.CFA_NFT_INVALID_TOKEN_ID.selector);
+ }
- constructor() FoundrySuperfluidTester(5) { }
+ function testRevertIfSetApprovalForAllOperatorApproveToCaller(address _flowSender) public {
+ vm.assume(_flowSender != address(0));
- function setUp() public virtual override {
- super.setUp();
+ vm.startPrank(_flowSender);
+ vm.expectRevert(IFlowNFTBase.CFA_NFT_APPROVE_TO_CALLER.selector);
+ flowNFTBaseMock.setApprovalForAll(_flowSender, true);
+ vm.stopPrank();
+ }
- // deploy outflow NFT contract
- UUPSProxy outflowProxy = new UUPSProxy();
+ function testRevertIfApproveToCurrentOwner(address _flowSender, address _flowReceiver) public {
+ _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
- // deploy inflow NFT contract
- UUPSProxy inflowProxy = new UUPSProxy();
+ uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
+ flowNFTBaseMock.mockMint(address(superTokenMock), _flowSender, _flowReceiver);
- // we deploy mock NFT contracts for the tests to access internal functions
- constantOutflowNFTLogic = new ConstantOutflowNFTMock(
- sf.host,
- IConstantInflowNFT(address(inflowProxy))
+ vm.startPrank(_flowSender);
+ vm.expectRevert(IFlowNFTBase.CFA_NFT_APPROVE_TO_CURRENT_OWNER.selector);
+ flowNFTBaseMock.approve(_flowSender, nftId);
+ vm.stopPrank();
+ }
+
+ function testRevertIfApproveAsNonOwner(
+ address _flowSender,
+ address _flowReceiver,
+ address _approver,
+ address _approvedAccount
+ ) public {
+ _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
+ /// @dev _flowSender is owner of outflow NFT
+ vm.assume(_approver != _flowSender);
+ vm.assume(_approvedAccount != _flowSender);
+
+ uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
+ flowNFTBaseMock.mockMint(address(superTokenMock), _flowSender, _flowReceiver);
+ vm.expectRevert(IFlowNFTBase.CFA_NFT_APPROVE_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector);
+ vm.startPrank(_approver);
+ flowNFTBaseMock.approve(_approvedAccount, nftId);
+ vm.stopPrank();
+ }
+
+ function testRevertIfTransferFrom(address _flowSender, address _flowReceiver) public {
+ _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
+ uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
+ flowNFTBaseMock.mockMint(address(superTokenMock), _flowSender, _flowReceiver);
+
+ _helperRevertIfTransferFrom(
+ flowNFTBaseMock,
+ _flowSender,
+ _flowSender,
+ _flowReceiver,
+ nftId,
+ IFlowNFTBase.CFA_NFT_TRANSFER_IS_NOT_ALLOWED.selector
);
- constantInflowNFTLogic = new ConstantInflowNFTMock(
- sf.host,
- IConstantOutflowNFT(address(outflowProxy))
+ }
+
+ function testRevertIfSafeTransferFrom(address _flowSender, address _flowReceiver) public {
+ _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
+ uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
+ flowNFTBaseMock.mockMint(address(superTokenMock), _flowSender, _flowReceiver);
+
+ _helperRevertIfSafeTransferFrom(
+ flowNFTBaseMock,
+ _flowSender,
+ _flowSender,
+ _flowReceiver,
+ nftId,
+ IFlowNFTBase.CFA_NFT_TRANSFER_IS_NOT_ALLOWED.selector
);
+ }
- constantOutflowNFTLogic.castrate();
- constantInflowNFTLogic.castrate();
+ function testRevertIfTransferFromWithData(address _flowSender, address _flowReceiver) public {
+ _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
+ uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
+ flowNFTBaseMock.mockMint(address(superTokenMock), _flowSender, _flowReceiver);
+
+ _helperRevertIfSafeTransferFrom(
+ flowNFTBaseMock,
+ _flowSender,
+ _flowSender,
+ _flowReceiver,
+ nftId,
+ "0x",
+ IFlowNFTBase.CFA_NFT_TRANSFER_IS_NOT_ALLOWED.selector
+ );
+ }
- // initialize proxy to point at logic
- outflowProxy.initializeProxy(address(constantOutflowNFTLogic));
+ function testRevertIfTransferFromAsNonOwner(address _flowSender, address _flowReceiver) public {
+ _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
+ uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
+ flowNFTBaseMock.mockMint(address(superTokenMock), _flowSender, _flowReceiver);
+
+ _helperRevertIfTransferFrom(
+ flowNFTBaseMock,
+ _flowReceiver,
+ _flowSender,
+ _flowReceiver,
+ nftId,
+ IFlowNFTBase.CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector
+ );
+ }
- // initialize proxy to point at logic
- inflowProxy.initializeProxy(address(constantInflowNFTLogic));
+ function testRevertIfSafeTransferFromAsNonOwner(address _flowSender, address _flowReceiver) public {
+ _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
+ uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
+ flowNFTBaseMock.mockMint(address(superTokenMock), _flowSender, _flowReceiver);
+
+ _helperRevertIfSafeTransferFrom(
+ flowNFTBaseMock,
+ _flowReceiver,
+ _flowSender,
+ _flowReceiver,
+ nftId,
+ IFlowNFTBase.CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector
+ );
+ }
- constantOutflowNFTProxy = ConstantOutflowNFTMock(address(outflowProxy));
- constantInflowNFTProxy = ConstantInflowNFTMock(address(inflowProxy));
+ function testRevertIfTransferFromWithDataAsNonOwner(address _flowSender, address _flowReceiver) public {
+ _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
+ uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
+ flowNFTBaseMock.mockMint(address(superTokenMock), _flowSender, _flowReceiver);
+
+ _helperRevertIfSafeTransferFrom(
+ flowNFTBaseMock,
+ _flowReceiver,
+ _flowSender,
+ _flowReceiver,
+ nftId,
+ "0x",
+ IFlowNFTBase.CFA_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector
+ );
+ }
- constantOutflowNFTProxy.initialize("Constant Outflow NFT", "COF");
+ /*//////////////////////////////////////////////////////////////////////////
+ Passing Tests
+ //////////////////////////////////////////////////////////////////////////*/
+ function testContractSupportsExpectedInterfaces() public {
+ assertEq(flowNFTBaseMock.supportsInterface(type(IERC165).interfaceId), true);
+ assertEq(flowNFTBaseMock.supportsInterface(type(IERC721).interfaceId), true);
+ assertEq(flowNFTBaseMock.supportsInterface(type(IERC721Metadata).interfaceId), true);
+ }
- constantInflowNFTProxy.initialize("Constant Inflow NFT", "CIF");
+ function testNFTBalanceOfIsAlwaysOne(address _owner) public {
+ assertEq(flowNFTBaseMock.balanceOf(_owner), 1);
+ }
- // Deploy TestToken
- TestToken testTokenMock = new TestToken(
- "Mock Test",
- "MT",
- 18,
- 100000000
- );
+ function testHostIsProperlySetInConstructor() public {
+ assertEq(address(flowNFTBaseMock.HOST()), address(sf.host));
+ }
- // Deploy SuperToken proxy
- UUPSProxy superTokenMockProxy = new UUPSProxy();
+ function testCFAv1IsProperlySetInConstructor() public {
+ assertEq(address(flowNFTBaseMock.CONSTANT_FLOW_AGREEMENT_V1()), address(sf.cfa));
+ }
- // deploy super token mock for testing with mock constant outflow/inflow NFTs
- SuperTokenMock superTokenMockLogic = new SuperTokenMock(
- sf.host,
- 0,
- IConstantOutflowNFT(address(constantOutflowNFTProxy)),
- IConstantInflowNFT(address(constantInflowNFTProxy))
- );
- superTokenMockProxy.initializeProxy(address(superTokenMockLogic));
+ function testGDAv1IsProperlySetInConstructor() public {
+ assertEq(address(flowNFTBaseMock.GENERAL_DISTRIBUTION_AGREEMENT_V1()), address(sf.gda));
+ }
- superTokenMock = SuperTokenMock(address(superTokenMockProxy));
- superTokenMock.initialize(testTokenMock, 18, "Super Mock Test", "MTx");
+ function testNFTMetadataIsProperlyInitialized() public {
+ assertEq(flowNFTBaseMock.name(), NAME);
+ assertEq(flowNFTBaseMock.symbol(), SYMBOL);
+ }
- // mint tokens to test accounts
- for (uint256 i = 0; i < N_TESTERS; i++) {
- superTokenMock.mintInternal(TEST_ACCOUNTS[i], INIT_SUPER_TOKEN_BALANCE, "0x", "0x");
- }
+ function testTriggerMetadataUpdate(uint256 tokenId) public {
+ _assertEventMetadataUpdate(address(flowNFTBaseMock), tokenId);
+ flowNFTBaseMock.triggerMetadataUpdate(tokenId);
+ }
+
+ function testTokenURI(uint256 tokenId) public {
+ assertEq(flowNFTBaseMock.tokenURI(tokenId), string(abi.encodePacked("tokenId=", tokenId.toString())));
+ }
+
+ function testApprove(address _flowSender, address _flowReceiver, address _approvedAccount)
+ public
+ virtual
+ returns (uint256 nftId)
+ {
+ _assumeSenderNEQReceiverAndNeitherAreZeroAddress(_flowSender, _flowReceiver);
+ vm.assume(_flowSender != _approvedAccount);
+
+ nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
+ flowNFTBaseMock.mockMint(address(superTokenMock), _flowSender, _flowReceiver);
+
+ _assertEventApproval(address(flowNFTBaseMock), _flowSender, _approvedAccount, nftId);
+
+ vm.startPrank(_flowSender);
+ flowNFTBaseMock.approve(_approvedAccount, nftId);
+ vm.stopPrank();
+
+ _assertApprovalIsExpected(flowNFTBaseMock, nftId, _approvedAccount);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -122,7 +262,7 @@ abstract contract FlowNFTBaseTest is FoundrySuperfluidTester {
uint32 _expectedFlowStartDate,
address _expectedFlowReceiver
) public {
- FlowNFTBase.FlowNFTData memory flowData = constantOutflowNFTProxy.flowDataByTokenId(_tokenId);
+ FlowNFTBase.FlowNFTData memory flowData = constantOutflowNFT.flowDataByTokenId(_tokenId);
assertEq(flowData.superToken, _expectedSuperToken);
@@ -136,82 +276,20 @@ abstract contract FlowNFTBaseTest is FoundrySuperfluidTester {
assertEq(flowData.flowReceiver, _expectedFlowReceiver);
// assert owner of outflow nft equal to expected flow sender
- _assertOwnerOf(constantOutflowNFTProxy, _tokenId, _expectedFlowSender, true);
+ _assertOwnerOfIsExpected(
+ constantOutflowNFT, _tokenId, _expectedFlowSender, "ConstantOutflowNFT: owner of COF nft not as expected"
+ );
// assert owner of inflow nft equal to expected flow receiver
- _assertOwnerOf(constantInflowNFTProxy, _tokenId, _expectedFlowReceiver, false);
+ _assertOwnerOfIsExpected(
+ constantInflowNFT, _tokenId, _expectedFlowReceiver, "ConstantInflowNFT: owner of COF nft not as expected"
+ );
}
function _assertNFTFlowDataStateIsEmpty(uint256 _tokenId) public {
_assertNFTFlowDataStateIsExpected(_tokenId, address(0), address(0), 0, address(0));
}
- function _assertOwnerOf(FlowNFTBase _nftContract, uint256 _tokenId, address _expectedOwner, bool _isOutflow)
- public
- {
- address actualOwner = _isOutflow
- ? ConstantOutflowNFTMock(address(_nftContract)).mockOwnerOf(_tokenId)
- : ConstantInflowNFTMock(address(_nftContract)).mockOwnerOf(_tokenId);
-
- assertEq(actualOwner, _expectedOwner);
- }
-
- function _assertApprovalIsExpected(FlowNFTBase _nftContract, uint256 _tokenId, address _expectedApproved) public {
- address approved = _nftContract.getApproved(_tokenId);
-
- assertEq(approved, _expectedApproved);
- }
-
- function _assertOperatorApprovalIsExpected(
- FlowNFTBase _nftContract,
- address _expectedOwner,
- address _expectedOperator,
- bool _expectedOperatorApproval
- ) public {
- bool operatorApproval = _nftContract.isApprovedForAll(_expectedOwner, _expectedOperator);
-
- assertEq(operatorApproval, _expectedOperatorApproval);
- }
-
- function _assertEventTransfer(
- address _emittingAddress,
- address _expectedFrom,
- address _expectedTo,
- uint256 _expectedTokenId
- ) public {
- vm.expectEmit(true, true, true, false, _emittingAddress);
-
- emit Transfer(_expectedFrom, _expectedTo, _expectedTokenId);
- }
-
- function _assertEventApproval(
- address _emittingAddress,
- address _expectedOwner,
- address _expectedApproved,
- uint256 _expectedTokenId
- ) public {
- vm.expectEmit(true, true, true, false, _emittingAddress);
-
- emit Approval(_expectedOwner, _expectedApproved, _expectedTokenId);
- }
-
- function _assertEventApprovalForAll(
- address _emittingAddress,
- address _expectedOwner,
- address _expectedOperator,
- bool _expectedApproved
- ) public {
- vm.expectEmit(true, true, false, true, _emittingAddress);
-
- emit ApprovalForAll(_expectedOwner, _expectedOperator, _expectedApproved);
- }
-
- function _assertEventMetadataUpdate(address _emittingAddress, uint256 _tokenId) public {
- vm.expectEmit(true, false, false, false, _emittingAddress);
-
- emit MetadataUpdate(_tokenId);
- }
-
/*//////////////////////////////////////////////////////////////////////////
Helper Functions
//////////////////////////////////////////////////////////////////////////*/
@@ -220,7 +298,7 @@ abstract contract FlowNFTBaseTest is FoundrySuperfluidTester {
view
returns (uint256)
{
- return constantOutflowNFTProxy.getTokenId(_superToken, _flowSender, _flowReceiver);
+ return constantOutflowNFT.getTokenId(_superToken, _flowSender, _flowReceiver);
}
function _helperCreateFlowAndAssertNFTInvariants(address _flowSender, address _flowReceiver, int96 _flowRate)
@@ -228,9 +306,9 @@ abstract contract FlowNFTBaseTest is FoundrySuperfluidTester {
{
uint256 nftId = _helperGetNFTID(address(superTokenMock), _flowSender, _flowReceiver);
- _assertEventTransfer(address(constantOutflowNFTProxy), address(0), _flowSender, nftId);
+ _assertEventTransfer(address(constantOutflowNFT), address(0), _flowSender, nftId);
- _assertEventTransfer(address(constantInflowNFTProxy), address(0), _flowReceiver, nftId);
+ _assertEventTransfer(address(constantInflowNFT), address(0), _flowReceiver, nftId);
vm.startPrank(_flowSender);
superTokenMock.createFlow(_flowReceiver, _flowRate);
@@ -256,55 +334,40 @@ abstract contract FlowNFTBaseTest is FoundrySuperfluidTester {
function _assumeCallerIsNotOtherAddress(address caller, address otherAddress) public pure {
vm.assume(caller != otherAddress);
}
-
- /*//////////////////////////////////////////////////////////////////////////
- Passing Tests
- //////////////////////////////////////////////////////////////////////////*/
- function testCFAv1IsProperlySetDuringInitialization() public {
- assertEq(address(constantOutflowNFTProxy.CONSTANT_FLOW_AGREEMENT_V1()), address(sf.cfa));
- assertEq(address(constantInflowNFTProxy.CONSTANT_FLOW_AGREEMENT_V1()), address(sf.cfa));
- }
}
-/// @title ConstantFAv1NFTsUpgradabilityTest
+/// @title CFAv1NFTUpgradabilityTest
/// @author Superfluid
-/// @notice Used for testing storage layout of CFAv1 NFT contracts
-contract ConstantFAv1NFTsUpgradabilityTest is FlowNFTBaseTest {
+/// @notice Used for testing storage layout and upgradability of CFAv1 NFT contracts
+contract CFAv1NFTUpgradabilityTest is FlowNFTBaseTest {
function setUp() public override {
super.setUp();
}
- /*//////////////////////////////////////////////////////////////////////////
- Assertion Helpers
- //////////////////////////////////////////////////////////////////////////*/
- function _assertExpectedLogicContractAddress(UUPSProxiable _proxy, address _expectedLogicContract) public {
- assertEq(_proxy.getCodeAddress(), _expectedLogicContract);
- }
-
/*//////////////////////////////////////////////////////////////////////////
Storage Layout Tests
//////////////////////////////////////////////////////////////////////////*/
- function testStorageLayoutOfFlowNFTBase() public {
+ function testFlowNFTBaseStorageLayout() public {
FlowNFTBaseStorageLayoutMock flowNFTBaseStorageLayoutMock = new FlowNFTBaseStorageLayoutMock(
sf.host
);
flowNFTBaseStorageLayoutMock.validateStorageLayout();
}
- function testStorageLayoutOfConstantInflowNFT() public {
+ function testConstantInflowNFTStorageLayout() public {
ConstantInflowNFTStorageLayoutMock constantInflowNFTBaseStorageLayoutMock =
new ConstantInflowNFTStorageLayoutMock(
sf.host,
- constantOutflowNFTProxy
+ constantOutflowNFT
);
constantInflowNFTBaseStorageLayoutMock.validateStorageLayout();
}
- function testStorageLayoutOfConstantOutflowNFT() public {
+ function testConstantOutflowNFTStorageLayout() public {
ConstantOutflowNFTStorageLayoutMock constantOutflowNFTBaseStorageLayoutMock =
new ConstantOutflowNFTStorageLayoutMock(
sf.host,
- constantInflowNFTProxy
+ constantInflowNFT
);
constantOutflowNFTBaseStorageLayoutMock.validateStorageLayout();
}
@@ -312,41 +375,41 @@ contract ConstantFAv1NFTsUpgradabilityTest is FlowNFTBaseTest {
/*//////////////////////////////////////////////////////////////////////////
Revert Tests
//////////////////////////////////////////////////////////////////////////*/
- function testRevertNFTContractsCannotBeUpgradedByNonHost(address notSuperTokenFactory) public {
+ function testRevertFlowNFTContractsCannotBeUpgradedByNonSuperTokenFactory(address notSuperTokenFactory) public {
vm.assume(notSuperTokenFactory != address(sf.superTokenFactory));
ConstantOutflowNFT newOutflowLogic = new ConstantOutflowNFT(
sf.host,
- constantInflowNFTProxy
+ constantInflowNFT
);
vm.expectRevert(IFlowNFTBase.CFA_NFT_ONLY_SUPER_TOKEN_FACTORY.selector);
vm.prank(notSuperTokenFactory);
- constantOutflowNFTProxy.updateCode(address(newOutflowLogic));
+ constantOutflowNFT.updateCode(address(newOutflowLogic));
ConstantInflowNFT newInflowLogic = new ConstantInflowNFT(
sf.host,
- constantOutflowNFTProxy
+ constantOutflowNFT
);
vm.expectRevert(IFlowNFTBase.CFA_NFT_ONLY_SUPER_TOKEN_FACTORY.selector);
vm.prank(notSuperTokenFactory);
- constantInflowNFTProxy.updateCode(address(newInflowLogic));
+ constantInflowNFT.updateCode(address(newInflowLogic));
}
/*//////////////////////////////////////////////////////////////////////////
Passing Tests
//////////////////////////////////////////////////////////////////////////*/
- function testNFTContractsCanBeUpgradedByHost() public {
+ function testFlowNFTContractsCanBeUpgradedBySuperTokenFactory() public {
ConstantOutflowNFT newOutflowLogic = new ConstantOutflowNFT(
sf.host,
- constantInflowNFTProxy
+ constantInflowNFT
);
vm.prank(address(sf.superTokenFactory));
- constantOutflowNFTProxy.updateCode(address(newOutflowLogic));
+ constantOutflowNFT.updateCode(address(newOutflowLogic));
ConstantInflowNFT newInflowLogic = new ConstantInflowNFT(
sf.host,
- constantOutflowNFTProxy
+ constantOutflowNFT
);
vm.prank(address(sf.superTokenFactory));
- constantInflowNFTProxy.updateCode(address(newInflowLogic));
+ constantInflowNFT.updateCode(address(newInflowLogic));
}
}
diff --git a/packages/ethereum-contracts/test/foundry/superfluid/PoolAdminNFT.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/PoolAdminNFT.t.sol
new file mode 100644
index 0000000000..53f5d9bec0
--- /dev/null
+++ b/packages/ethereum-contracts/test/foundry/superfluid/PoolAdminNFT.t.sol
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { IERC165, IERC721, IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
+import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
+import { PoolNFTBaseIntegrationTest, FakePool } from "./PoolNFTBase.t.sol";
+import { IPoolNFTBase } from "../../../contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol";
+import { IPoolAdminNFT } from "../../../contracts/interfaces/agreements/gdav1/IPoolAdminNFT.sol";
+import { ISuperfluidPool } from "../../../contracts/agreements/gdav1/SuperfluidPool.sol";
+
+contract PoolAdminNFTIntegrationTest is PoolNFTBaseIntegrationTest {
+ using Strings for uint256;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ Revert Tests
+ //////////////////////////////////////////////////////////////////////////*/
+
+ function testRevertIfTransferFromForPoolAdminNFT() public {
+ address poolAdmin = alice;
+ address receiver = bob;
+
+ ISuperfluidPool pool = sf.gda.createPool(superTokenMock, poolAdmin, poolConfig);
+ uint256 nftId = _helperGetPoolAdminNftId(address(pool), poolAdmin);
+
+ _helperRevertIfTransferFrom(
+ poolAdminNFT, poolAdmin, poolAdmin, receiver, nftId, IPoolNFTBase.POOL_NFT_TRANSFER_NOT_ALLOWED.selector
+ );
+ }
+
+ function testRevertIfMintingForFakePool() public {
+ FakePool pool = new FakePool(alice, address(superTokenMock));
+ vm.expectRevert(IPoolNFTBase.POOL_NFT_NOT_REGISTERED_POOL.selector);
+ poolAdminNFT.mockMint(address(pool));
+ }
+
+ function testRevertIfMintingForNotPool(address _pool) public {
+ vm.expectRevert();
+ poolAdminNFT.mockMint(_pool);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ Passing Tests
+ //////////////////////////////////////////////////////////////////////////*/
+
+ function testProxiableUUIDIsExpectedValue() public {
+ assertEq(
+ poolAdminNFT.proxiableUUID(), keccak256("org.superfluid-finance.contracts.PoolAdminNFT.implementation")
+ );
+ }
+
+ function testTokenURIForPoolAdminNFT(uint256 tokenId) public {
+ assertEq(poolAdminNFT.tokenURI(tokenId), string(abi.encodePacked(poolAdminNFT.baseURI())));
+ }
+}
diff --git a/packages/ethereum-contracts/test/foundry/superfluid/PoolMemberNFT.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/PoolMemberNFT.t.sol
new file mode 100644
index 0000000000..f8feedd2aa
--- /dev/null
+++ b/packages/ethereum-contracts/test/foundry/superfluid/PoolMemberNFT.t.sol
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { IERC165, IERC721, IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
+import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
+import { PoolNFTBaseIntegrationTest, FakePool } from "./PoolNFTBase.t.sol";
+import { IPoolNFTBase } from "../../../contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol";
+import { IPoolMemberNFT } from "../../../contracts/interfaces/agreements/gdav1/IPoolMemberNFT.sol";
+import { ISuperfluidPool } from "../../../contracts/agreements/gdav1/SuperfluidPool.sol";
+import { IGeneralDistributionAgreementV1 } from "../../../contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
+import "forge-std/Test.sol";
+
+contract PoolMemberNFTIntegrationTest is PoolNFTBaseIntegrationTest {
+ /*//////////////////////////////////////////////////////////////////////////
+ Revert Tests
+ //////////////////////////////////////////////////////////////////////////*/
+
+ function testRevertIfTransferFromForPoolMemberNFT(address _poolAdmin, address _member, address _receiver) public {
+ vm.assume(_poolAdmin != address(0));
+ vm.assume(_member != address(0));
+ vm.assume(_receiver != address(0));
+ vm.assume(_member != _receiver);
+
+ ISuperfluidPool pool = sf.gda.createPool(superTokenMock, _poolAdmin, poolConfig);
+ uint256 nftId = _helperGetPoolMemberNftId(address(pool), _member);
+
+ vm.startPrank(_poolAdmin);
+ pool.updateMemberUnits(_member, 1);
+ vm.stopPrank();
+
+ _helperRevertIfTransferFrom(
+ poolMemberNFT, _member, _member, _receiver, nftId, IPoolNFTBase.POOL_NFT_TRANSFER_NOT_ALLOWED.selector
+ );
+ }
+
+ function testRevertIfMintingForNotPool(address _pool, address _member) public {
+ vm.expectRevert();
+ poolMemberNFT.mockMint(_pool, _member);
+ }
+
+ function testRevertIfMintingForFakePool(address _admin, address _member) public {
+ vm.assume(_admin != address(0));
+ vm.assume(_member != address(0));
+ FakePool pool = new FakePool(_admin, address(superTokenMock));
+ vm.expectRevert(IPoolNFTBase.POOL_NFT_NOT_REGISTERED_POOL.selector);
+ poolMemberNFT.mockMint(address(pool), _member);
+ }
+
+ function testRevertIfMintingForZeroUnitMember() public {
+ address admin_ = alice;
+ address member = bob;
+ ISuperfluidPool pool = sf.gda.createPool(superTokenMock, admin_, poolConfig);
+ vm.expectRevert(IPoolMemberNFT.POOL_MEMBER_NFT_NO_UNITS.selector);
+ poolMemberNFT.mockMint(address(pool), member);
+ }
+
+ function testRevertIfBurningNFTOfMemberWithUnits(address _admin, address _member) public {
+ vm.assume(_admin != address(0));
+ vm.assume(_member != address(0));
+ ISuperfluidPool pool = sf.gda.createPool(superTokenMock, _admin, poolConfig);
+ uint256 nftId = _helperGetPoolMemberNftId(address(pool), _member);
+
+ vm.startPrank(_admin);
+ pool.updateMemberUnits(_member, 1);
+ vm.stopPrank();
+
+ vm.expectRevert(IPoolMemberNFT.POOL_MEMBER_NFT_HAS_UNITS.selector);
+ poolMemberNFT.mockBurn(nftId);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ Passing Tests
+ //////////////////////////////////////////////////////////////////////////*/
+
+ function testProxiableUUIDIsExpectedValue() public {
+ assertEq(
+ poolMemberNFT.proxiableUUID(), keccak256("org.superfluid-finance.contracts.PoolMemberNFT.implementation")
+ );
+ }
+
+ function testTokenURIForPoolMemberNFT(uint256 tokenId) public {
+ assertEq(poolMemberNFT.tokenURI(tokenId), string(abi.encodePacked(poolMemberNFT.baseURI())));
+ }
+}
diff --git a/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTBase.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTBase.t.sol
new file mode 100644
index 0000000000..8d3a7e43d4
--- /dev/null
+++ b/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTBase.t.sol
@@ -0,0 +1,408 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { IERC165, IERC721, IERC721Metadata } from "@openzeppelin/contracts/interfaces/IERC721Metadata.sol";
+import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
+import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol";
+import {
+ PoolNFTBaseStorageLayoutMock,
+ PoolAdminNFTStorageLayoutMock,
+ PoolMemberNFTStorageLayoutMock
+} from "../../../contracts/mocks/PoolNFTUpgradabilityMock.sol";
+import { IPoolNFTBase, PoolNFTBase } from "../../../contracts/agreements/gdav1/PoolNFTBase.sol";
+import { ConstantOutflowNFT, IConstantOutflowNFT } from "../../../contracts/superfluid/ConstantOutflowNFT.sol";
+import { ConstantInflowNFT, IConstantInflowNFT } from "../../../contracts/superfluid/ConstantInflowNFT.sol";
+import { TestToken } from "../../../contracts/utils/TestToken.sol";
+import { PoolAdminNFT, IPoolAdminNFT } from "../../../contracts/agreements/gdav1/PoolAdminNFT.sol";
+import { PoolMemberNFT, IPoolMemberNFT } from "../../../contracts/agreements/gdav1/PoolMemberNFT.sol";
+import { ConstantOutflowNFTMock, ConstantInflowNFTMock } from "../../../contracts/mocks/CFAv1NFTMock.sol";
+import { PoolNFTBaseMock } from "../../../contracts/mocks/PoolNFTMock.sol";
+import { ISuperfluidPool } from "../../../contracts/agreements/gdav1/SuperfluidPool.sol";
+import { ERC721IntegrationTest } from "./ERC721.t.sol";
+
+/// @title PoolNFTBaseIntegrationTest
+/// @author Superfluid
+/// @dev This is a base contract for testing PoolNFTBase
+/// We test the functions in the PoolNFTBase directly via the base contract
+/// and the assumption is that because it is tested here, it is tested for all
+/// the derived contracts.
+abstract contract PoolNFTBaseIntegrationTest is ERC721IntegrationTest {
+ using Strings for uint256;
+
+ string public constant NAME = "Pool NFT Base";
+ string public constant SYMBOL = "PNFTB";
+
+ PoolNFTBaseMock public poolNFTBaseMock;
+
+ function setUp() public virtual override {
+ super.setUp();
+ poolNFTBaseMock = new PoolNFTBaseMock(sf.host);
+ poolNFTBaseMock.initialize(NAME, SYMBOL);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ Revert Tests
+ //////////////////////////////////////////////////////////////////////////*/
+ function testRevertIfContractAlreadyInitialized() public {
+ vm.expectRevert("Initializable: contract is already initialized");
+
+ poolNFTBaseMock.initialize(NAME, SYMBOL);
+ }
+
+ function testRevertIfOwnerOfForNonExistentToken(uint256 _tokenId) public {
+ _helperRevertIfOwnerOf(poolAdminNFT, _tokenId, IPoolNFTBase.POOL_NFT_INVALID_TOKEN_ID.selector);
+ }
+
+ function testRevertIfGetApprovedCalledForNonExistentToken(uint256 _tokenId) public {
+ _helperRevertIfGetApproved(poolAdminNFT, _tokenId, IPoolNFTBase.POOL_NFT_INVALID_TOKEN_ID.selector);
+ }
+
+ function testRevertIfSetApprovalForAllOperatorApproveToCaller(address _account) public {
+ vm.assume(_account != address(0));
+
+ vm.startPrank(_account);
+ vm.expectRevert(IPoolNFTBase.POOL_NFT_APPROVE_TO_CALLER.selector);
+ poolNFTBaseMock.setApprovalForAll(_account, true);
+ vm.stopPrank();
+ }
+
+ function testRevertIfApproveToCurrentOwner(address _pool, address _account) public {
+ vm.assume(_pool != address(0));
+ vm.assume(_account != address(0));
+
+ uint256 nftId = _helperGetPoolNFTBaseMockNftId(_pool, _account);
+ poolNFTBaseMock.mockMint(_pool, _account);
+
+ vm.startPrank(_account);
+ vm.expectRevert(IPoolNFTBase.POOL_NFT_APPROVE_TO_CURRENT_OWNER.selector);
+ poolNFTBaseMock.approve(_account, nftId);
+ vm.stopPrank();
+ }
+
+ function testRevertIfApproveAsNonOwner(address _pool, address _account, address _approver, address _approvedAccount)
+ public
+ {
+ vm.assume(_pool != address(0));
+ vm.assume(_account != address(0));
+ /// @dev _account is owner of pool NFT
+ vm.assume(_approver != _account);
+ vm.assume(_approvedAccount != _account);
+
+ uint256 nftId = _helperGetPoolNFTBaseMockNftId(_pool, _account);
+ poolNFTBaseMock.mockMint(_pool, _account);
+ vm.expectRevert(IPoolNFTBase.POOL_NFT_APPROVE_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector);
+ vm.startPrank(_approver);
+ poolNFTBaseMock.approve(_approvedAccount, nftId);
+ vm.stopPrank();
+ }
+
+ function testRevertIfTransferFrom(address _pool, address _account, address _recipient) public {
+ vm.assume(_pool != address(0));
+ vm.assume(_account != address(0));
+ vm.assume(_recipient != address(0));
+
+ uint256 nftId = _helperGetPoolNFTBaseMockNftId(_pool, _account);
+
+ poolNFTBaseMock.mockMint(address(_pool), _account);
+
+ _helperRevertIfTransferFrom(
+ poolNFTBaseMock, _account, _account, _recipient, nftId, IPoolNFTBase.POOL_NFT_TRANSFER_NOT_ALLOWED.selector
+ );
+ }
+
+ function testRevertIfSafeTransferFrom(address _pool, address _account, address _recipient) public {
+ vm.assume(_pool != address(0));
+ vm.assume(_account != address(0));
+ vm.assume(_recipient != address(0));
+
+ uint256 nftId = _helperGetPoolNFTBaseMockNftId(_pool, _account);
+
+ poolNFTBaseMock.mockMint(address(_pool), _account);
+
+ _helperRevertIfSafeTransferFrom(
+ poolNFTBaseMock, _account, _account, _recipient, nftId, IPoolNFTBase.POOL_NFT_TRANSFER_NOT_ALLOWED.selector
+ );
+ }
+
+ function testRevertIfSafeTransferFromWithData(address _pool, address _account, address _recipient) public {
+ vm.assume(_pool != address(0));
+ vm.assume(_account != address(0));
+ vm.assume(_recipient != address(0));
+
+ uint256 nftId = _helperGetPoolNFTBaseMockNftId(_pool, _account);
+
+ poolNFTBaseMock.mockMint(address(_pool), _account);
+
+ _helperRevertIfSafeTransferFrom(
+ poolNFTBaseMock,
+ _account,
+ _account,
+ _recipient,
+ nftId,
+ "0x",
+ IPoolNFTBase.POOL_NFT_TRANSFER_NOT_ALLOWED.selector
+ );
+ }
+
+ function testRevertIfTransferFromAsNonOwner(address _pool, address _account, address _recipient) public {
+ vm.assume(_pool != address(0));
+ vm.assume(_account != address(0));
+ vm.assume(_recipient != address(0));
+ vm.assume(_recipient != _account);
+
+ uint256 nftId = _helperGetPoolNFTBaseMockNftId(_pool, _account);
+
+ poolNFTBaseMock.mockMint(address(_pool), _account);
+
+ _helperRevertIfTransferFrom(
+ poolNFTBaseMock,
+ _recipient,
+ _account,
+ _recipient,
+ nftId,
+ IPoolNFTBase.POOL_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector
+ );
+ }
+
+ function testRevertIfSafeTransferFromAsNonOwner(address _pool, address _account, address _recipient) public {
+ vm.assume(_pool != address(0));
+ vm.assume(_account != address(0));
+ vm.assume(_recipient != address(0));
+ vm.assume(_recipient != _account);
+
+ uint256 nftId = _helperGetPoolNFTBaseMockNftId(_pool, _account);
+
+ poolNFTBaseMock.mockMint(address(_pool), _account);
+
+ _helperRevertIfSafeTransferFrom(
+ poolNFTBaseMock,
+ _recipient,
+ _account,
+ _recipient,
+ nftId,
+ IPoolNFTBase.POOL_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector
+ );
+ }
+
+ function testRevertIfSafeTransferFromWithDataAsNonOwner(address _pool, address _account, address _recipient)
+ public
+ {
+ vm.assume(_pool != address(0));
+ vm.assume(_account != address(0));
+ vm.assume(_recipient != address(0));
+ vm.assume(_recipient != _account);
+
+ uint256 nftId = _helperGetPoolNFTBaseMockNftId(_pool, _account);
+
+ poolNFTBaseMock.mockMint(address(_pool), _account);
+
+ _helperRevertIfSafeTransferFrom(
+ poolNFTBaseMock,
+ _recipient,
+ _account,
+ _recipient,
+ nftId,
+ "0x",
+ IPoolNFTBase.POOL_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL.selector
+ );
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ Passing Tests
+ //////////////////////////////////////////////////////////////////////////*/
+
+ function testContractSupportsExpectedInterfaces() public {
+ assertEq(poolNFTBaseMock.supportsInterface(type(IERC165).interfaceId), true);
+ assertEq(poolNFTBaseMock.supportsInterface(type(IERC721).interfaceId), true);
+ assertEq(poolNFTBaseMock.supportsInterface(type(IERC721Metadata).interfaceId), true);
+ }
+
+ function testBalanceOfIsAlwaysOne(address owner) public {
+ assertEq(poolNFTBaseMock.balanceOf(owner), 1, "PoolNFTBase: balanceOf is not always one");
+ }
+
+ function testHostIsProperlySetInConstructor() public {
+ assertEq(address(poolNFTBaseMock.HOST()), address(sf.host));
+ }
+
+ function testGDAv1IsProperlySetInConstructor() public {
+ assertEq(address(poolNFTBaseMock.GENERAL_DISTRIBUTION_AGREEMENT_V1()), address(sf.gda));
+ }
+
+ function testNFTMetadataIsProperlyInitialized() public {
+ assertEq(poolNFTBaseMock.name(), NAME);
+ assertEq(poolNFTBaseMock.symbol(), SYMBOL);
+ }
+
+ function testTokenURI(uint256 tokenId) public {
+ assertEq(poolNFTBaseMock.tokenURI(tokenId), string(abi.encodePacked("tokenId=", tokenId.toString())));
+ }
+
+ function testTriggerMetadataUpdate(uint256 tokenId) public {
+ _assertEventMetadataUpdate(address(poolNFTBaseMock), tokenId);
+ poolNFTBaseMock.triggerMetadataUpdate(tokenId);
+ }
+
+ function testApprove(address _account, address _pool, address _approvedAccount)
+ public
+ virtual
+ returns (uint256 nftId)
+ {
+ vm.assume(_account != address(0));
+ vm.assume(_pool != address(0));
+ vm.assume(_account != _approvedAccount);
+
+ nftId = _helperGetPoolNFTBaseMockNftId(_pool, _account);
+ poolNFTBaseMock.mockMint(_pool, _account);
+
+ _assertEventApproval(address(poolNFTBaseMock), _account, _approvedAccount, nftId);
+
+ vm.startPrank(_account);
+ poolNFTBaseMock.approve(_approvedAccount, nftId);
+ vm.stopPrank();
+
+ _assertApprovalIsExpected(poolNFTBaseMock, nftId, _approvedAccount);
+ }
+
+ function testSetApprovalForAll(address _tokenOwner, address _operator, bool _approved) public {
+ vm.assume(_tokenOwner != address(0));
+ vm.assume(_tokenOwner != _operator);
+
+ _assertEventApprovalForAll(address(poolNFTBaseMock), _tokenOwner, _operator, _approved);
+
+ vm.startPrank(_tokenOwner);
+ poolNFTBaseMock.setApprovalForAll(_operator, _approved);
+ vm.stopPrank();
+
+ _assertOperatorApprovalIsExpected(poolNFTBaseMock, _tokenOwner, _operator, _approved);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ Helper Functions
+ //////////////////////////////////////////////////////////////////////////*/
+ function _helperGetPoolNFTBaseMockNftId(address _pool, address _account) internal view returns (uint256) {
+ return poolNFTBaseMock.getTokenId(_pool, _account);
+ }
+
+ function _helperGetPoolAdminNftId(address _pool, address _poolAdmin) internal view returns (uint256) {
+ return poolAdminNFT.getTokenId(_pool, _poolAdmin);
+ }
+
+ function _helperGetPoolMemberNftId(address _pool, address _poolMember) internal view returns (uint256) {
+ return poolMemberNFT.getTokenId(_pool, _poolMember);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ Assertion Helpers
+ //////////////////////////////////////////////////////////////////////////*/
+ function _assertPoolAdminNftStateIsExpected(uint256 _tokenId, address _expectedPool, address _expectedAdmin)
+ public
+ {
+ PoolAdminNFT.PoolAdminNFTData memory poolAdminNFTData = poolAdminNFT.poolAdminDataByTokenId(_tokenId);
+
+ assertEq(poolAdminNFTData.pool, _expectedPool, "PoolAdminNFT: pool address not as expected");
+
+ // assert admin is equal to expected admin
+ assertEq(poolAdminNFTData.admin, _expectedAdmin, "PoolAdminNFT: admin address not as expected");
+
+ // assert owner of pool admin nft equal to expected admin
+ _assertOwnerOfIsExpected(
+ poolAdminNFT, _tokenId, _expectedAdmin, "PoolAdminNFT: owner of pool admin nft not as expected"
+ );
+ }
+
+ function _assertPoolMemberNftStateIsExpected(
+ uint256 _tokenId,
+ address _expectedPool,
+ address _expectedMember,
+ uint128 _expectedUnits
+ ) public {
+ PoolMemberNFT.PoolMemberNFTData memory poolMemberNFTData = poolMemberNFT.poolMemberDataByTokenId(_tokenId);
+
+ assertEq(poolMemberNFTData.pool, _expectedPool, "PoolMemberNFT: pool address not as expected");
+
+ // assert member is equal to expected member
+ assertEq(poolMemberNFTData.member, _expectedMember, "PoolMemberNFT: member address not as expected");
+
+ // assert units is equal to expected units
+ assertEq(poolMemberNFTData.units, _expectedUnits, "PoolMemberNFT: units not as expected");
+
+ // assert owner of pool member nft equal to expected member
+ _assertOwnerOfIsExpected(
+ poolAdminNFT, _tokenId, _expectedMember, "PoolMemberNFT: owner of pool member nft not as expected"
+ );
+ }
+}
+
+/// @title PoolNFTUpgradabilityTest
+/// @author Superfluid
+/// @notice Used for testing storage layout and upgradability of Pool NFT contracts
+contract PoolNFTUpgradabilityTest is PoolNFTBaseIntegrationTest {
+ /*//////////////////////////////////////////////////////////////////////////
+ Storage Layout Tests
+ //////////////////////////////////////////////////////////////////////////*/
+ function testPoolNFTBaseStorageLayout() public {
+ PoolNFTBaseStorageLayoutMock poolNFTBaseStorageLayoutMock = new PoolNFTBaseStorageLayoutMock(sf.host);
+
+ poolNFTBaseStorageLayoutMock.validateStorageLayout();
+ }
+
+ function testPoolMemberNFTStorageLayout() public {
+ PoolMemberNFTStorageLayoutMock poolMemberNFTStorageLayoutMock = new PoolMemberNFTStorageLayoutMock(sf.host);
+
+ poolMemberNFTStorageLayoutMock.validateStorageLayout();
+ }
+
+ function testPoolAdminNFTStorageLayout() public {
+ PoolAdminNFTStorageLayoutMock poolAdminNFTStorageLayoutMock = new PoolAdminNFTStorageLayoutMock(sf.host);
+
+ poolAdminNFTStorageLayoutMock.validateStorageLayout();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ Revert Tests
+ //////////////////////////////////////////////////////////////////////////*/
+ function testRevertPoolNFTContractsCannotBeUpgradedByNonSuperTokenFactory(address notSuperTokenFactory) public {
+ vm.assume(notSuperTokenFactory != address(sf.superTokenFactory));
+ PoolAdminNFT newPoolAdminNFT = new PoolAdminNFT(
+ sf.host
+ );
+ vm.expectRevert(IPoolNFTBase.POOL_NFT_ONLY_SUPER_TOKEN_FACTORY.selector);
+ vm.prank(notSuperTokenFactory);
+ poolAdminNFT.updateCode(address(newPoolAdminNFT));
+
+ PoolMemberNFT newPoolMemberNFT = new PoolMemberNFT(
+ sf.host
+ );
+ vm.expectRevert(IPoolNFTBase.POOL_NFT_ONLY_SUPER_TOKEN_FACTORY.selector);
+ vm.prank(notSuperTokenFactory);
+ poolMemberNFT.updateCode(address(newPoolMemberNFT));
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ Passing Tests
+ //////////////////////////////////////////////////////////////////////////*/
+ function testPoolNFTContractsCanBeUpgradedBySuperTokenFactory() public {
+ PoolAdminNFT newPoolAdminNFT = new PoolAdminNFT(
+ sf.host
+ );
+ vm.prank(address(sf.superTokenFactory));
+ poolAdminNFT.updateCode(address(newPoolAdminNFT));
+
+ PoolMemberNFT newPoolMemberNFT = new PoolMemberNFT(
+ sf.host
+ );
+ vm.prank(address(sf.superTokenFactory));
+ poolMemberNFT.updateCode(address(newPoolMemberNFT));
+ }
+}
+
+contract FakePool {
+ address public admin;
+ address public superToken;
+
+ constructor(address _admin, address _superToken) {
+ admin = _admin;
+ superToken = _superToken;
+ }
+}
diff --git a/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol
index 9639a11401..bbd2d120e4 100644
--- a/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol
+++ b/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol
@@ -7,6 +7,8 @@ import { UUPSProxiable } from "../../../contracts/upgradability/UUPSProxiable.so
import { IERC20, ISuperToken, SuperToken } from "../../../contracts/superfluid/SuperToken.sol";
import { ConstantOutflowNFT, IConstantOutflowNFT } from "../../../contracts/superfluid/ConstantOutflowNFT.sol";
import { ConstantInflowNFT, IConstantInflowNFT } from "../../../contracts/superfluid/ConstantInflowNFT.sol";
+import { PoolAdminNFT, IPoolAdminNFT } from "../../../contracts/agreements/gdav1/PoolAdminNFT.sol";
+import { PoolMemberNFT, IPoolMemberNFT } from "../../../contracts/agreements/gdav1/PoolMemberNFT.sol";
import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.sol";
import { TestToken } from "../../../contracts/utils/TestToken.sol";
import { TokenDeployerLibrary } from "../../../contracts/utils/SuperfluidFrameworkDeploymentSteps.sol";
@@ -27,7 +29,7 @@ contract SuperTokenIntegrationTest is FoundrySuperfluidTester {
// We assume that most underlying tokens will not have more than 32 decimals
vm.assume(decimals <= 32);
(TestToken localToken, ISuperToken localSuperToken) =
- sfDeployer.deployWrapperSuperToken("FTT", "FTT", decimals, type(uint256).max);
+ sfDeployer.deployWrapperSuperToken("FTT", "FTT", decimals, type(uint256).max, address(0));
(uint256 underlyingAmount, uint256 adjustedAmount) = localSuperToken.toUnderlyingAmount(amount);
localToken.mint(alice, INIT_TOKEN_BALANCE);
vm.startPrank(alice);
@@ -46,7 +48,7 @@ contract SuperTokenIntegrationTest is FoundrySuperfluidTester {
vm.assume(decimals <= 32);
vm.assume(downgradeAmount < upgradeAmount);
(TestToken localToken, ISuperToken localSuperToken) =
- sfDeployer.deployWrapperSuperToken("FTT", "FTT", decimals, type(uint256).max);
+ sfDeployer.deployWrapperSuperToken("FTT", "FTT", decimals, type(uint256).max, address(0));
(uint256 underlyingAmount, uint256 adjustedAmount) = localSuperToken.toUnderlyingAmount(upgradeAmount);
localToken.mint(alice, INIT_TOKEN_BALANCE);
@@ -70,6 +72,8 @@ contract SuperTokenIntegrationTest is FoundrySuperfluidTester {
function testRevertSuperTokenUpdateCodeWrongNFTProxies() public {
UUPSProxy cifProxy = new UUPSProxy();
UUPSProxy cofProxy = new UUPSProxy();
+ UUPSProxy paProxy = new UUPSProxy();
+ UUPSProxy pmProxy = new UUPSProxy();
ConstantInflowNFT cifNFTLogic = new ConstantInflowNFT(
sf.host,
@@ -79,21 +83,35 @@ contract SuperTokenIntegrationTest is FoundrySuperfluidTester {
sf.host,
IConstantInflowNFT(address(cifProxy))
);
+ PoolAdminNFT paNFTLogic = new PoolAdminNFT(
+ sf.host
+ );
+ PoolMemberNFT pmNFTLogic = new PoolMemberNFT(
+ sf.host
+ );
cifNFTLogic.castrate();
cofNFTLogic.castrate();
+ paNFTLogic.castrate();
+ pmNFTLogic.castrate();
cifProxy.initializeProxy(address(cifNFTLogic));
cofProxy.initializeProxy(address(cofNFTLogic));
+ paProxy.initializeProxy(address(paNFTLogic));
+ pmProxy.initializeProxy(address(pmNFTLogic));
ConstantInflowNFT(address(cofProxy)).initialize("Constant Outflow NFT", "COF");
ConstantOutflowNFT(address(cifProxy)).initialize("Constant Inflow NFT", "CIF");
+ PoolAdminNFT(address(paProxy)).initialize("Pool Admin NFT", "PA");
+ PoolMemberNFT(address(pmProxy)).initialize("Pool Member NFT", "PM");
- // both nft proxies incorrect
+ // all nft proxies incorrect
SuperToken superTokenLogic = new SuperToken(
sf.host,
ConstantOutflowNFT(address(cofProxy)),
- ConstantInflowNFT(address(cifProxy))
+ ConstantInflowNFT(address(cifProxy)),
+ PoolAdminNFT(address(paProxy)),
+ PoolMemberNFT(address(pmProxy))
);
vm.prank(address(sf.host));
vm.expectRevert(ISuperToken.SUPER_TOKEN_NFT_PROXY_ADDRESS_CHANGED.selector);
@@ -103,7 +121,9 @@ contract SuperTokenIntegrationTest is FoundrySuperfluidTester {
superTokenLogic = new SuperToken(
sf.host,
superToken.CONSTANT_OUTFLOW_NFT(),
- ConstantInflowNFT(address(cifProxy))
+ ConstantInflowNFT(address(cifProxy)),
+ superToken.POOL_ADMIN_NFT(),
+ superToken.POOL_MEMBER_NFT()
);
vm.prank(address(sf.host));
vm.expectRevert(ISuperToken.SUPER_TOKEN_NFT_PROXY_ADDRESS_CHANGED.selector);
@@ -113,7 +133,9 @@ contract SuperTokenIntegrationTest is FoundrySuperfluidTester {
superTokenLogic = new SuperToken(
sf.host,
ConstantOutflowNFT(address(cofProxy)),
- superToken.CONSTANT_INFLOW_NFT()
+ superToken.CONSTANT_INFLOW_NFT(),
+ superToken.POOL_ADMIN_NFT(),
+ superToken.POOL_MEMBER_NFT()
);
vm.prank(address(sf.host));
vm.expectRevert(ISuperToken.SUPER_TOKEN_NFT_PROXY_ADDRESS_CHANGED.selector);
@@ -132,7 +154,8 @@ contract SuperTokenIntegrationTest is FoundrySuperfluidTester {
}
function testOnlyHostCanChangeAdminWhenNoAdmin(address _admin) public {
- (, ISuperToken localSuperToken) = sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max);
+ (, ISuperToken localSuperToken) =
+ sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, address(0));
vm.startPrank(address(sf.host));
localSuperToken.changeAdmin(_admin);
@@ -180,9 +203,25 @@ contract SuperTokenIntegrationTest is FoundrySuperfluidTester {
vm.stopPrank();
}
+ function testRevertWhenNonAdminTriesToUpdateCode(address _admin, address nonAdmin) public {
+ vm.assume(_admin != address(sf.host));
+ vm.assume(nonAdmin != address(sf.host));
+
+ (TestToken localTestToken, ISuperToken localSuperToken) =
+ sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, address(0));
+
+ SuperToken newSuperTokenLogic =
+ _helperDeploySuperTokenAndInitialize(localSuperToken, localTestToken, 18, "FTT", "FTT", _admin);
+
+ vm.startPrank(nonAdmin);
+ vm.expectRevert(ISuperToken.SUPER_TOKEN_ONLY_ADMIN.selector);
+ UUPSProxiable(address(localSuperToken)).updateCode(address(newSuperTokenLogic));
+ vm.stopPrank();
+ }
+
function testOnlyHostCanUpdateCodeWhenNoAdmin() public {
(TestToken localTestToken, ISuperToken localSuperToken) =
- sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max);
+ sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, address(0));
SuperToken newSuperTokenLogic =
_helperDeploySuperTokenAndInitialize(localSuperToken, localTestToken, 18, "FTT", "FTT", address(0));
@@ -219,19 +258,4 @@ contract SuperTokenIntegrationTest is FoundrySuperfluidTester {
"testOnlyHostCanUpdateCodeWhenNoAdmin: super token logic not updated correctly"
);
}
-
- function testRevertWhenNonAdminTriesToUpdateCode(address _admin, address nonAdmin) public {
- vm.assume(_admin != address(sf.host));
-
- (TestToken localTestToken, ISuperToken localSuperToken) =
- sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max);
-
- SuperToken newSuperTokenLogic =
- _helperDeploySuperTokenAndInitialize(localSuperToken, localTestToken, 18, "FTT", "FTT", _admin);
-
- vm.startPrank(nonAdmin);
- vm.expectRevert(ISuperToken.SUPER_TOKEN_ONLY_ADMIN.selector);
- UUPSProxiable(address(localSuperToken)).updateCode(address(newSuperTokenLogic));
- vm.stopPrank();
- }
}
diff --git a/packages/ethereum-contracts/test/foundry/superfluid/SuperTokenFactory.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/SuperTokenFactory.t.sol
index f7ac867161..90c0c991af 100644
--- a/packages/ethereum-contracts/test/foundry/superfluid/SuperTokenFactory.t.sol
+++ b/packages/ethereum-contracts/test/foundry/superfluid/SuperTokenFactory.t.sol
@@ -5,6 +5,8 @@ import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.sol";
import { SuperTokenFactory } from "../../../contracts/superfluid/SuperTokenFactory.sol";
import { ConstantOutflowNFT, IConstantOutflowNFT } from "../../../contracts/superfluid/ConstantOutflowNFT.sol";
import { ConstantInflowNFT, IConstantInflowNFT } from "../../../contracts/superfluid/ConstantInflowNFT.sol";
+import { PoolAdminNFT, IPoolAdminNFT } from "../../../contracts/agreements/gdav1/PoolAdminNFT.sol";
+import { PoolMemberNFT, IPoolMemberNFT } from "../../../contracts/agreements/gdav1/PoolMemberNFT.sol";
import { ISuperToken, SuperToken } from "../../../contracts/superfluid/SuperToken.sol";
import { UUPSProxiable } from "../../../contracts/upgradability/UUPSProxiable.sol";
@@ -19,7 +21,9 @@ contract SuperTokenFactoryTest is FoundrySuperfluidTester {
SuperToken newSuperTokenLogic = new SuperToken(
sf.host,
superToken.CONSTANT_OUTFLOW_NFT(),
- superToken.CONSTANT_INFLOW_NFT()
+ superToken.CONSTANT_INFLOW_NFT(),
+ superToken.POOL_ADMIN_NFT(),
+ superToken.POOL_MEMBER_NFT()
);
ConstantOutflowNFT newConstantOutflowNFTLogic = new ConstantOutflowNFT(
sf.host,
@@ -29,6 +33,8 @@ contract SuperTokenFactoryTest is FoundrySuperfluidTester {
sf.host,
IConstantOutflowNFT(address(superToken.CONSTANT_OUTFLOW_NFT()))
);
+ PoolAdminNFT newPoolAdminNFTLogic = new PoolAdminNFT(sf.host);
+ PoolMemberNFT newPoolMemberNFTLogic = new PoolMemberNFT(sf.host);
assertEq(
UUPSProxiable(address(superToken.CONSTANT_OUTFLOW_NFT())).getCodeAddress(),
address(sf.superTokenFactory.CONSTANT_OUTFLOW_NFT_LOGIC())
@@ -41,7 +47,9 @@ contract SuperTokenFactoryTest is FoundrySuperfluidTester {
sf.host,
newSuperTokenLogic,
newConstantOutflowNFTLogic,
- newConstantInflowNFTLogic
+ newConstantInflowNFTLogic,
+ newPoolAdminNFTLogic,
+ newPoolMemberNFTLogic
);
vm.startPrank(address(sf.host));
// We expect this to revert if the protocol is not upgradeable
diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.t.sol
index ff5d9e51c4..ffd5cd1163 100644
--- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.t.sol
+++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.t.sol
@@ -12,7 +12,7 @@ import { AgreementMock } from "../../../contracts/mocks/AgreementMock.sol";
contract SuperfluidIntegrationTest is FoundrySuperfluidTester {
using SuperTokenV1Library for SuperToken;
- uint32 private constant _NUM_AGREEMENTS = 2;
+ uint32 private constant _NUM_AGREEMENTS = 3;
constructor() FoundrySuperfluidTester(3) { }
@@ -23,15 +23,16 @@ contract SuperfluidIntegrationTest is FoundrySuperfluidTester {
);
mocks[0] = ISuperAgreement(address(sf.cfa));
mocks[1] = ISuperAgreement(address(sf.ida));
+ mocks[2] = ISuperAgreement(address(sf.gda));
for (uint256 i; i < maxNumAgreements - _NUM_AGREEMENTS; ++i) {
bytes32 id = keccak256(abi.encode("type.", i));
- AgreementMock mock = new AgreementMock(address(sf.host), id, i);
+ AgreementMock agreementMock = new AgreementMock(address(sf.host), id, i);
vm.startPrank(sf.governance.owner());
- sf.governance.registerAgreementClass(sf.host, address(mock));
+ sf.governance.registerAgreementClass(sf.host, address(agreementMock));
vm.stopPrank();
- mock = sf.host.NON_UPGRADABLE_DEPLOYMENT() ? mock : AgreementMock(address(sf.host.getAgreementClass(id)));
- mocks[i + _NUM_AGREEMENTS] = ISuperAgreement(address(mock));
+ agreementMock = sf.host.NON_UPGRADABLE_DEPLOYMENT() ? agreementMock : AgreementMock(address(sf.host.getAgreementClass(id)));
+ mocks[i + _NUM_AGREEMENTS] = ISuperAgreement(address(agreementMock));
}
ISuperAgreement[] memory agreementClasses = sf.host.mapAgreementClasses(type(uint256).max);
diff --git a/packages/ethereum-contracts/test/foundry/superfluid/SuperfluidPool.prop.sol b/packages/ethereum-contracts/test/foundry/superfluid/SuperfluidPool.prop.sol
new file mode 100644
index 0000000000..1e9f68018f
--- /dev/null
+++ b/packages/ethereum-contracts/test/foundry/superfluid/SuperfluidPool.prop.sol
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import "forge-std/Test.sol";
+import "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol";
+import { GeneralDistributionAgreementV1 } from "../../../contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol";
+import { SuperfluidPool } from "../../../contracts/agreements/gdav1/SuperfluidPool.sol";
+
+/// @title SuperfluidPool Property Tests
+/// @author Superfluid
+/// @notice This is a contract that runs property tests for the SuperfluidPool
+/// It involves testing the pure functions of the SuperfluidPool to ensure that we get
+/// the expected output for a range of inputs.
+contract SuperfluidPoolProperties is SuperfluidPool, Test {
+ constructor() SuperfluidPool(GeneralDistributionAgreementV1(address(0))) { }
+
+ function _helperAssertWrappedParticle(PoolIndexData memory poolIndexData, BasicParticle memory particle) internal {
+ assertEq(
+ FlowRate.unwrap(particle.flow_rate()),
+ int128(poolIndexData.wrappedFlowRate),
+ "SuperfluidPool.prop (PoolIndex): flowRate not equal"
+ );
+ assertEq(
+ Time.unwrap(particle.settled_at()),
+ poolIndexData.wrappedSettledAt,
+ "SuperfluidPool.prop (PoolIndex): settledAt not equal"
+ );
+ assertEq(
+ Value.unwrap(particle._settled_value),
+ poolIndexData.wrappedSettledValue,
+ "SuperfluidPool.prop (PoolIndex): settledValue not equal"
+ );
+ }
+
+ function _helperAssertWrappedParticle(MemberData memory memberData, BasicParticle memory particle) internal {
+ assertEq(
+ FlowRate.unwrap(particle.flow_rate()),
+ int128(memberData.syncedFlowRate),
+ "SuperfluidPool.prop (BasicParticle): flowRate not equal"
+ );
+ assertEq(
+ Time.unwrap(particle.settled_at()),
+ memberData.syncedSettledAt,
+ "SuperfluidPool.prop (BasicParticle): settledAt not equal"
+ );
+ assertEq(
+ Value.unwrap(particle._settled_value),
+ memberData.syncedSettledValue,
+ "SuperfluidPool.prop (BasicParticle): settledValue not equal"
+ );
+ }
+
+ function testPoolIndexDataToWrappedParticle(PoolIndexData memory data) public {
+ BasicParticle memory wrappedParticle = _poolIndexDataToWrappedParticle(data);
+ _helperAssertWrappedParticle(data, wrappedParticle);
+ }
+
+ function testPoolIndexDataToPDPoolIndex(PoolIndexData memory data) public {
+ vm.assume(data.totalUnits < uint128(type(int128).max));
+
+ PDPoolIndex memory pdPoolIndex = poolIndexDataToPDPoolIndex(data);
+ assertEq(
+ uint128(Unit.unwrap(pdPoolIndex.total_units)), data.totalUnits, "SuperfluidPool.prop: total units not equal"
+ );
+ _helperAssertWrappedParticle(data, pdPoolIndex._wrapped_particle);
+ }
+
+ function testPDPoolIndexToPoolIndexData(
+ int128 totalUnits,
+ uint32 wrappedSettledAt,
+ int96 wrappedFlowRate,
+ int256 wrappedSettledValue
+ ) public {
+ vm.assume(totalUnits > 0);
+ PDPoolIndex memory pdPoolIndex = PDPoolIndex(
+ Unit.wrap(totalUnits),
+ BasicParticle(Time.wrap(wrappedSettledAt), FlowRate.wrap(wrappedFlowRate), Value.wrap(wrappedSettledValue))
+ );
+ PoolIndexData memory poolIndexData = _pdPoolIndexToPoolIndexData(pdPoolIndex);
+ assertEq(
+ poolIndexData.totalUnits,
+ uint128(Unit.unwrap(pdPoolIndex.total_units)),
+ "SuperfluidPool.prop: total units not equal"
+ );
+ _helperAssertWrappedParticle(poolIndexData, pdPoolIndex._wrapped_particle);
+ }
+
+ function testMemberDataToPDPoolMember(MemberData memory data) public {
+ vm.assume(data.ownedUnits < uint128(type(int128).max));
+
+ PDPoolMember memory pdPoolMember = _memberDataToPDPoolMember(data);
+ assertEq(
+ uint128(Unit.unwrap(pdPoolMember.owned_units)),
+ data.ownedUnits,
+ "SuperfluidPool.prop: owned units not equal"
+ );
+ assertEq(
+ Value.unwrap(pdPoolMember._settled_value), data.settledValue, "SuperfluidPool.prop: settled value not equal"
+ );
+ _helperAssertWrappedParticle(data, pdPoolMember._synced_particle);
+ }
+}
diff --git a/packages/ethereum-contracts/test/foundry/upgradability/SuperfluidUpgradeableBeacon.t.sol b/packages/ethereum-contracts/test/foundry/upgradability/SuperfluidUpgradeableBeacon.t.sol
new file mode 100644
index 0000000000..c505fc7522
--- /dev/null
+++ b/packages/ethereum-contracts/test/foundry/upgradability/SuperfluidUpgradeableBeacon.t.sol
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: AGPLv3
+pragma solidity 0.8.19;
+
+import { Test } from "forge-std/Test.sol";
+
+import { SuperfluidUpgradeableBeacon } from "../../../contracts/upgradability/SuperfluidUpgradeableBeacon.sol";
+
+import { BeaconProxiable } from "../../../contracts/upgradability/BeaconProxiable.sol";
+
+contract ProxiableBeacon is BeaconProxiable {
+ function proxiableUUID() public pure override returns (bytes32) {
+ return keccak256("ProxiableBeacon");
+ }
+}
+
+contract BadProxiableBeacon is BeaconProxiable {
+ function proxiableUUID() public pure override returns (bytes32) {
+ return keccak256("BadProxiableBeacon");
+ }
+}
+
+contract SuperfluidUpgradeableBeaconTest is Test {
+ address public constant owner = address(0x420);
+ SuperfluidUpgradeableBeacon public beacon;
+
+ function setUp() public {
+ vm.startPrank(owner);
+ ProxiableBeacon proxiableBeacon = new ProxiableBeacon();
+ beacon = new SuperfluidUpgradeableBeacon(address(proxiableBeacon));
+ vm.stopPrank();
+ }
+
+ function testRevertNonOwnerUpgrade() public {
+ ProxiableBeacon proxiableBeacon = new ProxiableBeacon();
+ vm.expectRevert("Ownable: caller is not the owner");
+ beacon.upgradeTo(address(proxiableBeacon));
+ }
+
+ function testRevertUpgradeToZeroAddress() public {
+ vm.expectRevert(SuperfluidUpgradeableBeacon.ZERO_ADDRESS_IMPLEMENTATION.selector);
+ vm.startPrank(owner);
+ beacon.upgradeTo(address(0));
+ vm.stopPrank();
+ }
+
+ function testRevertUpgradeToIncompatibleLogic() public {
+ BadProxiableBeacon badProxiableBeacon = new BadProxiableBeacon();
+ vm.expectRevert(SuperfluidUpgradeableBeacon.INCOMPATIBLE_LOGIC.selector);
+ vm.startPrank(owner);
+ beacon.upgradeTo(address(badProxiableBeacon));
+ vm.stopPrank();
+ }
+
+ function testRevertWhenDoingProxyLoop() public {
+ vm.expectRevert(SuperfluidUpgradeableBeacon.NO_PROXY_LOOP.selector);
+ vm.startPrank(owner);
+ beacon.upgradeTo(address(beacon));
+ vm.stopPrank();
+ }
+
+ function testUpgradeTo() public {
+ ProxiableBeacon proxiableBeacon = new ProxiableBeacon();
+ vm.startPrank(owner);
+ beacon.upgradeTo(address(proxiableBeacon));
+ vm.stopPrank();
+ assertEq(
+ beacon.implementation(), address(proxiableBeacon), "SuperfluidUpgradeableBeacon.t: wrong implementation"
+ );
+ }
+}
diff --git a/packages/ethereum-contracts/test/foundry/utils/BatchLiquidator.t.sol b/packages/ethereum-contracts/test/foundry/utils/BatchLiquidator.t.sol
index dfaeea61cf..11e966caab 100644
--- a/packages/ethereum-contracts/test/foundry/utils/BatchLiquidator.t.sol
+++ b/packages/ethereum-contracts/test/foundry/utils/BatchLiquidator.t.sol
@@ -2,28 +2,38 @@
pragma solidity 0.8.19;
import { FoundrySuperfluidTester, SuperTokenV1Library } from "../FoundrySuperfluidTester.sol";
-import { ISuperToken, SuperToken, ISuperfluid, IConstantOutflowNFT, IConstantInflowNFT } from "../../../contracts/superfluid/SuperToken.sol";
+import {
+ ISuperfluid,
+ ISuperToken,
+ IConstantOutflowNFT,
+ IConstantInflowNFT,
+ IPoolAdminNFT,
+ IPoolMemberNFT,
+ SuperToken
+} from "../../../contracts/superfluid/SuperToken.sol";
+import { ISuperfluidPool } from "../../../contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol";
import { BatchLiquidator } from "../../../contracts/utils/BatchLiquidator.sol";
import "forge-std/Test.sol";
contract NonTransferableST is SuperToken {
- // transferFrom will always revert
- constructor(
- ISuperfluid host
- )
- SuperToken(host, IConstantOutflowNFT(address(0)), IConstantInflowNFT(address(0))) // solhint-disable-next-line no-empty-blocks
- {
- }
-
- function transferFrom(address holder, address recipient, uint256 amount) public override returns (bool) {
+ // transferFrom will always revert
+ constructor(ISuperfluid host)
+ SuperToken(
+ host,
+ IConstantOutflowNFT(address(0)),
+ IConstantInflowNFT(address(0)),
+ IPoolAdminNFT(address(0)),
+ IPoolMemberNFT(address(0))
+ ) // solhint-disable-next-line
+ // no-empty-blocks
+ { }
+
+ function transferFrom(address holder, address recipient, uint256 amount) public override returns (bool) {
revert();
}
- function mintInternal(
- address to,
- uint256 amount
- ) external {
- _mint(msg.sender, to, amount, false /* invokeHook */, false /* requireReceptionAck */, "", "");
+ function mintInternal(address to, uint256 amount) external {
+ _mint(msg.sender, to, amount, false, /* invokeHook */ false, /* requireReceptionAck */ "", "");
}
}
@@ -41,121 +51,278 @@ contract BatchLiquidatorTest is FoundrySuperfluidTester {
function setUp() public override {
super.setUp();
- batchLiquidator = new BatchLiquidator(address(sf.host), address(sf.cfa));
+ batchLiquidator = new BatchLiquidator(address(sf.host));
badToken = new NonTransferableST(sf.host);
}
// Helpers
- function _startStream(address sender, address receiver, int96 flowRate) internal {
- vm.startPrank(sender);
- superToken.createFlow(receiver, flowRate);
+
+ function _transferAllToSink(address sender) internal {
+ _helperTransferAll(superToken, sender, admin);
+ }
+
+ function _assertNoCFAFlow(address sender, address receiver) internal {
+ (, int96 flowRate,,) = sf.cfa.getFlow(superToken, sender, receiver);
+ assertEq(flowRate, 0, "BatchLiquidator: CFA Flowrate should be 0");
+ }
+
+ function _assertNoGDAFlow(address sender, ISuperfluidPool pool) internal {
+ int96 flowRate = sf.gda.getFlowRate(superToken, sender, pool);
+ assertEq(flowRate, 0, "BatchLiquidator: GDA Flowrate should be 0");
+ }
+
+ function _assertLiquidatorBalanceGreater(address _liqudidator, uint256 balanceBefore_) internal {
+ assertGt(
+ superToken.balanceOf(_liqudidator),
+ balanceBefore_,
+ "BatchLiquidator: SL - Balance should be greater than before"
+ );
+ }
+
+ function _createCFAFlowLiquidationData(address sender, address receiver)
+ internal
+ pure
+ returns (BatchLiquidator.FlowLiquidationData memory)
+ {
+ return BatchLiquidator.FlowLiquidationData({
+ agreementOperation: BatchLiquidator.FlowType.ConstantFlowAgreement,
+ sender: sender,
+ receiver: receiver
+ });
+ }
+
+ function _createGDAFlowLiquidationData(address sender, ISuperfluidPool pool)
+ internal
+ pure
+ returns (BatchLiquidator.FlowLiquidationData memory)
+ {
+ return BatchLiquidator.FlowLiquidationData({
+ agreementOperation: BatchLiquidator.FlowType.GeneralDistributionAgreement,
+ sender: sender,
+ receiver: address(pool)
+ });
+ }
+
+ function testCFAOnlySingleLiquidation() public {
+ _helperCreateFlow(superToken, alice, bob, FLOW_RATE);
+ _transferAllToSink(alice);
+
+ vm.startPrank(liquidator);
+ uint256 balanceBefore = superToken.balanceOf(liquidator);
+ vm.warp(4 hours); // jump 4 hours
+ batchLiquidator.deleteFlow(address(superToken), _createCFAFlowLiquidationData(alice, bob));
+ _assertNoCFAFlow(alice, bob);
+
+ _assertLiquidatorBalanceGreater(liquidator, balanceBefore);
vm.stopPrank();
}
- function _transferAllToSink(address sender) internal {
- vm.startPrank(sender);
- superToken.transferAll(admin);
+ function testGDAOnlySingleLiquidation() public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice);
+ _helperUpdateMemberUnits(pool, alice, bob, 1);
+ _helperDistributeFlow(superToken, alice, alice, pool, FLOW_RATE);
+
+ _transferAllToSink(alice);
+
+ vm.startPrank(liquidator);
+ uint256 balanceBefore = superToken.balanceOf(liquidator);
+ vm.warp(4 hours); // jump 4 hours
+ batchLiquidator.deleteFlow(address(superToken), _createGDAFlowLiquidationData(alice, pool));
+ _assertNoGDAFlow(alice, pool);
+
+ _assertLiquidatorBalanceGreater(liquidator, balanceBefore);
vm.stopPrank();
}
- function _assertNoFlow(address sender, address receiver) internal {
- (, int96 flow,,) = sf.cfa.getFlow(superToken, sender, receiver);
- assertEq(flow, 0, "BatchLiquidator: Flow should be 0");
+ function testCFAOnlySingleLiquidationRevert() public {
+ vm.startPrank(liquidator);
+ vm.expectRevert();
+ batchLiquidator.deleteFlow(address(superToken), _createCFAFlowLiquidationData(alice, bob));
+ vm.stopPrank();
}
- function testSingleLiquidation() public {
- _startStream(alice, bob, FLOW_RATE);
+ function testGDAOnlySingleLiquidationRevert() public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice);
+ vm.startPrank(liquidator);
+ vm.expectRevert();
+ batchLiquidator.deleteFlow(address(superToken), _createGDAFlowLiquidationData(alice, pool));
+ vm.stopPrank();
+ }
+
+ function testCFAOnlyBatchLiquidation() public {
+ _helperCreateFlow(superToken, alice, bob, FLOW_RATE);
+ _helperCreateFlow(superToken, carol, bob, FLOW_RATE);
+ _helperCreateFlow(superToken, dan, bob, FLOW_RATE);
+
_transferAllToSink(alice);
+ _transferAllToSink(carol);
+ _transferAllToSink(dan);
vm.startPrank(liquidator);
- uint256 balance = superToken.balanceOf(liquidator);
+ uint256 balanceBefore = superToken.balanceOf(liquidator);
vm.warp(4 hours); // jump 4 hours
- batchLiquidator.deleteFlow(address(superToken), alice, bob);
- _assertNoFlow(alice, bob);
+ BatchLiquidator.FlowLiquidationData[] memory data = new BatchLiquidator.FlowLiquidationData[](3);
+ data[0] = _createCFAFlowLiquidationData(alice, bob);
+ data[1] = _createCFAFlowLiquidationData(carol, bob);
+ data[2] = _createCFAFlowLiquidationData(dan, bob);
- assertTrue(
- superToken.balanceOf(liquidator) > balance, "BatchLiquidator: SL - Balance should be greater than before"
- );
+ batchLiquidator.deleteFlows(address(superToken), data);
+
+ _assertNoCFAFlow(alice, bob);
+ _assertNoCFAFlow(carol, bob);
+ _assertNoCFAFlow(dan, bob);
+
+ _assertLiquidatorBalanceGreater(liquidator, balanceBefore);
+ vm.stopPrank();
+ }
+
+ function testGDAOnlyBatchLiquidation() public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice);
+ _helperUpdateMemberUnits(pool, alice, bob, 1);
+ _helperDistributeFlow(superToken, alice, alice, pool, FLOW_RATE);
+ _helperDistributeFlow(superToken, carol, carol, pool, FLOW_RATE);
+ _helperDistributeFlow(superToken, dan, dan, pool, FLOW_RATE);
+
+ int96 flowRate = sf.gda.getFlowRate(superToken, alice, pool);
+
+ _transferAllToSink(alice);
+ _transferAllToSink(carol);
+ _transferAllToSink(dan);
+
+ vm.startPrank(liquidator);
+ uint256 balanceBefore = superToken.balanceOf(liquidator);
+ vm.warp(4 hours); // jump 4 hours
+ BatchLiquidator.FlowLiquidationData[] memory data = new BatchLiquidator.FlowLiquidationData[](3);
+ data[0] = _createGDAFlowLiquidationData(alice, pool);
+ data[1] = _createGDAFlowLiquidationData(carol, pool);
+ data[2] = _createGDAFlowLiquidationData(dan, pool);
+ batchLiquidator.deleteFlows(address(superToken), data);
+
+ flowRate = sf.gda.getFlowRate(superToken, alice, pool);
+
+ _assertNoGDAFlow(alice, pool);
+ _assertNoGDAFlow(carol, pool);
+ _assertNoGDAFlow(dan, pool);
+
+ _assertLiquidatorBalanceGreater(liquidator, balanceBefore);
vm.stopPrank();
}
- function testSingleLiquidationRevert() public {
+ function testCFAOnlyBatchLiquidationWithToleratedRevert() public {
+ _helperCreateFlow(superToken, alice, bob, FLOW_RATE);
+ _helperCreateFlow(superToken, dan, bob, FLOW_RATE);
+
+ _transferAllToSink(alice);
+ _transferAllToSink(dan);
+
vm.startPrank(liquidator);
- vm.expectRevert();
- batchLiquidator.deleteFlow(address(superToken), alice, bob);
+ uint256 balanceBefore = superToken.balanceOf(liquidator);
+ vm.warp(4 hours); // jump 4 hours
+ BatchLiquidator.FlowLiquidationData[] memory data = new BatchLiquidator.FlowLiquidationData[](3);
+ data[0] = _createCFAFlowLiquidationData(alice, bob);
+ data[1] = _createCFAFlowLiquidationData(carol, bob);
+ data[2] = _createCFAFlowLiquidationData(dan, bob);
+
+ batchLiquidator.deleteFlows(address(superToken), data);
+ _assertNoCFAFlow(alice, bob);
+ _assertNoCFAFlow(carol, bob);
+ _assertNoCFAFlow(dan, bob);
+
+ _assertLiquidatorBalanceGreater(liquidator, balanceBefore);
vm.stopPrank();
}
- function testRevertIfArrayLengthsDontMatch() public {
- address[] memory senders = new address[](8);
- address[] memory receivers = new address[](7);
- vm.expectRevert(BatchLiquidator.ARRAY_SIZES_DIFFERENT.selector);
- batchLiquidator.deleteFlows(address(superToken), senders, receivers);
+ function testGDAOnlyBatchLiquidationWithToleratedRevert() public {
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice);
+ _helperUpdateMemberUnits(pool, alice, bob, 1);
+ _helperDistributeFlow(superToken, alice, alice, pool, FLOW_RATE);
+ _helperDistributeFlow(superToken, dan, dan, pool, FLOW_RATE);
+
+ int96 flowRate = sf.gda.getFlowRate(superToken, alice, pool);
+
+ _transferAllToSink(alice);
+ _transferAllToSink(dan);
+
+ vm.startPrank(liquidator);
+ uint256 balanceBefore = superToken.balanceOf(liquidator);
+ vm.warp(4 hours); // jump 4 hours
+ BatchLiquidator.FlowLiquidationData[] memory data = new BatchLiquidator.FlowLiquidationData[](3);
+ data[0] = _createGDAFlowLiquidationData(alice, pool);
+ data[1] = _createGDAFlowLiquidationData(carol, pool);
+ data[2] = _createGDAFlowLiquidationData(dan, pool);
+
+ batchLiquidator.deleteFlows(address(superToken), data);
+
+ flowRate = sf.gda.getFlowRate(superToken, alice, pool);
+
+ _assertNoGDAFlow(alice, pool);
+ _assertNoGDAFlow(carol, pool);
+ _assertNoGDAFlow(dan, pool);
+
+ _assertLiquidatorBalanceGreater(liquidator, balanceBefore);
+ vm.stopPrank();
}
function testBatchLiquidation() public {
- _startStream(alice, bob, FLOW_RATE);
- _startStream(carol, bob, FLOW_RATE);
- _startStream(dan, bob, FLOW_RATE);
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice);
+ _helperUpdateMemberUnits(pool, alice, bob, 1);
+ _helperDistributeFlow(superToken, alice, alice, pool, FLOW_RATE);
+
+ _helperCreateFlow(superToken, carol, bob, FLOW_RATE);
+ _helperCreateFlow(superToken, dan, bob, FLOW_RATE);
_transferAllToSink(alice);
_transferAllToSink(carol);
_transferAllToSink(dan);
vm.startPrank(liquidator);
- uint256 balance = superToken.balanceOf(liquidator);
+ uint256 balanceBefore = superToken.balanceOf(liquidator);
vm.warp(4 hours); // jump 4 hours
- address[] memory senders = new address[](3);
- address[] memory receivers = new address[](3);
- senders[0] = alice;
- senders[1] = carol;
- senders[2] = dan;
- receivers[0] = bob;
- receivers[1] = bob;
- receivers[2] = bob;
-
- batchLiquidator.deleteFlows(address(superToken), senders, receivers);
- _assertNoFlow(alice, bob);
- _assertNoFlow(carol, bob);
- _assertNoFlow(dan, bob);
-
- assertTrue(
- superToken.balanceOf(liquidator) > balance, "BatchLiquidator: BL - Balance should be greater than before"
- );
+ BatchLiquidator.FlowLiquidationData[] memory data = new BatchLiquidator.FlowLiquidationData[](4);
+ data[0] = _createGDAFlowLiquidationData(alice, pool);
+ data[1] = _createCFAFlowLiquidationData(carol, bob);
+ data[2] = _createCFAFlowLiquidationData(dan, bob);
+
+ batchLiquidator.deleteFlows(address(superToken), data);
+
+ _assertNoGDAFlow(alice, pool);
+ _assertNoCFAFlow(carol, bob);
+ _assertNoCFAFlow(dan, bob);
+
+ _assertLiquidatorBalanceGreater(liquidator, balanceBefore);
vm.stopPrank();
}
function testBatchLiquidationWithToleratedRevert() public {
- _startStream(alice, bob, FLOW_RATE);
- _startStream(dan, bob, FLOW_RATE);
+ ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice);
+ _helperUpdateMemberUnits(pool, alice, bob, 1);
+ _helperDistributeFlow(superToken, alice, alice, pool, FLOW_RATE);
+
+ _helperCreateFlow(superToken, dan, bob, FLOW_RATE);
_transferAllToSink(alice);
_transferAllToSink(dan);
vm.startPrank(liquidator);
- uint256 balance = superToken.balanceOf(liquidator);
+ uint256 balanceBefore = superToken.balanceOf(liquidator);
vm.warp(4 hours); // jump 4 hours
- address[] memory senders = new address[](3);
- address[] memory receivers = new address[](3);
- senders[0] = alice;
- senders[1] = carol; // carol has no flow
- senders[2] = dan;
- receivers[0] = bob;
- receivers[1] = bob;
- receivers[2] = bob;
-
- batchLiquidator.deleteFlows(address(superToken), senders, receivers);
- _assertNoFlow(alice, bob);
- _assertNoFlow(carol, bob);
- _assertNoFlow(dan, bob);
-
- assertTrue(
- superToken.balanceOf(liquidator) > balance, "BatchLiquidator: BLR - Balance should be greater than before"
- );
+ BatchLiquidator.FlowLiquidationData[] memory data = new BatchLiquidator.FlowLiquidationData[](4);
+
+ data[0] = _createGDAFlowLiquidationData(alice, pool);
+ data[1] = _createCFAFlowLiquidationData(carol, bob);
+ data[2] = _createCFAFlowLiquidationData(dan, bob);
+
+ batchLiquidator.deleteFlows(address(superToken), data);
+
+ _assertNoGDAFlow(alice, pool);
+ _assertNoCFAFlow(carol, bob);
+ _assertNoCFAFlow(dan, bob);
+
+ _assertLiquidatorBalanceGreater(liquidator, balanceBefore);
vm.stopPrank();
}
- function testLiquidationWithCustomTokenRevert() public {
+ function testCFALiquidationWithCustomTokenRevert() public {
NonTransferableST(address(badToken)).mintInternal(alice, 10 ether);
vm.startPrank(alice);
@@ -165,17 +332,16 @@ contract BatchLiquidatorTest is FoundrySuperfluidTester {
vm.stopPrank();
vm.startPrank(liquidator);
- batchLiquidator.deleteFlow(address(badToken), alice, bob);
- _assertNoFlow(alice, bob);
+ BatchLiquidator.FlowLiquidationData memory data = _createCFAFlowLiquidationData(alice, bob);
- assertTrue(
- superToken.balanceOf(liquidator) == 0, "BatchLiquidator: SL - Balance should be 0 because of revert"
- );
- vm.stopPrank();
+ batchLiquidator.deleteFlow(address(badToken), data);
+ _assertNoCFAFlow(alice, bob);
+ assertTrue(superToken.balanceOf(liquidator) == 0, "BatchLiquidator: SL - Balance should be 0 because of revert");
+ vm.stopPrank();
}
- function testBatchLiquidationWithCustomTokenRevert() public {
+ function testCFABatchLiquidationWithCustomTokenRevert() public {
NonTransferableST(address(badToken)).mintInternal(alice, 10 ether);
NonTransferableST(address(badToken)).mintInternal(bob, 10 ether);
@@ -193,15 +359,12 @@ contract BatchLiquidatorTest is FoundrySuperfluidTester {
vm.startPrank(liquidator);
- address[] memory senders = new address[](2);
- address[] memory receivers = new address[](2);
- senders[0] = alice;
- senders[1] = bob;
- receivers[0] = bob;
- receivers[1] = carol;
+ BatchLiquidator.FlowLiquidationData[] memory data = new BatchLiquidator.FlowLiquidationData[](2);
+ data[0] = _createCFAFlowLiquidationData(alice, bob);
+ data[1] = _createCFAFlowLiquidationData(bob, carol);
- batchLiquidator.deleteFlows(address(superToken), senders, receivers);
- _assertNoFlow(alice, bob);
- _assertNoFlow(bob, carol);
+ batchLiquidator.deleteFlows(address(superToken), data);
+ _assertNoCFAFlow(alice, bob);
+ _assertNoCFAFlow(bob, carol);
}
}
diff --git a/packages/ethereum-contracts/test/foundry/utils/TOGA.t.sol b/packages/ethereum-contracts/test/foundry/utils/TOGA.t.sol
index 42e1fbb8c1..d3db0b4c53 100644
--- a/packages/ethereum-contracts/test/foundry/utils/TOGA.t.sol
+++ b/packages/ethereum-contracts/test/foundry/utils/TOGA.t.sol
@@ -431,7 +431,8 @@ contract TOGAIntegrationTest is FoundrySuperfluidTester {
function testMultiplePICsInParallel(uint256 bond) public {
bond = _boundBondValue(bond);
- (, ISuperToken superToken2) = sfDeployer.deployWrapperSuperToken("TEST2", "TEST2", 18, type(uint256).max);
+ (, ISuperToken superToken2) =
+ sfDeployer.deployWrapperSuperToken("TEST2", "TEST2", 18, type(uint256).max, address(0));
_helperDeal(superToken2, alice, INIT_SUPER_TOKEN_BALANCE);
_helperDeal(superToken2, bob, INIT_SUPER_TOKEN_BALANCE);
diff --git a/packages/ethereum-contracts/test/ops-scripts/deployment.test.js b/packages/ethereum-contracts/test/ops-scripts/deployment.test.js
index ee85a72150..f718d5a168 100644
--- a/packages/ethereum-contracts/test/ops-scripts/deployment.test.js
+++ b/packages/ethereum-contracts/test/ops-scripts/deployment.test.js
@@ -555,6 +555,7 @@ contract("Embedded deployment scripts", (accounts) => {
s.superfluid.address,
s.superfluid.address, // a dead loop proxy
[],
+ ZERO_ADDRESS,
ZERO_ADDRESS
);
} catch (err) {
diff --git a/packages/ethereum-contracts/test/types.ts b/packages/ethereum-contracts/test/types.ts
index 8ec12930be..dc59755f90 100644
--- a/packages/ethereum-contracts/test/types.ts
+++ b/packages/ethereum-contracts/test/types.ts
@@ -2,6 +2,7 @@ import {BigNumber} from "ethers";
import {
ConstantFlowAgreementV1,
+ GeneralDistributionAgreementV1,
IERC1820Registry,
InstantDistributionAgreementV1,
ISuperToken,
@@ -115,6 +116,7 @@ export interface TestEnvironmentContracts {
superfluid: SuperfluidMock;
cfa: ConstantFlowAgreementV1;
ida: InstantDistributionAgreementV1;
+ gda: GeneralDistributionAgreementV1;
governance: TestGovernance;
ISuperToken: ISuperToken;
resolver: Resolver;
diff --git a/packages/ethereum-contracts/testsuites/apps-contracts.ts b/packages/ethereum-contracts/testsuites/apps-contracts.ts
index 1d094a9175..20edbb21fc 100644
--- a/packages/ethereum-contracts/testsuites/apps-contracts.ts
+++ b/packages/ethereum-contracts/testsuites/apps-contracts.ts
@@ -1,4 +1,5 @@
import "../test/contracts/apps/SuperTokenV1Library.CFA.test";
import "../test/contracts/apps/SuperTokenV1Library.IDA.test";
+import "../test/contracts/apps/SuperTokenV1Library.GDA.test";
import "../test/contracts/apps/CFAv1Library.test";
import "../test/contracts/apps/IDAv1Library.test";
\ No newline at end of file
diff --git a/packages/hot-fuzz/README.md b/packages/hot-fuzz/README.md
index 39017b9967..ddc1dac72e 100644
--- a/packages/hot-fuzz/README.md
+++ b/packages/hot-fuzz/README.md
@@ -64,9 +64,9 @@ contract YouSuperAppHotFuzz is HotFuzzBase {
constructor() HotFuzzBase(10 /* nTesters */ ) {
// ... setup your app
_app = new YourApp(sf.host, sf.cfa, superToken);
- initTesters();
+ _initTesters();
...
- addAccount(address(_app));
+ _addAccount(address(_app));
}
```
As a convention, the contract file name should be `YourApp.hott.sol`.
diff --git a/packages/hot-fuzz/contracts/HotFuzzBase.sol b/packages/hot-fuzz/contracts/HotFuzzBase.sol
index d5560d9acc..7f3d776eaa 100644
--- a/packages/hot-fuzz/contracts/HotFuzzBase.sol
+++ b/packages/hot-fuzz/contracts/HotFuzzBase.sol
@@ -17,6 +17,9 @@ import {
} from "@superfluid-finance/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol";
import "@superfluid-finance/ethereum-contracts/contracts/apps/CFAv1Library.sol";
import "@superfluid-finance/ethereum-contracts/contracts/apps/IDAv1Library.sol";
+import {
+ SuperTokenV1Library
+} from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol";
import {
IERC20,
@@ -26,8 +29,8 @@ import {
SuperfluidTester
} from "./SuperfluidTester.sol";
-
contract HotFuzzBase {
+ using SuperTokenV1Library for SuperToken;
// constants
uint private constant INIT_TOKEN_BALANCE = type(uint160).max;
uint private constant INIT_SUPER_TOKEN_BALANCE = type(uint128).max;
@@ -44,23 +47,26 @@ contract HotFuzzBase {
SuperfluidTester[] internal testers;
address[] internal otherAccounts;
uint256 internal expectedTotalSupply = 0;
+ bool internal liquidationFails;
constructor(uint nTesters_) {
_sfDeployer = new SuperfluidFrameworkDeployer();
_sfDeployer.deployTestFramework();
sf = _sfDeployer.getFramework();
- (token, superToken) = _sfDeployer.deployWrapperSuperToken(
- "HOTFuzz Token", "HOTT", 18, type(uint256).max
- );
+ (token, superToken) =
+ _sfDeployer.deployWrapperSuperToken("HOTFuzz Token", "HOTT", 18, type(uint256).max, address(0));
nTesters = nTesters_;
otherAccounts = new address[](0);
+
+ _addAccount(address(sf.gda));
+ _addAccount(address(sf.toga));
}
- function initTesters() virtual internal {
+ function _initTesters() virtual internal {
testers = new SuperfluidTester[](nTesters);
for (uint i = 0; i < nTesters; ++i) {
- testers[i] = createTester();
+ testers[i] = _createTester();
token.mint(address(testers[i]), INIT_TOKEN_BALANCE);
testers[i].upgradeSuperToken(INIT_SUPER_TOKEN_BALANCE);
expectedTotalSupply += INIT_SUPER_TOKEN_BALANCE;
@@ -71,45 +77,59 @@ contract HotFuzzBase {
* IHotFuzz implementation
**************************************************************************/
- function createTester()
+ function _createTester()
virtual internal
returns (SuperfluidTester)
{
return new SuperfluidTester(sf, token, superToken);
}
- function addAccount(address a)
+ function _addAccount(address a)
internal
{
otherAccounts.push(a);
}
- function listAccounts()
+ function _listAccounts()
internal view
returns (address[] memory accounts)
{
- accounts = new address[](nTesters + otherAccounts.length);
+ accounts = new address[](_numAccounts());
for (uint i = 0; i < nTesters; ++i) accounts[i] = address(testers[i]);
for (uint i = 0; i < otherAccounts.length; ++i) accounts[i + nTesters] = otherAccounts[i];
}
- function getOneTester(uint8 a)
+ function _numAccounts() internal view returns (uint256) {
+ return nTesters + otherAccounts.length;
+ }
+
+ function _getOneTester(uint8 a)
internal view
- returns (SuperfluidTester testerA)
+ returns (SuperfluidTester tester)
{
- testerA = testers[a % nTesters];
+ tester = testers[a % _numAccounts()];
}
- function getTwoTesters(uint8 a, uint8 b)
+ /// @dev The testers returned may be the same
+ function _getTwoTesters(uint8 a, uint8 b)
internal view
returns (SuperfluidTester testerA, SuperfluidTester testerB)
{
- testerA = testers[a % nTesters];
- // avoid tester B to be the same as tester A
- testerB = testers[((a % nTesters) + (b % (nTesters - 1))) % nTesters];
+ testerA = _getOneTester(a);
+ testerB = _getOneTester(b);
}
- function superTokenBalanceOfNow(address a) internal view returns (int256 avb) {
+ /// @dev The testers returned may be the same
+ function _getThreeTesters(uint8 a, uint8 b, uint8 c)
+ internal view
+ returns (SuperfluidTester testerA, SuperfluidTester testerB, SuperfluidTester testerC)
+ {
+ testerA = _getOneTester(a);
+ testerB = _getOneTester(b);
+ testerC = _getOneTester(c);
+ }
+
+ function _superTokenBalanceOfNow(address a) internal view returns (int256 avb) {
(avb,,,) = superToken.realtimeBalanceOfNow(a);
}
@@ -124,7 +144,7 @@ contract HotFuzzBase {
function echidna_check_liquiditySumInvariance() public view returns (bool) {
int256 liquiditySum = 0;
- address[] memory accounts = listAccounts();
+ address[] memory accounts = _listAccounts();
for (uint i = 0; i < accounts.length; ++i) {
(int256 avb, uint256 d, uint256 od, ) = superToken.realtimeBalanceOfNow(accounts[i]);
// FIXME: correct formula
@@ -138,11 +158,17 @@ contract HotFuzzBase {
function echidna_check_netFlowRateSumInvariant() public view returns (bool) {
int96 netFlowRateSum = 0;
- address[] memory accounts = listAccounts();
+ address[] memory accounts = _listAccounts();
for (uint i = 0; i < accounts.length; ++i) {
- netFlowRateSum += sf.cfa.getNetFlow(superToken, accounts[i]);
+ netFlowRateSum += superToken.getNetFlowRate(accounts[i]);
}
assert(netFlowRateSum == 0);
return netFlowRateSum == 0;
}
+
+ function echidna_check_validLiquidationNeverRevertsInvariant() public view returns (bool) {
+ bool liquidationNeverFails = !liquidationFails;
+ assert(liquidationNeverFails);
+ return liquidationNeverFails;
+ }
}
diff --git a/packages/hot-fuzz/contracts/SuperfluidTester.sol b/packages/hot-fuzz/contracts/SuperfluidTester.sol
index c1534793a3..b6e1cf3e53 100644
--- a/packages/hot-fuzz/contracts/SuperfluidTester.sol
+++ b/packages/hot-fuzz/contracts/SuperfluidTester.sol
@@ -3,31 +3,44 @@ pragma solidity >= 0.8.0;
import "@superfluid-finance/ethereum-contracts/contracts/superfluid/Superfluid.sol";
import "@superfluid-finance/ethereum-contracts/contracts/superfluid/SuperToken.sol";
+import {ISuperfluidPool} from
+ "@superfluid-finance/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol";
+import {PoolConfig} from
+ "@superfluid-finance/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
import "@superfluid-finance/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol";
import "@superfluid-finance/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol";
import "@superfluid-finance/ethereum-contracts/contracts/apps/CFAv1Library.sol";
import "@superfluid-finance/ethereum-contracts/contracts/apps/IDAv1Library.sol";
+import {SuperTokenV1Library} from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol";
import "@superfluid-finance/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol";
contract SuperfluidTester {
+ using SuperTokenV1Library for ISuperToken;
SuperfluidFrameworkDeployer.Framework internal sf;
IERC20 internal token;
ISuperToken internal superToken;
- using CFAv1Library for CFAv1Library.InitData;
- using IDAv1Library for IDAv1Library.InitData;
-
- constructor (
- SuperfluidFrameworkDeployer.Framework memory sf_,
- IERC20 token_,
- ISuperToken superToken_)
- {
+ constructor(SuperfluidFrameworkDeployer.Framework memory sf_, IERC20 token_, ISuperToken superToken_) {
sf = sf_;
token = token_;
superToken = superToken_;
}
+ // ERC20 Functions
+ function approve(address spender, uint256 amount) public {
+ superToken.approve(spender, amount);
+ }
+
+ function transfer(address recipient, uint256 amount) public {
+ superToken.transfer(recipient, amount);
+ }
+
+ function transferFrom(address sender, address recipient, uint256 amount) public {
+ superToken.transferFrom(sender, recipient, amount);
+ }
+
+ // SuperToken Functions
function upgradeSuperToken(uint256 amount) public {
token.approve(address(superToken), amount);
superToken.upgrade(amount);
@@ -37,31 +50,168 @@ contract SuperfluidTester {
superToken.downgrade(amount);
}
+ function transferAll(address recipient) public {
+ superToken.transferAll(recipient);
+ }
+
+ function increaseAllowance(address spender, uint256 addedValue) public {
+ superToken.increaseAllowance(spender, addedValue);
+ }
+
+ function decreaseAllowance(address spender, uint256 subtractedValue) public {
+ superToken.decreaseAllowance(spender, subtractedValue);
+ }
+
+ // CFA functions
+
function flow(address receiver, int96 flowRate) public {
(, int96 currentFlowRate,,) = sf.cfa.getFlow(superToken, address(this), receiver);
if (flowRate == 0) {
- sf.cfaLib.deleteFlow(address(this), receiver, superToken);
+ superToken.deleteFlow(address(this), receiver);
} else if (currentFlowRate == 0) {
- sf.cfaLib.createFlow(receiver, superToken, flowRate);
+ superToken.createFlow(receiver, flowRate);
} else {
- sf.cfaLib.updateFlow(receiver, superToken, flowRate);
+ superToken.updateFlow(receiver, flowRate);
}
}
+ function cfaLiquidate(address sender, address receiver) public {
+ superToken.deleteFlow(sender, receiver);
+ }
+
+ function setFlowPermissions(
+ address flowOperator,
+ bool allowCreate,
+ bool allowUpdate,
+ bool allowDelete,
+ int96 flowRateAllowance
+ ) public {
+ superToken.setFlowPermissions(flowOperator, allowCreate, allowUpdate, allowDelete, flowRateAllowance);
+ }
+
+ function setMaxFlowPermissions(address flowOperator) public {
+ superToken.setMaxFlowPermissions(flowOperator);
+ }
+
+ function revokeFlowPermissions(address flowOperator) public {
+ superToken.revokeFlowPermissions(flowOperator);
+ }
+
+ function increaseFlowRateAllowance(address flowOperator, int96 addedFlowRateAllowance) public {
+ superToken.increaseFlowRateAllowance(flowOperator, addedFlowRateAllowance);
+ }
+
+ function decreaseFlowRateAllowance(address flowOperator, int96 subtractedFlowRateAllowance) public {
+ superToken.decreaseFlowRateAllowance(flowOperator, subtractedFlowRateAllowance);
+ }
+
+ function increaseFlowRateAllowanceWithPermissions(
+ address flowOperator,
+ uint8 permissionsToAdd,
+ int96 addedFlowRateAllowance
+ ) public {
+ superToken.increaseFlowRateAllowanceWithPermissions(flowOperator, permissionsToAdd, addedFlowRateAllowance);
+ }
+
+ function decreaseFlowRateAllowanceWithPermissions(
+ address flowOperator,
+ uint8 permissionsToRemove,
+ int96 subtractedFlowRateAllowance
+ ) public {
+ superToken.decreaseFlowRateAllowanceWithPermissions(
+ flowOperator, permissionsToRemove, subtractedFlowRateAllowance
+ );
+ }
+
+ // IDA functions
function createIndex(uint32 indexId) public {
- sf.idaLib.createIndex(superToken, indexId);
+ superToken.createIndex(indexId);
}
function updateSubscriptionUnits(uint32 indexId, address subscriber, uint128 units) public {
- sf.idaLib.updateSubscriptionUnits(superToken, indexId, subscriber, units);
+ superToken.updateSubscriptionUnits(indexId, subscriber, units);
+ }
+
+ function updateIndex(uint32 indexId, uint128 indexValue) public {
+ superToken.updateIndexValue(indexId, indexValue);
}
function distribute(uint32 indexId, uint256 amount) public {
- sf.idaLib.distribute(superToken, indexId, amount);
+ superToken.distribute(indexId, amount);
}
function approveSubscription(address publisher, uint32 indexId) public {
- sf.idaLib.approveSubscription(superToken, publisher, indexId);
+ superToken.approveSubscription(publisher, indexId);
}
+ function revokeSubscription(address publisher, uint32 indexId) public {
+ superToken.revokeSubscription(publisher, indexId);
+ }
+
+ function deleteSubscription(address publisher, uint32 indexId, address subscriber) public {
+ superToken.deleteSubscription(publisher, indexId, subscriber);
+ }
+
+ function claim(address publisher, uint32 indexId, address subscriber) public {
+ superToken.claim(publisher, indexId, subscriber);
+ }
+
+ // GDA functions
+ function createPool(address admin, PoolConfig memory config) public returns (ISuperfluidPool pool) {
+ pool = superToken.createPool(admin, config);
+ }
+
+ function connectPool(ISuperfluidPool pool) public {
+ superToken.connectPool(pool);
+ }
+
+ function disconnectPool(ISuperfluidPool pool) public {
+ superToken.disconnectPool(pool);
+ }
+
+ function distributeToPool(address from, ISuperfluidPool pool, uint256 requestedAmount) public {
+ superToken.distributeToPool(from, pool, requestedAmount);
+ }
+
+ function distributeFlow(address from, ISuperfluidPool pool, int96 flowRate) public {
+ superToken.distributeFlow(from, pool, flowRate);
+ }
+
+ function gdaLiquidate(address from, ISuperfluidPool pool) public {
+ superToken.distributeFlow(from, pool, 0);
+ }
+
+ // SuperfluidPool
+ function updateMemberUnits(ISuperfluidPool pool, address member, uint128 units) public {
+ pool.updateMemberUnits(member, units);
+ }
+
+ function claimAll(ISuperfluidPool pool) public {
+ pool.claimAll();
+ }
+
+ function claimAll(ISuperfluidPool pool, address memberAddress) public {
+ pool.claimAll(memberAddress);
+ }
+
+ // SuperfluidPool-ERC20
+ function transfer(ISuperfluidPool pool, address to, uint256 amount) public {
+ pool.transfer(to, amount);
+ }
+
+ function transferFrom(ISuperfluidPool pool, address from, address to, uint256 amount) public {
+ pool.transferFrom(from, to, amount);
+ }
+
+ function increaseAllowance(ISuperfluidPool pool, address spender, uint256 addedValue) public {
+ pool.increaseAllowance(spender, addedValue);
+ }
+
+ function decreaseAllowance(ISuperfluidPool pool, address spender, uint256 subtractedValue) public {
+ pool.decreaseAllowance(spender, subtractedValue);
+ }
+
+ function approve(ISuperfluidPool pool, address spender, uint256 amount) public {
+ pool.approve(spender, amount);
+ }
}
diff --git a/packages/hot-fuzz/contracts/superfluid-tests/ConstantFlowAgreementV1.hott.sol b/packages/hot-fuzz/contracts/superfluid-tests/ConstantFlowAgreementV1.hott.sol
index ed8650e71b..680ed89373 100644
--- a/packages/hot-fuzz/contracts/superfluid-tests/ConstantFlowAgreementV1.hott.sol
+++ b/packages/hot-fuzz/contracts/superfluid-tests/ConstantFlowAgreementV1.hott.sol
@@ -2,26 +2,113 @@
// solhint-disable reason-string
pragma solidity >= 0.8.0;
+import {SuperToken} from "@superfluid-finance/ethereum-contracts/contracts/superfluid/SuperToken.sol";
+import {SuperTokenV1Library} from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol";
import "../HotFuzzBase.sol";
-
abstract contract CFAHotFuzzMixin is HotFuzzBase {
+ using SuperTokenV1Library for SuperToken;
+
function createFlow(uint8 a, uint8 b, int64 flowRate) public {
require(flowRate > 0);
- (SuperfluidTester testerA, SuperfluidTester testerB) = getTwoTesters(a, b);
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
testerA.flow(address(testerB), int96(flowRate));
}
function deleteFlow(uint8 a, uint8 b) public {
- (SuperfluidTester testerA, SuperfluidTester testerB) = getTwoTesters(a, b);
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
testerA.flow(address(testerB), 0);
}
+
+ /// @notice testerA liquidates a flow from testerB to testerC
+ /// @dev testerA can be the same as testerB or testerC
+ function cfaLiquidateFlow(uint8 a, uint8 b, uint8 c) public {
+ (SuperfluidTester liquidator, SuperfluidTester sender, SuperfluidTester recipient) = _getThreeTesters(a, b, c);
+
+ // we first check the condition for whether a flow exists
+ bool flowExists = superToken.getFlowRate(address(sender), address(recipient)) > 0;
+
+ // then we ensure that the sender has a critical balance
+ (int256 availableBalance,,,) = superToken.realtimeBalanceOfNow(address(sender));
+ bool isSenderCritical = availableBalance < 0;
+
+ // if both conditions are met, a liquidation should occur without fail
+ bool isLiquidationValid = flowExists && isSenderCritical;
+ if (isLiquidationValid) {
+ // solhint-disable-next-line no-empty-blocks
+ try liquidator.cfaLiquidate(address(sender), address(recipient)) {}
+ catch {
+ liquidationFails = true;
+ }
+ }
+ }
+
+ function setFlowPermissions(
+ uint8 a,
+ uint8 b,
+ bool allowCreate,
+ bool allowUpdate,
+ bool allowDelete,
+ int96 flowRateAllowance
+ ) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+
+ testerA.setFlowPermissions(address(testerB), allowCreate, allowUpdate, allowDelete, flowRateAllowance);
+ }
+
+ function setMaxFlowPermissions(uint8 a, uint8 b) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+
+ testerA.setMaxFlowPermissions(address(testerB));
+ }
+
+ function revokeFlowPermissions(uint8 a, uint8 b) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+
+ testerA.revokeFlowPermissions(address(testerB));
+ }
+
+ function increaseFlowRateAllowance(uint8 a, uint8 b, int96 addedFlowRateAllowance) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+
+ testerA.increaseFlowRateAllowance(address(testerB), addedFlowRateAllowance);
+ }
+
+ function decreaseFlowRateAllowance(uint8 a, uint8 b, int96 subtractedFlowRateAllowance) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+
+ testerA.decreaseFlowRateAllowance(address(testerB), subtractedFlowRateAllowance);
+ }
+
+ function increaseFlowRateAllowanceWithPermissions(
+ uint8 a,
+ uint8 b,
+ uint8 permissionsToAdd,
+ int96 addedFlowRateAllowance
+ ) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+
+ testerA.increaseFlowRateAllowanceWithPermissions(address(testerB), permissionsToAdd, addedFlowRateAllowance);
+ }
+
+ function decreaseFlowRateAllowanceWithPermissions(
+ uint8 a,
+ uint8 b,
+ uint8 permissionsToRemove,
+ int96 subtractedFlowRateAllowance
+ ) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+
+ testerA.decreaseFlowRateAllowanceWithPermissions(
+ address(testerB), permissionsToRemove, subtractedFlowRateAllowance
+ );
+ }
}
contract CFAHotFuzz is CFAHotFuzzMixin {
constructor() HotFuzzBase(10) {
- initTesters();
+ _initTesters();
}
}
diff --git a/packages/hot-fuzz/contracts/superfluid-tests/GDAHotFuzz.yaml b/packages/hot-fuzz/contracts/superfluid-tests/GDAHotFuzz.yaml
new file mode 100644
index 0000000000..f663f5dc81
--- /dev/null
+++ b/packages/hot-fuzz/contracts/superfluid-tests/GDAHotFuzz.yaml
@@ -0,0 +1 @@
+testMode: "property"
diff --git a/packages/hot-fuzz/contracts/superfluid-tests/GeneralDistributionAgreementV1.hott.sol b/packages/hot-fuzz/contracts/superfluid-tests/GeneralDistributionAgreementV1.hott.sol
new file mode 100644
index 0000000000..b1ad780664
--- /dev/null
+++ b/packages/hot-fuzz/contracts/superfluid-tests/GeneralDistributionAgreementV1.hott.sol
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: AGPLv3
+// solhint-disable reason-string
+pragma solidity >= 0.8.0;
+
+import {SuperToken} from "@superfluid-finance/ethereum-contracts/contracts/superfluid/SuperToken.sol";
+import {SuperTokenV1Library} from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol";
+import {ISuperfluidPool} from
+ "@superfluid-finance/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol";
+import {PoolConfig} from
+ "@superfluid-finance/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
+import {HotFuzzBase, SuperfluidTester} from "../HotFuzzBase.sol";
+
+abstract contract GDAHotFuzzMixin is HotFuzzBase {
+ using SuperTokenV1Library for SuperToken;
+
+ ISuperfluidPool[] public pools;
+
+ function getRandomPool(uint8 input) public view returns (ISuperfluidPool pool) {
+ if (pools.length > 0) {
+ pool = pools[input % (pools.length - 1)];
+ }
+ }
+
+ function createPool(uint8 a, PoolConfig memory config) public {
+ (SuperfluidTester tester) = _getOneTester(a);
+ ISuperfluidPool pool = tester.createPool(address(tester), config);
+ _addPool(pool);
+ }
+
+ function maybeConnectPool(bool doConnect, uint8 a, uint8 b) public {
+ (SuperfluidTester tester) = _getOneTester(a);
+ ISuperfluidPool pool = getRandomPool(b);
+ if (doConnect) {
+ tester.connectPool(pool);
+ } else {
+ tester.disconnectPool(pool);
+ }
+ }
+
+ function distributeToPool(uint8 a, uint8 b, uint128 requestedAmount) public {
+ (SuperfluidTester tester) = _getOneTester(a);
+ ISuperfluidPool pool = getRandomPool(b);
+
+ tester.distributeToPool(address(tester), pool, requestedAmount);
+ }
+
+ function distributeFlow(uint8 a, uint8 b, uint8 c, int96 flowRate) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ ISuperfluidPool pool = getRandomPool(c);
+
+ testerA.distributeFlow(address(testerB), pool, flowRate);
+ }
+
+ /// @notice testerA liquidates a flow from testerB to pool
+ /// @dev testerA can be the same as testerB
+ function gdaLiquidateFlow(uint8 a, uint8 b, uint8 c) public {
+ (SuperfluidTester liquidator, SuperfluidTester distributor) = _getTwoTesters(a, b);
+ ISuperfluidPool pool = getRandomPool(c);
+
+ // we first check the condition for whether a flow exists
+ bool flowExists = superToken.getFlowDistributionFlowRate(address(distributor), pool) > 0;
+
+ // then we ensure that the sender has a critical balance
+ (int256 availableBalance,,,) = superToken.realtimeBalanceOfNow(address(distributor));
+ bool isDistributorCritical = availableBalance < 0;
+
+ // if both conditions are met, a liquidation should occur without fail
+ bool isLiquidationValid = flowExists && isDistributorCritical;
+ if (isLiquidationValid) {
+ // solhint-disable-next-line no-empty-blocks
+ try liquidator.gdaLiquidate(address(distributor), pool) {}
+ catch {
+ liquidationFails = true;
+ }
+ }
+ }
+
+ function updateMemberUnits(uint8 a, uint8 b, uint128 units) public {
+ (SuperfluidTester tester) = _getOneTester(a);
+ ISuperfluidPool pool = getRandomPool(b);
+
+ tester.updateMemberUnits(pool, address(tester), units);
+ }
+
+ function poolTransfer(uint8 a, uint8 b, uint256 amount) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ ISuperfluidPool pool = getRandomPool(b);
+
+ testerA.transfer(pool, address(testerB), amount);
+ }
+
+ function poolTransferFrom(uint8 a, uint8 b, uint8 c, uint256 amount) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ (SuperfluidTester tester) = _getOneTester(c);
+ ISuperfluidPool pool = getRandomPool(b);
+
+ testerA.transferFrom(pool, address(tester), address(testerB), amount);
+ }
+
+ function poolIncreaseAllowance(uint8 a, uint8 b, uint256 addedValue) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ ISuperfluidPool pool = getRandomPool(b);
+
+ testerA.increaseAllowance(pool, address(testerB), addedValue);
+ }
+
+ function poolDecreaseAllowance(uint8 a, uint8 b, uint256 subtractedValue) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ ISuperfluidPool pool = getRandomPool(b);
+
+ testerA.decreaseAllowance(pool, address(testerB), subtractedValue);
+ }
+
+ function poolApprove(uint8 a, uint8 b, uint256 amount) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ ISuperfluidPool pool = getRandomPool(b);
+
+ testerA.approve(pool, address(testerB), amount);
+ }
+
+ function claimAll(uint8 a, uint8 b) public {
+ (SuperfluidTester tester) = _getOneTester(a);
+ ISuperfluidPool pool = getRandomPool(b);
+
+ tester.claimAll(pool);
+ }
+
+ function claimAllForMember(uint8 a, uint8 b) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ ISuperfluidPool pool = getRandomPool(b);
+
+ testerA.claimAll(pool, address(testerB));
+ }
+
+ function _addPool(ISuperfluidPool pool) internal {
+ pools.push(pool);
+ _addAccount(address(pool));
+ }
+}
+
+contract GDAHotFuzz is HotFuzzBase(10), GDAHotFuzzMixin {
+ uint256 public constant NUM_POOLS = 3;
+
+ constructor() {
+ _initTesters();
+
+ PoolConfig memory config = PoolConfig({transferabilityForUnitsOwner: true, distributionFromAnyAddress: true});
+
+ for (uint256 i; i < NUM_POOLS; i++) {
+ (SuperfluidTester tester) = _getOneTester(uint8(i));
+ ISuperfluidPool pool = tester.createPool(address(tester), config);
+ _addPool(pool);
+ }
+ }
+}
diff --git a/packages/hot-fuzz/contracts/superfluid-tests/InstantDistributionAgreementV1.hott.sol b/packages/hot-fuzz/contracts/superfluid-tests/InstantDistributionAgreementV1.hott.sol
index 704ebd251f..aadbb7080b 100644
--- a/packages/hot-fuzz/contracts/superfluid-tests/InstantDistributionAgreementV1.hott.sol
+++ b/packages/hot-fuzz/contracts/superfluid-tests/InstantDistributionAgreementV1.hott.sol
@@ -11,7 +11,7 @@ abstract contract IDAHotFuzzMixin is HotFuzzBase {
function setupIndex(uint8 a, uint8 b, uint32 indexId, uint128 units) public {
indexId = indexId % MAX_NUM_INDICES;
- (SuperfluidTester testerA, SuperfluidTester testerB) = getTwoTesters(a, b);
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
(bool exists,,,) = sf.ida.getIndex(superToken, address(testerA), indexId);
if (!exists) {
@@ -22,23 +22,52 @@ abstract contract IDAHotFuzzMixin is HotFuzzBase {
function distributeIfIndexExists(uint8 a, uint32 indexId, uint256 amount) public {
indexId = indexId % MAX_NUM_INDICES;
- (SuperfluidTester testerA) = getOneTester(a);
+ (SuperfluidTester testerA) = _getOneTester(a);
(bool exists,,,) = sf.ida.getIndex(superToken, address(testerA), indexId);
if (exists) {
(uint256 actualAmount, ) = sf.ida.calculateDistribution(superToken, address(testerA), indexId, amount);
+ int256 a1 = _superTokenBalanceOfNow(address(testerA));
testerA.distribute(indexId, amount);
- int256 a1 = superTokenBalanceOfNow(address(testerA));
- int256 a2 = superTokenBalanceOfNow(address(testerA));
+ int256 a2 = _superTokenBalanceOfNow(address(testerA));
assert(a1 - a2 == int256(actualAmount));
}
}
+ function updateIndexIfIndexExists(uint8 a, uint32 indexId, uint128 indexValue) public {
+ indexId = indexId % MAX_NUM_INDICES;
+ (SuperfluidTester testerA) = _getOneTester(a);
+
+ (bool exists,,,) = sf.ida.getIndex(superToken, address(testerA), indexId);
+ if (exists) {
+ testerA.updateIndex(indexId, indexValue);
+ }
+ }
+
+ function revokeSubscription(uint8 a, uint8 b, uint32 indexId) public {
+ indexId = indexId % MAX_NUM_INDICES;
+
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ testerB.revokeSubscription(address(testerA), indexId);
+ }
+
+ function deleteSubscription(uint8 a, uint8 b, uint32 indexId) public {
+ indexId = indexId % MAX_NUM_INDICES;
+
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ testerB.deleteSubscription(address(testerB), indexId, address(testerA));
+ }
+
+ function claim(uint8 a, uint8 b, uint32 indexId) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ testerB.claim(address(testerB), indexId, address(testerA));
+ }
+
function updateSubscriptionUnits(uint8 a, uint8 b, uint32 indexId, uint128 units) public {
require(units > 0);
indexId = indexId % MAX_NUM_INDICES;
- (SuperfluidTester testerA, SuperfluidTester testerB) = getTwoTesters(a, b);
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
testerA.updateSubscriptionUnits(indexId, address(testerB), units);
bool exist;
@@ -51,7 +80,7 @@ abstract contract IDAHotFuzzMixin is HotFuzzBase {
function approveSubscriptionUnits(uint8 a, uint8 b, uint32 indexId) public {
indexId = indexId % MAX_NUM_INDICES;
- (SuperfluidTester testerA, SuperfluidTester testerB) = getTwoTesters(a, b);
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
bool exist;
bool approved;
sf.ida.getSubscription(superToken, address(testerA), indexId, address(testerB));
@@ -65,6 +94,6 @@ abstract contract IDAHotFuzzMixin is HotFuzzBase {
contract IDAHotFuzz is IDAHotFuzzMixin {
constructor() HotFuzzBase(10) {
- initTesters();
+ _initTesters();
}
}
diff --git a/packages/hot-fuzz/contracts/superfluid-tests/SuperHotFuzz.sol b/packages/hot-fuzz/contracts/superfluid-tests/SuperHotFuzz.sol
index 8485ee7dbc..300c89846e 100644
--- a/packages/hot-fuzz/contracts/superfluid-tests/SuperHotFuzz.sol
+++ b/packages/hot-fuzz/contracts/superfluid-tests/SuperHotFuzz.sol
@@ -3,12 +3,12 @@ pragma solidity >= 0.8.0;
import "./ConstantFlowAgreementV1.hott.sol";
import "./InstantDistributionAgreementV1.hott.sol";
+import "./GeneralDistributionAgreementV1.hott.sol";
import "./SuperToken.hott.sol";
-
// Combine all the hot fuzzes
-contract SuperHotFuzz is CFAHotFuzzMixin, IDAHotFuzzMixin, SuperTokenHotFuzzMixin {
- constructor() HotFuzzBase(10) {
- initTesters();
+contract SuperHotFuzz is HotFuzzBase(10), CFAHotFuzzMixin, IDAHotFuzzMixin, GDAHotFuzzMixin, SuperTokenHotFuzzMixin {
+ constructor() {
+ _initTesters();
}
}
diff --git a/packages/hot-fuzz/contracts/superfluid-tests/SuperToken.hott.sol b/packages/hot-fuzz/contracts/superfluid-tests/SuperToken.hott.sol
index bbf55855fc..d833ce9de4 100644
--- a/packages/hot-fuzz/contracts/superfluid-tests/SuperToken.hott.sol
+++ b/packages/hot-fuzz/contracts/superfluid-tests/SuperToken.hott.sol
@@ -4,16 +4,46 @@ pragma solidity >= 0.8.0;
import "../HotFuzzBase.sol";
-
abstract contract SuperTokenHotFuzzMixin is HotFuzzBase {
+ function approve(uint8 a, uint8 b, uint256 amount) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ testerA.approve(address(testerB), amount);
+ }
+
+ function increaseAllowance(uint8 a, uint8 b, uint256 addedValue) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ testerA.increaseAllowance(address(testerB), addedValue);
+ }
+
+ function decreaseAllowance(uint8 a, uint8 b, uint256 subtractedValue) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ testerA.decreaseAllowance(address(testerB), subtractedValue);
+ }
+
+ function transfer(uint8 a, uint8 b, uint256 amount) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ testerA.transfer(address(testerB), amount);
+ }
+
+ function transferFrom(uint8 a, uint8 b, uint8 c, uint256 amount) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ SuperfluidTester testerC = _getOneTester(c);
+ testerA.transferFrom(address(testerB), address(testerC), amount);
+ }
+
+ function transferAll(uint8 a, uint8 b) public {
+ (SuperfluidTester testerA, SuperfluidTester testerB) = _getTwoTesters(a, b);
+ testerA.transferAll(address(testerB));
+ }
+
function upgrade(uint8 a, uint64 amount) public {
require(amount > 0);
- SuperfluidTester tester = getOneTester(a);
+ SuperfluidTester tester = _getOneTester(a);
- int256 a1 = superTokenBalanceOfNow(address(tester));
+ int256 a1 = _superTokenBalanceOfNow(address(tester));
int256 b1 = int256(token.balanceOf(address(tester)));
tester.upgradeSuperToken(amount);
- int256 a2 = superTokenBalanceOfNow(address(tester));
+ int256 a2 = _superTokenBalanceOfNow(address(tester));
int256 b2 = int256(token.balanceOf(address(tester)));
assert(int256(uint256(amount)) == b1 - b2);
assert(b1 - b2 == a2 - a1);
@@ -22,13 +52,13 @@ abstract contract SuperTokenHotFuzzMixin is HotFuzzBase {
function downgrade(uint8 a, uint64 amount) public {
require(amount > 0);
- SuperfluidTester tester = getOneTester(a);
+ SuperfluidTester tester = _getOneTester(a);
- int256 a1 = superTokenBalanceOfNow(address(tester));
+ int256 a1 = _superTokenBalanceOfNow(address(tester));
require(a1 >= int256(uint256(amount)));
int256 b1 = int256(token.balanceOf(address(tester)));
tester.downgradeSuperToken(amount);
- int256 a2 = superTokenBalanceOfNow(address(tester));
+ int256 a2 = _superTokenBalanceOfNow(address(tester));
int256 b2 = int256(token.balanceOf(address(tester)));
assert(int256(uint256(amount)) == b2 - b1);
assert(b2 - b1 == a1 - a2);
@@ -38,6 +68,6 @@ abstract contract SuperTokenHotFuzzMixin is HotFuzzBase {
contract SuperTokenHotFuzz is SuperTokenHotFuzzMixin {
constructor() HotFuzzBase(10) {
- initTesters();
+ _initTesters();
}
}
diff --git a/packages/hot-fuzz/echidna.yaml b/packages/hot-fuzz/echidna.yaml
index 75abbe03e5..814b007eca 100644
--- a/packages/hot-fuzz/echidna.yaml
+++ b/packages/hot-fuzz/echidna.yaml
@@ -7,24 +7,29 @@ cryticArgs: [
"--foundry-out-directory=build/foundry/out",
# to generate:
# $ (j=$((0xf01));tasks/list-all-linked-libraries.sh | while read i;do echo -n "($i,$(printf "0x%x" $j)),";j=$((j+1));done)
- "--compile-libraries=(CFAv1ForwarderDeployerLibrary,0xf01),(IDAv1ForwarderDeployerLibrary,0xf02),(ProxyDeployerLibrary,0xf03),(SlotsBitmapLibrary,0xf04),(SuperfluidCFAv1DeployerLibrary,0xf05),(SuperfluidGovDeployerLibrary,0xf06),(SuperfluidHostDeployerLibrary,0xf07),(SuperfluidIDAv1DeployerLibrary,0xf08),(SuperfluidLoaderDeployerLibrary,0xf09),(SuperfluidNFTLogicDeployerLibrary,0xf0a),(SuperfluidPeripheryDeployerLibrary,0xf0b),(SuperTokenDeployerLibrary,0xf0c),(TokenDeployerLibrary,0xf0d)"
+ "--compile-libraries=(CFAv1ForwarderDeployerLibrary,0xf01),(GDAv1ForwarderDeployerLibrary,0xf02),(IDAv1ForwarderDeployerLibrary,0xf03),(ProxyDeployerLibrary,0xf04),(SlotsBitmapLibrary,0xf05),(SuperfluidCFAv1DeployerLibrary,0xf06),(SuperfluidFlowNFTLogicDeployerLibrary,0xf07),(SuperfluidGDAv1DeployerLibrary,0xf08),(SuperfluidGovDeployerLibrary,0xf09),(SuperfluidHostDeployerLibrary,0xf0a),(SuperfluidIDAv1DeployerLibrary,0xf0b),(SuperfluidLoaderDeployerLibrary,0xf0c),(SuperfluidPeripheryDeployerLibrary,0xf0d),(SuperfluidPoolDeployerLibrary,0xf0e),(SuperfluidPoolLogicDeployerLibrary,0xf0f),(SuperfluidPoolNFTLogicDeployerLibrary,0xf10),(SuperTokenDeployerLibrary,0xf11),(TokenDeployerLibrary,0xf12)"
]
deployContracts: [
# to generate:
# $ (j=$((0xf01));tasks/list-all-linked-libraries.sh | while read i;do echo "[\"$(printf "0x%x" $j)\", \"$i\"],";j=$((j+1));done)
["0xf01", "CFAv1ForwarderDeployerLibrary"],
- ["0xf02", "IDAv1ForwarderDeployerLibrary"],
- ["0xf03", "ProxyDeployerLibrary"],
- ["0xf04", "SlotsBitmapLibrary"],
- ["0xf05", "SuperfluidCFAv1DeployerLibrary"],
- ["0xf06", "SuperfluidGovDeployerLibrary"],
- ["0xf07", "SuperfluidHostDeployerLibrary"],
- ["0xf08", "SuperfluidIDAv1DeployerLibrary"],
- ["0xf09", "SuperfluidLoaderDeployerLibrary"],
- ["0xf0a", "SuperfluidNFTLogicDeployerLibrary"],
- ["0xf0b", "SuperfluidPeripheryDeployerLibrary"],
- ["0xf0c", "SuperTokenDeployerLibrary"],
- ["0xf0d", "TokenDeployerLibrary"],
+ ["0xf02", "GDAv1ForwarderDeployerLibrary"],
+ ["0xf03", "IDAv1ForwarderDeployerLibrary"],
+ ["0xf04", "ProxyDeployerLibrary"],
+ ["0xf05", "SlotsBitmapLibrary"],
+ ["0xf06", "SuperfluidCFAv1DeployerLibrary"],
+ ["0xf07", "SuperfluidFlowNFTLogicDeployerLibrary"],
+ ["0xf08", "SuperfluidGDAv1DeployerLibrary"],
+ ["0xf09", "SuperfluidGovDeployerLibrary"],
+ ["0xf0a", "SuperfluidHostDeployerLibrary"],
+ ["0xf0b", "SuperfluidIDAv1DeployerLibrary"],
+ ["0xf0c", "SuperfluidLoaderDeployerLibrary"],
+ ["0xf0d", "SuperfluidPeripheryDeployerLibrary"],
+ ["0xf0e", "SuperfluidPoolDeployerLibrary"],
+ ["0xf0f", "SuperfluidPoolLogicDeployerLibrary"],
+ ["0xf10", "SuperfluidPoolNFTLogicDeployerLibrary"],
+ ["0xf11", "SuperTokenDeployerLibrary"],
+ ["0xf12", "TokenDeployerLibrary"],
]
deployBytecodes: [
["0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24", "608060405234801561001057600080fd5b506109c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063a41e7d5111610078578063a41e7d51146101d4578063aabbb8ca1461020a578063b705676514610236578063f712f3e814610280576100a5565b806329965a1d146100aa5780633d584063146100e25780635df8122f1461012457806365ba36c114610152575b600080fd5b6100e0600480360360608110156100c057600080fd5b50600160a060020a038135811691602081013591604090910135166102b6565b005b610108600480360360208110156100f857600080fd5b5035600160a060020a0316610570565b60408051600160a060020a039092168252519081900360200190f35b6100e06004803603604081101561013a57600080fd5b50600160a060020a03813581169160200135166105bc565b6101c26004803603602081101561016857600080fd5b81019060208101813564010000000081111561018357600080fd5b82018360208201111561019557600080fd5b803590602001918460018302840111640100000000831117156101b757600080fd5b5090925090506106b3565b60408051918252519081900360200190f35b6100e0600480360360408110156101ea57600080fd5b508035600160a060020a03169060200135600160e060020a0319166106ee565b6101086004803603604081101561022057600080fd5b50600160a060020a038135169060200135610778565b61026c6004803603604081101561024c57600080fd5b508035600160a060020a03169060200135600160e060020a0319166107ef565b604080519115158252519081900360200190f35b61026c6004803603604081101561029657600080fd5b508035600160a060020a03169060200135600160e060020a0319166108aa565b6000600160a060020a038416156102cd57836102cf565b335b9050336102db82610570565b600160a060020a031614610339576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b6103428361092a565b15610397576040805160e560020a62461bcd02815260206004820152601a60248201527f4d757374206e6f7420626520616e204552433136352068617368000000000000604482015290519081900360640190fd5b600160a060020a038216158015906103b85750600160a060020a0382163314155b156104ff5760405160200180807f455243313832305f4143434550545f4d4147494300000000000000000000000081525060140190506040516020818303038152906040528051906020012082600160a060020a031663249cb3fa85846040518363ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018083815260200182600160a060020a0316600160a060020a031681526020019250505060206040518083038186803b15801561047e57600080fd5b505afa158015610492573d6000803e3d6000fd5b505050506040513d60208110156104a857600080fd5b5051146104ff576040805160e560020a62461bcd02815260206004820181905260248201527f446f6573206e6f7420696d706c656d656e742074686520696e74657266616365604482015290519081900360640190fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03818116600090815260016020526040812054909116151561059a5750806105b7565b50600160a060020a03808216600090815260016020526040902054165b919050565b336105c683610570565b600160a060020a031614610624576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b81600160a060020a031681600160a060020a0316146106435780610646565b60005b600160a060020a03838116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519184169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a35050565b600082826040516020018083838082843780830192505050925050506040516020818303038152906040528051906020012090505b92915050565b6106f882826107ef565b610703576000610705565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b600080600160a060020a038416156107905783610792565b335b905061079d8361092a565b156107c357826107ad82826108aa565b6107b85760006107ba565b815b925050506106e8565b600160a060020a0390811660009081526020818152604080832086845290915290205416905092915050565b6000808061081d857f01ffc9a70000000000000000000000000000000000000000000000000000000061094c565b909250905081158061082d575080155b1561083d576000925050506106e8565b61084f85600160e060020a031961094c565b909250905081158061086057508015155b15610870576000925050506106e8565b61087a858561094c565b909250905060018214801561088f5750806001145b1561089f576001925050506106e8565b506000949350505050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff1615156108f2576108eb83836107ef565b90506106e8565b50600160a060020a03808316600081815260208181526040808320600160e060020a0319871684529091529020549091161492915050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565b6040517f01ffc9a7000000000000000000000000000000000000000000000000000000008082526004820183905260009182919060208160248189617530fa90519096909550935050505056fea165627a7a72305820377f4a2d4301ede9949f163f319021a6e9c687c292a5e2b2c4734c126b524e6c0029"],
diff --git a/packages/hot-fuzz/package.json b/packages/hot-fuzz/package.json
index 9e2f4244a1..c462bd46d0 100644
--- a/packages/hot-fuzz/package.json
+++ b/packages/hot-fuzz/package.json
@@ -25,7 +25,7 @@
"@superfluid-finance/ethereum-contracts": "1.8.0"
},
"devDependencies": {
- "@superfluid-finance/ethereum-contracts": "1.8.1"
+ "@superfluid-finance/ethereum-contracts": "1.9.0"
},
"license": "AGPL-3.0",
"bugs": {
diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json
index e636244cb4..53d072a2ed 100644
--- a/packages/js-sdk/package.json
+++ b/packages/js-sdk/package.json
@@ -49,7 +49,7 @@
"node-fetch": "2.7.0"
},
"devDependencies": {
- "@superfluid-finance/ethereum-contracts": "1.8.1",
+ "@superfluid-finance/ethereum-contracts": "1.9.0",
"chai-as-promised": "^7.1.1",
"webpack": "^5.88.2",
"webpack-bundle-analyzer": "^4.9.1",
diff --git a/packages/metadata/main/networks/list.cjs b/packages/metadata/main/networks/list.cjs
index 2878ce9c6f..46c14981ae 100644
--- a/packages/metadata/main/networks/list.cjs
+++ b/packages/metadata/main/networks/list.cjs
@@ -20,6 +20,7 @@ module.exports =
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0xfDdcdac21D64B639546f3Ce2868C7EF06036990c",
"gdaV1": "0x3dB8Abd8B696F6c4150212A85961f954825Dd4B9",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0x94f26B4c8AD12B18c12f38E878618f7664bdcCE2",
"constantOutflowNFT": "0xB18cbFeA12b5CB2626C74c94920dB1B37Ae91506",
"constantInflowNFT": "0xF07df8b66ed80399B1E00981D61aD34EB4293032",
@@ -81,6 +82,7 @@ module.exports =
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0x804348D4960a61f2d5F9ce9103027A3E849E09b8",
"gdaV1": "0x63ab406B6eF6c8be732c1edbd15464de16a8F46D",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0xB798553db6EB3D3C56912378409370145E97324B",
"constantOutflowNFT": "0x502CC982947216C0f94e433BC78c413806301C07",
"constantInflowNFT": "0x9906A7e948C642B6bc74b9A5EAfCddB3580b44e0",
@@ -144,6 +146,7 @@ module.exports =
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0x96215257F2FcbB00135578f766c0449d239bd92F",
"gdaV1": "0xe87F46A15C410F151309Bf7516e130087Fc6a5E5",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0xfafe31cf998Df4e5D8310B03EBa8fb5bF327Eaf5",
"constantOutflowNFT": "0xDF874BA132D8C68FEb5De513790f7612Fe20dDbd",
"constantInflowNFT": "0xf88dd7208438Fdc5Ad05857eA701b7b51cdae0a9",
@@ -186,6 +189,7 @@ module.exports =
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0x96215257F2FcbB00135578f766c0449d239bd92F",
"gdaV1": "0xe87F46A15C410F151309Bf7516e130087Fc6a5E5",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0xfafe31cf998Df4e5D8310B03EBa8fb5bF327Eaf5",
"constantOutflowNFT": "0xDF874BA132D8C68FEb5De513790f7612Fe20dDbd",
"constantInflowNFT": "0xf88dd7208438Fdc5Ad05857eA701b7b51cdae0a9",
@@ -227,10 +231,11 @@ module.exports =
"cfaV1Forwarder": "0x2CDd45c5182602a36d391F7F16DD9f8386C3bD8D",
"idaV1": "0xA44dEC7A0Dde1a56AeDe4143C1ef89cf5d956782",
"gdaV1": "0x51f571D934C59185f13d17301a36c07A2268B814",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0x1C92042426B6bAAe497bEf461B6d8342D03aEc92",
"constantOutflowNFT": "0x49583f57EFeBe733EC872c5d5437116085a3eE3c",
"constantInflowNFT": "0x67d0Efab10b390206b356BA7FB453Ab56AAB7480",
- "superfluidLoader": "0x96C3C2d23d143301cF363a02cB7fe3596d2834d7",
+ "superfluidLoader": "0x36446Ec9C7909608065dEB7f491701d815B880e5",
"autowrap": {
"manager": "0x30aE282CF477E2eF28B14d0125aCEAd57Fe1d7a1",
"wrapStrategy": "0x1D65c6d3AD39d454Ea8F682c49aE7744706eA96d"
diff --git a/packages/metadata/module/networks/list.js b/packages/metadata/module/networks/list.js
index 7c012a9a9a..4e13384864 100644
--- a/packages/metadata/module/networks/list.js
+++ b/packages/metadata/module/networks/list.js
@@ -20,6 +20,7 @@ export default
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0xfDdcdac21D64B639546f3Ce2868C7EF06036990c",
"gdaV1": "0x3dB8Abd8B696F6c4150212A85961f954825Dd4B9",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0x94f26B4c8AD12B18c12f38E878618f7664bdcCE2",
"constantOutflowNFT": "0xB18cbFeA12b5CB2626C74c94920dB1B37Ae91506",
"constantInflowNFT": "0xF07df8b66ed80399B1E00981D61aD34EB4293032",
@@ -81,6 +82,7 @@ export default
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0x804348D4960a61f2d5F9ce9103027A3E849E09b8",
"gdaV1": "0x63ab406B6eF6c8be732c1edbd15464de16a8F46D",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0xB798553db6EB3D3C56912378409370145E97324B",
"constantOutflowNFT": "0x502CC982947216C0f94e433BC78c413806301C07",
"constantInflowNFT": "0x9906A7e948C642B6bc74b9A5EAfCddB3580b44e0",
@@ -144,6 +146,7 @@ export default
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0x96215257F2FcbB00135578f766c0449d239bd92F",
"gdaV1": "0xe87F46A15C410F151309Bf7516e130087Fc6a5E5",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0xfafe31cf998Df4e5D8310B03EBa8fb5bF327Eaf5",
"constantOutflowNFT": "0xDF874BA132D8C68FEb5De513790f7612Fe20dDbd",
"constantInflowNFT": "0xf88dd7208438Fdc5Ad05857eA701b7b51cdae0a9",
@@ -186,6 +189,7 @@ export default
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0x96215257F2FcbB00135578f766c0449d239bd92F",
"gdaV1": "0xe87F46A15C410F151309Bf7516e130087Fc6a5E5",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0xfafe31cf998Df4e5D8310B03EBa8fb5bF327Eaf5",
"constantOutflowNFT": "0xDF874BA132D8C68FEb5De513790f7612Fe20dDbd",
"constantInflowNFT": "0xf88dd7208438Fdc5Ad05857eA701b7b51cdae0a9",
@@ -227,10 +231,11 @@ export default
"cfaV1Forwarder": "0x2CDd45c5182602a36d391F7F16DD9f8386C3bD8D",
"idaV1": "0xA44dEC7A0Dde1a56AeDe4143C1ef89cf5d956782",
"gdaV1": "0x51f571D934C59185f13d17301a36c07A2268B814",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0x1C92042426B6bAAe497bEf461B6d8342D03aEc92",
"constantOutflowNFT": "0x49583f57EFeBe733EC872c5d5437116085a3eE3c",
"constantInflowNFT": "0x67d0Efab10b390206b356BA7FB453Ab56AAB7480",
- "superfluidLoader": "0x96C3C2d23d143301cF363a02cB7fe3596d2834d7",
+ "superfluidLoader": "0x36446Ec9C7909608065dEB7f491701d815B880e5",
"autowrap": {
"manager": "0x30aE282CF477E2eF28B14d0125aCEAd57Fe1d7a1",
"wrapStrategy": "0x1D65c6d3AD39d454Ea8F682c49aE7744706eA96d"
diff --git a/packages/metadata/networks.json b/packages/metadata/networks.json
index d1028380c4..2e6414d381 100644
--- a/packages/metadata/networks.json
+++ b/packages/metadata/networks.json
@@ -18,6 +18,7 @@
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0xfDdcdac21D64B639546f3Ce2868C7EF06036990c",
"gdaV1": "0x3dB8Abd8B696F6c4150212A85961f954825Dd4B9",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0x94f26B4c8AD12B18c12f38E878618f7664bdcCE2",
"constantOutflowNFT": "0xB18cbFeA12b5CB2626C74c94920dB1B37Ae91506",
"constantInflowNFT": "0xF07df8b66ed80399B1E00981D61aD34EB4293032",
@@ -79,6 +80,7 @@
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0x804348D4960a61f2d5F9ce9103027A3E849E09b8",
"gdaV1": "0x63ab406B6eF6c8be732c1edbd15464de16a8F46D",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0xB798553db6EB3D3C56912378409370145E97324B",
"constantOutflowNFT": "0x502CC982947216C0f94e433BC78c413806301C07",
"constantInflowNFT": "0x9906A7e948C642B6bc74b9A5EAfCddB3580b44e0",
@@ -142,6 +144,7 @@
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0x96215257F2FcbB00135578f766c0449d239bd92F",
"gdaV1": "0xe87F46A15C410F151309Bf7516e130087Fc6a5E5",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0xfafe31cf998Df4e5D8310B03EBa8fb5bF327Eaf5",
"constantOutflowNFT": "0xDF874BA132D8C68FEb5De513790f7612Fe20dDbd",
"constantInflowNFT": "0xf88dd7208438Fdc5Ad05857eA701b7b51cdae0a9",
@@ -184,6 +187,7 @@
"cfaV1Forwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
"idaV1": "0x96215257F2FcbB00135578f766c0449d239bd92F",
"gdaV1": "0xe87F46A15C410F151309Bf7516e130087Fc6a5E5",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0xfafe31cf998Df4e5D8310B03EBa8fb5bF327Eaf5",
"constantOutflowNFT": "0xDF874BA132D8C68FEb5De513790f7612Fe20dDbd",
"constantInflowNFT": "0xf88dd7208438Fdc5Ad05857eA701b7b51cdae0a9",
@@ -225,10 +229,11 @@
"cfaV1Forwarder": "0x2CDd45c5182602a36d391F7F16DD9f8386C3bD8D",
"idaV1": "0xA44dEC7A0Dde1a56AeDe4143C1ef89cf5d956782",
"gdaV1": "0x51f571D934C59185f13d17301a36c07A2268B814",
+ "gdaV1Forwarder": "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08",
"superTokenFactory": "0x1C92042426B6bAAe497bEf461B6d8342D03aEc92",
"constantOutflowNFT": "0x49583f57EFeBe733EC872c5d5437116085a3eE3c",
"constantInflowNFT": "0x67d0Efab10b390206b356BA7FB453Ab56AAB7480",
- "superfluidLoader": "0x96C3C2d23d143301cF363a02cB7fe3596d2834d7",
+ "superfluidLoader": "0x36446Ec9C7909608065dEB7f491701d815B880e5",
"autowrap": {
"manager": "0x30aE282CF477E2eF28B14d0125aCEAd57Fe1d7a1",
"wrapStrategy": "0x1D65c6d3AD39d454Ea8F682c49aE7744706eA96d"
diff --git a/packages/sdk-core/CHANGELOG.md b/packages/sdk-core/CHANGELOG.md
index 5e2e52bbdd..f6b7afaffa 100644
--- a/packages/sdk-core/CHANGELOG.md
+++ b/packages/sdk-core/CHANGELOG.md
@@ -7,7 +7,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]
### Changed
-
- Map the name from subgraph to an unknown event, instead of "\_Unknown".
## [0.6.12] - 2023-10-23
diff --git a/packages/sdk-core/package.json b/packages/sdk-core/package.json
index 831fcda00d..54ba7e620f 100644
--- a/packages/sdk-core/package.json
+++ b/packages/sdk-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@superfluid-finance/sdk-core",
- "version": "0.6.12",
+ "version": "0.6.13",
"description": "SDK Core for building with Superfluid Protocol",
"homepage": "https://github.com/superfluid-finance/protocol-monorepo/tree/dev/packages/sdk-core#readme",
"repository": {
@@ -56,7 +56,7 @@
"url": "https://github.com/superfluid-finance/protocol-monorepo/issues"
},
"dependencies": {
- "@superfluid-finance/ethereum-contracts": "1.8.1",
+ "@superfluid-finance/ethereum-contracts": "1.9.0",
"@superfluid-finance/metadata": "1.1.22",
"browserify": "^17.0.0",
"graphql-request": "^6.1.0",
diff --git a/packages/sdk-core/src/ConstantFlowAgreementV1.ts b/packages/sdk-core/src/ConstantFlowAgreementV1.ts
index 62260a7269..a978d55651 100644
--- a/packages/sdk-core/src/ConstantFlowAgreementV1.ts
+++ b/packages/sdk-core/src/ConstantFlowAgreementV1.ts
@@ -3,6 +3,7 @@ import { ethers } from "ethers";
import Host from "./Host";
import Operation from "./Operation";
import { SFError } from "./SFError";
+import SuperfluidAgreement from "./SuperfluidAgreement";
import {
FlowRateAllowanceParams,
FlowRateAllowanceWithPermissionsParams,
@@ -40,7 +41,7 @@ const cfaInterface = IConstantFlowAgreementV1__factory.createInterface();
* Constant Flow Agreement V1 Helper Class
* @description A helper class to interact with the CFAV1 contract.
*/
-export default class ConstantFlowAgreementV1 {
+export default class ConstantFlowAgreementV1 extends SuperfluidAgreement {
readonly host: Host;
readonly contract: IConstantFlowAgreementV1;
readonly forwarder: CFAv1Forwarder;
@@ -50,6 +51,7 @@ export default class ConstantFlowAgreementV1 {
cfaV1Address: string,
cfaV1ForwarderAddress: string
) {
+ super();
this.host = new Host(hostAddress);
this.contract = new ethers.Contract(
cfaV1Address,
@@ -743,26 +745,6 @@ export default class ConstantFlowAgreementV1 {
/** ### Internal Helper Functions ### */
- /**
- * Returns the desired Operation based on shouldUseCallAgreement.
- * @param shouldUseCallAgreement whether or not to use host.callAgreement
- * @param callAgreementOperation the host.callAgreement created Operation
- * @param forwarderPopulatedTransactionPromise the populated forwarder transaction promise
- */
- _getCallAgreementOperation = (
- callAgreementOperation: Operation,
- forwarderPopulatedTransactionPromise?: Promise,
- shouldUseCallAgreement?: boolean
- ) => {
- return shouldUseCallAgreement
- ? callAgreementOperation
- : new Operation(
- callAgreementOperation.populateTransactionPromise,
- callAgreementOperation.type,
- forwarderPopulatedTransactionPromise
- );
- };
-
/**
* Sanitizes flow info, converting BigNumber to string.
* @param timestamp last updated timestamp of flow
diff --git a/packages/sdk-core/src/Framework.ts b/packages/sdk-core/src/Framework.ts
index f1102681d5..80893b60dd 100644
--- a/packages/sdk-core/src/Framework.ts
+++ b/packages/sdk-core/src/Framework.ts
@@ -4,6 +4,7 @@ import Web3 from "web3";
import BatchCall from "./BatchCall";
import ConstantFlowAgreementV1 from "./ConstantFlowAgreementV1";
+import GeneralDistributionAgreementV1 from "./GeneralDistributionAgreementV1";
import Governance from "./Governance";
import Host from "./Host";
import InstantDistributionAgreementV1 from "./InstantDistributionAgreementV1";
@@ -69,6 +70,7 @@ export default class Framework {
governance: Governance;
host: Host;
idaV1: InstantDistributionAgreementV1;
+ gdaV1: GeneralDistributionAgreementV1;
query: Query;
private constructor(
@@ -92,6 +94,11 @@ export default class Framework {
settings.config.hostAddress,
settings.config.idaV1Address
);
+ this.gdaV1 = new GeneralDistributionAgreementV1(
+ settings.config.hostAddress,
+ settings.config.gdaV1Address,
+ settings.config.gdaV1ForwarderAddress
+ );
this.query = new Query(settings);
const resolver = new ethers.Contract(
settings.config.resolverAddress,
@@ -103,6 +110,7 @@ export default class Framework {
governance: this.governance.contract,
host: this.host.contract,
idaV1: this.idaV1.contract,
+ gdaV1: this.gdaV1.contract,
resolver,
};
}
@@ -191,6 +199,14 @@ export default class Framework {
hostAddress: networkData.addresses.host,
cfaV1Address: networkData.addresses.cfaV1,
idaV1Address: networkData.addresses.idaV1,
+ // @note TODO - remove the any once you add gdaV1 and gdaV1Forwarder to metadata
+ // add idaV1Forwarder to metadata as well
+ gdaV1Address:
+ (networkData.addresses as any).gdaV1 ||
+ networkData.addresses.idaV1,
+ gdaV1ForwarderAddress:
+ (networkData.addresses as any).gdaV1Forwarder ||
+ networkData.addresses.idaV1,
governanceAddress,
cfaV1ForwarderAddress:
networkData.addresses.cfaV1Forwarder,
@@ -205,6 +221,8 @@ export default class Framework {
);
const cfaV1ForwarderAddress =
await resolver.get("CFAv1Forwarder");
+ const gdaV1ForwarderAddress =
+ await resolver.get("GDAv1Forwarder");
const superfluidLoader = SuperfluidLoader__factory.connect(
superfluidLoaderAddress,
provider
@@ -224,8 +242,10 @@ export default class Framework {
hostAddress: framework.superfluid,
cfaV1Address: framework.agreementCFAv1,
idaV1Address: framework.agreementIDAv1,
+ gdaV1Address: framework.agreementGDAv1,
governanceAddress,
cfaV1ForwarderAddress,
+ gdaV1ForwarderAddress,
},
};
diff --git a/packages/sdk-core/src/GeneralDistributionAgreementV1.ts b/packages/sdk-core/src/GeneralDistributionAgreementV1.ts
new file mode 100644
index 0000000000..acd8563d85
--- /dev/null
+++ b/packages/sdk-core/src/GeneralDistributionAgreementV1.ts
@@ -0,0 +1,517 @@
+import { ethers } from "ethers";
+
+import Host from "./Host";
+import { SFError } from "./SFError";
+import SuperfluidAgreement from "./SuperfluidAgreement";
+import SuperfluidPoolClass from "./SuperfluidPool";
+import {
+ ConnectPoolParams,
+ CreatePoolParams,
+ DisconnectPoolParams,
+ DistributeFlowParams,
+ DistributeParams,
+ EstimateDistributionActualAmountParams,
+ EstimateFlowDistributionActualFlowRateParams,
+ FlowDistributionActualFlowRateData,
+ GDAGetFlowRateParams,
+ GDAGetNetFlowParams,
+ GetPoolAdjustmentFlowInfoParams,
+ GetPoolAdjustmentFlowRateParams,
+ IsMemberConnectedParams,
+ IsPoolParams,
+ PoolAdjustmentFlowInfo,
+} from "./interfaces";
+import {
+ GDAv1Forwarder,
+ GDAv1Forwarder__factory,
+ IGeneralDistributionAgreementV1,
+ IGeneralDistributionAgreementV1__factory,
+} from "./typechain-types";
+import { normalizeAddress } from "./utils";
+
+const gdaInterface = IGeneralDistributionAgreementV1__factory.createInterface();
+
+/**
+ * General Distribution Agreement V1 Helper Class
+ * @description A helper class to interact with the GDAV1 contract.
+ */
+export default class GeneralDistributionAgreementV1 extends SuperfluidAgreement {
+ readonly host: Host;
+ readonly contract: IGeneralDistributionAgreementV1;
+ readonly forwarder: GDAv1Forwarder;
+
+ constructor(
+ hostAddress: string,
+ gdaV1Address: string,
+ gdaV1ForwarderAddress: string
+ ) {
+ super();
+ this.host = new Host(hostAddress);
+ this.contract = new ethers.Contract(
+ gdaV1Address,
+ IGeneralDistributionAgreementV1__factory.abi
+ ) as IGeneralDistributionAgreementV1;
+ this.forwarder = new ethers.Contract(
+ gdaV1ForwarderAddress,
+ GDAv1Forwarder__factory.abi
+ ) as GDAv1Forwarder;
+ }
+
+ /**
+ * Retrieves the net flow for a specific token and account.
+ *
+ * @param token The token address.
+ * @param account The account address.
+ * @param providerOrSigner A provider or signer object
+ * @returns The net flow of the account for the token.
+ */
+ getNetFlow = async (params: GDAGetNetFlowParams): Promise => {
+ const normalizedToken = normalizeAddress(params.token);
+ const normalizedAccount = normalizeAddress(params.account);
+ try {
+ return (
+ await this.contract
+ .connect(params.providerOrSigner)
+ .getNetFlow(normalizedToken, normalizedAccount)
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "GDAV1_READ",
+ message: "There was an error getting the GDA net flow.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the flow rate for a specific token, sender, and pool.
+ *
+ * @param token The token address.
+ * @param from The sender address.
+ * @param pool The pool address.
+ * @param providerOrSigner A provider or signer object
+ * @returns The flow rate from the sender to the pool for the token.
+ */
+ getFlowRate = async (params: GDAGetFlowRateParams): Promise => {
+ const normalizedToken = normalizeAddress(params.token);
+ const normalizedFrom = normalizeAddress(params.from);
+ const normalizedPool = normalizeAddress(params.pool);
+
+ try {
+ return (
+ await this.contract
+ .connect(params.providerOrSigner)
+ .getFlowRate(
+ normalizedToken,
+ normalizedFrom,
+ normalizedPool
+ )
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "GDAV1_READ",
+ message: "There was an error getting the GDA flow rate.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Estimates the flow distribution's actual flow rate for a specific token, sender, and pool.
+ *
+ * @param token The token address.
+ * @param from The sender address.
+ * @param pool The pool address.
+ * @param requestedFlowRate The requested flow rate.
+ * @param providerOrSigner A provider or signer object
+ * @returns The flow distribution's actual flow rate and the total distribution flow rate for the pool.
+ */
+ estimateFlowDistributionActualFlowRate = async (
+ params: EstimateFlowDistributionActualFlowRateParams
+ ): Promise => {
+ const normalizedToken = normalizeAddress(params.token);
+ const normalizedFrom = normalizeAddress(params.from);
+ const normalizedPool = normalizeAddress(params.pool);
+
+ try {
+ const data = await this.contract
+ .connect(params.providerOrSigner)
+ .estimateFlowDistributionActualFlowRate(
+ normalizedToken,
+ normalizedFrom,
+ normalizedPool,
+ params.requestedFlowRate
+ );
+ return {
+ actualFlowRate: data.actualFlowRate.toString(),
+ totalDistributionFlowRate:
+ data.totalDistributionFlowRate.toString(),
+ };
+ } catch (err) {
+ throw new SFError({
+ type: "GDAV1_READ",
+ message:
+ "There was an error estimating the GDA flow distribution's actual flow rate.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Estimates the distribution's actual amount for a specific token, sender, and pool.
+ *
+ * @param token The token address.
+ * @param from The sender address.
+ * @param pool The pool address.
+ * @param requestedAmount The requested amount.
+ * @param providerOrSigner A provider or signer object
+ * @returns The actual amount that will be distributed.
+ */
+ estimateDistributionActualAmount = async (
+ params: EstimateDistributionActualAmountParams
+ ): Promise => {
+ const normalizedToken = normalizeAddress(params.token);
+ const normalizedFrom = normalizeAddress(params.from);
+ const normalizedPool = normalizeAddress(params.pool);
+ try {
+ return (
+ await this.contract
+ .connect(params.providerOrSigner)
+ .estimateDistributionActualAmount(
+ normalizedToken,
+ normalizedFrom,
+ normalizedPool,
+ params.requestedAmount
+ )
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "GDAV1_READ",
+ message:
+ "There was an error estimating the GDA distribution's actual amount.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the pool adjustment flow rate for a specific token and pool.
+ *
+ * @param token The token address.
+ * @param pool The pool address.
+ * @param providerOrSigner A provider or signer object
+ * @returns The pool adjustment flow rate for the token and pool.
+ */
+ getPoolAdjustmentFlowRate = async (
+ params: GetPoolAdjustmentFlowRateParams
+ ): Promise => {
+ const normalizedPool = normalizeAddress(params.pool);
+
+ try {
+ return (
+ await this.contract
+ .connect(params.providerOrSigner)
+ .getPoolAdjustmentFlowRate(normalizedPool)
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "GDAV1_READ",
+ message:
+ "There was an error getting the GDA pool adjustment flow rate.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Checks if a given token and account form a pool.
+ *
+ * @param token The token address.
+ * @param account The account address.
+ * @param providerOrSigner A provider or signer object
+ * @returns Whether the account is a pool for the token.
+ */
+ isPool = async (params: IsPoolParams): Promise => {
+ const normalizedToken = normalizeAddress(params.token);
+ const normalizedAccount = normalizeAddress(params.account);
+
+ try {
+ return await this.contract
+ .connect(params.providerOrSigner)
+ .isPool(normalizedToken, normalizedAccount);
+ } catch (err) {
+ throw new SFError({
+ type: "GDAV1_READ",
+ message:
+ "There was an error checking if the account is a pool.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Checks if a member is connected to a specific pool.
+ *
+ * @param pool The pool address.
+ * @param member The member address.
+ * @param providerOrSigner A provider or signer object
+ * @returns Whether the member is connected to the pool.
+ */
+ isMemberConnected = async (
+ params: IsMemberConnectedParams
+ ): Promise => {
+ const normalizedPool = normalizeAddress(params.pool);
+ const normalizedMember = normalizeAddress(params.member);
+
+ try {
+ return await this.contract
+ .connect(params.providerOrSigner)
+ .isMemberConnected(normalizedPool, normalizedMember);
+ } catch (err) {
+ throw new SFError({
+ type: "GDAV1_READ",
+ message:
+ "There was an error checking if the member is connected to the pool.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the pool adjustment flow information for a specific pool.
+ *
+ * @param pool The address of the pool.
+ * @param providerOrSigner A provider or signer object
+ * @returns The recipient of the pool adjustment flow, the flow hash and the rate of the adjustment flow.
+ */
+ getPoolAdjustmentFlowInfo = async (
+ params: GetPoolAdjustmentFlowInfoParams
+ ): Promise => {
+ const normalizedPool = normalizeAddress(params.pool);
+
+ try {
+ const data = await this.contract
+ .connect(params.providerOrSigner)
+ .getPoolAdjustmentFlowInfo(normalizedPool);
+ return {
+ recipient: data[0],
+ flowHash: data[1],
+ flowRate: data[2].toString(),
+ };
+ } catch (err) {
+ throw new SFError({
+ type: "GDAV1_READ",
+ message:
+ "There was an error getting the GDA pool adjustment flow information.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Creates a new pool with the given token and admin.
+ *
+ * @param token The token address.
+ * @param admin The admin address.
+ * @returns CreatePoolTxn and SuperfluidPool instance
+ */
+ createPool = async (
+ params: CreatePoolParams
+ ): Promise<{
+ createPoolTxn: ethers.ContractTransaction;
+ pool: SuperfluidPoolClass;
+ }> => {
+ const normalizedToken = normalizeAddress(params.token);
+ const normalizedAdmin = normalizeAddress(params.admin);
+
+ try {
+ const createPoolTxn = await this.contract
+ .connect(params.signer)
+ .createPool(normalizedToken, normalizedAdmin, params.config);
+ const txnReceipt = await createPoolTxn.wait();
+ const poolCreatedEvent = txnReceipt.events?.find(
+ (x) => x.event === "PoolCreated"
+ );
+ const poolAddress =
+ poolCreatedEvent?.args?.pool || ethers.constants.AddressZero;
+ return {
+ createPoolTxn,
+ pool: new SuperfluidPoolClass(poolAddress),
+ };
+ } catch (err) {
+ throw new SFError({
+ type: "GDAV1_WRITE",
+ message: "There was an error creating the GDA pool.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Connects a pool to the contract.
+ *
+ * @param pool The pool address.
+ * @param userData The user data.
+ * @param overrides The transaction overrides.
+ * @returns The call agreement operation result.
+ */
+ connectPool = (params: ConnectPoolParams) => {
+ const normalizedPool = normalizeAddress(params.pool);
+ const callData = gdaInterface.encodeFunctionData("connectPool", [
+ normalizedPool,
+ "0x",
+ ]);
+
+ const callAgreementOperation = this.host.callAgreement(
+ this.contract.address,
+ callData,
+ params.userData || "0x",
+ params.overrides
+ );
+
+ const forwarderPopulatedTxnPromise =
+ this.forwarder.populateTransaction.connectPool(
+ normalizedPool,
+ params.userData || "0x",
+ params.overrides || {}
+ );
+
+ return this._getCallAgreementOperation(
+ callAgreementOperation,
+ forwarderPopulatedTxnPromise,
+ params.shouldUseCallAgreement
+ );
+ };
+
+ /**
+ * Disconnects a pool from the contract.
+ *
+ * @param pool The pool address.
+ * @param userData The user data.
+ * @param overrides The transaction overrides.
+ * @returns The call agreement operation result.
+ */
+ disconnectPool = (params: DisconnectPoolParams) => {
+ const normalizedPool = normalizeAddress(params.pool);
+ const callData = gdaInterface.encodeFunctionData("disconnectPool", [
+ normalizedPool,
+ "0x",
+ ]);
+
+ const callAgreementOperation = this.host.callAgreement(
+ this.contract.address,
+ callData,
+ params.userData || "0x",
+ params.overrides
+ );
+
+ const forwarderPopulatedTxnPromise =
+ this.forwarder.populateTransaction.disconnectPool(
+ normalizedPool,
+ params.userData || "0x",
+ params.overrides || {}
+ );
+
+ return this._getCallAgreementOperation(
+ callAgreementOperation,
+ forwarderPopulatedTxnPromise,
+ params.shouldUseCallAgreement
+ );
+ };
+
+ /**
+ * Distributes funds from the sender's account to the specified pool.
+ *
+ * @param token The token address.
+ * @param from The sender's address.
+ * @param pool The pool address.
+ * @param requestedAmount The requested amount to distribute.
+ * @param userData The user data.
+ * @param overrides The transaction overrides.
+ * @returns The call agreement operation result.
+ */
+ distribute = (params: DistributeParams) => {
+ const normalizedToken = normalizeAddress(params.token);
+ const normalizedFrom = normalizeAddress(params.from);
+ const normalizedPool = normalizeAddress(params.pool);
+
+ const callData = gdaInterface.encodeFunctionData("distribute", [
+ normalizedToken,
+ normalizedFrom,
+ normalizedPool,
+ params.requestedAmount,
+ "0x",
+ ]);
+
+ const callAgreementOperation = this.host.callAgreement(
+ this.contract.address,
+ callData,
+ params.userData || "0x",
+ params.overrides
+ );
+
+ const forwarderPopulatedTxnPromise =
+ this.forwarder.populateTransaction.distribute(
+ normalizedToken,
+ normalizedFrom,
+ normalizedPool,
+ params.requestedAmount,
+ params.userData || "0x",
+ params.overrides || {}
+ );
+
+ return this._getCallAgreementOperation(
+ callAgreementOperation,
+ forwarderPopulatedTxnPromise,
+ params.shouldUseCallAgreement
+ );
+ };
+
+ /**
+ * Distributes the flow from the sender's account to the specified pool.
+ *
+ * @param token The token address.
+ * @param from The sender's address.
+ * @param pool The pool address.
+ * @param requestedFlowRate The requested flow rate.
+ * @param userData The user data.
+ * @param overrides The transaction overrides.
+ * @returns The call agreement operation result.
+ */
+ distributeFlow = (params: DistributeFlowParams) => {
+ const normalizedToken = normalizeAddress(params.token);
+ const normalizedFrom = normalizeAddress(params.from);
+ const normalizedPool = normalizeAddress(params.pool);
+
+ const callData = gdaInterface.encodeFunctionData("distributeFlow", [
+ normalizedToken,
+ normalizedFrom,
+ normalizedPool,
+ params.requestedFlowRate,
+ "0x",
+ ]);
+
+ const callAgreementOperation = this.host.callAgreement(
+ this.contract.address,
+ callData,
+ params.userData || "0x",
+ params.overrides
+ );
+
+ const forwarderPopulatedTxnPromise =
+ this.forwarder.populateTransaction.distributeFlow(
+ normalizedToken,
+ normalizedFrom,
+ normalizedPool,
+ params.requestedFlowRate,
+ params.userData || "0x",
+ params.overrides || {}
+ );
+
+ return this._getCallAgreementOperation(
+ callAgreementOperation,
+ forwarderPopulatedTxnPromise,
+ params.shouldUseCallAgreement
+ );
+ };
+}
diff --git a/packages/sdk-core/src/InstantDistributionAgreementV1.ts b/packages/sdk-core/src/InstantDistributionAgreementV1.ts
index 58e046be9c..c889a8d9f4 100644
--- a/packages/sdk-core/src/InstantDistributionAgreementV1.ts
+++ b/packages/sdk-core/src/InstantDistributionAgreementV1.ts
@@ -3,6 +3,7 @@ import { ethers } from "ethers";
import Host from "./Host";
import Operation from "./Operation";
import { SFError } from "./SFError";
+import SuperfluidAgreement from "./SuperfluidAgreement";
import {
IApproveSubscriptionParams,
IClaimParams,
@@ -29,11 +30,12 @@ const idaInterface = IInstantDistributionAgreementV1__factory.createInterface();
* Instant Distribution Agreement V1 Helper Class
* @description A helper class to interact with the IDAV1 contract.
*/
-export default class InstantDistributionAgreementV1 {
+export default class InstantDistributionAgreementV1 extends SuperfluidAgreement {
readonly host: Host;
readonly contract: IInstantDistributionAgreementV1;
constructor(hostAddress: string, idaV1Address: string) {
+ super();
this.host = new Host(hostAddress);
this.contract = new ethers.Contract(
idaV1Address,
diff --git a/packages/sdk-core/src/SFError.ts b/packages/sdk-core/src/SFError.ts
index f3eec58e68..79623c85ff 100644
--- a/packages/sdk-core/src/SFError.ts
+++ b/packages/sdk-core/src/SFError.ts
@@ -8,6 +8,9 @@ export type ErrorType =
| "CFAV1_READ"
| "NFT_READ"
| "IDAV1_READ"
+ | "GDAV1_READ"
+ | "SUPERFLUID_POOL_READ"
+ | "GDAV1_WRITE"
| "INVALID_ADDRESS"
| "INVALID_OBJECT"
| "UNCLEAN_PERMISSIONS"
@@ -24,6 +27,9 @@ const errorTypeToTitleMap = new Map([
["SUPERTOKEN_READ", "SuperToken Read"],
["CFAV1_READ", "ConstantFlowAgreementV1 Read"],
["IDAV1_READ", "InstantDistributionAgreementV1 Read"],
+ ["GDAV1_READ", "GeneralDistributionAgreementV1 Read"],
+ ["GDAV1_WRITE", "GeneralDistributionAgreementV1 Write"],
+ ["SUPERFLUID_POOL_READ", "Superfluid Pool Read"],
["INVALID_ADDRESS", "Invalid Address"],
["INVALID_OBJECT", "Invalid Object"],
["UNSUPPORTED_OPERATION", "Unsupported Batch Call Operation"],
diff --git a/packages/sdk-core/src/SuperToken.ts b/packages/sdk-core/src/SuperToken.ts
index 1d7f67e889..b44286b5d4 100644
--- a/packages/sdk-core/src/SuperToken.ts
+++ b/packages/sdk-core/src/SuperToken.ts
@@ -4,6 +4,7 @@ import ConstantFlowAgreementV1 from "./ConstantFlowAgreementV1";
import ConstantInflowNFT from "./ConstantInflowNFT";
import ConstantOutflowNFT from "./ConstantOutflowNFT";
import ERC20Token from "./ERC20Token";
+import GeneralDistributionAgreementV1 from "./GeneralDistributionAgreementV1";
import Governance from "./Governance";
import InstantDistributionAgreementV1 from "./InstantDistributionAgreementV1";
import Operation from "./Operation";
@@ -11,11 +12,16 @@ import { SFError } from "./SFError";
import { chainIdToResolverDataMap, networkNameToChainIdMap } from "./constants";
import { getNetworkName } from "./frameworkHelpers";
import {
+ ConnectPoolParams,
+ DisconnectPoolParams,
ERC20DecreaseAllowanceParams,
ERC20IncreaseAllowanceParams,
ERC777SendParams,
+ FlowDistributionActualFlowRateData,
+ GetPoolAdjustmentFlowInfoParams,
IConfig,
IRealtimeBalanceOfParams,
+ IsMemberConnectedParams,
ISuperTokenBaseIDAParams,
ISuperTokenCreateFlowByOperatorParams,
ISuperTokenCreateFlowParams,
@@ -41,8 +47,17 @@ import {
IWeb3Index,
IWeb3RealTimeBalanceOf,
IWeb3Subscription,
+ SuperTokenCreatePoolParams,
+ SuperTokenDistributeFlowParams,
+ SuperTokenDistributeParams,
+ SuperTokenEstimateDistributionActualAmountParams,
+ SuperTokenEstimateDistributionActualFlowRateParams,
SuperTokenFlowRateAllowanceParams,
SuperTokenFlowRateAllowanceWithPermissionsParams,
+ SuperTokenGDAGetFlowRateParams,
+ SuperTokenGDAGetNetFlowParams,
+ SuperTokenGetPoolAdjustmentFlowRateParams,
+ SuperTokenIsPoolParams,
} from "./interfaces";
import {
ISETH,
@@ -87,6 +102,7 @@ export default abstract class SuperToken extends ERC20Token {
readonly settings: ITokenSettings;
readonly cfaV1: ConstantFlowAgreementV1;
readonly idaV1: InstantDistributionAgreementV1;
+ readonly gdaV1: GeneralDistributionAgreementV1;
readonly governance: Governance;
readonly underlyingToken?: ERC20Token;
readonly constantOutflowNFTProxy?: ConstantOutflowNFT;
@@ -110,6 +126,11 @@ export default abstract class SuperToken extends ERC20Token {
settings.config.hostAddress,
settings.config.idaV1Address
);
+ this.gdaV1 = new GeneralDistributionAgreementV1(
+ settings.config.hostAddress,
+ settings.config.gdaV1Address,
+ settings.config.gdaV1ForwarderAddress
+ );
this.governance = new Governance(
settings.config.hostAddress,
settings.config.governanceAddress
@@ -797,6 +818,213 @@ export default abstract class SuperToken extends ERC20Token {
});
};
+ /** ### GDA Read Functions ### */
+
+ /**
+ * Retrieves the net flow for a specific token and account.
+ *
+ * @param account The account address.
+ * @param providerOrSigner A provider or signer object
+ * @returns The net flow of the account for the token.
+ */
+ getGDANetFlow = async (
+ params: SuperTokenGDAGetNetFlowParams
+ ): Promise => {
+ return this.gdaV1.getNetFlow({
+ token: this.settings.address,
+ ...params,
+ });
+ };
+
+ /**
+ * Retrieves the flow rate for a specific token, sender, and pool.
+ *
+ * @param from The sender address.
+ * @param pool The pool address.
+ * @param providerOrSigner A provider or signer object
+ * @returns The flow rate from the sender to the pool for the token.
+ */
+ getFlowRate = async (
+ params: SuperTokenGDAGetFlowRateParams
+ ): Promise => {
+ return this.gdaV1.getFlowRate({
+ token: this.settings.address,
+ ...params,
+ });
+ };
+
+ /**
+ * Estimates the flow distribution's actual flow rate for a specific token, sender, and pool.
+ *
+ * @param from The sender address.
+ * @param pool The pool address.
+ * @param requestedFlowRate The requested flow rate.
+ * @param providerOrSigner A provider or signer object
+ * @returns The flow distribution's actual flow rate and the total distribution flow rate for the pool.
+ */
+ estimateFlowDistributionActualFlowRate = async (
+ params: SuperTokenEstimateDistributionActualFlowRateParams
+ ): Promise => {
+ return this.gdaV1.estimateFlowDistributionActualFlowRate({
+ token: this.settings.address,
+ ...params,
+ });
+ };
+
+ /**
+ * Estimates the distribution's actual amount for a specific token, sender, and pool.
+ *
+ * @param from The sender address.
+ * @param pool The pool address.
+ * @param requestedAmount The requested amount.
+ * @param providerOrSigner A provider or signer object
+ * @returns The actual amount that will be distributed.
+ */
+ estimateDistributionActualAmount = async (
+ params: SuperTokenEstimateDistributionActualAmountParams
+ ): Promise => {
+ return this.gdaV1.estimateDistributionActualAmount({
+ token: this.settings.address,
+ ...params,
+ });
+ };
+
+ /**
+ * Retrieves the pool adjustment flow rate for a specific token and pool.
+ *
+ * @param pool The pool address.
+ * @param providerOrSigner A provider or signer object
+ * @returns The pool adjustment flow rate for the token and pool.
+ */
+ getPoolAdjustmentFlowRate = async (
+ params: SuperTokenGetPoolAdjustmentFlowRateParams
+ ): Promise => {
+ return this.gdaV1.getPoolAdjustmentFlowRate({
+ ...params,
+ });
+ };
+
+ /**
+ * Checks if a given token and account form a pool.
+ *
+ * @param account The account address.
+ * @param providerOrSigner A provider or signer object
+ * @returns Whether the account is a pool for the token.
+ */
+ isPool = async (params: SuperTokenIsPoolParams): Promise => {
+ return this.gdaV1.isPool({
+ token: this.settings.address,
+ ...params,
+ });
+ };
+
+ /**
+ * Checks if a member is connected to a specific pool.
+ *
+ * @param pool The pool address.
+ * @param member The member address.
+ * @param providerOrSigner A provider or signer object
+ * @returns Whether the member is connected to the pool.
+ */
+ isMemberConnected = async (
+ params: IsMemberConnectedParams
+ ): Promise => {
+ return this.gdaV1.isMemberConnected({
+ ...params,
+ });
+ };
+
+ /**
+ * Retrieves the pool adjustment flow information for a specific pool.
+ *
+ * @param poolAddress The address of the pool.
+ * @param providerOrSigner A provider or signer object
+ * @returns The recipient of the pool adjustment flow, the flow hash and the rate of the adjustment flow.
+ */
+ getPoolAdjustmentFlowInfo = async (
+ params: GetPoolAdjustmentFlowInfoParams
+ ) => {
+ return this.gdaV1.getPoolAdjustmentFlowInfo(params);
+ };
+
+ /** ### GDA Write Functions ### */
+
+ /**
+ * Creates a new pool with the given token and admin.
+ *
+ * @param admin The admin address.
+ * @param overrides The transaction overrides.
+ * @returns The contract transaction and the pool address
+ */
+ createPool = async (params: SuperTokenCreatePoolParams) => {
+ return await this.gdaV1.createPool({
+ token: this.settings.address,
+ ...params,
+ });
+ };
+
+ /**
+ * Connects a pool to the contract.
+ *
+ * @param pool The pool address.
+ * @param userData The user data.
+ * @param overrides The transaction overrides.
+ * @returns The call agreement operation result.
+ */
+ connectPool = (params: ConnectPoolParams): Operation => {
+ return this.gdaV1.connectPool({
+ ...params,
+ });
+ };
+
+ /**
+ * Disconnects a pool from the contract.
+ *
+ * @param pool The pool address.
+ * @param userData The user data.
+ * @param overrides The transaction overrides.
+ * @returns The call agreement operation result.
+ */
+ disconnectPool = (params: DisconnectPoolParams): Operation => {
+ return this.gdaV1.disconnectPool({
+ ...params,
+ });
+ };
+
+ /**
+ * Distributes funds from the sender's account to the specified pool.
+ *
+ * @param from The sender's address.
+ * @param pool The pool address.
+ * @param requestedAmount The requested amount to distribute.
+ * @param userData The user data.
+ * @param overrides The transaction overrides.
+ * @returns The call agreement operation result.
+ */
+ distributeWithGDA = (params: SuperTokenDistributeParams): Operation => {
+ return this.gdaV1.distribute({
+ token: this.settings.address,
+ ...params,
+ });
+ };
+
+ /**
+ * Distributes the flow from the sender's account to the specified pool.
+ *
+ * @param from The sender's address.
+ * @param pool The pool address.
+ * @param requestedFlowRate The requested flow rate.
+ * @param userData The user data.
+ * @param overrides The transaction overrides.
+ * @returns The call agreement operation result.
+ */
+ distributeFlow = (params: SuperTokenDistributeFlowParams): Operation => {
+ return this.gdaV1.distributeFlow({
+ token: this.settings.address,
+ ...params,
+ });
+ };
+
/** ### Governance Read Functions ### */
getGovernanceParameters = async (
diff --git a/packages/sdk-core/src/SuperfluidAgreement.ts b/packages/sdk-core/src/SuperfluidAgreement.ts
new file mode 100644
index 0000000000..ddc981bf85
--- /dev/null
+++ b/packages/sdk-core/src/SuperfluidAgreement.ts
@@ -0,0 +1,25 @@
+import { ethers } from "ethers";
+
+import Operation from "./Operation";
+
+export default class SuperfluidAgreement {
+ /**
+ * Returns the desired Operation based on shouldUseCallAgreement.
+ * @param shouldUseCallAgreement whether or not to use host.callAgreement
+ * @param callAgreementOperation the host.callAgreement created Operation
+ * @param forwarderPopulatedTransactionPromise the populated forwarder transaction promise
+ */
+ _getCallAgreementOperation = (
+ callAgreementOperation: Operation,
+ forwarderPopulatedTransactionPromise?: Promise,
+ shouldUseCallAgreement?: boolean
+ ) => {
+ return shouldUseCallAgreement
+ ? callAgreementOperation
+ : new Operation(
+ callAgreementOperation.populateTransactionPromise,
+ callAgreementOperation.type,
+ forwarderPopulatedTransactionPromise
+ );
+ };
+}
diff --git a/packages/sdk-core/src/SuperfluidPool.ts b/packages/sdk-core/src/SuperfluidPool.ts
new file mode 100644
index 0000000000..62312f1cfe
--- /dev/null
+++ b/packages/sdk-core/src/SuperfluidPool.ts
@@ -0,0 +1,478 @@
+import { ContractTransaction, ethers } from "ethers";
+
+import { SFError } from "./SFError";
+import {
+ ClaimableData,
+ ClaimAllForMemberParams,
+ ERC20AllowanceParams,
+ ERC20ApproveParams,
+ ERC20BalanceOfParams,
+ ERC20TransferFromParams,
+ ERC20TransferParams,
+ GetClaimableNowParams,
+ GetClaimableParams,
+ GetDisconnectedBalanceParams,
+ GetMemberFlowRateParams,
+ GetUnitsParams,
+ SuperfluidPoolDecreaseAllowanceParams,
+ SuperfluidPoolIncreaseAllowanceParams,
+ UpdateMemberParams,
+} from "./interfaces";
+import { ISuperfluidPool, ISuperfluidPool__factory } from "./typechain-types";
+import { normalizeAddress } from "./utils";
+
+/**
+ * Superfluid Pool Helper Class
+ * @description A helper class to interact with the SuperfluidPool contract.
+ */
+export default class SuperfluidPoolClass {
+ readonly contract: ISuperfluidPool;
+
+ constructor(poolAddress: string) {
+ this.contract = new ethers.Contract(
+ poolAddress,
+ ISuperfluidPool__factory.abi
+ ) as ISuperfluidPool;
+ }
+
+ /**
+ * Retrieves the pool admin.
+ * @param providerOrSigner A provider or signer object
+ * @returns The pool admin.
+ */
+ getPoolAdmin = async (
+ providerOrSigner: ethers.providers.Provider | ethers.Signer
+ ): Promise => {
+ try {
+ return await this.contract.connect(providerOrSigner).admin();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting the pool admin.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the SuperToken.
+ * @param providerOrSigner A provider or signer object
+ * @returns The SuperToken for this pool.
+ */
+ getSuperToken = async (
+ providerOrSigner: ethers.providers.Provider | ethers.Signer
+ ): Promise => {
+ try {
+ return await this.contract.connect(providerOrSigner).superToken();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting the pool's SuperToken.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the total units.
+ * Returns the same value as totalSupply.
+ * @param providerOrSigner A provider or signer object
+ * @returns The total units.
+ */
+ getTotalUnits = async (
+ providerOrSigner: ethers.providers.Provider | ethers.Signer
+ ): Promise => {
+ try {
+ return (
+ await this.contract.connect(providerOrSigner).getTotalUnits()
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting total units.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the total connected units.
+ * @param providerOrSigner A provider or signer object
+ * @returns The total connected units.
+ */
+ getTotalConnectedUnits = async (
+ providerOrSigner: ethers.providers.Provider | ethers.Signer
+ ): Promise => {
+ try {
+ return (
+ await this.contract
+ .connect(providerOrSigner)
+ .getTotalConnectedUnits()
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting total connected units.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the units for a specific member.
+ * @param member The member's address.
+ * @param providerOrSigner A provider or signer object
+ * @returns The units for the specified member.
+ */
+ getUnits = async (params: GetUnitsParams): Promise => {
+ try {
+ return (
+ await this.contract
+ .connect(params.providerOrSigner)
+ .getUnits(params.member)
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting units.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the total connected flow rate.
+ * @param providerOrSigner A provider or signer object
+ * @returns The total connected flow rate.
+ */
+ getTotalConnectedFlowRate = async (
+ providerOrSigner: ethers.providers.Provider | ethers.Signer
+ ): Promise => {
+ try {
+ return (
+ await this.contract
+ .connect(providerOrSigner)
+ .getTotalConnectedFlowRate()
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message:
+ "There was an error getting total connected flow rate.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the total disconnected flow rate.
+ * @param providerOrSigner A provider or signer object
+ * @returns The total disconnected flow rate.
+ */
+ getTotalDisconnectedFlowRate = async (
+ providerOrSigner: ethers.providers.Provider | ethers.Signer
+ ): Promise => {
+ try {
+ return (
+ await this.contract
+ .connect(providerOrSigner)
+ .getTotalDisconnectedFlowRate()
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message:
+ "There was an error getting total disconnected flow rate.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the disconnected balance for the pool a specific time.
+ * @param time The time of the disconnected balance.
+ * @param providerOrSigner A provider or signer object
+ * @returns The disconnected balance.
+ */
+ getDisconnectedBalance = async (
+ params: GetDisconnectedBalanceParams
+ ): Promise => {
+ try {
+ return (
+ await this.contract
+ .connect(params.providerOrSigner)
+ .getDisconnectedBalance(params.time)
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting disconnected balance.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the flow rate for a specific member.
+ * @param member The member's address.
+ * @param providerOrSigner A provider or signer object
+ * @returns The flow rate for the specified member.
+ */
+ getMemberFlowRate = async (
+ params: GetMemberFlowRateParams
+ ): Promise => {
+ try {
+ return (
+ await this.contract
+ .connect(params.providerOrSigner)
+ .getMemberFlowRate(params.member)
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting member flow rate.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the claimable amount for a specific member and time.
+ * @param member The member's address.
+ * @param time The amount claimable at time.
+ * @param providerOrSigner A provider or signer object
+ * @returns The claimable amount.
+ */
+ getClaimable = async (params: GetClaimableParams): Promise => {
+ const normalizedMember = normalizeAddress(params.member);
+ try {
+ return (
+ await this.contract
+ .connect(params.providerOrSigner)
+ .getClaimable(normalizedMember, params.time)
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting claimable amount.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the claimable amount for a specific member at the current time.
+ * @param member The member's address.
+ * @param providerOrSigner A provider or signer object
+ * @returns ClaimableData: { timestamp, claimableBalance }
+ */
+ getClaimableNow = async (
+ params: GetClaimableNowParams
+ ): Promise => {
+ try {
+ const data = await this.contract
+ .connect(params.providerOrSigner)
+ .getClaimableNow(params.member);
+ return {
+ timestamp: data.timestamp.toString(),
+ claimableBalance: data.claimableBalance.toString(),
+ };
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting claimable amount.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Updates the units for a specific member.
+ * @param member The member's address.
+ * @param newUnits The new units value.
+ * @param signer The transaction signer.
+ * @returns A promise that resolves when the update is complete.
+ */
+ updateMemberUnits = async (
+ params: UpdateMemberParams
+ ): Promise => {
+ const normalizedMember = normalizeAddress(params.member);
+ return await this.contract
+ .connect(params.signer)
+ .updateMemberUnits(normalizedMember, params.newUnits);
+ };
+
+ /**
+ * Claims all available funds for a specific member.
+ * @param member The member's address.
+ * @param signer The transaction signer.
+ * @returns A promise that resolves when the claim is complete.
+ */
+ claimAllForMember = async (
+ params: ClaimAllForMemberParams
+ ): Promise => {
+ return await this.contract
+ .connect(params.signer)
+ ["claimAll(address)"](params.member);
+ };
+
+ /**
+ * Claims all available funds.
+ * @returns A promise that resolves when the claim is complete.
+ */
+ claimAll = async (signer: ethers.Signer): Promise => {
+ return await this.contract.connect(signer)["claimAll()"]();
+ };
+
+ /**
+ * Increases the allowance for a specific spender.
+ * @param spender The spender's address.
+ * @param amount The amount to increase the allowance by.
+ * @param signer The transaction signer.
+ * @returns A promise that resolves when the allowance increase is complete.
+ */
+ increaseAllowance = async (
+ params: SuperfluidPoolIncreaseAllowanceParams
+ ): Promise => {
+ const normalizedSpender = normalizeAddress(params.spender);
+ return await this.contract
+ .connect(params.signer)
+ .increaseAllowance(normalizedSpender, params.amount);
+ };
+
+ /**
+ * Decreases the allowance for a specific spender.
+ * @param spender The spender's address.
+ * @param amount The amount to decrease the allowance by.
+ * @param signer The transaction signer.
+ * @param overrides The transaction overrides.
+ * @returns A promise that resolves when the allowance decrease is complete.
+ */
+ decreaseAllowance = async (
+ params: SuperfluidPoolDecreaseAllowanceParams
+ ): Promise => {
+ const normalizedSpender = normalizeAddress(params.spender);
+ return await this.contract
+ .connect(params.signer)
+ .decreaseAllowance(normalizedSpender, params.amount);
+ };
+
+ /**
+ * Retrieves the total supply.
+ * Returns the same value as getTotalUnits.
+ * @returns The total supply.
+ */
+ totalSupply = async (
+ providerOrSigner: ethers.providers.Provider | ethers.Signer
+ ): Promise => {
+ try {
+ return (
+ await this.contract.connect(providerOrSigner).totalSupply()
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting total supply.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the balance of an account.
+ * @param account The account's address.
+ * @param providerOrSigner A provider or signer object
+ * @returns The account's balance.
+ */
+ balanceOf = async (params: ERC20BalanceOfParams): Promise => {
+ const normalizedAccount = normalizeAddress(params.account);
+ try {
+ return (
+ await this.contract
+ .connect(params.providerOrSigner)
+ .balanceOf(normalizedAccount)
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting balance.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Retrieves the allowance for a specific owner and spender.
+ * @param owner The owner's address.
+ * @param spender The spender's address.
+ * @param providerOrSigner A provider or signer object
+ * @returns The allowance.
+ */
+ allowance = async (params: ERC20AllowanceParams): Promise => {
+ const normalizedOwner = normalizeAddress(params.owner);
+ const normalizedSpender = normalizeAddress(params.spender);
+ try {
+ return (
+ await this.contract
+ .connect(params.providerOrSigner)
+ .allowance(normalizedOwner, normalizedSpender)
+ ).toString();
+ } catch (err) {
+ throw new SFError({
+ type: "SUPERFLUID_POOL_READ",
+ message: "There was an error getting allowance.",
+ cause: err,
+ });
+ }
+ };
+
+ /**
+ * Approves an amount to be spent by a specific spender.
+ * @param spender The spender's address.
+ * @param amount The amount to approve.
+ * @param signer The transaction signer.
+ * @returns A promise that resolves when the approval is complete.
+ */
+ approve = async (
+ params: ERC20ApproveParams
+ ): Promise => {
+ const normalizedSpender = normalizeAddress(params.spender);
+ return await this.contract
+ .connect(params.signer)
+ .approve(normalizedSpender, params.amount);
+ };
+
+ /**
+ * Transfers an amount to a specific recipient.
+ * @param to The recipient's address.
+ * @param amount The amount to transfer.
+ * @param signer The transaction signer.
+ * @returns A promise that resolves when the transfer is complete.
+ */
+ transfer = async (
+ params: ERC20TransferParams
+ ): Promise => {
+ const normalizedTo = normalizeAddress(params.to);
+ return await this.contract
+ .connect(params.signer)
+ .transfer(normalizedTo, params.amount);
+ };
+
+ /**
+ * Transfers an amount from a specific sender to a recipient.
+ * @param from The sender's address.
+ * @param to The recipient's address.
+ * @param amount The amount to transfer.
+ * @param signer The transaction signer.
+ * @returns A promise that resolves when the transfer is complete.
+ */
+ transferFrom = async (
+ params: ERC20TransferFromParams
+ ): Promise => {
+ const normalizedFrom = normalizeAddress(params.from);
+ const normalizedTo = normalizeAddress(params.to);
+ return await this.contract
+ .connect(params.signer)
+ .transferFrom(normalizedFrom, normalizedTo, params.amount);
+ };
+}
diff --git a/packages/sdk-core/src/index.ts b/packages/sdk-core/src/index.ts
index 037dba5181..6834a8bc16 100644
--- a/packages/sdk-core/src/index.ts
+++ b/packages/sdk-core/src/index.ts
@@ -4,6 +4,7 @@ import BatchCall from "./BatchCall";
import ConstantFlowAgreementV1 from "./ConstantFlowAgreementV1";
import ERC20Token from "./ERC20Token";
import Framework from "./Framework";
+import GeneralDistributionAgreementV1 from "./GeneralDistributionAgreementV1";
import Governance from "./Governance";
import Host from "./Host";
import InstantDistributionAgreementV1 from "./InstantDistributionAgreementV1";
@@ -14,6 +15,7 @@ import SuperToken, {
PureSuperToken,
WrapperSuperToken,
} from "./SuperToken";
+import SuperfluidPoolClass from "./SuperfluidPool";
export * from "./interfaces";
export * from "./constants";
@@ -32,6 +34,8 @@ export { Framework };
export { Governance };
export { Host };
export { InstantDistributionAgreementV1 };
+export { GeneralDistributionAgreementV1 };
+export { SuperfluidPoolClass };
export { NativeAssetSuperToken };
export { PureSuperToken };
export { Query };
diff --git a/packages/sdk-core/src/interfaces.ts b/packages/sdk-core/src/interfaces.ts
index b2e532ee50..5207ecc4f2 100644
--- a/packages/sdk-core/src/interfaces.ts
+++ b/packages/sdk-core/src/interfaces.ts
@@ -2,11 +2,13 @@ import { ethers, Overrides } from "ethers";
import {
IConstantFlowAgreementV1,
+ IGeneralDistributionAgreementV1,
IInstantDistributionAgreementV1,
IResolver,
Superfluid,
SuperfluidGovernanceII,
} from "./typechain-types";
+import { PoolConfigStruct } from "./typechain-types/contracts/utils/GDAv1Forwarder";
// TODO (0xdavinchee): reorganize this
// Maybe moving these into categorical files
@@ -47,7 +49,7 @@ export interface ISuperTokenRequestFilter {
// A better thought out inheritance pattern - SuperToken is parent
// CFA/IDA inherits and tacks on superToken property
-export interface IShouldUseCallAgreement {
+export interface ShouldUseCallAgreement {
readonly shouldUseCallAgreement?: boolean;
}
@@ -57,7 +59,7 @@ export interface EthersParams {
// write request interfaces
export interface ISuperTokenModifyFlowParams
- extends IShouldUseCallAgreement,
+ extends ShouldUseCallAgreement,
EthersParams {
readonly flowRate?: string;
readonly receiver: string;
@@ -129,7 +131,7 @@ export interface ISuperTokenUpdateSubscriptionUnitsParams extends EthersParams {
}
export interface IModifyFlowParams
- extends IShouldUseCallAgreement,
+ extends ShouldUseCallAgreement,
EthersParams {
readonly flowRate?: string;
readonly receiver: string;
@@ -168,13 +170,13 @@ export interface ISuperTokenFullControlParams extends EthersParams {
export interface IUpdateFlowOperatorPermissionsParams
extends ISuperTokenUpdateFlowOperatorPermissionsParams,
- IShouldUseCallAgreement {
+ ShouldUseCallAgreement {
readonly superToken: string;
}
export interface IFullControlParams
extends ISuperTokenFullControlParams,
- IShouldUseCallAgreement {
+ ShouldUseCallAgreement {
readonly superToken: string;
}
@@ -447,8 +449,10 @@ export interface IConfig {
readonly hostAddress: string;
readonly cfaV1Address: string;
readonly idaV1Address: string;
+ readonly gdaV1Address: string;
readonly governanceAddress: string;
readonly cfaV1ForwarderAddress: string;
+ readonly gdaV1ForwarderAddress: string;
}
export interface IContracts {
@@ -456,6 +460,7 @@ export interface IContracts {
readonly governance: SuperfluidGovernanceII;
readonly host: Superfluid;
readonly idaV1: IInstantDistributionAgreementV1;
+ readonly gdaV1: IGeneralDistributionAgreementV1;
readonly resolver: IResolver;
}
@@ -518,14 +523,28 @@ export interface ERC20BalanceOfParams {
readonly account: string;
readonly providerOrSigner: ProviderOrSigner;
}
+
export interface ERC20AllowanceParams {
readonly owner: string;
readonly spender: string;
readonly providerOrSigner: ProviderOrSigner;
}
-export interface ERC20BalanceOfParams {
- readonly account: string;
- readonly providerOrSigner: ProviderOrSigner;
+
+export interface ERC20ApproveParams extends EthersParams {
+ readonly spender: string;
+ readonly amount: string;
+ readonly signer: ethers.Signer;
+}
+
+export interface ERC20TransferParams extends EthersParams {
+ readonly to: string;
+ readonly amount: string;
+ readonly signer: ethers.Signer;
+}
+
+export interface ERC20TransferFromParams extends ERC20TransferParams {
+ readonly from: string;
+ readonly signer: ethers.Signer;
}
// ERC721
@@ -581,6 +600,14 @@ export interface ERC20IncreaseAllowanceParams extends EthersParams {
export type ERC20DecreaseAllowanceParams = ERC20IncreaseAllowanceParams;
+export interface SuperfluidPoolIncreaseAllowanceParams
+ extends ERC20IncreaseAllowanceParams {
+ readonly signer: ethers.Signer;
+}
+
+export type SuperfluidPoolDecreaseAllowanceParams =
+ SuperfluidPoolIncreaseAllowanceParams;
+
export interface SuperTokenFlowRateAllowanceParams extends EthersParams {
readonly flowOperator: string;
readonly flowRateAllowanceDelta: string;
@@ -591,6 +618,176 @@ export interface FlowRateAllowanceParams
readonly superToken: string;
}
+export interface SuperTokenGDAGetNetFlowParams {
+ readonly account: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface GDAGetNetFlowParams extends SuperTokenGDAGetNetFlowParams {
+ readonly token: string;
+}
+
+export interface SuperTokenGDAGetFlowRateParams {
+ readonly from: string;
+ readonly pool: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface GDAGetFlowRateParams extends SuperTokenGDAGetFlowRateParams {
+ readonly token: string;
+}
+
+export interface SuperTokenEstimateDistributionActualFlowRateParams {
+ readonly from: string;
+ readonly pool: string;
+ readonly requestedFlowRate: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface EstimateFlowDistributionActualFlowRateParams
+ extends SuperTokenEstimateDistributionActualFlowRateParams {
+ readonly token: string;
+}
+
+export interface SuperTokenEstimateDistributionActualAmountParams {
+ readonly from: string;
+ readonly pool: string;
+ readonly requestedAmount: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+export interface EstimateDistributionActualAmountParams
+ extends SuperTokenEstimateDistributionActualAmountParams {
+ readonly token: string;
+}
+
+export interface SuperTokenGetPoolAdjustmentFlowRateParams {
+ readonly pool: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface GetPoolAdjustmentFlowRateParams
+ extends SuperTokenGetPoolAdjustmentFlowRateParams {}
+
+export interface SuperTokenIsPoolParams {
+ readonly account: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface IsPoolParams extends SuperTokenIsPoolParams {
+ readonly token: string;
+}
+
+export interface IsMemberConnectedParams {
+ readonly pool: string;
+ readonly member: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface GetPoolAdjustmentFlowInfoParams {
+ readonly pool: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface PoolAdjustmentFlowInfo {
+ readonly recipient: string;
+ readonly flowRate: string;
+ readonly flowHash: string;
+}
+
+export interface SuperTokenCreatePoolParams {
+ readonly admin: string;
+ readonly config: PoolConfigStruct;
+ readonly signer: ethers.Signer;
+}
+export interface CreatePoolParams extends SuperTokenCreatePoolParams {
+ readonly token: string;
+}
+
+export interface ConnectPoolParams
+ extends EthersParams,
+ ShouldUseCallAgreement {
+ readonly pool: string;
+ readonly userData?: string;
+}
+
+export interface DisconnectPoolParams
+ extends EthersParams,
+ ShouldUseCallAgreement {
+ readonly pool: string;
+ readonly userData?: string;
+}
+
+export interface SuperTokenDistributeParams
+ extends EthersParams,
+ ShouldUseCallAgreement {
+ readonly from: string;
+ readonly pool: string;
+ readonly requestedAmount: string;
+ readonly userData?: string;
+}
+export interface DistributeParams extends SuperTokenDistributeParams {
+ readonly token: string;
+}
+
+export interface SuperTokenDistributeFlowParams
+ extends EthersParams,
+ ShouldUseCallAgreement {
+ readonly from: string;
+ readonly pool: string;
+ readonly requestedFlowRate: string;
+ readonly userData?: string;
+}
+
+export interface DistributeFlowParams extends SuperTokenDistributeFlowParams {
+ readonly token: string;
+}
+
+export interface FlowDistributionActualFlowRateData {
+ readonly actualFlowRate: string;
+ readonly totalDistributionFlowRate: string;
+}
+
+export interface GetClaimableParams {
+ readonly member: string;
+ readonly time: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface GetClaimableNowParams {
+ readonly member: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface ClaimableData {
+ readonly claimableBalance: string;
+ readonly timestamp: string;
+}
+
+export interface GetUnitsParams {
+ readonly member: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface GetDisconnectedBalanceParams {
+ readonly time: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface GetMemberFlowRateParams {
+ readonly member: string;
+ readonly providerOrSigner: ethers.providers.Provider | ethers.Signer;
+}
+
+export interface ClaimAllForMemberParams {
+ readonly member: string;
+ readonly signer: ethers.Signer;
+}
+
+export interface UpdateMemberParams {
+ readonly member: string;
+ readonly newUnits: string;
+ readonly signer: ethers.Signer;
+}
export interface FlowRateAllowanceWithPermissionsParams
extends FlowRateAllowanceParams {
readonly permissionsDelta: number;
diff --git a/packages/sdk-core/test/1.4_supertoken_gda.test.ts b/packages/sdk-core/test/1.4_supertoken_gda.test.ts
new file mode 100644
index 0000000000..c0043e7542
--- /dev/null
+++ b/packages/sdk-core/test/1.4_supertoken_gda.test.ts
@@ -0,0 +1,1061 @@
+import { expect } from "chai";
+import {
+ TestEnvironment,
+ makeSuite,
+ validateOperationShouldUseCallAgreement,
+} from "./TestEnvironment";
+import SuperfluidPool from "../src/SuperfluidPool";
+import { ethers } from "ethers";
+import { WrapperSuperToken, toBN } from "../src";
+import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
+
+interface ShouldConnectPoolParams {
+ testEnv: TestEnvironment;
+ shouldUseCallAgreement: boolean;
+ superToken: WrapperSuperToken;
+ pool: SuperfluidPool;
+ member: SignerWithAddress;
+ doConnect: boolean;
+}
+
+interface ShouldUpdateMemberParams {
+ pool: SuperfluidPool;
+ newUnits: string;
+ member: SignerWithAddress;
+ admin: SignerWithAddress;
+}
+
+interface ShouldClaimAllForMemberParams {
+ pool: SuperfluidPool;
+ member: SignerWithAddress;
+ claimer: SignerWithAddress;
+ superToken: WrapperSuperToken;
+ claimAll?: boolean;
+}
+
+interface ShouldInstantDistributeParams {
+ testEnv: TestEnvironment;
+ shouldUseCallAgreement: boolean;
+ newUnits: string;
+ amountToDistribute: string;
+ admin: SignerWithAddress;
+ distributor: SignerWithAddress;
+ member: SignerWithAddress;
+}
+
+interface ShouldFlowDistributeParams {
+ testEnv: TestEnvironment;
+ shouldUseCallAgreement: boolean;
+ newUnits: string;
+ requestedFlowRate: string;
+ admin: SignerWithAddress;
+ distributor: SignerWithAddress;
+ member: SignerWithAddress;
+ superToken: WrapperSuperToken;
+}
+
+makeSuite(
+ "SuperToken-GDA and SuperfluidPool Tests",
+ (testEnv: TestEnvironment) => {
+ describe("Revert cases", () => {
+ it("Should throw an error on GDA view functions when wrong params passed", async () => {
+ try {
+ await testEnv.wrapperSuperToken.getGDANetFlow({
+ account: "",
+ providerOrSigner: testEnv.alice,
+ });
+ } catch (err) {
+ expect(err.type).to.equal("GDAV1_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting the GDA net flow."
+ );
+ }
+
+ try {
+ await testEnv.wrapperSuperToken.getFlowRate({
+ from: "",
+ pool: "",
+ providerOrSigner: testEnv.alice,
+ });
+ } catch (err) {
+ expect(err.type).to.equal("GDAV1_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting the GDA flow rate."
+ );
+ }
+
+ try {
+ await testEnv.wrapperSuperToken.estimateFlowDistributionActualFlowRate(
+ {
+ from: "",
+ pool: "",
+ requestedFlowRate: "",
+ providerOrSigner: testEnv.alice,
+ }
+ );
+ } catch (err) {
+ expect(err.type).to.equal("GDAV1_READ");
+ expect(err.message).to.have.string(
+ "There was an error estimating the GDA flow distribution's actual flow rate."
+ );
+ }
+
+ try {
+ await testEnv.wrapperSuperToken.estimateDistributionActualAmount(
+ {
+ from: "",
+ pool: "",
+ requestedAmount: "",
+ providerOrSigner: testEnv.alice,
+ }
+ );
+ } catch (err) {
+ expect(err.type).to.equal("GDAV1_READ");
+ expect(err.message).to.have.string(
+ "There was an error estimating the GDA distribution's actual amount."
+ );
+ }
+
+ try {
+ await testEnv.wrapperSuperToken.getPoolAdjustmentFlowRate({
+ pool: "",
+ providerOrSigner: testEnv.alice,
+ });
+ } catch (err) {
+ expect(err.type).to.equal("GDAV1_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting the GDA pool adjustment flow rate."
+ );
+ }
+
+ try {
+ await testEnv.wrapperSuperToken.isPool({
+ account: "",
+ providerOrSigner: testEnv.alice,
+ });
+ } catch (err) {
+ expect(err.type).to.equal("GDAV1_READ");
+ expect(err.message).to.have.string(
+ "There was an error checking if the account is a pool."
+ );
+ }
+
+ try {
+ await testEnv.wrapperSuperToken.isMemberConnected({
+ pool: "",
+ member: "",
+ providerOrSigner: testEnv.alice,
+ });
+ } catch (err) {
+ expect(err.type).to.equal("GDAV1_READ");
+ expect(err.message).to.have.string(
+ "There was an error checking if the member is connected to the pool."
+ );
+ }
+
+ try {
+ await testEnv.wrapperSuperToken.getPoolAdjustmentFlowInfo({
+ pool: "",
+ providerOrSigner: testEnv.alice,
+ });
+ } catch (err) {
+ expect(err.type).to.equal("GDAV1_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting the GDA pool adjustment flow information."
+ );
+ }
+ });
+
+ it("Should throw when trying to createPool with bad params", async () => {
+ try {
+ await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ ""
+ );
+ } catch (err) {
+ expect(err.type).to.equal("GDAV1_WRITE");
+ expect(err.message).to.have.string(
+ "There was an error creating the GDA pool."
+ );
+ }
+ });
+
+ it("Should throw an error on SuperfluidPool view functions when wrong params passed", async () => {
+ const pool = await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+
+ try {
+ await pool.getPoolAdmin("" as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting the pool admin."
+ );
+ }
+
+ try {
+ await pool.getSuperToken("" as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting the pool's SuperToken."
+ );
+ }
+
+ try {
+ await pool.getTotalUnits("" as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting total units."
+ );
+ }
+
+ try {
+ await pool.getTotalConnectedUnits("" as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting total connected units."
+ );
+ }
+
+ try {
+ await pool.getUnits({} as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting units."
+ );
+ }
+
+ try {
+ await pool.getTotalConnectedFlowRate("" as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting total connected flow rate."
+ );
+ }
+
+ try {
+ await pool.getTotalDisconnectedFlowRate("" as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting total disconnected flow rate."
+ );
+ }
+
+ try {
+ await pool.getDisconnectedBalance({} as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting disconnected balance."
+ );
+ }
+
+ try {
+ await pool.getMemberFlowRate({} as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting member flow rate."
+ );
+ }
+
+ try {
+ await pool.getClaimable({} as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting claimable amount."
+ );
+ }
+
+ try {
+ await pool.getClaimableNow({} as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting claimable amount."
+ );
+ }
+
+ try {
+ await pool.totalSupply({} as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting total supply."
+ );
+ }
+
+ try {
+ await pool.balanceOf({} as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting balance."
+ );
+ }
+
+ try {
+ await pool.allowance({} as any);
+ } catch (err) {
+ expect(err.type).to.equal("SUPERFLUID_POOL_READ");
+ expect(err.message).to.have.string(
+ "There was an error getting allowance."
+ );
+ }
+ });
+ });
+
+ describe("Happy Path Tests", () => {
+ it("Should be able to create pool", async () => {
+ await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+ });
+
+ it("Should be able to update units for member", async () => {
+ const pool = await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+ const newUnits = "10000";
+ await shouldUpdateMember({
+ pool,
+ admin: testEnv.alice,
+ member: testEnv.bob,
+ newUnits,
+ });
+ });
+
+ it("Should be able to approve units", async () => {
+ const pool = await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+ const newUnits = "10000";
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits,
+ signer: testEnv.alice,
+ });
+ const approvedUnits = "420";
+ await pool.approve({
+ spender: testEnv.charlie.address,
+ amount: approvedUnits,
+ signer: testEnv.bob,
+ });
+ expect(
+ await pool.allowance({
+ owner: testEnv.bob.address,
+ spender: testEnv.charlie.address,
+ providerOrSigner: testEnv.bob,
+ })
+ ).to.equal(approvedUnits);
+ });
+
+ it("Should be able to increase allowance", async () => {
+ const pool = await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+ const newUnits = "10000";
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits,
+ signer: testEnv.alice,
+ });
+ const approvedUnits = "420";
+ await pool.approve({
+ spender: testEnv.charlie.address,
+ amount: approvedUnits,
+ signer: testEnv.bob,
+ });
+ const increasedUnits = "69";
+ await pool.increaseAllowance({
+ spender: testEnv.charlie.address,
+ amount: increasedUnits,
+ signer: testEnv.bob,
+ });
+ expect(
+ await pool.allowance({
+ owner: testEnv.bob.address,
+ spender: testEnv.charlie.address,
+ providerOrSigner: testEnv.bob,
+ })
+ ).to.equal(toBN(approvedUnits).add(toBN(increasedUnits)));
+ });
+
+ it("Should be able to decrease allowance", async () => {
+ const pool = await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+ const newUnits = "10000";
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits,
+ signer: testEnv.alice,
+ });
+ const approvedUnits = "420";
+ await pool.approve({
+ spender: testEnv.charlie.address,
+ amount: approvedUnits,
+ signer: testEnv.bob,
+ });
+ const decreasedUnits = "69";
+ await pool.decreaseAllowance({
+ spender: testEnv.charlie.address,
+ amount: decreasedUnits,
+ signer: testEnv.bob,
+ });
+ expect(
+ await pool.allowance({
+ owner: testEnv.bob.address,
+ spender: testEnv.charlie.address,
+ providerOrSigner: testEnv.bob,
+ })
+ ).to.equal(toBN(approvedUnits).sub(toBN(decreasedUnits)));
+ });
+
+ it("Should be able to transfer units", async () => {
+ const pool = await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+ const newUnits = "10000";
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits,
+ signer: testEnv.alice,
+ });
+ const transferUnits = "420";
+ await pool.transfer({
+ to: testEnv.charlie.address,
+ amount: transferUnits,
+ signer: testEnv.bob,
+ });
+ expect(
+ await pool.balanceOf({
+ account: testEnv.charlie.address,
+ providerOrSigner: testEnv.charlie,
+ })
+ ).to.equal(transferUnits);
+ expect(
+ await pool.balanceOf({
+ account: testEnv.bob.address,
+ providerOrSigner: testEnv.bob,
+ })
+ ).to.equal(toBN(newUnits).sub(toBN(transferUnits)));
+ });
+
+ it("Should be able to transferFrom units", async () => {
+ const pool = await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+ const newUnits = "10000";
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits,
+ signer: testEnv.alice,
+ });
+ const approvedUnits = "420";
+ await pool.approve({
+ spender: testEnv.charlie.address,
+ amount: approvedUnits,
+ signer: testEnv.bob,
+ });
+ const transferUnits = "69";
+ await pool.transferFrom({
+ from: testEnv.bob.address,
+ to: testEnv.charlie.address,
+ amount: transferUnits,
+ signer: testEnv.charlie,
+ });
+ expect(
+ await pool.balanceOf({
+ account: testEnv.charlie.address,
+ providerOrSigner: testEnv.charlie,
+ })
+ ).to.equal(transferUnits);
+ expect(
+ await pool.balanceOf({
+ account: testEnv.bob.address,
+ providerOrSigner: testEnv.bob,
+ })
+ ).to.equal(toBN(newUnits).sub(toBN(transferUnits)));
+ });
+
+ it("Should be able to update units (increase)", async () => {
+ const pool = await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+ const newUnits = "69";
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits,
+ signer: testEnv.alice,
+ });
+ const increasedUnits = "420";
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits: increasedUnits,
+ signer: testEnv.alice,
+ });
+ expect(
+ await pool.balanceOf({
+ account: testEnv.bob.address,
+ providerOrSigner: testEnv.bob,
+ })
+ ).to.equal(increasedUnits);
+ });
+
+ it("Should be able to update units (decrease)", async () => {
+ const pool = await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+ const newUnits = "420";
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits,
+ signer: testEnv.alice,
+ });
+ const decreasedUnits = "69";
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits: decreasedUnits,
+ signer: testEnv.alice,
+ });
+ expect(
+ await pool.balanceOf({
+ account: testEnv.bob.address,
+ providerOrSigner: testEnv.bob,
+ })
+ ).to.equal(decreasedUnits);
+ });
+
+ it("Should be able to update units (remove all)", async () => {
+ const pool = await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+ const newUnits = "420";
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits,
+ signer: testEnv.alice,
+ });
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits: "0",
+ signer: testEnv.alice,
+ });
+ expect(
+ await pool.balanceOf({
+ account: testEnv.bob.address,
+ providerOrSigner: testEnv.bob,
+ })
+ ).to.equal("0");
+ });
+
+ context(
+ "Should be able to connect and disconnect from pool",
+ async () => {
+ let pool: SuperfluidPool;
+ beforeEach(async () => {
+ pool = await shouldCreatePool(
+ testEnv.wrapperSuperToken,
+ testEnv.alice,
+ testEnv.alice.address
+ );
+ const newUnits = "420";
+ await pool.updateMemberUnits({
+ member: testEnv.bob.address,
+ newUnits,
+ signer: testEnv.alice,
+ });
+ });
+
+ it("With Call Agreement", async () => {
+ await shouldConnectPool({
+ testEnv,
+ shouldUseCallAgreement: true,
+ superToken: testEnv.wrapperSuperToken,
+ pool,
+ member: testEnv.bob,
+ doConnect: true,
+ });
+ await shouldConnectPool({
+ testEnv,
+ shouldUseCallAgreement: true,
+ superToken: testEnv.wrapperSuperToken,
+ pool,
+ member: testEnv.bob,
+ doConnect: false,
+ });
+ });
+
+ it("With Forwarder", async () => {
+ await shouldConnectPool({
+ testEnv,
+ shouldUseCallAgreement: false,
+ superToken: testEnv.wrapperSuperToken,
+ pool,
+ member: testEnv.bob,
+ doConnect: true,
+ });
+ await shouldConnectPool({
+ testEnv,
+ shouldUseCallAgreement: false,
+ superToken: testEnv.wrapperSuperToken,
+ pool,
+ member: testEnv.bob,
+ doConnect: false,
+ });
+ });
+ }
+ );
+
+ context("Should be able to distribute tokens", async () => {
+ it("With Call Agreement", async () => {
+ await shouldInstantDistributeTokensToOneMember({
+ testEnv,
+ newUnits: "10",
+ amountToDistribute: "1000",
+ admin: testEnv.alice,
+ distributor: testEnv.alice,
+ member: testEnv.bob,
+ shouldUseCallAgreement: true,
+ });
+ });
+
+ it("With Forwarder", async () => {
+ await shouldInstantDistributeTokensToOneMember({
+ testEnv,
+ newUnits: "10",
+ amountToDistribute: "1000",
+ admin: testEnv.alice,
+ distributor: testEnv.alice,
+ member: testEnv.bob,
+ shouldUseCallAgreement: false,
+ });
+ });
+ });
+
+ it("Should be able to distribute flow tokens", async () => {
+ it("With Call Agreement", async () => {
+ await shouldDistributeFlow({
+ testEnv,
+ superToken: testEnv.wrapperSuperToken,
+ newUnits: "10",
+ requestedFlowRate: "1000",
+ admin: testEnv.alice,
+ distributor: testEnv.alice,
+ member: testEnv.bob,
+ shouldUseCallAgreement: true,
+ });
+ });
+
+ it("With Forwarder", async () => {
+ await shouldDistributeFlow({
+ testEnv,
+ superToken: testEnv.wrapperSuperToken,
+ newUnits: "10",
+ requestedFlowRate: "1000",
+ admin: testEnv.alice,
+ distributor: testEnv.alice,
+ member: testEnv.bob,
+ shouldUseCallAgreement: false,
+ });
+ });
+ });
+
+ it("Should be able to claimAllForMember as the member", async () => {
+ const pool = await shouldInstantDistributeTokensToOneMember({
+ testEnv,
+ shouldUseCallAgreement: true,
+ newUnits: "1000",
+ amountToDistribute: "100000",
+ admin: testEnv.alice,
+ distributor: testEnv.alice,
+ member: testEnv.bob,
+ });
+
+ await shouldClaimAllForMember({
+ pool,
+ member: testEnv.bob,
+ claimer: testEnv.bob,
+ superToken: testEnv.wrapperSuperToken,
+ });
+ });
+
+ it("Should be able to claimAllForMember for someone else (alice claims for bob)", async () => {
+ const pool = await shouldInstantDistributeTokensToOneMember({
+ testEnv,
+ shouldUseCallAgreement: true,
+ newUnits: "1000",
+ amountToDistribute: "100000",
+ admin: testEnv.alice,
+ distributor: testEnv.alice,
+ member: testEnv.bob,
+ });
+ await shouldClaimAllForMember({
+ pool,
+ member: testEnv.bob,
+ claimer: testEnv.alice,
+ superToken: testEnv.wrapperSuperToken,
+ });
+ });
+
+ it("Should be able to claimAll", async () => {
+ const pool = await shouldInstantDistributeTokensToOneMember({
+ testEnv,
+ shouldUseCallAgreement: true,
+ newUnits: "1000",
+ amountToDistribute: "100000",
+ admin: testEnv.alice,
+ distributor: testEnv.alice,
+ member: testEnv.bob,
+ });
+ await shouldClaimAllForMember({
+ pool,
+ member: testEnv.bob,
+ claimer: testEnv.bob,
+ superToken: testEnv.wrapperSuperToken,
+ claimAll: true,
+ });
+ });
+ });
+ }
+);
+
+const shouldCreatePool = async (
+ superToken: WrapperSuperToken,
+ signer: SignerWithAddress,
+ admin: string
+) => {
+ const data = await superToken.createPool({
+ admin,
+ config: {
+ transferabilityForUnitsOwner: true,
+ distributionFromAnyAddress: true,
+ },
+ signer,
+ });
+
+ expect(
+ await superToken.isPool({
+ account: data.pool.contract.address,
+ providerOrSigner: signer,
+ })
+ ).to.be.true;
+ const pool = new SuperfluidPool(data.pool.contract.address);
+
+ expect(await pool.getPoolAdmin(signer)).to.be.equal(signer.address);
+ expect(await pool.getSuperToken(signer)).to.be.equal(superToken.address);
+
+ return pool;
+};
+
+const shouldUpdateMember = async (params: ShouldUpdateMemberParams) => {
+ const providerSigner = params.member;
+ const memberUnitsBefore = await params.pool.getUnits({
+ member: params.member.address,
+ providerOrSigner: providerSigner,
+ });
+ const balanceOfBefore = await params.pool.balanceOf({
+ account: params.member.address,
+ providerOrSigner: providerSigner,
+ });
+ const totalSupplyBefore = await params.pool.totalSupply(providerSigner);
+ const totalUnitsBefore = await params.pool.getTotalUnits(providerSigner);
+ const unitsDelta = toBN(params.newUnits).sub(memberUnitsBefore);
+ await params.pool.updateMemberUnits({
+ member: params.member.address,
+ newUnits: params.newUnits,
+ signer: params.admin,
+ });
+
+ // assert total balance/total supply
+ expect(await params.pool.totalSupply(providerSigner)).to.equal(
+ toBN(totalSupplyBefore).add(unitsDelta)
+ );
+ expect(await params.pool.getTotalUnits(providerSigner)).to.equal(
+ toBN(totalUnitsBefore).add(unitsDelta)
+ );
+
+ // assert member's balance/units
+ expect(
+ await params.pool.balanceOf({
+ account: params.member.address,
+ providerOrSigner: providerSigner,
+ })
+ ).to.equal(toBN(balanceOfBefore).add(unitsDelta));
+ expect(
+ await params.pool.getUnits({
+ member: params.member.address,
+ providerOrSigner: providerSigner,
+ })
+ ).to.equal(toBN(memberUnitsBefore).add(unitsDelta));
+};
+
+const shouldConnectPool = async (params: ShouldConnectPoolParams) => {
+ const connectPoolOperation = params.doConnect
+ ? await params.superToken.connectPool({
+ pool: params.pool.contract.address,
+ shouldUseCallAgreement: params.shouldUseCallAgreement,
+ })
+ : await params.superToken.disconnectPool({
+ pool: params.pool.contract.address,
+ shouldUseCallAgreement: params.shouldUseCallAgreement,
+ });
+ await connectPoolOperation.exec(params.member);
+
+ await validateOperationShouldUseCallAgreement(
+ params.testEnv,
+ connectPoolOperation,
+ params.shouldUseCallAgreement,
+ params.testEnv.sdkFramework.gdaV1.forwarder.address
+ );
+
+ expect(
+ await params.superToken.isMemberConnected({
+ pool: params.pool.contract.address,
+ member: params.member.address,
+ providerOrSigner: params.member,
+ })
+ ).to.equal(params.doConnect);
+
+ if (params.doConnect) {
+ expect(
+ await params.pool.getTotalConnectedUnits(params.member)
+ ).to.equal(
+ await params.pool.getUnits({
+ member: params.member.address,
+ providerOrSigner: params.member,
+ })
+ );
+ } else {
+ expect(
+ await params.pool.getTotalConnectedUnits(params.member)
+ ).to.equal("0");
+ }
+};
+
+const shouldInstantDistributeTokensToOneMember = async (
+ params: ShouldInstantDistributeParams
+) => {
+ const pool = await shouldCreatePool(
+ params.testEnv.wrapperSuperToken,
+ params.admin,
+ params.admin.address
+ );
+ const distributorBalanceBefore =
+ await params.testEnv.wrapperSuperToken.balanceOf({
+ account: params.distributor.address,
+ providerOrSigner: params.distributor,
+ });
+ const memberBalanceBefore =
+ await params.testEnv.wrapperSuperToken.balanceOf({
+ account: params.member.address,
+ providerOrSigner: params.member,
+ });
+ await pool.updateMemberUnits({
+ member: params.member.address,
+ newUnits: params.newUnits,
+ signer: params.admin,
+ });
+ const actualAmountDistributed =
+ await params.testEnv.wrapperSuperToken.estimateDistributionActualAmount(
+ {
+ from: params.distributor.address,
+ requestedAmount: params.amountToDistribute,
+ pool: pool.contract.address,
+ providerOrSigner: params.distributor,
+ }
+ );
+ const operation = await params.testEnv.wrapperSuperToken.distributeWithGDA({
+ from: params.distributor.address,
+ requestedAmount: params.amountToDistribute,
+ pool: pool.contract.address,
+ shouldUseCallAgreement: params.shouldUseCallAgreement,
+ });
+ await operation.exec(params.distributor);
+ await validateOperationShouldUseCallAgreement(
+ params.testEnv,
+ operation,
+ params.shouldUseCallAgreement,
+ params.testEnv.sdkFramework.gdaV1.forwarder.address
+ );
+
+ const distributorBalanceAfter =
+ await params.testEnv.wrapperSuperToken.balanceOf({
+ account: params.distributor.address,
+ providerOrSigner: params.distributor,
+ });
+ const memberBalanceAfter = await params.testEnv.wrapperSuperToken.balanceOf(
+ {
+ account: params.member.address,
+ providerOrSigner: params.member,
+ }
+ );
+ expect(distributorBalanceAfter).to.equal(
+ toBN(distributorBalanceBefore).sub(toBN(actualAmountDistributed))
+ );
+
+ const isMemberConnected =
+ await params.testEnv.wrapperSuperToken.isMemberConnected({
+ pool: pool.contract.address,
+ member: params.member.address,
+ providerOrSigner: params.member,
+ });
+ if (isMemberConnected) {
+ expect(memberBalanceAfter).to.equal(
+ toBN(memberBalanceBefore).add(toBN(actualAmountDistributed))
+ );
+ } else {
+ expect(memberBalanceAfter).to.equal(memberBalanceBefore);
+ }
+
+ return pool;
+};
+
+const shouldDistributeFlow = async (params: ShouldFlowDistributeParams) => {
+ const pool = await shouldCreatePool(
+ params.superToken,
+ params.admin,
+ params.admin.address
+ );
+ const newUnits = "10";
+ await pool.updateMemberUnits({
+ member: params.member.address,
+ newUnits,
+ signer: params.admin,
+ });
+ const providerOrSigner = params.admin;
+ const requestedFlowRate = "1000";
+ const actualDistributionFlowRate =
+ await params.superToken.estimateFlowDistributionActualFlowRate({
+ from: params.distributor.address,
+ requestedFlowRate: requestedFlowRate,
+ pool: pool.contract.address,
+ providerOrSigner,
+ });
+ const operation = await params.superToken.distributeFlow({
+ from: params.distributor.address,
+ requestedFlowRate: requestedFlowRate,
+ pool: pool.contract.address,
+ shouldUseCallAgreement: true,
+ });
+ await operation.exec(params.distributor);
+
+ await validateOperationShouldUseCallAgreement(
+ params.testEnv,
+ operation,
+ params.shouldUseCallAgreement,
+ params.testEnv.sdkFramework.gdaV1.forwarder.address
+ );
+
+ expect(
+ await params.superToken.getGDANetFlow({
+ account: params.distributor.address,
+ providerOrSigner,
+ })
+ ).to.equal(toBN(actualDistributionFlowRate.actualFlowRate).mul(toBN("-1")));
+
+ const connectPoolOperation = await params.superToken.connectPool({
+ pool: pool.contract.address,
+ shouldUseCallAgreement: true,
+ });
+ await connectPoolOperation.exec(params.member);
+
+ expect(
+ await params.superToken.getGDANetFlow({
+ account: params.member.address,
+ providerOrSigner,
+ })
+ ).to.equal(toBN(actualDistributionFlowRate.actualFlowRate));
+
+ expect(
+ await params.superToken.getPoolAdjustmentFlowRate({
+ pool: pool.contract.address,
+ providerOrSigner,
+ })
+ ).to.equal("0");
+
+ const poolAdjustmentFlowInfo =
+ await params.superToken.getPoolAdjustmentFlowInfo({
+ pool: pool.contract.address,
+ providerOrSigner,
+ });
+ expect(poolAdjustmentFlowInfo.flowRate).to.equal("0");
+ expect(poolAdjustmentFlowInfo.recipient).to.equal(params.admin.address);
+ const encoder = new ethers.utils.AbiCoder();
+ const network = await providerOrSigner.provider?.getNetwork();
+ if (!network) throw new Error("no network");
+
+ const encodedData = encoder.encode(
+ ["uint256", "string", "address", "address"],
+ [
+ network.chainId,
+ "poolAdjustmentFlow",
+ pool.contract.address,
+ params.admin.address,
+ ]
+ );
+ const flowHash = ethers.utils.keccak256(encodedData);
+ expect(poolAdjustmentFlowInfo.flowHash).to.equal(flowHash);
+};
+
+const shouldClaimAllForMember = async (
+ params: ShouldClaimAllForMemberParams
+) => {
+ const memberBalanceBefore = await params.superToken.balanceOf({
+ account: params.member.address,
+ providerOrSigner: params.member,
+ });
+ const claimableBalanceData = await params.pool.getClaimableNow({
+ member: params.member.address,
+ providerOrSigner: params.member,
+ });
+
+ if (params.claimAll) {
+ await params.pool.claimAll(params.member);
+ } else {
+ await params.pool.claimAllForMember({
+ member: params.member.address,
+ signer: params.claimer,
+ });
+ }
+
+ const memberBalanceAfter = await params.superToken.balanceOf({
+ account: params.member.address,
+ providerOrSigner: params.member,
+ });
+ expect(toBN(memberBalanceAfter).sub(toBN(memberBalanceBefore))).to.equal(
+ claimableBalanceData.claimableBalance
+ );
+};
diff --git a/packages/sdk-core/test/4_governance.test.ts b/packages/sdk-core/test/4_governance.test.ts
index b807295c90..38e0f6e5bb 100644
--- a/packages/sdk-core/test/4_governance.test.ts
+++ b/packages/sdk-core/test/4_governance.test.ts
@@ -13,9 +13,11 @@ makeSuite("Governance Tests", (testEnv: TestEnvironment) => {
expect(defaultParams.patricianPeriod).to.equal(
testEnv.constants.PATRICIAN_PERIOD
);
- expect(defaultParams.rewardAddress).to.equal(
- testEnv.constants.DEFAULT_REWARD_ADDRESS
- );
+ const defaultRewardAddress =
+ await testEnv.sdkFramework.governance.getRewardAddress({
+ providerOrSigner: testEnv.alice,
+ });
+ expect(defaultParams.rewardAddress).to.equal(defaultRewardAddress);
expect(defaultParams.minimumDeposit).to.equal("0");
});
@@ -31,8 +33,12 @@ makeSuite("Governance Tests", (testEnv: TestEnvironment) => {
expect(tokenSpecificParams.patricianPeriod).to.equal(
testEnv.constants.PATRICIAN_PERIOD
);
+ const defaultRewardAddress =
+ await testEnv.sdkFramework.governance.getRewardAddress({
+ providerOrSigner: testEnv.alice,
+ });
expect(tokenSpecificParams.rewardAddress).to.equal(
- testEnv.constants.DEFAULT_REWARD_ADDRESS
+ defaultRewardAddress
);
expect(tokenSpecificParams.minimumDeposit).to.equal("0");
});
diff --git a/packages/sdk-core/test/TestEnvironment.ts b/packages/sdk-core/test/TestEnvironment.ts
index a395442059..e0c2e17f73 100644
--- a/packages/sdk-core/test/TestEnvironment.ts
+++ b/packages/sdk-core/test/TestEnvironment.ts
@@ -1,8 +1,10 @@
import hre, { ethers } from "hardhat";
import {
IConstantFlowAgreementV1,
+ IGeneralDistributionAgreementV1,
IInstantDistributionAgreementV1,
SuperfluidFrameworkDeployer,
+ SuperfluidFrameworkDeploymentSteps,
TestToken,
TestToken__factory,
} from "../src/typechain-types";
@@ -34,7 +36,7 @@ export interface TestEnvironment {
provider: JsonRpcProvider;
sdkFramework: Framework;
superfluidFrameworkDeployer: SuperfluidFrameworkDeployer;
- frameworkAddresses: SuperfluidFrameworkDeployer.FrameworkStructOutput;
+ frameworkAddresses: SuperfluidFrameworkDeploymentSteps.FrameworkStructOutput;
constants: typeof TEST_ENVIRONMENT_CONSTANTS;
users: SignerWithAddress[];
alice: SignerWithAddress;
@@ -42,6 +44,7 @@ export interface TestEnvironment {
charlie: SignerWithAddress;
cfaV1: IConstantFlowAgreementV1;
idaV1: IInstantDistributionAgreementV1;
+ gdaV1: IGeneralDistributionAgreementV1;
wrapperSuperToken: WrapperSuperToken;
nativeAssetSuperToken: NativeAssetSuperToken;
pureSuperToken: PureSuperToken;
@@ -53,7 +56,8 @@ const testEnv: TestEnvironment = {
provider: hre.ethers.provider,
sdkFramework: {} as Framework,
superfluidFrameworkDeployer: {} as SuperfluidFrameworkDeployer,
- frameworkAddresses: {} as SuperfluidFrameworkDeployer.FrameworkStructOutput,
+ frameworkAddresses:
+ {} as SuperfluidFrameworkDeploymentSteps.FrameworkStructOutput,
constants: TEST_ENVIRONMENT_CONSTANTS,
alice: {} as SignerWithAddress,
bob: {} as SignerWithAddress,
@@ -61,6 +65,7 @@ const testEnv: TestEnvironment = {
users: [],
cfaV1: {} as IConstantFlowAgreementV1,
idaV1: {} as IInstantDistributionAgreementV1,
+ gdaV1: {} as IGeneralDistributionAgreementV1,
token: {} as TestToken,
wrapperSuperToken: {} as WrapperSuperToken,
nativeAssetSuperToken: {} as NativeAssetSuperToken,
@@ -96,6 +101,7 @@ export const initializeTestEnvironment = async () => {
console.log("Set Agreement Contracts...");
testEnv.cfaV1 = testEnv.sdkFramework.cfaV1.contract.connect(testEnv.alice);
testEnv.idaV1 = testEnv.sdkFramework.idaV1.contract.connect(testEnv.alice);
+ testEnv.gdaV1 = testEnv.sdkFramework.gdaV1.contract.connect(testEnv.alice);
console.log("Load SuperToken and TestToken...");
testEnv.wrapperSuperToken =
diff --git a/packages/solidity-semantic-money/src/ref-impl/ISuperfluidPool.sol b/packages/solidity-semantic-money/src/ref-impl/ISuperfluidPool.sol
index cf1c1a4074..86ffe6cc33 100644
--- a/packages/solidity-semantic-money/src/ref-impl/ISuperfluidPool.sol
+++ b/packages/solidity-semantic-money/src/ref-impl/ISuperfluidPool.sol
@@ -67,4 +67,4 @@ interface ISuperfluidPoolOperator {
/// Settle the claim
function poolSettleClaim(address claimRecipient, Value amount) external returns (bool);
-}
+}
\ No newline at end of file
diff --git a/packages/subgraph/.prettierrc.js b/packages/subgraph/.prettierrc.js
index bc6a4ec169..11e1937436 100644
--- a/packages/subgraph/.prettierrc.js
+++ b/packages/subgraph/.prettierrc.js
@@ -2,4 +2,5 @@ module.exports = {
trailingComma: "es5",
singleQuote: false,
bracketSpacing: true,
+ printWidth: 120
};
diff --git a/packages/subgraph/config/mock.json b/packages/subgraph/config/mock.json
index 2828806c15..0a91e0f421 100644
--- a/packages/subgraph/config/mock.json
+++ b/packages/subgraph/config/mock.json
@@ -4,6 +4,7 @@
"hostAddress": "0x0000000000000000000000000000000000000000",
"cfaAddress": "0x0000000000000000000000000000000000000000",
"idaAddress": "0x0000000000000000000000000000000000000000",
+ "gdaAddress": "0x0000000000000000000000000000000000000000",
"superTokenFactoryAddress": "0x0000000000000000000000000000000000000000",
"resolverV1Address": "0x0000000000000000000000000000000000000000",
"nativeAssetSuperTokenAddress": "0x0000000000000000000000000000000000000000",
diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json
index 9a9c89f0dd..ff8c4b0dc9 100644
--- a/packages/subgraph/package.json
+++ b/packages/subgraph/package.json
@@ -23,7 +23,7 @@
"matchstick:prepare-addresses": "mustache config/polygon-mainnet.json src/addresses.template.ts > src/addresses.ts",
"matchstick:prepare-generated": "yarn getAbi && yarn codegen && yarn generate-sf-meta-local",
"matchstick:test": "graph test",
- "dev": "nodemon -e ts -x yarn matchstick:test",
+ "dev": "yarn matchstick && nodemon -e ts -x yarn matchstick:test",
"posttest": "yarn testenv:stop",
"integrity": "npx hardhat run scripts/dataIntegrity/dataIntegrityTest.ts --network",
"check-indexing-completeness": "ts-node scripts/checkIsDeployedOnAllNetworks.ts",
@@ -33,6 +33,7 @@
"remove-local": "graph remove superfluid-test --node http://localhost:8020/",
"deploy-local": "graph deploy superfluid-test --node http://localhost:8020/ --ipfs http://localhost:5001 --version-label v1.0.0",
"prepare-local": "run-s prepare-local:*",
+ "prepare-local:deploy-contracts": "cd ../ethereum-contracts && npx hardhat run dev-scripts/run-deploy-contracts-and-token.js && cd ../subgraph",
"prepare-local:manifest": "yarn prepare-manifest-local",
"prepare-local:network": "yarn set-network-local",
"prepare-local:abi": "yarn getAbi",
@@ -51,7 +52,7 @@
"dependencies": {
"@graphprotocol/graph-cli": "0.57.0",
"@graphprotocol/graph-ts": "0.31.0",
- "@superfluid-finance/sdk-core": "0.6.8",
+ "@superfluid-finance/sdk-core": "0.6.13",
"mustache": "^4.2.0"
},
"devDependencies": {
diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql
index 7074712f4c..da309b0a93 100644
--- a/packages/subgraph/schema.graphql
+++ b/packages/subgraph/schema.graphql
@@ -613,6 +613,212 @@ type SubscriptionUnitsUpdatedEvent implements Event @entity(immutable: true) {
subscription: IndexSubscription!
}
+# GeneralDistributionAgreementV1 #
+
+type PoolCreatedEvent implements Event @entity(immutable: true) {
+ id: ID!
+ transactionHash: Bytes!
+ gasPrice: BigInt!
+ gasUsed: BigInt!
+ timestamp: BigInt!
+ name: String!
+
+ """
+ Contains the addresses that were impacted by this event:
+ addresses[0] = `token` (superToken)
+ addresses[1] = `pool`
+ addresses[2] = `caller`
+ addresses[3] = `admin`
+ """
+ addresses: [Bytes!]!
+ blockNumber: BigInt!
+ logIndex: BigInt!
+ order: BigInt!
+
+ token: Bytes!
+ caller: Bytes!
+ admin: Bytes!
+
+ pool: Pool!
+}
+
+type PoolConnectionUpdatedEvent implements Event @entity(immutable: true) {
+ id: ID!
+ transactionHash: Bytes!
+ gasPrice: BigInt!
+ gasUsed: BigInt!
+ timestamp: BigInt!
+ name: String!
+
+ """
+ Contains the addresses that were impacted by this event:
+ addresses[0] = `token` (superToken)
+ addresses[1] = `pool`
+ addresses[2] = `poolMember`
+ """
+ addresses: [Bytes!]!
+ blockNumber: BigInt!
+ logIndex: BigInt!
+ order: BigInt!
+
+ token: Bytes!
+ connected: Boolean!
+ userData: Bytes!
+
+ pool: Pool!
+ poolMember: PoolMember!
+}
+
+type BufferAdjustedEvent implements Event @entity(immutable: true) {
+ id: ID!
+ transactionHash: Bytes!
+ gasPrice: BigInt!
+ gasUsed: BigInt!
+ timestamp: BigInt!
+ name: String!
+
+ """
+ Contains the addresses that were impacted by this event:
+ addresses[0] = `token` (superToken)
+ addresses[1] = `pool`
+ addresses[2] = `distributor`
+ addresses[3] = `operator`
+ """
+ addresses: [Bytes!]!
+ blockNumber: BigInt!
+ logIndex: BigInt!
+ order: BigInt!
+
+ token: Bytes!
+ bufferDelta: BigInt!
+ newBufferAmount: BigInt!
+ totalBufferAmount: BigInt!
+
+ pool: Pool!
+ poolDistributor: PoolDistributor!
+}
+
+type InstantDistributionUpdatedEvent implements Event @entity(immutable: true) {
+ id: ID!
+ transactionHash: Bytes!
+ gasPrice: BigInt!
+ gasUsed: BigInt!
+ timestamp: BigInt!
+ name: String!
+
+ """
+ Contains the addresses that were impacted by this event:
+ addresses[0] = `token` (superToken)
+ addresses[1] = `pool`
+ addresses[2] = `poolDistributor`
+ addresses[3] = `operator`
+ """
+ addresses: [Bytes!]!
+ blockNumber: BigInt!
+ logIndex: BigInt!
+ order: BigInt!
+
+ token: Bytes!
+ operator: Bytes!
+ requestedAmount: BigInt!
+ actualAmount: BigInt!
+ totalUnits: BigInt!
+ userData: Bytes!
+
+ pool: Pool!
+ poolDistributor: PoolDistributor!
+}
+
+type FlowDistributionUpdatedEvent implements Event @entity(immutable: true) {
+ id: ID!
+ transactionHash: Bytes!
+ gasPrice: BigInt!
+ gasUsed: BigInt!
+ timestamp: BigInt!
+ name: String!
+
+ """
+ Contains the addresses that were impacted by this event:
+ addresses[0] = `token` (superToken)
+ addresses[1] = `pool`
+ addresses[2] = `poolDistributor`
+ addresses[3] = `operator`
+ """
+ addresses: [Bytes!]!
+ blockNumber: BigInt!
+ logIndex: BigInt!
+ order: BigInt!
+
+ token: Bytes!
+ operator: Bytes!
+ oldFlowRate: BigInt!
+ newDistributorToPoolFlowRate: BigInt!
+ newTotalDistributionFlowRate: BigInt!
+ adjustmentFlowRecipient: Bytes!
+ adjustmentFlowRate: BigInt!
+ totalUnits: BigInt!
+ userData: Bytes!
+
+ pool: Pool!
+ poolDistributor: PoolDistributor!
+}
+
+# SuperfluidPool #
+type DistributionClaimedEvent implements Event @entity(immutable: true) {
+ id: ID!
+ transactionHash: Bytes!
+ gasPrice: BigInt!
+ gasUsed: BigInt!
+ timestamp: BigInt!
+ name: String!
+
+ """
+ Contains the addresses that were impacted by this event:
+ addresses[0] = `token` (superToken)
+ addresses[1] = `pool`
+ addresses[2] = `member`
+ """
+ addresses: [Bytes!]!
+ blockNumber: BigInt!
+ logIndex: BigInt!
+ order: BigInt!
+
+ token: Bytes!
+ claimedAmount: BigInt!
+ totalClaimed: BigInt!
+
+ pool: Pool!
+ poolMember: PoolMember!
+}
+
+type MemberUnitsUpdatedEvent implements Event @entity(immutable: true) {
+ id: ID!
+ transactionHash: Bytes!
+ gasPrice: BigInt!
+ gasUsed: BigInt!
+ timestamp: BigInt!
+ name: String!
+
+ """
+ Contains the addresses that were impacted by this event:
+ addresses[0] = `token` (superToken)
+ addresses[1] = `pool`
+ addresses[2] = `member`
+ """
+ addresses: [Bytes!]!
+ blockNumber: BigInt!
+ logIndex: BigInt!
+ order: BigInt!
+
+ token: Bytes!
+ oldUnits: BigInt!
+ units: BigInt!
+ totalUnits: BigInt!
+
+ pool: Pool!
+ poolMember: PoolMember!
+}
+
# Host #
type AgreementClassRegisteredEvent implements Event @entity(immutable: true) {
@@ -943,7 +1149,8 @@ type PPPConfigurationChangedEvent implements Event @entity(immutable: true) {
patricianPeriod: BigInt!
}
-type SuperTokenMinimumDepositChangedEvent implements Event @entity(immutable: true) {
+type SuperTokenMinimumDepositChangedEvent implements Event
+ @entity(immutable: true) {
id: ID!
transactionHash: Bytes!
gasPrice: BigInt!
@@ -1520,6 +1727,9 @@ type Account @entity {
subscriptions: [IndexSubscription!]! @derivedFrom(field: "subscriber")
publishedIndexes: [Index!]! @derivedFrom(field: "publisher")
+ pools: [Pool!]! @derivedFrom(field: "admin")
+ poolMemberships: [PoolMember!]! @derivedFrom(field: "account")
+
sentTransferEvents: [TransferEvent!]! @derivedFrom(field: "from")
receivedTransferEvents: [TransferEvent!]! @derivedFrom(field: "to")
@@ -1531,6 +1741,115 @@ type Account @entity {
@derivedFrom(field: "account")
}
+type Pool @entity {
+ """
+ ID: poolAddress
+ """
+ id: ID!
+ createdAtTimestamp: BigInt!
+ createdAtBlockNumber: BigInt!
+ updatedAtTimestamp: BigInt!
+ updatedAtBlockNumber: BigInt!
+
+ totalUnits: BigInt!
+ totalConnectedUnits: BigInt!
+ totalDisconnectedUnits: BigInt!
+ totalAmountInstantlyDistributedUntilUpdatedAt: BigInt!
+ totalAmountFlowedDistributedUntilUpdatedAt: BigInt!
+ totalAmountDistributedUntilUpdatedAt: BigInt!
+ """
+ A member is any account which has more than 0 units in the pool.
+ """
+ totalMembers: Int!
+ """
+ A connected member is any account which has more than 0 units in the pool and is connected.
+ """
+ totalConnectedMembers: Int!
+ """
+ A disconnected member is any account which has more than 0 units in the pool and is not connected.
+ """
+ totalDisconnectedMembers: Int!
+ adjustmentFlowRate: BigInt!
+ flowRate: BigInt!
+ totalBuffer: BigInt!
+ token: Token!
+ admin: Account!
+
+ # ---------------------------------- links ----------------------------------
+ # HOL Entity Links
+ poolDistributors: [PoolDistributor!]! @derivedFrom(field: "pool")
+ poolMembers: [PoolMember!]! @derivedFrom(field: "pool")
+
+ # Created Event Entity Link
+ poolCreatedEvent: PoolCreatedEvent! @derivedFrom(field: "pool")
+
+ # Event Entity Links
+ poolConnectionUpdatedEvents: [PoolConnectionUpdatedEvent!]!
+ @derivedFrom(field: "pool")
+ bufferAdjustedEvents: [BufferAdjustedEvent!]! @derivedFrom(field: "pool")
+ instantDistributionUpdatedEvents: [InstantDistributionUpdatedEvent!]!
+ @derivedFrom(field: "pool")
+ flowDistributionUpdatedEvents: [FlowDistributionUpdatedEvent!]!
+ @derivedFrom(field: "pool")
+ memberUnitsUpdatedEvents: [MemberUnitsUpdatedEvent!]! @derivedFrom(field: "pool")
+ distributionClaimedEvents: [DistributionClaimedEvent!]!
+ @derivedFrom(field: "pool")
+}
+
+type PoolMember @entity {
+ """
+ ID composed of: "poolMember"-poolAddress-poolMemberAddress
+ """
+ id: ID!
+ createdAtTimestamp: BigInt!
+ createdAtBlockNumber: BigInt!
+ updatedAtTimestamp: BigInt!
+ updatedAtBlockNumber: BigInt!
+
+ units: BigInt!
+ isConnected: Boolean!
+ totalAmountClaimed: BigInt!
+
+ account: Account!
+ pool: Pool!
+
+ # ---------------------------------- links ----------------------------------
+ poolConnectionUpdatedEvents: [PoolConnectionUpdatedEvent!]!
+ @derivedFrom(field: "poolMember")
+ memberUnitsUpdatedEvents: [MemberUnitsUpdatedEvent!]!
+ @derivedFrom(field: "poolMember")
+ distributionClaimedEvents: [DistributionClaimedEvent!]!
+ @derivedFrom(field: "poolMember")
+}
+
+type PoolDistributor @entity {
+ """
+ ID composed of: "poolDistributor"-pool-poolDistributorAddress
+ """
+ id: ID!
+ createdAtTimestamp: BigInt!
+ createdAtBlockNumber: BigInt!
+ updatedAtTimestamp: BigInt!
+ updatedAtBlockNumber: BigInt!
+
+ totalAmountInstantlyDistributedUntilUpdatedAt: BigInt!
+ totalAmountFlowedDistributedUntilUpdatedAt: BigInt!
+ totalAmountDistributedUntilUpdatedAt: BigInt!
+ totalBuffer: BigInt!
+ flowRate: BigInt!
+
+ account: Account!
+ pool: Pool!
+
+ # ---------------------------------- links ----------------------------------
+ bufferAdjustedEvents: [BufferAdjustedEvent!]!
+ @derivedFrom(field: "poolDistributor")
+ instantDistributionUpdatedEvents: [InstantDistributionUpdatedEvent!]!
+ @derivedFrom(field: "poolDistributor")
+ flowDistributionUpdatedEvents: [FlowDistributionUpdatedEvent!]!
+ @derivedFrom(field: "poolDistributor")
+}
+
"""
Index: An Index higher order entity.
"""
@@ -1931,35 +2250,97 @@ type AccountTokenSnapshot @entity {
maybeCriticalAtTimestamp: BigInt
"""
- The count of currently open streams for an account, both incoming and outgoing.
+ The count of currently open streams for an account, both incoming and outgoing for all agreements.
"""
totalNumberOfActiveStreams: Int!
"""
- The count of active outgoing streams from this account.
+ The count of currently open streams for an account, both incoming and outgoing for the CFA.
+ """
+ totalCFANumberOfActiveStreams: Int!
+
+ # delete this property
+ """
+ The count of currently open streams for an account, both incoming and outgoing for the GDA.
+ """
+ totalGDANumberOfActiveStreams: Int!
+
+ """
+ The count of active outgoing streams from this account for all agreements.
"""
activeOutgoingStreamCount: Int!
"""
- The count of active incoming streams to this account.
+ The count of active outgoing streams from this account for the CFA.
+ """
+ activeCFAOutgoingStreamCount: Int!
+
+ """
+ The count of active outgoing streams from this account for the GDA.
+ """
+ activeGDAOutgoingStreamCount: Int!
+
+ """
+ The count of active incoming streams to this account for all agreements.
"""
activeIncomingStreamCount: Int!
"""
- The count of closed streams by `account`, both incoming and outgoing.
+ The count of active incoming streams to this account for the CFA.
+ """
+ activeCFAIncomingStreamCount: Int!
+
+ # delete this property
+ """
+ The count of active incoming streams to this account for the GDA.
+ """
+ activeGDAIncomingStreamCount: Int!
+
+ """
+ The count of closed streams by `account`, both incoming and outgoing for all agreements.
"""
totalNumberOfClosedStreams: Int!
"""
- The count of closed outgoing streams by `account`.
+ The count of closed streams by `account`, both incoming and outgoing for the CFA.
+ """
+ totalCFANumberOfClosedStreams: Int!
+
+ """
+ The count of closed streams by `account`, both incoming and outgoing for the GDA.
+ """
+ totalGDANumberOfClosedStreams: Int!
+
+ """
+ The count of closed outgoing streams by `account` for all agreements.
"""
inactiveOutgoingStreamCount: Int!
"""
- The count of closed incoming streams by `account`.
+ The count of closed outgoing streams by `account` for the CFA.
+ """
+ inactiveCFAOutgoingStreamCount: Int!
+
+ """
+ The count of closed outgoing streams by `account` for the GDA.
+ """
+ inactiveGDAOutgoingStreamCount: Int!
+
+ """
+ The count of closed incoming streams by `account` for all agreements.
"""
inactiveIncomingStreamCount: Int!
+ """
+ The count of closed incoming streams by `account` for the CFA.
+ """
+ inactiveCFAIncomingStreamCount: Int!
+
+ """
+ The count of closed incoming streams by `account` for the GDA.
+ """
+ inactiveGDAIncomingStreamCount: Int!
+
"""
The current (as of updatedAt) number of subscriptions with units allocated to them tied to this `account`.
"""
@@ -1970,46 +2351,127 @@ type AccountTokenSnapshot @entity {
"""
totalApprovedSubscriptions: Int!
+ """
+ The current (as of updatedAt) number of membership with units allocated to them tied to this `account`.
+ """
+ totalMembershipsWithUnits: Int!
+
+ """
+ Counts all currently (as of updatedAt) approved membership whether or not they have units.
+ """
+ totalConnectedMemberships: Int!
+
"""
Balance of `account` as of `updatedAtTimestamp`/`updatedAtBlock`.
"""
balanceUntilUpdatedAt: BigInt!
"""
- The total deposit this account has held by the CFA agreement for `account` active streams.
+ The total deposit this account has held by all flow agreements for `account` active streams.
"""
totalDeposit: BigInt!
"""
- The total net flow rate of the `account` as of `updatedAtTimestamp`/`updatedAtBlock`.
+ The total deposit this account has held by the CFA agreement for `account` active streams.
+ """
+ totalCFADeposit: BigInt!
+
+ """
+ The total deposit this account has held by the GDA agreement for `account` active streams.
+ """
+ totalGDADeposit: BigInt!
+
+ """
+ The total net flow rate of the `account` as of `updatedAtTimestamp`/`updatedAtBlock` for all flow agreements.
+ This can be obtained by: `totalInflowRate - totalOutflowRate`.
"""
totalNetFlowRate: BigInt!
"""
- The total inflow rate (receive flowRate per second) of the `account`.
+ The total net flow rate of the `account` as of `updatedAtTimestamp`/`updatedAtBlock` for the CFA.
+ """
+ totalCFANetFlowRate: BigInt!
+
+ """
+ The total net flow rate of the `account` as of `updatedAtTimestamp`/`updatedAtBlock` for the GDA.
+ """
+ totalGDANetFlowRate: BigInt!
+
+ """
+ The total inflow rate (receive flowRate per second) of the `account` for all flow agreements.
"""
totalInflowRate: BigInt!
"""
- The total outflow rate (send flowrate per second) of the `account`.
+ The total inflow rate (receive flowRate per second) of the `account` for the CFA.
+ """
+ totalCFAInflowRate: BigInt!
+
+ """
+ The total inflow rate (receive flowRate per second) of the `account` for the GDA.
+ """
+ totalGDAInflowRate: BigInt!
+
+ """
+ The total outflow rate (send flowrate per second) of the `account` for all flow agreements.
"""
totalOutflowRate: BigInt!
"""
- The total amount of `token` streamed into this `account` until the `updatedAtTimestamp`/`updatedAtBlock`.
+ The total outflow rate (send flowrate per second) of the `account` for the CFA.
+ """
+ totalCFAOutflowRate: BigInt!
+
+ """
+ The total outflow rate (send flowrate per second) of the `account` for the GDA.
+ """
+ totalGDAOutflowRate: BigInt!
+
+ """
+ The total amount of `token` streamed into this `account` until the `updatedAtTimestamp`/`updatedAtBlock` for all flow agreements.
"""
totalAmountStreamedInUntilUpdatedAt: BigInt!
"""
- The total amount of `token` streamed from this `account` until the `updatedAtTimestamp`/`updatedAtBlock`.
+ The total amount of `token` streamed into this `account` until the `updatedAtTimestamp`/`updatedAtBlock` for the CFA.
+ """
+ totalCFAAmountStreamedInUntilUpdatedAt: BigInt!
+
+ """
+ The total amount of `token` streamed into this `account` until the `updatedAtTimestamp`/`updatedAtBlock` for the GDA.
+ """
+ totalGDAAmountStreamedInUntilUpdatedAt: BigInt!
+
+ """
+ The total amount of `token` streamed from this `account` until the `updatedAtTimestamp`/`updatedAtBlock` for all flow agreements.
"""
totalAmountStreamedOutUntilUpdatedAt: BigInt!
"""
- The total amount of `token` streamed through this `account` until the `updatedAtTimestamp`/`updatedAtBlock`.
+ The total amount of `token` streamed from this `account` until the `updatedAtTimestamp`/`updatedAtBlock` for the CFA.
+ """
+ totalCFAAmountStreamedOutUntilUpdatedAt: BigInt!
+
+ """
+ The total amount of `token` streamed from this `account` until the `updatedAtTimestamp`/`updatedAtBlock` for the GDA.
+ """
+ totalGDAAmountStreamedOutUntilUpdatedAt: BigInt!
+
+ """
+ The total amount of `token` streamed through this `account` until the `updatedAtTimestamp`/`updatedAtBlock` for all flow agreements.
"""
totalAmountStreamedUntilUpdatedAt: BigInt!
+ """
+ The total amount of `token` streamed through this `account` until the `updatedAtTimestamp`/`updatedAtBlock` for the CFA.
+ """
+ totalCFAAmountStreamedUntilUpdatedAt: BigInt!
+
+ """
+ The total amount of `token` streamed through this `account` until the `updatedAtTimestamp`/`updatedAtBlock` for the GDA.
+ """
+ totalGDAAmountStreamedUntilUpdatedAt: BigInt!
+
"""
The total amount of `token` this `account` has transferred.
"""
@@ -2042,35 +2504,95 @@ type AccountTokenSnapshotLog @entity {
maybeCriticalAtTimestamp: BigInt
"""
- The current (as of timestamp) number of open streams.
+ The current (as of timestamp) number of open streams for all agreements.
"""
totalNumberOfActiveStreams: Int!
"""
- The count of active outgoing streams from this account.
+ The current (as of timestamp) number of open streams.
+ """
+ totalCFANumberOfActiveStreams: Int!
+
+ """
+ The current (as of timestamp) number of open streams.
+ """
+ totalGDANumberOfActiveStreams: Int!
+
+ """
+ The count of active outgoing streams from this account for all agreements.
"""
activeOutgoingStreamCount: Int!
"""
- The count of active incoming streams to this account.
+ The count of active outgoing streams from this account.
+ """
+ activeCFAOutgoingStreamCount: Int!
+
+ """
+ The count of active outgoing streams from this account.
+ """
+ activeGDAOutgoingStreamCount: Int!
+
+ """
+ The count of active incoming streams to this account for all agreements.
"""
activeIncomingStreamCount: Int!
"""
- The current (as of timestamp) count of closed streams.
+ The count of active incoming streams to this account for the CFA.
+ """
+ activeCFAIncomingStreamCount: Int!
+
+ """
+ The count of active incoming streams to this account for the GDA.
+ """
+ activeGDAIncomingStreamCount: Int!
+
+ """
+ The current (as of timestamp) count of closed streams for all agreements.
"""
totalNumberOfClosedStreams: Int!
"""
- The count of closed outgoing streams by `account`.
+ The current (as of timestamp) count of closed streams for the CFA.
+ """
+ totalCFANumberOfClosedStreams: Int!
+
+ """
+ The current (as of timestamp) count of closed streams for the GDA.
+ """
+ totalGDANumberOfClosedStreams: Int!
+
+ """
+ The count of closed outgoing streams by `account` for all agreements.
"""
inactiveOutgoingStreamCount: Int!
"""
- The count of closed incoming streams by `account`.
+ The count of closed outgoing streams by `account` for the CFA.
+ """
+ inactiveCFAOutgoingStreamCount: Int!
+
+ """
+ The count of closed outgoing streams by `account` for the GDA.
+ """
+ inactiveGDAOutgoingStreamCount: Int!
+
+ """
+ The count of closed incoming streams by `account` for all agreements.
"""
inactiveIncomingStreamCount: Int!
+ """
+ The count of closed incoming streams by `account` for the CFA.
+ """
+ inactiveCFAIncomingStreamCount: Int!
+
+ """
+ The count of closed incoming streams by `account` for the GDA.
+ """
+ inactiveGDAIncomingStreamCount: Int!
+
"""
The current (as of timestamp) number of subscriptions with units allocated to them tied to this `account`.
"""
@@ -2081,47 +2603,129 @@ type AccountTokenSnapshotLog @entity {
"""
totalApprovedSubscriptions: Int!
+ """
+ The current (as of timestamp) number of membership with units allocated to them tied to this `account`.
+ """
+ totalMembershipsWithUnits: Int!
+
+ """
+ Counts all currently (as of timestamp) connected membership whether or not they have units.
+ """
+ totalConnectedMemberships: Int!
+
"""
Balance of `account` as of `timestamp`/`block`.
"""
balance: BigInt!
"""
- The total (as of timestamp) deposit this account has held by the CFA agreement for `account` active streams.
+ The total (as of timestamp) deposit this account has held by all flow agreements for `account` active streams.
"""
totalDeposit: BigInt!
+ """
+ The total (as of timestamp) deposit this account has held by the CFA agreement for `account` active streams.
+ """
+ totalCFADeposit: BigInt!
+
+ """
+ The total (as of timestamp) deposit this account has held by the GDA agreement for `account` active streams.
+ """
+ totalGDADeposit: BigInt!
+
"""
The total (as of timestamp) net flow rate of the `account` as of `timestamp`/`block`.
This can be obtained by: `totalInflowRate - totalOutflowRate`
"""
totalNetFlowRate: BigInt!
+ """
+ The total (as of timestamp) net flow rate of the `account` as of `timestamp`/`block` for the CFA.
+ This can be obtained by: `totalCFAInflowRate - totalCFAOutflowRate`
+ """
+ totalCFANetFlowRate: BigInt!
+
+ """
+ The total (as of timestamp) net flow rate of the `account` as of `timestamp`/`block` for the GDA.
+ This can be obtained by: `totalGDAInflowRate - totalGDAOutflowRate`
+ """
+ totalGDANetFlowRate: BigInt!
+
"""
The total (as of timestamp) inflow rate (receive flowRate per second) of the `account`.
"""
totalInflowRate: BigInt!
+ """
+ The total (as of timestamp) inflow rate (receive flowRate per second) of the `account` for the CFA.
+ """
+ totalCFAInflowRate: BigInt!
+
+ """
+ The total (as of timestamp) inflow rate (receive flowRate per second) of the `account` for the GDA.
+ """
+ totalGDAInflowRate: BigInt!
+
"""
The total (as of timestamp) outflow rate (send flowrate per second) of the `account`.
"""
totalOutflowRate: BigInt!
+ """
+ The total (as of timestamp) outflow rate (send flowrate per second) of the `account` for the CFA.
+ """
+ totalCFAOutflowRate: BigInt!
+
+ """
+ The total (as of timestamp) outflow rate (send flowrate per second) of the `account` for the GDA.
+ """
+ totalGDAOutflowRate: BigInt!
+
"""
The total (as of timestamp) amount of `token` streamed into this `account` until the `timestamp`/`block`.
"""
totalAmountStreamedIn: BigInt!
+ """
+ The total (as of timestamp) amount of `token` streamed into this `account` until the `timestamp`/`block` for the CFA.
+ """
+ totalCFAAmountStreamedIn: BigInt!
+
+ """
+ The total (as of timestamp) amount of `token` streamed into this `account` until the `timestamp`/`block` for the GDA.
+ """
+ totalGDAAmountStreamedIn: BigInt!
+
"""
The total (as of timestamp) amount of `token` streamed from this `account` until the `timestamp`/`block`.
"""
totalAmountStreamedOut: BigInt!
+ """
+ The total (as of timestamp) amount of `token` streamed from this `account` until the `timestamp`/`block` for the CFA.
+ """
+ totalCFAAmountStreamedOut: BigInt!
+
+ """
+ The total (as of timestamp) amount of `token` streamed from this `account` until the `timestamp`/`block` for the GDA.
+ """
+ totalGDAAmountStreamedOut: BigInt!
+
"""
The total (as of timestamp) net amount of `token` streamed through this `account` until the `timestamp`/`block`.
"""
totalAmountStreamed: BigInt!
+ """
+ The total (as of timestamp) net amount of `token` streamed through this `account` until the `timestamp`/`block` for the CFA.
+ """
+ totalCFAAmountStreamed: BigInt!
+
+ """
+ The total (as of timestamp) net amount of `token` streamed through this `account` until the `timestamp`/`block` for the GDA.
+ """
+ totalGDAAmountStreamed: BigInt!
+
"""
The total (as of timestamp) amount of `token` this `account` has transferred out until the `timestamp`/`block`.
"""
@@ -2148,11 +2752,31 @@ type TokenStatistic @entity {
"""
totalNumberOfActiveStreams: Int!
+ """
+ The total number of currently active `token` streams for the CFA.
+ """
+ totalCFANumberOfActiveStreams: Int!
+
+ """
+ The total number of currently active `token` streams for the GDA.
+ """
+ totalGDANumberOfActiveStreams: Int!
+
"""
The count of closed streams for `token`.
"""
totalNumberOfClosedStreams: Int!
+ """
+ The count of closed streams for `token` for the CFA.
+ """
+ totalCFANumberOfClosedStreams: Int!
+
+ """
+ The count of closed streams for `token` for the GDA.
+ """
+ totalGDANumberOfClosedStreams: Int!
+
"""
The total number of Indexes created with `token`.
"""
@@ -2174,20 +2798,70 @@ type TokenStatistic @entity {
totalApprovedSubscriptions: Int!
"""
- The total deposit held by the CFA agreement for this particular `token`.
+ The total number of Pools created with `token`.
+ """
+ totalNumberOfPools: Int!
+
+ """
+ The total number of "active" (has greater than 0 units and has distributed it at least once) Pools created with `token`.
+ """
+ totalNumberOfActivePools: Int!
+
+ """
+ The number of memberships which have units allocated to them created with Pools that distribute `token`.
+ """
+ totalMembershipsWithUnits: Int!
+
+ """
+ Counts all approved memberships whether or not they have units.
+ """
+ totalConnectedMemberships: Int!
+
+ """
+ The total deposit held by all flow agreements for this particular `token`.
"""
totalDeposit: BigInt!
"""
- The total outflow rate of the `token` (how much value is being moved).
+ The total deposit held by the CFA for this particular `token`.
+ """
+ totalCFADeposit: BigInt!
+
+ """
+ The total deposit held by the GDA agreement for this particular `token`.
+ """
+ totalGDADeposit: BigInt!
+
+ """
+ The total outflow rate of the `token` (how much value is being moved) for all flow agreements.
"""
totalOutflowRate: BigInt!
"""
- The all-time total amount streamed (outflows) until the `updatedAtTimestamp`/`updatedAtBlock`.
+ The total outflow rate of the `token` (how much value is being moved) for the CFA.
+ """
+ totalCFAOutflowRate: BigInt!
+
+ """
+ The total outflow rate of the `token` (how much value is being moved) for the GDA.
+ """
+ totalGDAOutflowRate: BigInt!
+
+ """
+ The all-time total amount streamed (outflows) until the `updatedAtTimestamp`/`updatedAtBlock` for all flow agreements.
"""
totalAmountStreamedUntilUpdatedAt: BigInt!
+ """
+ The all-time total amount streamed (outflows) until the `updatedAtTimestamp`/`updatedAtBlock` for the CFA.
+ """
+ totalCFAAmountStreamedUntilUpdatedAt: BigInt!
+
+ """
+ The all-time total amount streamed (outflows) until the `updatedAtTimestamp`/`updatedAtBlock` for the GDA.
+ """
+ totalGDAAmountStreamedUntilUpdatedAt: BigInt!
+
"""
The all-time total amount transferred until the `updatedAtTimestamp`/`updatedAtBlock`.
"""
@@ -2233,15 +2907,35 @@ type TokenStatisticLog @entity {
triggeredByEventName: String!
# ---------------------------------- state ----------------------------------
"""
- The total number of currently active `token` streams.
+ The total number of currently active `token` streams for all flow agreements.
"""
totalNumberOfActiveStreams: Int!
"""
- The count of closed streams for `token`.
+ The total number of currently active `token` streams for the CFA.
+ """
+ totalCFANumberOfActiveStreams: Int!
+
+ """
+ The total number of currently active `token` streams for the GDA.
+ """
+ totalGDANumberOfActiveStreams: Int!
+
+ """
+ The count of closed streams for `token` for all flow agreements.
"""
totalNumberOfClosedStreams: Int!
+ """
+ The count of closed streams for `token` for the CFA.
+ """
+ totalCFANumberOfClosedStreams: Int!
+
+ """
+ The count of closed streams for `token` for the GDA.
+ """
+ totalGDANumberOfClosedStreams: Int!
+
"""
The total number of Indexes created with `token`.
"""
@@ -2263,20 +2957,70 @@ type TokenStatisticLog @entity {
totalApprovedSubscriptions: Int!
"""
- The total deposit held by the CFA agreement for this particular `token`.
+ The total number of Pools created with `token`.
+ """
+ totalNumberOfPools: Int!
+
+ """
+ The total number of "active" (has greater than 0 units and has distributed it at least once) Pools created with `token`.
+ """
+ totalNumberOfActivePools: Int!
+
+ """
+ The number of memberships which have units allocated to them created with Pools that distribute `token`.
+ """
+ totalMembershipsWithUnits: Int!
+
+ """
+ Counts all connected memberships whether or not they have units.
+ """
+ totalConnectedMemberships: Int!
+
+ """
+ The total deposit held by the CFA agreement for this particular `token` for all flow agreements.
"""
totalDeposit: BigInt!
"""
- The total outflow rate of the `token` (how much value is being moved).
+ The total deposit held by the CFA agreement for this particular `token` for the CFA.
+ """
+ totalCFADeposit: BigInt!
+
+ """
+ The total deposit held by the CFA agreement for this particular `token` for the GDA.
+ """
+ totalGDADeposit: BigInt!
+
+ """
+ The total outflow rate of the `token` (how much value is being moved) for all flow agreements.
"""
totalOutflowRate: BigInt!
"""
- The all-time total amount of `token` streamed (outflows) until the `timestamp`/`block`.
+ The total outflow rate of the `token` (how much value is being moved) for the CFA.
+ """
+ totalCFAOutflowRate: BigInt!
+
+ """
+ The total outflow rate of the `token` (how much value is being moved) for the GDA.
+ """
+ totalGDAOutflowRate: BigInt!
+
+ """
+ The all-time total amount of `token` streamed (outflows) until the `timestamp`/`block` for all flow agreements.
"""
totalAmountStreamed: BigInt!
+ """
+ The all-time total amount of `token` streamed (outflows) until the `timestamp`/`block` for the CFA.
+ """
+ totalCFAAmountStreamed: BigInt!
+
+ """
+ The all-time total amount of `token` streamed (outflows) until the `timestamp`/`block` for the GDA.
+ """
+ totalGDAAmountStreamed: BigInt!
+
"""
The all-time total amount of `token` transferred until the `timestamp`/`block`.
"""
diff --git a/packages/subgraph/scripts/buildNetworkConfig.ts b/packages/subgraph/scripts/buildNetworkConfig.ts
index f3d938fc68..fbf0aec5f2 100644
--- a/packages/subgraph/scripts/buildNetworkConfig.ts
+++ b/packages/subgraph/scripts/buildNetworkConfig.ts
@@ -7,6 +7,7 @@ interface SubgraphConfig {
readonly hostAddress: string;
readonly cfaAddress: string;
readonly idaAddress: string;
+ readonly gdaAddress: string;
readonly superTokenFactoryAddress: string;
readonly resolverV1Address: string;
readonly nativeAssetSuperTokenAddress: string;
@@ -33,6 +34,7 @@ function main() {
hostAddress: networkMetadata.contractsV1.host,
cfaAddress: networkMetadata.contractsV1.cfaV1,
idaAddress: networkMetadata.contractsV1.idaV1,
+ gdaAddress: networkMetadata.contractsV1.gdaV1 || ADDRESS_ZERO,
superTokenFactoryAddress: networkMetadata.contractsV1.superTokenFactory,
resolverV1Address: networkMetadata.contractsV1.resolver,
nativeAssetSuperTokenAddress: networkMetadata.nativeTokenWrapper,
diff --git a/packages/subgraph/scripts/getAbi.js b/packages/subgraph/scripts/getAbi.js
index 60a3c65834..b3502b1385 100755
--- a/packages/subgraph/scripts/getAbi.js
+++ b/packages/subgraph/scripts/getAbi.js
@@ -2,27 +2,22 @@ const fs = require("fs");
const path = require("path");
const contracts = [
- "ConstantFlowAgreementV1",
"ERC20",
"IConstantFlowAgreementV1",
"IFlowNFTBase",
- "IResolver",
"ISuperTokenFactory",
"ISuperToken",
"ISuperfluid",
"Resolver",
"IInstantDistributionAgreementV1",
- "InstantDistributionAgreementV1",
+ "IGeneralDistributionAgreementV1",
+ "ISuperfluidPool",
"SuperfluidGovernanceBase",
- "SuperToken",
"TestToken",
"TOGA",
];
-const directoryPath = path.join(
- __dirname,
- "../../ethereum-contracts/build/truffle"
-);
+const directoryPath = path.join(__dirname, "../../ethereum-contracts/build/truffle");
fs.mkdir("abis/", (err) => {
if (err) return; //console.error(err);
diff --git a/packages/subgraph/src/mappingHelpers.ts b/packages/subgraph/src/mappingHelpers.ts
index 94380371e6..65632f86b6 100644
--- a/packages/subgraph/src/mappingHelpers.ts
+++ b/packages/subgraph/src/mappingHelpers.ts
@@ -7,6 +7,9 @@ import {
FlowOperator,
Index,
IndexSubscription,
+ Pool,
+ PoolDistributor,
+ PoolMember,
ResolverEntry,
Stream,
StreamRevision,
@@ -30,6 +33,10 @@ import {
getInitialTotalSupplyForSuperToken,
ZERO_ADDRESS,
handleTokenRPCCalls,
+ getPoolMemberID,
+ getPoolDistributorID,
+ getActiveStreamsDelta,
+ getClosedStreamsDelta,
} from "./utils";
import { SuperToken as SuperTokenTemplate } from "../generated/templates";
import { ISuperToken as SuperToken } from "../generated/templates/SuperToken/ISuperToken";
@@ -478,6 +485,146 @@ export function getOrInitResolverEntry(
return resolverEntry as ResolverEntry;
}
+export function getOrInitPool(event: ethereum.Event, poolId: string): Pool {
+ // get existing pool
+ let pool = Pool.load(poolId);
+
+ // init new pool if non-existent
+ if (pool == null) {
+ pool = new Pool(poolId);
+ pool.createdAtTimestamp = event.block.timestamp;
+ pool.createdAtBlockNumber = event.block.number;
+ pool.updatedAtTimestamp = event.block.timestamp;
+ pool.updatedAtBlockNumber = event.block.number;
+
+ pool.totalUnits = BIG_INT_ZERO;
+ pool.totalConnectedUnits = BIG_INT_ZERO;
+ pool.totalDisconnectedUnits = BIG_INT_ZERO;
+ pool.totalAmountInstantlyDistributedUntilUpdatedAt = BIG_INT_ZERO;
+ pool.totalAmountFlowedDistributedUntilUpdatedAt = BIG_INT_ZERO;
+ pool.totalAmountDistributedUntilUpdatedAt = BIG_INT_ZERO;
+ pool.totalMembers = 0;
+ pool.totalConnectedMembers = 0;
+ pool.totalDisconnectedMembers = 0;
+ pool.adjustmentFlowRate = BIG_INT_ZERO;
+ pool.flowRate = BIG_INT_ZERO;
+ pool.totalBuffer = BIG_INT_ZERO;
+ pool.token = ZERO_ADDRESS.toHex();
+ pool.admin = ZERO_ADDRESS.toHex();
+ }
+
+ return pool;
+}
+
+export function updatePoolTotalAmountFlowedAndDistributed(
+ event: ethereum.Event,
+ pool: Pool
+): Pool {
+ const timeDelta = event.block.timestamp.minus(pool.updatedAtTimestamp);
+ const amountFlowedSinceLastUpdate = pool.flowRate.times(timeDelta);
+
+ pool.updatedAtBlockNumber = event.block.number;
+ pool.updatedAtTimestamp = event.block.timestamp;
+
+ pool.totalAmountFlowedDistributedUntilUpdatedAt =
+ pool.totalAmountFlowedDistributedUntilUpdatedAt.plus(
+ amountFlowedSinceLastUpdate
+ );
+ pool.totalAmountDistributedUntilUpdatedAt =
+ pool.totalAmountDistributedUntilUpdatedAt.plus(
+ amountFlowedSinceLastUpdate
+ );
+
+ pool.save();
+
+ return pool;
+}
+
+export function getOrInitPoolMember(
+ event: ethereum.Event,
+ poolAddress: Address,
+ poolMemberAddress: Address
+): PoolMember {
+ const poolMemberID = getPoolMemberID(poolAddress, poolMemberAddress);
+ let poolMember = PoolMember.load(poolMemberID);
+
+ if (poolMember == null) {
+ poolMember = new PoolMember(poolMemberID);
+ poolMember.createdAtTimestamp = event.block.timestamp;
+ poolMember.createdAtBlockNumber = event.block.number;
+ poolMember.updatedAtTimestamp = event.block.timestamp;
+ poolMember.updatedAtBlockNumber = event.block.number;
+
+ poolMember.units = BIG_INT_ZERO;
+ poolMember.isConnected = false;
+ poolMember.totalAmountClaimed = BIG_INT_ZERO;
+
+ poolMember.account = poolMemberAddress.toHex();
+ poolMember.pool = poolAddress.toHex();
+ }
+
+ return poolMember;
+}
+
+export function getOrInitPoolDistributor(
+ event: ethereum.Event,
+ poolAddress: Address,
+ poolDistributorAddress: Address
+): PoolDistributor {
+ const poolDistributorID = getPoolDistributorID(
+ poolAddress,
+ poolDistributorAddress
+ );
+ let poolDistributor = PoolDistributor.load(poolDistributorID);
+
+ if (poolDistributor == null) {
+ poolDistributor = new PoolDistributor(poolDistributorID);
+ poolDistributor.createdAtTimestamp = event.block.timestamp;
+ poolDistributor.createdAtBlockNumber = event.block.number;
+ poolDistributor.updatedAtTimestamp = event.block.timestamp;
+ poolDistributor.updatedAtBlockNumber = event.block.number;
+
+ poolDistributor.totalAmountInstantlyDistributedUntilUpdatedAt =
+ BIG_INT_ZERO;
+ poolDistributor.totalAmountFlowedDistributedUntilUpdatedAt =
+ BIG_INT_ZERO;
+ poolDistributor.totalAmountDistributedUntilUpdatedAt = BIG_INT_ZERO;
+ poolDistributor.totalBuffer = BIG_INT_ZERO;
+ poolDistributor.flowRate = BIG_INT_ZERO;
+
+ poolDistributor.account = poolDistributorAddress.toHex();
+ poolDistributor.pool = poolAddress.toHex();
+ }
+
+ return poolDistributor;
+}
+export function updatePoolDistributorTotalAmountFlowedAndDistributed(
+ event: ethereum.Event,
+ poolDistributor: PoolDistributor
+): PoolDistributor {
+ const timeDelta = event.block.timestamp.minus(
+ poolDistributor.updatedAtTimestamp
+ );
+ const amountFlowedSinceLastUpdate =
+ poolDistributor.flowRate.times(timeDelta);
+
+ poolDistributor.updatedAtBlockNumber = event.block.number;
+ poolDistributor.updatedAtTimestamp = event.block.timestamp;
+
+ poolDistributor.totalAmountFlowedDistributedUntilUpdatedAt =
+ poolDistributor.totalAmountFlowedDistributedUntilUpdatedAt.plus(
+ amountFlowedSinceLastUpdate
+ );
+ poolDistributor.totalAmountDistributedUntilUpdatedAt =
+ poolDistributor.totalAmountDistributedUntilUpdatedAt.plus(
+ amountFlowedSinceLastUpdate
+ );
+
+ poolDistributor.save();
+
+ return poolDistributor;
+}
+
/**************************************************************************
* Aggregate initializer functions
*************************************************************************/
@@ -494,25 +641,59 @@ if (accountTokenSnapshot == null) {
accountTokenSnapshot.updatedAtTimestamp = block.timestamp;
accountTokenSnapshot.updatedAtBlockNumber = block.number;
accountTokenSnapshot.totalNumberOfActiveStreams = 0;
+ accountTokenSnapshot.totalCFANumberOfActiveStreams = 0;
+ accountTokenSnapshot.totalGDANumberOfActiveStreams = 0;
accountTokenSnapshot.activeIncomingStreamCount = 0;
+ accountTokenSnapshot.activeCFAIncomingStreamCount = 0;
+ accountTokenSnapshot.activeGDAIncomingStreamCount = 0;
accountTokenSnapshot.activeOutgoingStreamCount = 0;
+ accountTokenSnapshot.activeCFAOutgoingStreamCount = 0;
+ accountTokenSnapshot.activeGDAOutgoingStreamCount = 0;
accountTokenSnapshot.inactiveIncomingStreamCount = 0;
+ accountTokenSnapshot.inactiveCFAIncomingStreamCount = 0;
+ accountTokenSnapshot.inactiveGDAIncomingStreamCount = 0;
accountTokenSnapshot.inactiveOutgoingStreamCount = 0;
+ accountTokenSnapshot.inactiveCFAOutgoingStreamCount = 0;
+ accountTokenSnapshot.inactiveGDAOutgoingStreamCount = 0;
accountTokenSnapshot.totalNumberOfClosedStreams = 0;
- accountTokenSnapshot.totalSubscriptionsWithUnits = 0;
+ accountTokenSnapshot.totalCFANumberOfClosedStreams = 0;
+ accountTokenSnapshot.totalGDANumberOfClosedStreams = 0;
accountTokenSnapshot.isLiquidationEstimateOptimistic = false;
+ accountTokenSnapshot.totalSubscriptionsWithUnits = 0;
accountTokenSnapshot.totalApprovedSubscriptions = 0;
+ accountTokenSnapshot.totalMembershipsWithUnits = 0;
+ accountTokenSnapshot.totalConnectedMemberships = 0;
accountTokenSnapshot.balanceUntilUpdatedAt = BIG_INT_ZERO;
accountTokenSnapshot.totalNetFlowRate = BIG_INT_ZERO;
+ accountTokenSnapshot.totalCFANetFlowRate = BIG_INT_ZERO;
+ accountTokenSnapshot.totalGDANetFlowRate = BIG_INT_ZERO;
accountTokenSnapshot.totalInflowRate = BIG_INT_ZERO;
+ accountTokenSnapshot.totalCFAInflowRate = BIG_INT_ZERO;
+ accountTokenSnapshot.totalGDAInflowRate = BIG_INT_ZERO;
accountTokenSnapshot.totalOutflowRate = BIG_INT_ZERO;
+ accountTokenSnapshot.totalCFAOutflowRate = BIG_INT_ZERO;
+ accountTokenSnapshot.totalGDAOutflowRate = BIG_INT_ZERO;
accountTokenSnapshot.totalAmountStreamedInUntilUpdatedAt = BIG_INT_ZERO;
+ accountTokenSnapshot.totalCFAAmountStreamedInUntilUpdatedAt =
+ BIG_INT_ZERO;
+ accountTokenSnapshot.totalGDAAmountStreamedInUntilUpdatedAt =
+ BIG_INT_ZERO;
accountTokenSnapshot.totalAmountStreamedOutUntilUpdatedAt =
BIG_INT_ZERO;
+ accountTokenSnapshot.totalCFAAmountStreamedOutUntilUpdatedAt =
+ BIG_INT_ZERO;
+ accountTokenSnapshot.totalGDAAmountStreamedOutUntilUpdatedAt =
+ BIG_INT_ZERO;
accountTokenSnapshot.totalAmountStreamedUntilUpdatedAt = BIG_INT_ZERO;
+ accountTokenSnapshot.totalCFAAmountStreamedUntilUpdatedAt =
+ BIG_INT_ZERO;
+ accountTokenSnapshot.totalGDAAmountStreamedUntilUpdatedAt =
+ BIG_INT_ZERO;
accountTokenSnapshot.totalAmountTransferredUntilUpdatedAt =
BIG_INT_ZERO;
accountTokenSnapshot.totalDeposit = BIG_INT_ZERO;
+ accountTokenSnapshot.totalCFADeposit = BIG_INT_ZERO;
+ accountTokenSnapshot.totalGDADeposit = BIG_INT_ZERO;
accountTokenSnapshot.maybeCriticalAtTimestamp = null;
accountTokenSnapshot.account = accountAddress.toHex();
accountTokenSnapshot.token = tokenAddress.toHex();
@@ -523,7 +704,7 @@ if (accountTokenSnapshot == null) {
tokenStatistic.save();
}
-
+
return accountTokenSnapshot as AccountTokenSnapshot;
}
@@ -536,14 +717,14 @@ export function _createAccountTokenSnapshotLogEntity(
if (accountAddress.equals(ZERO_ADDRESS)) {
return;
}
- const accountTokenSnapshot = getOrInitAccountTokenSnapshot(
+ const ats = getOrInitAccountTokenSnapshot(
accountAddress,
tokenAddress,
event.block
);
// Transaction
const atsLog = new AccountTokenSnapshotLog(
- createLogID("ATSLog", accountTokenSnapshot.id, event)
+ createLogID("ATSLog", ats.id, event)
);
atsLog.transactionHash = event.transaction.hash;
atsLog.timestamp = event.block.timestamp;
@@ -552,40 +733,59 @@ export function _createAccountTokenSnapshotLogEntity(
atsLog.logIndex = event.logIndex;
atsLog.triggeredByEventName = eventName;
// Account token snapshot state
- atsLog.totalNumberOfActiveStreams =
- accountTokenSnapshot.totalNumberOfActiveStreams;
- atsLog.activeIncomingStreamCount =
- accountTokenSnapshot.activeIncomingStreamCount;
- atsLog.activeOutgoingStreamCount =
- accountTokenSnapshot.activeOutgoingStreamCount;
- atsLog.totalNumberOfClosedStreams =
- accountTokenSnapshot.totalNumberOfClosedStreams;
- atsLog.inactiveIncomingStreamCount =
- accountTokenSnapshot.inactiveIncomingStreamCount;
- atsLog.inactiveOutgoingStreamCount =
- accountTokenSnapshot.inactiveOutgoingStreamCount;
- atsLog.totalSubscriptionsWithUnits =
- accountTokenSnapshot.totalSubscriptionsWithUnits;
- atsLog.totalApprovedSubscriptions =
- accountTokenSnapshot.totalApprovedSubscriptions;
- atsLog.balance = accountTokenSnapshot.balanceUntilUpdatedAt;
- atsLog.totalNetFlowRate = accountTokenSnapshot.totalNetFlowRate;
- atsLog.totalInflowRate = accountTokenSnapshot.totalInflowRate;
- atsLog.totalOutflowRate = accountTokenSnapshot.totalOutflowRate;
- atsLog.totalAmountStreamed =
- accountTokenSnapshot.totalAmountStreamedUntilUpdatedAt;
- atsLog.totalAmountStreamedIn =
- accountTokenSnapshot.totalAmountStreamedInUntilUpdatedAt;
- atsLog.totalAmountStreamedOut =
- accountTokenSnapshot.totalAmountStreamedOutUntilUpdatedAt;
- atsLog.totalAmountTransferred =
- accountTokenSnapshot.totalAmountTransferredUntilUpdatedAt;
- atsLog.totalDeposit = accountTokenSnapshot.totalDeposit;
- atsLog.maybeCriticalAtTimestamp =
- accountTokenSnapshot.maybeCriticalAtTimestamp;
- atsLog.account = accountTokenSnapshot.account;
- atsLog.token = accountTokenSnapshot.token;
- atsLog.accountTokenSnapshot = accountTokenSnapshot.id;
+ atsLog.totalNumberOfActiveStreams = ats.totalNumberOfActiveStreams;
+ atsLog.totalCFANumberOfActiveStreams = ats.totalCFANumberOfActiveStreams;
+ atsLog.totalGDANumberOfActiveStreams = ats.totalGDANumberOfActiveStreams;
+ atsLog.activeIncomingStreamCount = ats.activeIncomingStreamCount;
+ atsLog.activeCFAIncomingStreamCount = ats.activeCFAIncomingStreamCount;
+ atsLog.activeGDAIncomingStreamCount = ats.activeGDAIncomingStreamCount;
+ atsLog.activeOutgoingStreamCount = ats.activeOutgoingStreamCount;
+ atsLog.activeCFAOutgoingStreamCount = ats.activeCFAOutgoingStreamCount;
+ atsLog.activeGDAOutgoingStreamCount = ats.activeGDAOutgoingStreamCount;
+ atsLog.totalNumberOfClosedStreams = ats.totalNumberOfClosedStreams;
+ atsLog.totalCFANumberOfClosedStreams = ats.totalCFANumberOfClosedStreams;
+ atsLog.totalGDANumberOfClosedStreams = ats.totalGDANumberOfClosedStreams;
+ atsLog.inactiveIncomingStreamCount = ats.inactiveIncomingStreamCount;
+ atsLog.inactiveCFAIncomingStreamCount = ats.inactiveCFAIncomingStreamCount;
+ atsLog.inactiveGDAIncomingStreamCount = ats.inactiveGDAIncomingStreamCount;
+ atsLog.inactiveOutgoingStreamCount = ats.inactiveOutgoingStreamCount;
+ atsLog.inactiveCFAOutgoingStreamCount = ats.inactiveCFAOutgoingStreamCount;
+ atsLog.inactiveGDAOutgoingStreamCount = ats.inactiveGDAOutgoingStreamCount;
+ atsLog.totalSubscriptionsWithUnits = ats.totalSubscriptionsWithUnits;
+ atsLog.totalApprovedSubscriptions = ats.totalApprovedSubscriptions;
+ atsLog.totalMembershipsWithUnits = ats.totalMembershipsWithUnits;
+ atsLog.totalConnectedMemberships = ats.totalConnectedMemberships;
+ atsLog.balance = ats.balanceUntilUpdatedAt;
+ atsLog.totalNetFlowRate = ats.totalNetFlowRate;
+ atsLog.totalCFANetFlowRate = ats.totalCFANetFlowRate;
+ atsLog.totalGDANetFlowRate = ats.totalGDANetFlowRate;
+ atsLog.totalInflowRate = ats.totalInflowRate;
+ atsLog.totalCFAInflowRate = ats.totalCFAInflowRate;
+ atsLog.totalGDAInflowRate = ats.totalGDAInflowRate;
+ atsLog.totalOutflowRate = ats.totalOutflowRate;
+ atsLog.totalCFAOutflowRate = ats.totalCFAOutflowRate;
+ atsLog.totalGDAOutflowRate = ats.totalGDAOutflowRate;
+ atsLog.totalAmountStreamed = ats.totalAmountStreamedUntilUpdatedAt;
+ atsLog.totalCFAAmountStreamed = ats.totalCFAAmountStreamedUntilUpdatedAt;
+ atsLog.totalGDAAmountStreamed = ats.totalGDAAmountStreamedUntilUpdatedAt;
+ atsLog.totalAmountStreamedIn = ats.totalAmountStreamedInUntilUpdatedAt;
+ atsLog.totalCFAAmountStreamedIn =
+ ats.totalCFAAmountStreamedInUntilUpdatedAt;
+ atsLog.totalAmountStreamedOut = ats.totalAmountStreamedOutUntilUpdatedAt;
+ atsLog.totalGDAAmountStreamedIn =
+ ats.totalGDAAmountStreamedInUntilUpdatedAt;
+ atsLog.totalCFAAmountStreamedOut =
+ ats.totalCFAAmountStreamedOutUntilUpdatedAt;
+ atsLog.totalGDAAmountStreamedOut =
+ ats.totalGDAAmountStreamedOutUntilUpdatedAt;
+ atsLog.totalAmountTransferred = ats.totalAmountTransferredUntilUpdatedAt;
+ atsLog.totalDeposit = ats.totalDeposit;
+ atsLog.totalCFADeposit = ats.totalCFADeposit;
+ atsLog.totalGDADeposit = ats.totalGDADeposit;
+ atsLog.maybeCriticalAtTimestamp = ats.maybeCriticalAtTimestamp;
+ atsLog.account = ats.account;
+ atsLog.token = ats.token;
+ atsLog.accountTokenSnapshot = ats.id;
atsLog.save();
}
@@ -600,18 +800,32 @@ export function getOrInitTokenStatistic(
tokenStatistic.updatedAtTimestamp = block.timestamp;
tokenStatistic.updatedAtBlockNumber = block.number;
tokenStatistic.totalNumberOfActiveStreams = 0;
+ tokenStatistic.totalCFANumberOfActiveStreams = 0;
+ tokenStatistic.totalGDANumberOfActiveStreams = 0;
tokenStatistic.totalNumberOfClosedStreams = 0;
+ tokenStatistic.totalCFANumberOfClosedStreams = 0;
+ tokenStatistic.totalGDANumberOfClosedStreams = 0;
tokenStatistic.totalNumberOfIndexes = 0;
tokenStatistic.totalNumberOfActiveIndexes = 0;
tokenStatistic.totalSubscriptionsWithUnits = 0;
tokenStatistic.totalApprovedSubscriptions = 0;
+ tokenStatistic.totalNumberOfPools = 0;
+ tokenStatistic.totalNumberOfActivePools = 0;
+ tokenStatistic.totalMembershipsWithUnits = 0;
+ tokenStatistic.totalConnectedMemberships = 0;
tokenStatistic.totalOutflowRate = BIG_INT_ZERO;
+ tokenStatistic.totalCFAOutflowRate = BIG_INT_ZERO;
+ tokenStatistic.totalGDAOutflowRate = BIG_INT_ZERO;
tokenStatistic.totalNumberOfAccounts = 0;
tokenStatistic.totalAmountStreamedUntilUpdatedAt = BIG_INT_ZERO;
+ tokenStatistic.totalCFAAmountStreamedUntilUpdatedAt = BIG_INT_ZERO;
+ tokenStatistic.totalGDAAmountStreamedUntilUpdatedAt = BIG_INT_ZERO;
tokenStatistic.totalAmountTransferredUntilUpdatedAt = BIG_INT_ZERO;
tokenStatistic.totalAmountDistributedUntilUpdatedAt = BIG_INT_ZERO;
tokenStatistic.totalSupply = BIG_INT_ZERO;
tokenStatistic.totalDeposit = BIG_INT_ZERO;
+ tokenStatistic.totalCFADeposit = BIG_INT_ZERO;
+ tokenStatistic.totalGDADeposit = BIG_INT_ZERO;
tokenStatistic.totalNumberOfHolders = 0;
tokenStatistic.token = tokenId;
tokenStatistic.save();
@@ -640,8 +854,16 @@ export function _createTokenStatisticLogEntity(
// Token Statistic State
tokenStatisticLog.totalNumberOfActiveStreams =
tokenStatistic.totalNumberOfActiveStreams;
+ tokenStatisticLog.totalCFANumberOfActiveStreams =
+ tokenStatistic.totalCFANumberOfActiveStreams;
+ tokenStatisticLog.totalGDANumberOfActiveStreams =
+ tokenStatistic.totalGDANumberOfActiveStreams;
tokenStatisticLog.totalNumberOfClosedStreams =
tokenStatistic.totalNumberOfClosedStreams;
+ tokenStatisticLog.totalCFANumberOfClosedStreams =
+ tokenStatistic.totalCFANumberOfClosedStreams;
+ tokenStatisticLog.totalGDANumberOfClosedStreams =
+ tokenStatistic.totalGDANumberOfClosedStreams;
tokenStatisticLog.totalNumberOfIndexes =
tokenStatistic.totalNumberOfIndexes;
tokenStatisticLog.totalNumberOfActiveIndexes =
@@ -650,10 +872,25 @@ export function _createTokenStatisticLogEntity(
tokenStatistic.totalSubscriptionsWithUnits;
tokenStatisticLog.totalApprovedSubscriptions =
tokenStatistic.totalApprovedSubscriptions;
+ tokenStatisticLog.totalNumberOfPools = tokenStatistic.totalNumberOfPools;
+ tokenStatisticLog.totalNumberOfActivePools =
+ tokenStatistic.totalNumberOfActivePools;
+ tokenStatisticLog.totalMembershipsWithUnits =
+ tokenStatistic.totalMembershipsWithUnits;
+ tokenStatisticLog.totalConnectedMemberships =
+ tokenStatistic.totalConnectedMemberships;
tokenStatisticLog.totalDeposit = tokenStatistic.totalDeposit;
+ tokenStatisticLog.totalCFADeposit = tokenStatistic.totalCFADeposit;
+ tokenStatisticLog.totalGDADeposit = tokenStatistic.totalGDADeposit;
tokenStatisticLog.totalOutflowRate = tokenStatistic.totalOutflowRate;
+ tokenStatisticLog.totalCFAOutflowRate = tokenStatistic.totalCFAOutflowRate;
+ tokenStatisticLog.totalGDAOutflowRate = tokenStatistic.totalGDAOutflowRate;
tokenStatisticLog.totalAmountStreamed =
tokenStatistic.totalAmountStreamedUntilUpdatedAt;
+ tokenStatisticLog.totalCFAAmountStreamed =
+ tokenStatistic.totalCFAAmountStreamedUntilUpdatedAt;
+ tokenStatisticLog.totalGDAAmountStreamed =
+ tokenStatistic.totalGDAAmountStreamedUntilUpdatedAt;
tokenStatisticLog.totalAmountTransferred =
tokenStatistic.totalAmountTransferredUntilUpdatedAt;
tokenStatisticLog.totalAmountDistributed =
@@ -688,9 +925,9 @@ export function updateAccountUpdatedAt(
*************************************************************************/
/**
- * Updates ATS and TokenStats IDA Subscriptions data.
+ * Updates ATS and TokenStats distribution agreement data (IDA or GDA).
*/
-export function updateAggregateIDASubscriptionsData(
+export function updateAggregateDistributionAgreementData(
accountAddress: Address,
tokenAddress: Address,
subscriptionWithUnitsExists: boolean,
@@ -699,15 +936,18 @@ export function updateAggregateIDASubscriptionsData(
isRevokingSubscription: boolean,
isDeletingSubscription: boolean,
isApproving: boolean,
- block: ethereum.Block
+ block: ethereum.Block,
+ isIDA: boolean
): void {
- const tokenStatistic = getOrInitTokenStatistic(tokenAddress, block);
const totalSubscriptionWithUnitsDelta =
+ // we only decrement if the subscription exists and we are deleting
isDeletingSubscription && subscriptionWithUnitsExists
? -1
- : isIncrementingSubWithUnits && !subscriptionWithUnitsExists
+ : // we only increment if the subscription does not exist and we are incrementing
+ isIncrementingSubWithUnits && !subscriptionWithUnitsExists
? 1
: 0;
+
const totalApprovedSubscriptionsDelta = isApproving
? 1
: isRevokingSubscription && subscriptionApproved
@@ -721,28 +961,47 @@ export function updateAggregateIDASubscriptionsData(
block
);
- accountTokenSnapshot.totalSubscriptionsWithUnits =
- accountTokenSnapshot.totalSubscriptionsWithUnits +
- totalSubscriptionWithUnitsDelta;
+ if (isIDA) {
+ accountTokenSnapshot.totalSubscriptionsWithUnits =
+ accountTokenSnapshot.totalSubscriptionsWithUnits +
+ totalSubscriptionWithUnitsDelta;
+ accountTokenSnapshot.totalApprovedSubscriptions =
+ accountTokenSnapshot.totalApprovedSubscriptions +
+ totalApprovedSubscriptionsDelta;
+ } else {
+ accountTokenSnapshot.totalMembershipsWithUnits =
+ accountTokenSnapshot.totalMembershipsWithUnits +
+ totalSubscriptionWithUnitsDelta;
+ accountTokenSnapshot.totalConnectedMemberships =
+ accountTokenSnapshot.totalConnectedMemberships +
+ totalApprovedSubscriptionsDelta;
+ }
+
accountTokenSnapshot.isLiquidationEstimateOptimistic =
- accountTokenSnapshot.totalSubscriptionsWithUnits > 0;
- accountTokenSnapshot.totalApprovedSubscriptions =
- accountTokenSnapshot.totalApprovedSubscriptions +
- totalApprovedSubscriptionsDelta;
+ accountTokenSnapshot.totalSubscriptionsWithUnits > 0 ||
+ accountTokenSnapshot.totalMembershipsWithUnits > 0;
accountTokenSnapshot.updatedAtTimestamp = block.timestamp;
accountTokenSnapshot.updatedAtBlockNumber = block.number;
-
accountTokenSnapshot.save();
- // update tokenStatistic Subscription data
- tokenStatistic.totalSubscriptionsWithUnits =
- tokenStatistic.totalSubscriptionsWithUnits +
- totalSubscriptionWithUnitsDelta;
- accountTokenSnapshot.isLiquidationEstimateOptimistic =
- accountTokenSnapshot.totalSubscriptionsWithUnits > 0;
- tokenStatistic.totalApprovedSubscriptions =
- tokenStatistic.totalApprovedSubscriptions +
- totalApprovedSubscriptionsDelta;
+ // update TokenStatistic entity
+ const tokenStatistic = getOrInitTokenStatistic(tokenAddress, block);
+ if (isIDA) {
+ tokenStatistic.totalSubscriptionsWithUnits =
+ tokenStatistic.totalSubscriptionsWithUnits +
+ totalSubscriptionWithUnitsDelta;
+ tokenStatistic.totalApprovedSubscriptions =
+ tokenStatistic.totalApprovedSubscriptions +
+ totalApprovedSubscriptionsDelta;
+ } else {
+ tokenStatistic.totalMembershipsWithUnits =
+ tokenStatistic.totalMembershipsWithUnits +
+ totalSubscriptionWithUnitsDelta;
+ tokenStatistic.totalConnectedMemberships =
+ tokenStatistic.totalConnectedMemberships +
+ totalApprovedSubscriptionsDelta;
+ }
+
tokenStatistic.updatedAtTimestamp = block.timestamp;
tokenStatistic.updatedAtBlockNumber = block.number;
@@ -821,20 +1080,21 @@ export function updateATSStreamedAndBalanceUntilUpdatedAt(
);
const balanceUntilUpdatedAtBeforeUpdate = accountTokenSnapshot.balanceUntilUpdatedAt;
-
- const amountStreamedSinceLastUpdatedAt =
+
+ //////////////// CFA + GDA streamed amounts ////////////////
+ const totalAmountStreamedSinceLastUpdatedAt =
getAmountStreamedSinceLastUpdatedAt(
block.timestamp,
accountTokenSnapshot.updatedAtTimestamp,
accountTokenSnapshot.totalNetFlowRate
);
- const amountStreamedInSinceLastUpdatedAt =
+ const totalAmountStreamedInSinceLastUpdatedAt =
getAmountStreamedSinceLastUpdatedAt(
block.timestamp,
accountTokenSnapshot.updatedAtTimestamp,
accountTokenSnapshot.totalInflowRate
);
- const amountStreamedOutSinceLastUpdatedAt =
+ const totalAmountStreamedOutSinceLastUpdatedAt =
getAmountStreamedSinceLastUpdatedAt(
block.timestamp,
accountTokenSnapshot.updatedAtTimestamp,
@@ -844,26 +1104,19 @@ export function updateATSStreamedAndBalanceUntilUpdatedAt(
// update the totalStreamedUntilUpdatedAt (net)
accountTokenSnapshot.totalAmountStreamedUntilUpdatedAt =
accountTokenSnapshot.totalAmountStreamedUntilUpdatedAt.plus(
- amountStreamedSinceLastUpdatedAt
+ totalAmountStreamedSinceLastUpdatedAt
);
// update the totalStreamedUntilUpdatedAt (in)
accountTokenSnapshot.totalAmountStreamedInUntilUpdatedAt =
accountTokenSnapshot.totalAmountStreamedInUntilUpdatedAt.plus(
- amountStreamedInSinceLastUpdatedAt
+ totalAmountStreamedInSinceLastUpdatedAt
);
// update the totalStreamedUntilUpdatedAt (out)
accountTokenSnapshot.totalAmountStreamedOutUntilUpdatedAt =
accountTokenSnapshot.totalAmountStreamedOutUntilUpdatedAt.plus(
- amountStreamedOutSinceLastUpdatedAt
- );
-
- const netAmountStreamedInSinceLastUpdatedAt =
- getAmountStreamedSinceLastUpdatedAt(
- block.timestamp,
- accountTokenSnapshot.updatedAtTimestamp,
- accountTokenSnapshot.totalNetFlowRate
+ totalAmountStreamedOutSinceLastUpdatedAt
);
// update the balance via external call if account has any subscription with more than 0 units
@@ -873,9 +1126,86 @@ export function updateATSStreamedAndBalanceUntilUpdatedAt(
accountTokenSnapshot,
block,
balanceDelta
- ? balanceDelta.plus(netAmountStreamedInSinceLastUpdatedAt)
+ ? balanceDelta.plus(totalAmountStreamedSinceLastUpdatedAt)
: balanceDelta
);
+
+ //////////////// CFA streamed amounts ////////////////
+ const totalCFAAmountStreamedSinceLastUpdatedAt =
+ getAmountStreamedSinceLastUpdatedAt(
+ block.timestamp,
+ accountTokenSnapshot.updatedAtTimestamp,
+ accountTokenSnapshot.totalCFANetFlowRate
+ );
+ const totalCFAAmountStreamedInSinceLastUpdatedAt =
+ getAmountStreamedSinceLastUpdatedAt(
+ block.timestamp,
+ accountTokenSnapshot.updatedAtTimestamp,
+ accountTokenSnapshot.totalCFAInflowRate
+ );
+ const totalCFAAmountStreamedOutSinceLastUpdatedAt =
+ getAmountStreamedSinceLastUpdatedAt(
+ block.timestamp,
+ accountTokenSnapshot.updatedAtTimestamp,
+ accountTokenSnapshot.totalCFAOutflowRate
+ );
+
+ // update the totalCFAStreamedUntilUpdatedAt (net)
+ accountTokenSnapshot.totalCFAAmountStreamedUntilUpdatedAt =
+ accountTokenSnapshot.totalCFAAmountStreamedUntilUpdatedAt.plus(
+ totalCFAAmountStreamedSinceLastUpdatedAt
+ );
+
+ // update the totalCFAStreamedUntilUpdatedAt (in)
+ accountTokenSnapshot.totalCFAAmountStreamedInUntilUpdatedAt =
+ accountTokenSnapshot.totalCFAAmountStreamedInUntilUpdatedAt.plus(
+ totalCFAAmountStreamedInSinceLastUpdatedAt
+ );
+
+ // update the totalCFAStreamedUntilUpdatedAt (out)
+ accountTokenSnapshot.totalCFAAmountStreamedOutUntilUpdatedAt =
+ accountTokenSnapshot.totalCFAAmountStreamedOutUntilUpdatedAt.plus(
+ totalCFAAmountStreamedOutSinceLastUpdatedAt
+ );
+
+ //////////////// GDA streamed amounts ////////////////
+ const totalGDAAmountStreamedSinceLastUpdatedAt =
+ getAmountStreamedSinceLastUpdatedAt(
+ block.timestamp,
+ accountTokenSnapshot.updatedAtTimestamp,
+ accountTokenSnapshot.totalGDANetFlowRate
+ );
+ const totalGDAAmountStreamedInSinceLastUpdatedAt =
+ getAmountStreamedSinceLastUpdatedAt(
+ block.timestamp,
+ accountTokenSnapshot.updatedAtTimestamp,
+ accountTokenSnapshot.totalGDAInflowRate
+ );
+ const totalGDAAmountStreamedOutSinceLastUpdatedAt =
+ getAmountStreamedSinceLastUpdatedAt(
+ block.timestamp,
+ accountTokenSnapshot.updatedAtTimestamp,
+ accountTokenSnapshot.totalGDAOutflowRate
+ );
+
+ // update the totalGDAStreamedUntilUpdatedAt (net)
+ accountTokenSnapshot.totalGDAAmountStreamedUntilUpdatedAt =
+ accountTokenSnapshot.totalGDAAmountStreamedUntilUpdatedAt.plus(
+ totalGDAAmountStreamedSinceLastUpdatedAt
+ );
+
+ // update the totalGDAStreamedUntilUpdatedAt (in)
+ accountTokenSnapshot.totalGDAAmountStreamedInUntilUpdatedAt =
+ accountTokenSnapshot.totalGDAAmountStreamedInUntilUpdatedAt.plus(
+ totalGDAAmountStreamedInSinceLastUpdatedAt
+ );
+
+ // update the totalGDAStreamedUntilUpdatedAt (out)
+ accountTokenSnapshot.totalGDAAmountStreamedOutUntilUpdatedAt =
+ accountTokenSnapshot.totalGDAAmountStreamedOutUntilUpdatedAt.plus(
+ totalGDAAmountStreamedOutSinceLastUpdatedAt
+ );
+
accountTokenSnapshot.save();
const balanceUntilUpdatedAtAfterUpdate = accountTokenSnapshot.balanceUntilUpdatedAt;
@@ -908,7 +1238,9 @@ export function updateATSStreamedAndBalanceUntilUpdatedAt(
}
/**
- * This function should always be called with updateATSStreamedAndBalanceUntilUpdatedAt
+ * This function updates the token stats streamed amounts as well as the
+ * updatedAtTimestamp and updatedAtBlockNumber.
+ * It should always be called with updateATSStreamedAndBalanceUntilUpdatedAt.
* @param tokenAddress
* @param block
*/
@@ -917,6 +1249,8 @@ export function updateTokenStatsStreamedUntilUpdatedAt(
block: ethereum.Block
): void {
const tokenStats = getOrInitTokenStatistic(tokenAddress, block);
+
+ //// CFA + GDA streamed amounts ////
const amountStreamedSinceLastUpdatedAt =
getAmountStreamedSinceLastUpdatedAt(
block.timestamp,
@@ -927,36 +1261,52 @@ export function updateTokenStatsStreamedUntilUpdatedAt(
tokenStats.totalAmountStreamedUntilUpdatedAt.plus(
amountStreamedSinceLastUpdatedAt
);
+
+ //// CFA streamed amounts ////
+ const cfaAmountStreamedSinceLastUpdatedAt =
+ getAmountStreamedSinceLastUpdatedAt(
+ block.timestamp,
+ tokenStats.updatedAtTimestamp,
+ tokenStats.totalCFAOutflowRate
+ );
+ tokenStats.totalCFAAmountStreamedUntilUpdatedAt =
+ tokenStats.totalCFAAmountStreamedUntilUpdatedAt.plus(
+ cfaAmountStreamedSinceLastUpdatedAt
+ );
+
+ //// GDA streamed amounts ////
+ const gdaAmountStreamedSinceLastUpdatedAt =
+ getAmountStreamedSinceLastUpdatedAt(
+ block.timestamp,
+ tokenStats.updatedAtTimestamp,
+ tokenStats.totalGDAOutflowRate
+ );
+ tokenStats.totalGDAAmountStreamedUntilUpdatedAt =
+ tokenStats.totalGDAAmountStreamedUntilUpdatedAt.plus(
+ gdaAmountStreamedSinceLastUpdatedAt
+ );
+
tokenStats.updatedAtTimestamp = block.timestamp;
tokenStats.updatedAtBlockNumber = block.number;
tokenStats.save();
}
-/**
- * Updates TokenStatistic and AccountTokenSnapshot countable stream
- * data. Must be called after updating streamed amount data for the
- * AccountTokenSnapshot entities.
- */
-export function updateAggregateEntitiesStreamData(
- senderAddress: Address,
- receiverAddress: Address,
+export function updateTokenStatisticStreamData(
tokenAddress: Address,
newFlowRate: BigInt,
flowRateDelta: BigInt,
depositDelta: BigInt,
isCreate: boolean,
isDelete: boolean,
+ isCFA: boolean,
block: ethereum.Block
): void {
const tokenStatistic = getOrInitTokenStatistic(tokenAddress, block);
- const totalNumberOfActiveStreamsDelta = isCreate ? 1 : isDelete ? -1 : 0;
- const totalNumberOfClosedStreamsDelta = isDelete ? 1 : 0;
- const tokenStatsAmountStreamedSinceLastUpdate =
- getAmountStreamedSinceLastUpdatedAt(
- block.timestamp,
- tokenStatistic.updatedAtTimestamp,
- tokenStatistic.totalOutflowRate
- );
+ const totalNumberOfActiveStreamsDelta = getActiveStreamsDelta(
+ isCreate,
+ isDelete
+ );
+ const totalNumberOfClosedStreamsDelta = getClosedStreamsDelta(isDelete);
// the outflow rate should never go below 0.
tokenStatistic.totalOutflowRate = tokenStatistic.totalOutflowRate
@@ -973,16 +1323,68 @@ export function updateAggregateEntitiesStreamData(
tokenStatistic.totalNumberOfClosedStreams +
totalNumberOfClosedStreamsDelta;
- tokenStatistic.totalAmountStreamedUntilUpdatedAt =
- tokenStatistic.totalAmountStreamedUntilUpdatedAt.plus(
- tokenStatsAmountStreamedSinceLastUpdate
- );
- tokenStatistic.updatedAtTimestamp = block.timestamp;
- tokenStatistic.updatedAtBlockNumber = block.number;
-
tokenStatistic.totalDeposit =
tokenStatistic.totalDeposit.plus(depositDelta);
+ if (isCFA) {
+ tokenStatistic.totalCFAOutflowRate = tokenStatistic.totalCFAOutflowRate
+ .plus(flowRateDelta)
+ .lt(BIG_INT_ZERO)
+ ? newFlowRate
+ : tokenStatistic.totalCFAOutflowRate.plus(flowRateDelta);
+
+ tokenStatistic.totalCFANumberOfActiveStreams =
+ tokenStatistic.totalCFANumberOfActiveStreams +
+ totalNumberOfActiveStreamsDelta;
+
+ tokenStatistic.totalCFANumberOfClosedStreams =
+ tokenStatistic.totalCFANumberOfClosedStreams +
+ totalNumberOfClosedStreamsDelta;
+
+ tokenStatistic.totalCFADeposit =
+ tokenStatistic.totalCFADeposit.plus(depositDelta);
+ } else {
+ tokenStatistic.totalGDAOutflowRate = tokenStatistic.totalGDAOutflowRate
+ .plus(flowRateDelta)
+ .lt(BIG_INT_ZERO)
+ ? newFlowRate
+ : tokenStatistic.totalGDAOutflowRate.plus(flowRateDelta);
+
+ tokenStatistic.totalGDANumberOfActiveStreams =
+ tokenStatistic.totalGDANumberOfActiveStreams +
+ totalNumberOfActiveStreamsDelta;
+
+ tokenStatistic.totalGDANumberOfClosedStreams =
+ tokenStatistic.totalGDANumberOfClosedStreams +
+ totalNumberOfClosedStreamsDelta;
+
+ tokenStatistic.totalGDADeposit =
+ tokenStatistic.totalGDADeposit.plus(depositDelta);
+ }
+ tokenStatistic.save();
+}
+
+/**
+ * Updates ATS stream counter data.
+ * Must be called after updating streamed amount data for the
+ * AccountTokenSnapshot entities.
+ */
+export function updateSenderATSStreamData(
+ senderAddress: Address,
+ tokenAddress: Address,
+ newFlowRate: BigInt,
+ flowRateDelta: BigInt,
+ depositDelta: BigInt,
+ isCreate: boolean,
+ isDelete: boolean,
+ isCFA: boolean,
+ block: ethereum.Block
+): void {
+ const totalNumberOfActiveStreamsDelta = getActiveStreamsDelta(
+ isCreate,
+ isDelete
+ );
+ const totalNumberOfClosedStreamsDelta = getClosedStreamsDelta(isDelete);
const senderATS = getOrInitAccountTokenSnapshot(
senderAddress,
tokenAddress,
@@ -1015,6 +1417,83 @@ export function updateAggregateEntitiesStreamData(
senderATS.maybeCriticalAtTimestamp
);
+ if (isCFA) {
+ senderATS.totalCFANetFlowRate =
+ senderATS.totalCFANetFlowRate.minus(flowRateDelta);
+
+ // the outflow rate should never go below 0.
+ senderATS.totalCFAOutflowRate = senderATS.totalCFAOutflowRate
+ .plus(flowRateDelta)
+ .lt(BIG_INT_ZERO)
+ ? newFlowRate
+ : senderATS.totalCFAOutflowRate.plus(flowRateDelta);
+
+ senderATS.totalCFANumberOfActiveStreams =
+ senderATS.totalCFANumberOfActiveStreams +
+ totalNumberOfActiveStreamsDelta;
+ senderATS.activeCFAOutgoingStreamCount =
+ senderATS.activeCFAOutgoingStreamCount +
+ totalNumberOfActiveStreamsDelta;
+ senderATS.inactiveCFAOutgoingStreamCount =
+ senderATS.inactiveCFAOutgoingStreamCount +
+ totalNumberOfClosedStreamsDelta;
+
+ senderATS.totalCFANumberOfClosedStreams =
+ senderATS.totalCFANumberOfClosedStreams +
+ totalNumberOfClosedStreamsDelta;
+ senderATS.totalCFADeposit =
+ senderATS.totalCFADeposit.plus(depositDelta);
+ } else {
+ senderATS.totalGDANetFlowRate =
+ senderATS.totalGDANetFlowRate.minus(flowRateDelta);
+
+ // the outflow rate should never go below 0.
+ senderATS.totalGDAOutflowRate = senderATS.totalGDAOutflowRate
+ .plus(flowRateDelta)
+ .lt(BIG_INT_ZERO)
+ ? newFlowRate
+ : senderATS.totalGDAOutflowRate.plus(flowRateDelta);
+
+ senderATS.totalGDANumberOfActiveStreams =
+ senderATS.totalGDANumberOfActiveStreams +
+ totalNumberOfActiveStreamsDelta;
+ senderATS.activeGDAOutgoingStreamCount =
+ senderATS.activeGDAOutgoingStreamCount +
+ totalNumberOfActiveStreamsDelta;
+ senderATS.inactiveGDAOutgoingStreamCount =
+ senderATS.inactiveGDAOutgoingStreamCount +
+ totalNumberOfClosedStreamsDelta;
+
+ senderATS.totalGDANumberOfClosedStreams =
+ senderATS.totalGDANumberOfClosedStreams +
+ totalNumberOfClosedStreamsDelta;
+ senderATS.totalGDADeposit =
+ senderATS.totalGDADeposit.plus(depositDelta);
+ }
+
+ senderATS.save();
+}
+
+/**
+ * Updates TokenStatistic and AccountTokenSnapshot countable stream
+ * data. Must be called after updating streamed amount data for the
+ * AccountTokenSnapshot entities.
+ */
+export function updateReceiverATSStreamData(
+ receiverAddress: Address,
+ tokenAddress: Address,
+ newFlowRate: BigInt,
+ flowRateDelta: BigInt,
+ isCreate: boolean,
+ isDelete: boolean,
+ isCFA: boolean,
+ block: ethereum.Block
+): void {
+ const totalNumberOfActiveStreamsDelta = getActiveStreamsDelta(
+ isCreate,
+ isDelete
+ );
+ const totalNumberOfClosedStreamsDelta = getClosedStreamsDelta(isDelete);
const receiverATS = getOrInitAccountTokenSnapshot(
receiverAddress,
tokenAddress,
@@ -1049,10 +1528,58 @@ export function updateAggregateEntitiesStreamData(
receiverATS.totalNetFlowRate,
receiverATS.maybeCriticalAtTimestamp
);
- receiverATS.save();
- tokenStatistic.save();
- senderATS.save();
+ if (isCFA) {
+ receiverATS.totalCFANetFlowRate =
+ receiverATS.totalCFANetFlowRate.plus(flowRateDelta);
+
+ // the inflow rate should never go below 0.
+ receiverATS.totalCFAInflowRate = receiverATS.totalCFAInflowRate
+ .plus(flowRateDelta)
+ .lt(BIG_INT_ZERO)
+ ? newFlowRate
+ : receiverATS.totalCFAInflowRate.plus(flowRateDelta);
+
+ receiverATS.totalCFANumberOfActiveStreams =
+ receiverATS.totalCFANumberOfActiveStreams +
+ totalNumberOfActiveStreamsDelta;
+ receiverATS.activeCFAIncomingStreamCount =
+ receiverATS.activeCFAIncomingStreamCount +
+ totalNumberOfActiveStreamsDelta;
+ receiverATS.inactiveCFAIncomingStreamCount =
+ receiverATS.inactiveCFAIncomingStreamCount +
+ totalNumberOfClosedStreamsDelta;
+
+ receiverATS.totalCFANumberOfClosedStreams =
+ receiverATS.totalCFANumberOfClosedStreams +
+ totalNumberOfClosedStreamsDelta;
+ } else {
+ receiverATS.totalGDANetFlowRate =
+ receiverATS.totalGDANetFlowRate.plus(flowRateDelta);
+
+ // the inflow rate should never go below 0.
+ receiverATS.totalGDAInflowRate = receiverATS.totalGDAInflowRate
+ .plus(flowRateDelta)
+ .lt(BIG_INT_ZERO)
+ ? newFlowRate
+ : receiverATS.totalGDAInflowRate.plus(flowRateDelta);
+
+ receiverATS.totalGDANumberOfActiveStreams =
+ receiverATS.totalGDANumberOfActiveStreams +
+ totalNumberOfActiveStreamsDelta;
+ receiverATS.activeGDAIncomingStreamCount =
+ receiverATS.activeGDAIncomingStreamCount +
+ totalNumberOfActiveStreamsDelta;
+ receiverATS.inactiveGDAIncomingStreamCount =
+ receiverATS.inactiveGDAIncomingStreamCount +
+ totalNumberOfClosedStreamsDelta;
+
+ receiverATS.totalGDANumberOfClosedStreams =
+ receiverATS.totalGDANumberOfClosedStreams +
+ totalNumberOfClosedStreamsDelta;
+ }
+
+ receiverATS.save();
}
export function updateAggregateEntitiesTransferData(
diff --git a/packages/subgraph/src/mappings/cfav1.ts b/packages/subgraph/src/mappings/cfav1.ts
index 4176441ce3..fa548d6091 100644
--- a/packages/subgraph/src/mappings/cfav1.ts
+++ b/packages/subgraph/src/mappings/cfav1.ts
@@ -29,8 +29,11 @@ import {
getOrInitFlowOperator,
getOrInitStream,
getOrInitStreamRevision,
- updateAggregateEntitiesStreamData,
+ updateSenderATSStreamData,
+ updateReceiverATSStreamData,
updateATSStreamedAndBalanceUntilUpdatedAt,
+ updateTokenStatisticStreamData,
+ updateTokenStatsStreamedUntilUpdatedAt,
} from "../mappingHelpers";
import { getHostAddress } from "../addresses";
@@ -78,7 +81,9 @@ export function handleFlowUpdated(event: FlowUpdated): void {
const oldDeposit = stream.deposit;
const oldFlowRate = stream.currentFlowRate;
- const timeSinceLastUpdate = currentTimestamp.minus(stream.updatedAtTimestamp);
+ const timeSinceLastUpdate = currentTimestamp.minus(
+ stream.updatedAtTimestamp
+ );
const userAmountStreamedSinceLastUpdate =
oldFlowRate.times(timeSinceLastUpdate);
const newStreamedUntilLastUpdate = stream.streamedUntilUpdatedAt.plus(
@@ -124,6 +129,7 @@ export function handleFlowUpdated(event: FlowUpdated): void {
newDeposit
);
+ // update streamed and balance until updated at for sender and receiver
updateATSStreamedAndBalanceUntilUpdatedAt(
senderAddress,
tokenAddress,
@@ -137,19 +143,46 @@ export function handleFlowUpdated(event: FlowUpdated): void {
event.block,
null
);
- // @note EXCEPTION for not calling updateTokenStatsStreamedUntilUpdatedAt
- // because updateAggregateEntitiesStreamData updates tokenStats.streamedUntilUpdatedAt
- updateAggregateEntitiesStreamData(
+
+ // update stream counter data for sender and receiver ATS
+ updateSenderATSStreamData(
senderAddress,
+ tokenAddress,
+ flowRate,
+ flowRateDelta,
+ depositDelta,
+ isCreate,
+ isDelete,
+ true,
+ event.block
+ );
+ updateReceiverATSStreamData(
receiverAddress,
+ tokenAddress,
+ flowRate,
+ flowRateDelta,
+ isCreate,
+ isDelete,
+ true,
+ event.block
+ );
+
+ // update token stats streamed until updated at
+ updateTokenStatsStreamedUntilUpdatedAt(tokenAddress, event.block);
+
+ // update token stats stream counter data
+ updateTokenStatisticStreamData(
tokenAddress,
flowRate,
flowRateDelta,
depositDelta,
isCreate,
isDelete,
+ true,
event.block
);
+
+ // create ATS and token statistic log entities
_createAccountTokenSnapshotLogEntity(
event,
senderAddress,
diff --git a/packages/subgraph/src/mappings/gdav1.ts b/packages/subgraph/src/mappings/gdav1.ts
new file mode 100644
index 0000000000..58f57be3ae
--- /dev/null
+++ b/packages/subgraph/src/mappings/gdav1.ts
@@ -0,0 +1,490 @@
+import { BigInt } from "@graphprotocol/graph-ts";
+import {
+ BufferAdjusted,
+ FlowDistributionUpdated,
+ InstantDistributionUpdated,
+ PoolConnectionUpdated,
+ PoolCreated,
+} from "../../generated/GeneralDistributionAgreementV1/IGeneralDistributionAgreementV1";
+import {
+ BufferAdjustedEvent,
+ FlowDistributionUpdatedEvent,
+ InstantDistributionUpdatedEvent,
+ PoolConnectionUpdatedEvent,
+ PoolCreatedEvent,
+} from "../../generated/schema";
+import { SuperfluidPool as SuperfluidPoolTemplate } from "../../generated/templates";
+import {
+ _createAccountTokenSnapshotLogEntity,
+ _createTokenStatisticLogEntity,
+ getOrInitPool,
+ getOrInitPoolDistributor,
+ getOrInitPoolMember,
+ getOrInitTokenStatistic,
+ updateATSStreamedAndBalanceUntilUpdatedAt,
+ updateAggregateDistributionAgreementData,
+ updatePoolDistributorTotalAmountFlowedAndDistributed,
+ updatePoolTotalAmountFlowedAndDistributed,
+ updateSenderATSStreamData,
+ updateTokenStatisticStreamData,
+ updateTokenStatsStreamedUntilUpdatedAt,
+} from "../mappingHelpers";
+import {
+ BIG_INT_ZERO,
+ createEventID,
+ initializeEventEntity,
+ membershipWithUnitsExists,
+} from "../utils";
+
+// @note use deltas where applicable
+
+export function handlePoolCreated(event: PoolCreated): void {
+ const eventName = "PoolCreated";
+
+ const pool = getOrInitPool(event, event.params.pool.toHex());
+ pool.token = event.params.token.toHex();
+ pool.admin = event.params.admin.toHex();
+ pool.save();
+
+ // Note: this is necessary otherwise we will not be able to capture
+ // template data source events.
+ SuperfluidPoolTemplate.create(event.params.pool);
+
+ updateTokenStatsStreamedUntilUpdatedAt(event.params.token, event.block);
+
+ const tokenStatistic = getOrInitTokenStatistic(
+ event.params.token,
+ event.block
+ );
+ tokenStatistic.totalNumberOfPools = tokenStatistic.totalNumberOfPools + 1;
+
+ tokenStatistic.save();
+
+ updateATSStreamedAndBalanceUntilUpdatedAt(
+ event.params.admin,
+ event.params.token,
+ event.block,
+ null
+ );
+
+ _createAccountTokenSnapshotLogEntity(
+ event,
+ event.params.admin,
+ event.params.token,
+ eventName
+ );
+
+ _createTokenStatisticLogEntity(event, event.params.token, eventName);
+ // Create Event Entity
+ _createPoolCreatedEntity(event);
+}
+
+export function handlePoolConnectionUpdated(
+ event: PoolConnectionUpdated
+): void {
+ // Update Pool Member Entity
+ const poolMember = getOrInitPoolMember(
+ event,
+ event.params.pool,
+ event.params.account
+ );
+ const previousIsConnected = poolMember.isConnected;
+ const memberConnectedStatusUpdated =
+ previousIsConnected !== event.params.connected;
+ poolMember.isConnected = event.params.connected;
+ poolMember.save();
+
+ const hasMembershipWithUnits = membershipWithUnitsExists(poolMember.id);
+
+ // Update Pool Entity
+ let pool = getOrInitPool(event, event.params.pool.toHex());
+ pool = updatePoolTotalAmountFlowedAndDistributed(event, pool);
+ if (poolMember.units.gt(BIG_INT_ZERO)) {
+ if (memberConnectedStatusUpdated) {
+ // disconnected -> connected case
+ if (event.params.connected) {
+ pool.totalConnectedUnits = pool.totalConnectedUnits.plus(
+ poolMember.units
+ );
+ pool.totalDisconnectedUnits = pool.totalDisconnectedUnits.minus(
+ poolMember.units
+ );
+ pool.totalConnectedMembers = pool.totalConnectedMembers + 1;
+ pool.totalDisconnectedMembers =
+ pool.totalDisconnectedMembers - 1;
+ } else {
+ // connected -> disconnected case
+ pool.totalConnectedUnits = pool.totalConnectedUnits.minus(
+ poolMember.units
+ );
+ pool.totalDisconnectedUnits = pool.totalDisconnectedUnits.plus(
+ poolMember.units
+ );
+ pool.totalConnectedMembers = pool.totalConnectedMembers - 1;
+ pool.totalDisconnectedMembers =
+ pool.totalDisconnectedMembers + 1;
+ }
+ }
+ }
+ pool.save();
+
+ // Update Token Stats Streamed Until Updated At
+ updateTokenStatsStreamedUntilUpdatedAt(event.params.token, event.block);
+ // Update ATS Balance and Streamed Until Updated At
+ updateATSStreamedAndBalanceUntilUpdatedAt(
+ event.params.account,
+ event.params.token,
+ event.block,
+ null
+ );
+
+ const isConnecting = event.params.connected;
+
+ // there is no concept of revoking in GDA, but in the subgraph
+ // revoking is disconnecting and deleting is setting units to 0
+ const isRevoking = !event.params.connected;
+
+ updateAggregateDistributionAgreementData(
+ event.params.account,
+ event.params.token,
+ hasMembershipWithUnits || poolMember.isConnected,
+ poolMember.isConnected,
+ false, // don't increment memberWithUnits
+ isRevoking, // isRevoking
+ false, // not deleting (setting units to 0)
+ isConnecting, // approving membership here
+ event.block,
+ false // isIDA
+ );
+
+ // Create ATS and Token Statistic Log Entities
+ const eventName = "PoolConnectionUpdated";
+ _createAccountTokenSnapshotLogEntity(
+ event,
+ event.params.account,
+ event.params.token,
+ eventName
+ );
+
+ _createTokenStatisticLogEntity(event, event.params.token, eventName);
+
+ // Create Event Entity
+ _createPoolConnectionUpdatedEntity(event, poolMember.id);
+}
+
+export function handleBufferAdjusted(event: BufferAdjusted): void {
+ // Update Pool Distributor
+ let poolDistributor = getOrInitPoolDistributor(
+ event,
+ event.params.pool,
+ event.params.from
+ );
+ poolDistributor = updatePoolDistributorTotalAmountFlowedAndDistributed(
+ event,
+ poolDistributor
+ );
+ poolDistributor.totalBuffer = event.params.newBufferAmount;
+ poolDistributor.save();
+
+ // Update Pool
+ let pool = getOrInitPool(event, event.params.pool.toHex());
+ pool = updatePoolTotalAmountFlowedAndDistributed(event, pool);
+ pool.totalBuffer = pool.totalBuffer.plus(event.params.bufferDelta);
+ pool.save();
+
+ // Update Token Stats Buffer
+ const tokenStatistic = getOrInitTokenStatistic(
+ event.params.token,
+ event.block
+ );
+ tokenStatistic.totalGDADeposit = tokenStatistic.totalGDADeposit.plus(
+ event.params.bufferDelta
+ );
+ tokenStatistic.save();
+
+ // Create Event Entity
+ _createBufferAdjustedEntity(event, poolDistributor.id);
+}
+
+export function handleFlowDistributionUpdated(
+ event: FlowDistributionUpdated
+): void {
+ // Update Pool Distributor
+ let poolDistributor = getOrInitPoolDistributor(
+ event,
+ event.params.pool,
+ event.params.distributor
+ );
+ poolDistributor = updatePoolDistributorTotalAmountFlowedAndDistributed(
+ event,
+ poolDistributor
+ );
+ poolDistributor.flowRate = event.params.newDistributorToPoolFlowRate;
+ poolDistributor.save();
+
+ // Update Pool
+ let pool = getOrInitPool(event, event.params.pool.toHex());
+ pool = updatePoolTotalAmountFlowedAndDistributed(event, pool);
+ pool.flowRate = event.params.newTotalDistributionFlowRate;
+ pool.adjustmentFlowRate = event.params.adjustmentFlowRate;
+ pool.save();
+
+ const flowRateDelta = event.params.newDistributorToPoolFlowRate.minus(
+ event.params.oldFlowRate
+ );
+
+ const isCreate = event.params.oldFlowRate.equals(BIG_INT_ZERO);
+ const isDelete =
+ event.params.newDistributorToPoolFlowRate.equals(BIG_INT_ZERO);
+
+ // Update Token Statistics
+ const eventName = "FlowDistributionUpdated";
+ updateTokenStatsStreamedUntilUpdatedAt(event.params.token, event.block);
+ _createTokenStatisticLogEntity(event, event.params.token, eventName);
+ updateTokenStatisticStreamData(
+ event.params.token,
+ event.params.newDistributorToPoolFlowRate,
+ flowRateDelta,
+ BIG_INT_ZERO,
+ isCreate,
+ isDelete,
+ false,
+ event.block
+ );
+ _createTokenStatisticLogEntity(event, event.params.token, eventName);
+
+ // Update ATS
+ updateSenderATSStreamData(
+ event.params.distributor,
+ event.params.token,
+ event.params.newDistributorToPoolFlowRate,
+ flowRateDelta,
+ BIG_INT_ZERO,
+ isCreate,
+ isDelete,
+ false,
+ event.block
+ );
+ updateATSStreamedAndBalanceUntilUpdatedAt(
+ event.params.distributor,
+ event.params.token,
+ event.block,
+ null
+ );
+ _createAccountTokenSnapshotLogEntity(
+ event,
+ event.params.distributor,
+ event.params.token,
+ eventName
+ );
+
+ // Create Event Entity
+ _createFlowDistributionUpdatedEntity(event, poolDistributor.id, pool.totalUnits);
+}
+
+export function handleInstantDistributionUpdated(
+ event: InstantDistributionUpdated
+): void {
+ // Update Pool Distributor
+ let poolDistributor = getOrInitPoolDistributor(
+ event,
+ event.params.pool,
+ event.params.distributor
+ );
+ poolDistributor = updatePoolDistributorTotalAmountFlowedAndDistributed(
+ event,
+ poolDistributor
+ );
+ poolDistributor.totalAmountInstantlyDistributedUntilUpdatedAt =
+ poolDistributor.totalAmountInstantlyDistributedUntilUpdatedAt.plus(
+ event.params.actualAmount
+ );
+ poolDistributor.totalAmountDistributedUntilUpdatedAt =
+ poolDistributor.totalAmountDistributedUntilUpdatedAt.plus(
+ event.params.actualAmount
+ );
+ poolDistributor.save();
+
+ // Update Pool
+ let pool = getOrInitPool(event, event.params.pool.toHex());
+ pool = updatePoolTotalAmountFlowedAndDistributed(event, pool);
+ const previousTotalAmountDistributed =
+ pool.totalAmountDistributedUntilUpdatedAt;
+ pool.totalAmountInstantlyDistributedUntilUpdatedAt =
+ pool.totalAmountInstantlyDistributedUntilUpdatedAt.plus(
+ event.params.actualAmount
+ );
+ pool.totalAmountDistributedUntilUpdatedAt =
+ pool.totalAmountDistributedUntilUpdatedAt.plus(
+ event.params.actualAmount
+ );
+ pool.save();
+
+ // Update Token Statistic
+ const tokenStatistic = getOrInitTokenStatistic(
+ event.params.token,
+ event.block
+ );
+
+ if (previousTotalAmountDistributed.equals(BIG_INT_ZERO)) {
+ tokenStatistic.totalNumberOfActivePools =
+ tokenStatistic.totalNumberOfActivePools + 1;
+ }
+
+ tokenStatistic.totalAmountDistributedUntilUpdatedAt =
+ tokenStatistic.totalAmountDistributedUntilUpdatedAt.plus(
+ event.params.actualAmount
+ );
+ tokenStatistic.save();
+
+ const eventName = "InstantDistributionUpdated";
+ updateTokenStatsStreamedUntilUpdatedAt(event.params.token, event.block);
+ _createTokenStatisticLogEntity(event, event.params.token, eventName);
+
+ // Update ATS
+ updateATSStreamedAndBalanceUntilUpdatedAt(
+ event.params.distributor,
+ event.params.token,
+ event.block,
+ null
+ );
+
+ _createAccountTokenSnapshotLogEntity(
+ event,
+ event.params.distributor,
+ event.params.token,
+ eventName
+ );
+
+ // Create Event Entity
+ _createInstantDistributionUpdatedEntity(event, poolDistributor.id, pool.totalUnits);
+}
+
+// Event Entity Creation Functions
+
+function _createPoolCreatedEntity(event: PoolCreated): PoolCreatedEvent {
+ const ev = new PoolCreatedEvent(createEventID("PoolCreated", event));
+ initializeEventEntity(ev, event, [
+ event.params.token,
+ event.params.pool,
+ event.transaction.from,
+ event.params.admin,
+ ]);
+
+ ev.token = event.params.token;
+ ev.caller = event.transaction.from;
+ ev.admin = event.params.admin;
+ ev.pool = event.params.pool.toHex();
+
+ ev.save();
+
+ return ev;
+}
+
+function _createPoolConnectionUpdatedEntity(
+ event: PoolConnectionUpdated,
+ poolMemberId: string
+): PoolConnectionUpdatedEvent {
+ const ev = new PoolConnectionUpdatedEvent(
+ createEventID("PoolConnectionUpdated", event)
+ );
+ initializeEventEntity(ev, event, [
+ event.params.token,
+ event.params.pool,
+ event.params.account,
+ ]);
+
+ ev.token = event.params.token;
+ ev.connected = event.params.connected;
+ ev.pool = event.params.pool.toHex();
+ ev.poolMember = poolMemberId;
+ ev.userData = event.params.userData;
+
+ ev.save();
+
+ return ev;
+}
+
+function _createBufferAdjustedEntity(
+ event: BufferAdjusted,
+ poolDistributorId: string
+): BufferAdjustedEvent {
+ const ev = new BufferAdjustedEvent(createEventID("BufferAdjusted", event));
+ initializeEventEntity(ev, event, [
+ event.params.token,
+ event.params.pool,
+ event.params.from,
+ ]);
+
+ ev.token = event.params.token;
+ ev.bufferDelta = event.params.bufferDelta;
+ ev.newBufferAmount = event.params.newBufferAmount;
+ ev.totalBufferAmount = event.params.totalBufferAmount;
+ ev.pool = event.params.pool.toHex();
+ ev.poolDistributor = poolDistributorId;
+
+ ev.save();
+
+ return ev;
+}
+
+function _createInstantDistributionUpdatedEntity(
+ event: InstantDistributionUpdated,
+ poolDistributorId: string,
+ totalUnits: BigInt
+): InstantDistributionUpdatedEvent {
+ const ev = new InstantDistributionUpdatedEvent(
+ createEventID("InstantDistributionUpdated", event)
+ );
+ initializeEventEntity(ev, event, [
+ event.params.token,
+ event.params.pool,
+ event.params.distributor,
+ event.params.operator,
+ ]);
+
+ ev.token = event.params.token;
+ ev.operator = event.params.operator;
+ ev.requestedAmount = event.params.requestedAmount;
+ ev.actualAmount = event.params.actualAmount;
+ ev.pool = event.params.pool.toHex();
+ ev.poolDistributor = poolDistributorId;
+ ev.totalUnits = totalUnits;
+ ev.userData = event.params.userData;
+
+ ev.save();
+
+ return ev;
+}
+
+function _createFlowDistributionUpdatedEntity(
+ event: FlowDistributionUpdated,
+ poolDistributorId: string,
+ totalUnits: BigInt
+): FlowDistributionUpdatedEvent {
+ const ev = new FlowDistributionUpdatedEvent(
+ createEventID("FlowDistributionUpdated", event)
+ );
+ initializeEventEntity(ev, event, [
+ event.params.token,
+ event.params.pool,
+ event.params.distributor,
+ event.params.operator,
+ ]);
+
+ ev.token = event.params.token;
+ ev.operator = event.params.operator;
+ ev.oldFlowRate = event.params.oldFlowRate;
+ ev.newDistributorToPoolFlowRate = event.params.newDistributorToPoolFlowRate;
+ ev.newTotalDistributionFlowRate = event.params.newTotalDistributionFlowRate;
+ ev.adjustmentFlowRecipient = event.params.adjustmentFlowRecipient;
+ ev.adjustmentFlowRate = event.params.adjustmentFlowRate;
+ ev.pool = event.params.pool.toHex();
+ ev.poolDistributor = poolDistributorId;
+ ev.totalUnits = totalUnits;
+ ev.userData = event.params.userData;
+
+ ev.save();
+
+ return ev;
+}
diff --git a/packages/subgraph/src/mappings/idav1.ts b/packages/subgraph/src/mappings/idav1.ts
index 3ab12b8111..5ebcf73d4d 100644
--- a/packages/subgraph/src/mappings/idav1.ts
+++ b/packages/subgraph/src/mappings/idav1.ts
@@ -28,7 +28,7 @@ import {
createEventID,
getIndexID,
initializeEventEntity,
- subscriptionExists as subscriptionWithUnitsExists,
+ subscriptionWithUnitsExists,
tokenHasValidHost,
} from "../utils";
import {
@@ -37,7 +37,7 @@ import {
getOrInitIndex,
getOrInitSubscription,
getOrInitTokenStatistic,
- updateAggregateIDASubscriptionsData,
+ updateAggregateDistributionAgreementData,
updateATSStreamedAndBalanceUntilUpdatedAt,
updateTokenStatsStreamedUntilUpdatedAt,
} from "../mappingHelpers";
@@ -54,7 +54,6 @@ export function handleIndexCreated(event: IndexCreated): void {
return;
}
- const currentTimestamp = event.block.timestamp;
const indexCreatedId = createEventID(eventName, event);
const index = getOrInitIndex(
event,
@@ -75,8 +74,7 @@ export function handleIndexCreated(event: IndexCreated): void {
);
tokenStatistic.totalNumberOfIndexes =
tokenStatistic.totalNumberOfIndexes + 1;
- tokenStatistic.updatedAtTimestamp = currentTimestamp;
- tokenStatistic.updatedAtBlockNumber = event.block.number;
+
tokenStatistic.save();
updateATSStreamedAndBalanceUntilUpdatedAt(
@@ -297,16 +295,17 @@ export function handleSubscriptionApproved(event: SubscriptionApproved): void {
updateTokenStatsStreamedUntilUpdatedAt(event.params.token, event.block);
// we only want to increment approved here ALWAYS
- updateAggregateIDASubscriptionsData(
+ updateAggregateDistributionAgreementData(
event.params.subscriber,
event.params.token,
hasSubscriptionWithUnits || subscription.approved,
subscription.approved,
false, // don't increment subWithUnits
false, // not revoking
- false, // not deleting
+ false, // not deleting (setting units to 0)
true, // approving subscription here
- event.block
+ event.block,
+ true // isIDA
);
index.save();
@@ -451,7 +450,7 @@ export function handleSubscriptionRevoked(event: SubscriptionRevoked): void {
updateTokenStatsStreamedUntilUpdatedAt(event.params.token, event.block);
- updateAggregateIDASubscriptionsData(
+ updateAggregateDistributionAgreementData(
event.params.subscriber,
event.params.token,
true,
@@ -460,7 +459,8 @@ export function handleSubscriptionRevoked(event: SubscriptionRevoked): void {
true, // revoking subscription here
false, // not deleting
false, // not approving
- event.block
+ event.block,
+ true // isIDA
);
// mimic ida logic more closely
updateATSStreamedAndBalanceUntilUpdatedAt(
@@ -578,7 +578,7 @@ export function handleSubscriptionUnitsUpdated(
// and therefore subtracts the number of totalSubscriptionWithUnits and
// totalApprovedSubscriptions
if (units.equals(BIG_INT_ZERO)) {
- updateAggregateIDASubscriptionsData(
+ updateAggregateDistributionAgreementData(
event.params.subscriber,
event.params.token,
hasSubscriptionWithUnits,
@@ -587,7 +587,8 @@ export function handleSubscriptionUnitsUpdated(
false, // not revoking subscription
true, // only place we decrement subWithUnits IF subscriber has subWithUnits
false, // not approving
- event.block
+ event.block,
+ true // isIDA
);
index.totalSubscriptionsWithUnits = hasSubscriptionWithUnits
? index.totalSubscriptionsWithUnits - 1
@@ -604,7 +605,7 @@ export function handleSubscriptionUnitsUpdated(
index.totalSubscriptionsWithUnits =
index.totalSubscriptionsWithUnits + 1;
- updateAggregateIDASubscriptionsData(
+ updateAggregateDistributionAgreementData(
event.params.subscriber,
event.params.token,
hasSubscriptionWithUnits,
@@ -613,7 +614,8 @@ export function handleSubscriptionUnitsUpdated(
false, // not revoking
false, // not deleting
false, // not approving
- event.block
+ event.block,
+ true // isIDA
);
}
diff --git a/packages/subgraph/src/mappings/superfluidPool.ts b/packages/subgraph/src/mappings/superfluidPool.ts
new file mode 100644
index 0000000000..cdc3e5eb6d
--- /dev/null
+++ b/packages/subgraph/src/mappings/superfluidPool.ts
@@ -0,0 +1,162 @@
+import { BigInt } from "@graphprotocol/graph-ts";
+import {
+ DistributionClaimed,
+ MemberUnitsUpdated,
+} from "../../generated/GeneralDistributionAgreementV1/ISuperfluidPool";
+import { DistributionClaimedEvent, MemberUnitsUpdatedEvent } from "../../generated/schema";
+import {
+ _createAccountTokenSnapshotLogEntity,
+ _createTokenStatisticLogEntity,
+ getOrInitPool,
+ getOrInitPoolMember,
+ updateATSStreamedAndBalanceUntilUpdatedAt,
+ updateAggregateDistributionAgreementData,
+ updatePoolTotalAmountFlowedAndDistributed,
+ updateTokenStatsStreamedUntilUpdatedAt,
+} from "../mappingHelpers";
+import { BIG_INT_ZERO, createEventID, initializeEventEntity, membershipWithUnitsExists } from "../utils";
+
+// @note use deltas where applicable
+
+export function handleDistributionClaimed(event: DistributionClaimed): void {
+ const token = event.params.token;
+
+ // Update Pool
+ let pool = getOrInitPool(event, event.address.toHex());
+ pool = updatePoolTotalAmountFlowedAndDistributed(event, pool);
+ pool.save();
+
+ // Update PoolMember
+ const poolMember = getOrInitPoolMember(event, event.address, event.params.member);
+ poolMember.totalAmountClaimed = event.params.totalClaimed;
+ poolMember.save();
+
+ // Update Token Statistics
+ const eventName = "DistributionClaimed";
+ updateTokenStatsStreamedUntilUpdatedAt(token, event.block);
+ _createTokenStatisticLogEntity(event, token, eventName);
+
+ // Update ATS
+ updateATSStreamedAndBalanceUntilUpdatedAt(event.params.member, token, event.block, null);
+ _createAccountTokenSnapshotLogEntity(event, event.params.member, token, eventName);
+
+ // Create Event Entity
+ _createDistributionClaimedEntity(event, poolMember.id);
+}
+
+export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void {
+ // - PoolMember
+ // - units
+ const poolMember = getOrInitPoolMember(event, event.address, event.params.member);
+ const hasMembershipWithUnits = membershipWithUnitsExists(poolMember.id);
+
+ const previousUnits = poolMember.units;
+ const unitsDelta = event.params.newUnits.minus(previousUnits);
+ poolMember.units = event.params.newUnits;
+
+ poolMember.save();
+
+ const eventName = "MemberUnitsUpdated";
+ updateTokenStatsStreamedUntilUpdatedAt(event.params.token, event.block);
+ _createTokenStatisticLogEntity(event, event.params.token, eventName);
+
+ updateATSStreamedAndBalanceUntilUpdatedAt(event.params.member, event.params.token, event.block, null);
+ _createAccountTokenSnapshotLogEntity(event, event.params.member, event.params.token, eventName);
+
+ let pool = getOrInitPool(event, event.address.toHex());
+ pool = updatePoolTotalAmountFlowedAndDistributed(event, pool);
+ if (poolMember.isConnected) {
+ pool.totalConnectedUnits = pool.totalConnectedUnits.plus(unitsDelta);
+ } else {
+ pool.totalDisconnectedUnits = pool.totalDisconnectedUnits.plus(unitsDelta);
+ }
+ pool.totalUnits = pool.totalUnits.plus(unitsDelta);
+ pool.save();
+
+ // 0 units to > 0 units
+ if (previousUnits.equals(BIG_INT_ZERO) && event.params.newUnits.gt(BIG_INT_ZERO)) {
+ pool.totalMembers = pool.totalMembers + 1;
+ // if the member is connected with units now, we add one to connected
+ if (poolMember.isConnected) {
+ pool.totalConnectedMembers = pool.totalConnectedMembers + 1;
+ } else {
+ // if the member is disconnected with units now, we add one to disconnected
+ pool.totalDisconnectedMembers = pool.totalDisconnectedMembers + 1;
+ }
+ pool.save();
+
+ updateAggregateDistributionAgreementData(
+ event.params.member,
+ event.params.token,
+ hasMembershipWithUnits,
+ poolMember.isConnected,
+ true, // only place we increment subWithUnits
+ false, // not deleting
+ false, // not deleting
+ false, // not connecting
+ event.block,
+ false // isIDA
+ );
+ }
+ // > 0 units to 0 units
+ if (previousUnits.gt(BIG_INT_ZERO) && poolMember.units.equals(BIG_INT_ZERO)) {
+ pool.totalMembers = pool.totalMembers - 1;
+ // if the member is connected with no units now, we subtract one from connected
+ if (poolMember.isConnected) {
+ pool.totalConnectedMembers = pool.totalConnectedMembers - 1;
+ } else {
+ // if the member is disconnected with no units now, we subtract one from disconnected
+ pool.totalDisconnectedMembers = pool.totalDisconnectedMembers - 1;
+ }
+ pool.save();
+
+ updateAggregateDistributionAgreementData(
+ event.params.member,
+ event.params.token,
+ hasMembershipWithUnits,
+ poolMember.isConnected,
+ false, // don't increment memberWithUnits
+ false, // not disconnecting membership
+ true, // only place we decrement membershipWithUnits IF member has memberShipWithUnits
+ false, // not connecting
+ event.block,
+ false // isIDA
+ );
+ }
+
+ // Create Event Entity
+ _createMemberUnitsUpdatedEntity(event, poolMember.id, pool.totalUnits);
+}
+
+function _createDistributionClaimedEntity(event: DistributionClaimed, poolMemberId: string): DistributionClaimedEvent {
+ const ev = new DistributionClaimedEvent(createEventID("DistributionClaimed", event));
+ initializeEventEntity(ev, event, [event.params.token, event.address, event.params.member]);
+
+ ev.token = event.params.token;
+ ev.claimedAmount = event.params.claimedAmount;
+ ev.totalClaimed = event.params.totalClaimed;
+ ev.pool = event.address.toHex();
+ ev.poolMember = poolMemberId;
+ ev.save();
+
+ return ev;
+}
+
+function _createMemberUnitsUpdatedEntity(
+ event: MemberUnitsUpdated,
+ poolMemberId: string,
+ totalUnits: BigInt
+): MemberUnitsUpdatedEvent {
+ const ev = new MemberUnitsUpdatedEvent(createEventID("MemberUnitsUpdated", event));
+ initializeEventEntity(ev, event, [event.params.token, event.address, event.params.member]);
+
+ ev.token = event.params.token;
+ ev.oldUnits = event.params.oldUnits;
+ ev.units = event.params.newUnits;
+ ev.totalUnits = totalUnits;
+ ev.pool = event.address.toHex();
+ ev.poolMember = poolMemberId;
+ ev.save();
+
+ return ev;
+}
diff --git a/packages/subgraph/src/utils.ts b/packages/subgraph/src/utils.ts
index 7f8373a9cc..1d11509bc2 100644
--- a/packages/subgraph/src/utils.ts
+++ b/packages/subgraph/src/utils.ts
@@ -1,10 +1,19 @@
-import { Address, BigInt, Bytes, crypto, Entity, ethereum, log, Value } from "@graphprotocol/graph-ts";
+import {
+ Address,
+ BigInt,
+ Bytes,
+ crypto,
+ Entity,
+ ethereum,
+ log,
+ Value,
+} from "@graphprotocol/graph-ts";
import { ISuperToken as SuperToken } from "../generated/templates/SuperToken/ISuperToken";
-import { Resolver } from "../generated/ResolverV1/Resolver";
import {
IndexSubscription,
Token,
TokenStatistic,
+ PoolMember,
} from "../generated/schema";
/**************************************************************************
@@ -29,7 +38,7 @@ export function bytesToAddress(bytes: Bytes): Address {
* @param values
* @returns the encoded bytes
*/
- export function encode(values: Array): Bytes {
+export function encode(values: Array): Bytes {
return ethereum.encode(
// forcefully cast Value[] -> Tuple
ethereum.Value.fromTuple(changetype(values))
@@ -63,7 +72,7 @@ export function initializeEventEntity(
entity: Entity,
event: ethereum.Event,
addresses: Bytes[]
- ): Entity {
+): Entity {
const idValue = entity.get("id");
if (!idValue) return entity;
@@ -72,7 +81,10 @@ export function initializeEventEntity(
entity.set("blockNumber", Value.fromBigInt(event.block.number));
entity.set("logIndex", Value.fromBigInt(event.logIndex));
- entity.set("order", Value.fromBigInt(getOrder(event.block.number, event.logIndex)));
+ entity.set(
+ "order",
+ Value.fromBigInt(getOrder(event.block.number, event.logIndex))
+ );
entity.set("name", Value.fromString(name));
entity.set("addresses", Value.fromBytesArray(addresses));
entity.set("timestamp", Value.fromBigInt(event.block.timestamp));
@@ -82,7 +94,7 @@ export function initializeEventEntity(
if (receipt) {
entity.set("gasUsed", Value.fromBigInt(receipt.gasUsed));
} else {
- // @note `gasUsed` is a non-nullable property in our `schema.graphql` file, so when we attempt to save
+ // @note `gasUsed` is a non-nullable property in our `schema.graphql` file, so when we attempt to save
// the entity with a null field, it will halt the subgraph indexing.
// Nonetheless, we explicitly throw if receipt is null, as this can arise due forgetting to include
// `receipt: true` under `eventHandlers` in our manifest (`subgraph.template.yaml`) file.
@@ -90,7 +102,7 @@ export function initializeEventEntity(
}
return entity;
- }
+}
/**************************************************************************
* HOL entities util functions
@@ -127,8 +139,8 @@ export function getTokenInfoAndReturn(token: Token): Token {
/**
* Gets and sets the total supply for TokenStatistic of a SuperToken upon initial creation
- * @param tokenStatistic
- * @param tokenAddress
+ * @param tokenStatistic
+ * @param tokenAddress
* @returns TokenStatistic
*/
export function getInitialTotalSupplyForSuperToken(
@@ -183,11 +195,7 @@ export function getStreamRevisionID(
ethereum.Value.fromAddress(receiverAddress),
];
const flowId = crypto.keccak256(encode(values));
- return (
- flowId.toHex() +
- "-" +
- tokenAddress.toHex()
- );
+ return flowId.toHex() + "-" + tokenAddress.toHex();
}
export function getStreamID(
@@ -260,6 +268,27 @@ export function getIndexID(
);
}
+export function getPoolMemberID(
+ poolAddress: Address,
+ poolMemberAddress: Address
+): string {
+ return (
+ "poolMember-" + poolAddress.toHex() + "-" + poolMemberAddress.toHex()
+ );
+}
+
+export function getPoolDistributorID(
+ poolAddress: Address,
+ poolDistributorAddress: Address
+): string {
+ return (
+ "poolDistributor-" +
+ poolAddress.toHex() +
+ "-" +
+ poolDistributorAddress.toHex()
+ );
+}
+
// Get Aggregate ID functions
export function getAccountTokenSnapshotID(
accountAddress: Address,
@@ -278,11 +307,24 @@ export function getAccountTokenSnapshotID(
* @param id
* @returns
*/
-export function subscriptionExists(id: string): boolean {
+export function subscriptionWithUnitsExists(id: string): boolean {
const subscription = IndexSubscription.load(id);
return subscription != null && subscription.units.gt(BIG_INT_ZERO);
}
+/**
+ * If your units get set to 0, you will still have a pool member
+ * entity, but your pool member technically no longer exists.
+ * Similarly, you may be approved, but the pool member by this
+ * definition does not exist.
+ * @param id
+ * @returns
+ */
+export function membershipWithUnitsExists(id: string): boolean {
+ const poolMembership = PoolMember.load(id);
+ return poolMembership != null && poolMembership.units.gt(BIG_INT_ZERO);
+}
+
export function getAmountStreamedSinceLastUpdatedAt(
currentTime: BigInt,
lastUpdatedTime: BigInt,
@@ -292,6 +334,17 @@ export function getAmountStreamedSinceLastUpdatedAt(
return timeDelta.times(flowRate);
}
+export function getActiveStreamsDelta(
+ isCreate: boolean,
+ isDelete: boolean
+): i32 {
+ return isCreate ? 1 : isDelete ? -1 : 0;
+}
+
+export function getClosedStreamsDelta(isDelete: boolean): i32 {
+ return isDelete ? 1 : 0;
+}
+
/**
* calculateMaybeCriticalAtTimestamp will return optimistic date based on updatedAtTimestamp, balanceUntilUpdatedAt and totalNetFlowRate.
* @param updatedAtTimestamp
diff --git a/packages/subgraph/subgraph.template.yaml b/packages/subgraph/subgraph.template.yaml
index 7abe8db0d4..ab144634cf 100644
--- a/packages/subgraph/subgraph.template.yaml
+++ b/packages/subgraph/subgraph.template.yaml
@@ -102,6 +102,7 @@ dataSources:
entities:
- Account
- AccountTokenSnapshot
+ - AccountTokenSnapshotLog
- FlowOperator
- FlowOperatorUpdatedEvent
- FlowUpdatedEvent
@@ -109,6 +110,7 @@ dataSources:
- StreamPeriod
- StreamRevision
- TokenStatistic
+ - TokenStatisticLog
abis:
- name: IConstantFlowAgreementV1
file: ./abis/IConstantFlowAgreementV1.json
@@ -143,6 +145,7 @@ dataSources:
entities:
- Account
- AccountTokenSnapshot
+ - AccountTokenSnapshotLog
- Index
- IndexCreatedEvent
- IndexDistributionClaimedEvent
@@ -153,6 +156,7 @@ dataSources:
- IndexUnsubscribedEvent
- Token
- TokenStatistic
+ - TokenStatisticLog
- SubscriptionApprovedEvent
- SubscriptionDistributionClaimedEvent
- SubscriptionRevokedEvent
@@ -197,6 +201,59 @@ dataSources:
- event: SubscriptionUnitsUpdated(indexed address,indexed address,address,uint32,uint128,bytes)
handler: handleSubscriptionUnitsUpdated
receipt: true
+ - kind: ethereum/contract
+ name: GeneralDistributionAgreementV1
+ network: {{ network }}
+ source:
+ address: "{{ gdaAddress }}"
+ abi: IGeneralDistributionAgreementV1
+ startBlock: {{ hostStartBlock }}
+ mapping:
+ kind: ethereum/events
+ apiVersion: 0.0.7
+ language: wasm/assemblyscript
+ file: ./src/mappings/gdav1.ts
+ entities:
+ - Account
+ - AccountTokenSnapshot
+ - AccountTokenSnapshotLog
+ - FlowDistributionUpdatedEvent
+ - InstantDistributionUpdatedEvent
+ - Pool
+ - PoolConnectionUpdatedEvent
+ - PoolCreatedEvent
+ - PoolDistributor
+ - PoolMember
+ - Token
+ - TokenStatistic
+ - TokenStatisticLog
+ abis:
+ - name: IGeneralDistributionAgreementV1
+ file: ./abis/IGeneralDistributionAgreementV1.json
+ - name: ISuperfluidPool
+ file: ./abis/ISuperfluidPool.json
+ - name: ISuperToken
+ file: ./abis/ISuperToken.json
+ - name: Resolver
+ file: ./abis/Resolver.json
+ - name: ISuperfluid
+ file: ./abis/ISuperfluid.json
+ eventHandlers:
+ - event: BufferAdjusted(indexed address,indexed address,indexed address,int256,uint256,uint256)
+ handler: handleBufferAdjusted
+ receipt: true
+ - event: FlowDistributionUpdated(indexed address,indexed address,indexed address,address,int96,int96,int96,address,int96,bytes)
+ handler: handleFlowDistributionUpdated
+ receipt: true
+ - event: InstantDistributionUpdated(indexed address,indexed address,indexed address,address,uint256,uint256,bytes)
+ handler: handleInstantDistributionUpdated
+ receipt: true
+ - event: PoolConnectionUpdated(indexed address,indexed address,indexed address,bool,bytes)
+ handler: handlePoolConnectionUpdated
+ receipt: true
+ - event: PoolCreated(indexed address,indexed address,address)
+ handler: handlePoolCreated
+ receipt: true
- kind: ethereum/contract
name: ResolverV1
network: {{ network }}
@@ -424,4 +481,41 @@ templates:
receipt: true
- event: BondIncreased(indexed address,uint256)
handler: handleBondIncreased
- receipt: true
\ No newline at end of file
+ receipt: true
+ - kind: ethereum/contract
+ name: SuperfluidPool
+ network: {{ network }}
+ source:
+ abi: ISuperfluidPool
+ mapping:
+ kind: ethereum/events
+ apiVersion: 0.0.7
+ language: wasm/assemblyscript
+ file: ./src/mappings/superfluidPool.ts
+ entities:
+ - Account
+ - AccountTokenSnapshot
+ - AccountTokenSnapshotLog
+ - DistributionClaimedEvent
+ - MemberUnitsUpdatedEvent
+ - Pool
+ - PoolMember
+ - Token
+ - TokenStatistic
+ - TokenStatisticLog
+ abis:
+ - name: ISuperfluidPool
+ file: ./abis/ISuperfluidPool.json
+ - name: ISuperToken
+ file: ./abis/ISuperToken.json
+ - name: Resolver
+ file: ./abis/Resolver.json
+ - name: ISuperfluid
+ file: ./abis/ISuperfluid.json
+ eventHandlers:
+ - event: MemberUnitsUpdated(indexed address,indexed address,uint128,uint128)
+ handler: handleMemberUnitsUpdated
+ receipt: true
+ - event: DistributionClaimed(indexed address,indexed address,int256,int256)
+ handler: handleDistributionClaimed
+ receipt: true
diff --git a/packages/subgraph/tests/assertionHelpers.ts b/packages/subgraph/tests/assertionHelpers.ts
index 572bd9080a..d844ead10d 100644
--- a/packages/subgraph/tests/assertionHelpers.ts
+++ b/packages/subgraph/tests/assertionHelpers.ts
@@ -1,6 +1,6 @@
import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts";
import { assert, log } from "matchstick-as/assembly/index";
-import { createEventID, createLogID, getIndexID, getOrder } from "../src/utils";
+import { BIG_INT_ZERO, createEventID, createLogID, getIndexID, getOrder } from "../src/utils";
// General Assertion Helpers
@@ -45,14 +45,11 @@ export function assertEventBaseProperties(
export function assertHigherOrderBaseProperties(
entityName: string,
id: string,
- createdAtTimestamp: BigInt,
- createdAtBlockNumber: BigInt,
- updatedAtTimestamp: BigInt,
- updatedAtBlockNumber: BigInt
+ event: ethereum.Event,
): void {
- assertAggregateBaseProperties(entityName, id, updatedAtTimestamp, updatedAtBlockNumber);
- assert.fieldEquals(entityName, id, "createdAtTimestamp", createdAtTimestamp.toString());
- assert.fieldEquals(entityName, id, "createdAtBlockNumber", createdAtBlockNumber.toString());
+ assertAggregateBaseProperties(entityName, id, event.block.timestamp, event.block.number);
+ assert.fieldEquals(entityName, id, "createdAtTimestamp", event.block.timestamp.toString());
+ assert.fieldEquals(entityName, id, "createdAtBlockNumber", event.block.number.toString());
}
/**
@@ -145,15 +142,25 @@ export function assertIDAEventBaseProperties(
* @param triggeredByEventName if triggeredByEventName is passed, we validate TokenStatisticLog
* @param updatedAtTimestamp timestamp retrieved from the event
* @param updatedAtBlockNumber block number retrieved from the event
- * @param totalNumberOfActiveStreams expected count of active streams for the token
- * @param totalNumberOfClosedStreams expected count of closed streams for the token
+ * @param totalNumberOfActiveStreams expected count of active streams for the token for all flow agreements
+ * @param totalCFANumberOfActiveStreams expected count of active streams for the token for the CFA
+ * @param totalGDANumberOfActiveStreams expected count of active streams for the token for the GDA
+ * @param totalNumberOfClosedStreams expected count of closed streams for the token for all flow agreements
+ * @param totalNumberOfCFAClosedStreams expected count of closed streams for the token for the CFA
+ * @param totalNumberOfGDAClosedStreams expected count of closed streams for the token for the GDA
* @param totalNumberOfIndexes expected count of indexes for the token
* @param totalNumberOfActiveIndexes expected count of active indexes for the token
* @param totalSubscriptionsWithUnits expected count of subscriptions with allocated units for the token
* @param totalApprovedSubscriptions expected totalNumber of approved subscriptions for the token
- * @param totalDeposit expected total deposit amount
- * @param totalOutflowRate expected total outflow rate
- * @param totalAmountStreamedUntilUpdatedAt expected total amount streamed until updated at timestamp
+ * @param totalDeposit expected total deposit amount for all flow agreements
+ * @param totalCFADeposit expected total deposit amount for the CFA
+ * @param totalGDADeposit expected total deposit amount for the GDA
+ * @param totalOutflowRate expected total outflow rate for all flow agreements
+ * @param totalCFAOutflowRate expected total outflow rate for the CFA
+ * @param totalGDAOutflowRate expected total outflow rate for the GDA
+ * @param totalAmountStreamedUntilUpdatedAt expected total amount streamed until updated at timestamp for all flow agreements
+ * @param totalCFAAmountStreamedUntilUpdatedAt expected total amount streamed until updated at timestamp for the CFA
+ * @param totalGDAAmountStreamedUntilUpdatedAt expected total amount streamed until updated at timestamp for the GDA
* @param totalAmountTransferredUntilUpdatedAt expected total amount transferred until updated at timestamp
* @param totalAmountDistributedUntilUpdatedAt expected total amount distributed (with IDA) until updated at timestamp
* @param totalSupply expected total supply
@@ -167,14 +174,24 @@ export function assertTokenStatisticProperties(
updatedAtTimestamp: BigInt,
updatedAtBlockNumber: BigInt,
totalNumberOfActiveStreams: i32,
+ totalCFANumberOfActiveStreams: i32,
+ totalGDANumberOfActiveStreams: i32,
totalNumberOfClosedStreams: i32,
+ totalCFANumberOfClosedStreams: i32,
+ totalGDANumberOfClosedStreams: i32,
totalNumberOfIndexes: i32,
totalNumberOfActiveIndexes: i32,
totalSubscriptionsWithUnits: i32,
totalApprovedSubscriptions: i32,
totalDeposit: BigInt,
+ totalCFADeposit: BigInt,
+ totalGDADeposit: BigInt,
totalOutflowRate: BigInt,
+ totalCFAOutflowRate: BigInt,
+ totalGDAOutflowRate: BigInt,
totalAmountStreamedUntilUpdatedAt: BigInt,
+ totalCFAAmountStreamedUntilUpdatedAt: BigInt,
+ totalGDAAmountStreamedUntilUpdatedAt: BigInt,
totalAmountTransferredUntilUpdatedAt: BigInt,
totalAmountDistributedUntilUpdatedAt: BigInt,
totalSupply: BigInt,
@@ -188,14 +205,24 @@ export function assertTokenStatisticProperties(
const entityName = "TokenStatistic";
assertAggregateBaseProperties(entityName, id, updatedAtTimestamp, updatedAtBlockNumber);
assert.fieldEquals(entityName, id, "totalNumberOfActiveStreams", totalNumberOfActiveStreams.toString());
+ assert.fieldEquals(entityName, id, "totalCFANumberOfActiveStreams", totalCFANumberOfActiveStreams.toString());
+ assert.fieldEquals(entityName, id, "totalGDANumberOfActiveStreams", totalGDANumberOfActiveStreams.toString());
assert.fieldEquals(entityName, id, "totalNumberOfClosedStreams", totalNumberOfClosedStreams.toString());
+ assert.fieldEquals(entityName, id, "totalCFANumberOfClosedStreams", totalCFANumberOfClosedStreams.toString());
+ assert.fieldEquals(entityName, id, "totalGDANumberOfClosedStreams", totalGDANumberOfClosedStreams.toString());
assert.fieldEquals(entityName, id, "totalNumberOfIndexes", totalNumberOfIndexes.toString());
assert.fieldEquals(entityName, id, "totalNumberOfActiveIndexes", totalNumberOfActiveIndexes.toString());
assert.fieldEquals(entityName, id, "totalSubscriptionsWithUnits", totalSubscriptionsWithUnits.toString());
assert.fieldEquals(entityName, id, "totalApprovedSubscriptions", totalApprovedSubscriptions.toString());
assert.fieldEquals(entityName, id, "totalDeposit", totalDeposit.toString());
+ assert.fieldEquals(entityName, id, "totalCFADeposit", totalCFADeposit.toString());
+ assert.fieldEquals(entityName, id, "totalGDADeposit", totalGDADeposit.toString());
assert.fieldEquals(entityName, id, "totalOutflowRate", totalOutflowRate.toString());
+ assert.fieldEquals(entityName, id, "totalCFAOutflowRate", totalCFAOutflowRate.toString());
+ assert.fieldEquals(entityName, id, "totalGDAOutflowRate", totalGDAOutflowRate.toString());
assert.fieldEquals(entityName, id, "totalAmountStreamedUntilUpdatedAt", totalAmountStreamedUntilUpdatedAt.toString());
+ assert.fieldEquals(entityName, id, "totalCFAAmountStreamedUntilUpdatedAt", totalCFAAmountStreamedUntilUpdatedAt.toString());
+ assert.fieldEquals(entityName, id, "totalGDAAmountStreamedUntilUpdatedAt", totalGDAAmountStreamedUntilUpdatedAt.toString());
assert.fieldEquals(entityName, id, "totalAmountTransferredUntilUpdatedAt", totalAmountTransferredUntilUpdatedAt.toString());
assert.fieldEquals(entityName, id, "totalAmountDistributedUntilUpdatedAt", totalAmountDistributedUntilUpdatedAt.toString());
assert.fieldEquals(entityName, id, "totalSupply", totalSupply.toString());
@@ -209,14 +236,24 @@ export function assertTokenStatisticProperties(
event,
triggeredByEventName,
totalNumberOfActiveStreams,
+ totalCFANumberOfActiveStreams,
+ totalGDANumberOfActiveStreams,
totalNumberOfClosedStreams,
+ totalCFANumberOfClosedStreams,
+ totalGDANumberOfClosedStreams,
totalNumberOfIndexes,
totalNumberOfActiveIndexes,
totalSubscriptionsWithUnits,
totalApprovedSubscriptions,
totalDeposit,
+ totalCFADeposit,
+ totalGDADeposit,
totalOutflowRate,
+ totalCFAOutflowRate,
+ totalGDAOutflowRate,
totalAmountStreamedUntilUpdatedAt,
+ totalCFAAmountStreamedUntilUpdatedAt,
+ totalGDAAmountStreamedUntilUpdatedAt,
totalAmountTransferredUntilUpdatedAt,
totalAmountDistributedUntilUpdatedAt,
totalSupply,
@@ -250,15 +287,25 @@ export function assertTokenStatisticProperties(
* Asserts that the properties on a TokenStatisticLog entity are correct.
* @param event ethereum event object
* @param triggeredByEventName name of the event which triggered the creation of this log
- * @param totalNumberOfActiveStreams expected count of active streams for the token
- * @param totalNumberOfClosedStreams expected count of closed streams for the token
+ * @param totalNumberOfActiveStreams expected count of active streams for the token for all flow agreements
+ * @param totalCFANumberOfActiveStreams expected count of active streams for the token for the CFA
+ * @param totalGDANumberOfActiveStreams expected count of active streams for the token for the GDA
+ * @param totalNumberOfClosedStreams expected count of closed streams for the token for all flow agreements
+ * @param totalNumberOfCFAClosedStreams expected count of closed streams for the token for the CFA
+ * @param totalNumberOfGDAClosedStreams expected count of closed streams for the token for the GDA
* @param totalNumberOfIndexes expected count of indexes for the token
* @param totalNumberOfActiveIndexes expected count of active indexes for the token
* @param totalSubscriptionsWithUnits expected count of subscriptions with allocated units for the token
* @param totalApprovedSubscriptions expected totalNumber of approved subscriptions for the token
- * @param totalDeposit expected total deposit amount
- * @param totalOutflowRate expected total outflow rate
- * @param totalAmountStreamed expected total amount streamed until timestamp
+ * @param totalDeposit expected total deposit amount for all flow agreements
+ * @param totalCFADeposit expected total deposit amount for the CFA
+ * @param totalGDADeposit expected total deposit amount for the GDA
+ * @param totalOutflowRate expected total outflow rate for all flow agreements
+ * @param totalCFAOutflowRate expected total outflow rate for the CFA
+ * @param totalGDAOutflowRate expected total outflow rate for the GDA
+ * @param totalAmountStreamed expected total amount streamed until timestamp for all flow agreements
+ * @param totalCFAAmountStreamed expected total amount streamed until timestamp for the CFA
+ * @param totalGDAAmountStreamed expected total amount streamed until timestamp for the GDA
* @param totalAmountTransferred expected total amount transferred until timestamp
* @param totalAmountDistributed expected total amount distributed (with IDA) until timestamp
* @param totalSupply expected total supply
@@ -270,14 +317,24 @@ export function assertTokenStatisticLogProperties(
event: ethereum.Event,
triggeredByEventName: string,
totalNumberOfActiveStreams: i32,
+ totalCFANumberOfActiveStreams: i32,
+ totalGDANumberOfActiveStreams: i32,
totalNumberOfClosedStreams: i32,
+ totalCFANumberOfClosedStreams: i32,
+ totalGDANumberOfClosedStreams: i32,
totalNumberOfIndexes: i32,
totalNumberOfActiveIndexes: i32,
totalSubscriptionsWithUnits: i32,
totalApprovedSubscriptions: i32,
totalDeposit: BigInt,
+ totalCFADeposit: BigInt,
+ totalGDADeposit: BigInt,
totalOutflowRate: BigInt,
+ totalCFAOutflowRate: BigInt,
+ totalGDAOutflowRate: BigInt,
totalAmountStreamed: BigInt,
+ totalCFAAmountStreamed: BigInt,
+ totalGDAAmountStreamed: BigInt,
totalAmountTransferred: BigInt,
totalAmountDistributed: BigInt,
totalSupply: BigInt,
@@ -298,19 +355,80 @@ export function assertTokenStatisticLogProperties(
assert.fieldEquals(entityName, id, "order", order.toString());
assert.fieldEquals(entityName, id, "triggeredByEventName", triggeredByEventName);
assert.fieldEquals(entityName, id, "totalNumberOfActiveStreams", totalNumberOfActiveStreams.toString());
+ assert.fieldEquals(entityName, id, "totalCFANumberOfActiveStreams", totalCFANumberOfActiveStreams.toString());
+ assert.fieldEquals(entityName, id, "totalGDANumberOfActiveStreams", totalGDANumberOfActiveStreams.toString());
+ assert.fieldEquals(entityName, id, "totalNumberOfClosedStreams", totalNumberOfClosedStreams.toString());
+ assert.fieldEquals(entityName, id, "totalCFANumberOfClosedStreams", totalCFANumberOfClosedStreams.toString());
+ assert.fieldEquals(entityName, id, "totalGDANumberOfClosedStreams", totalGDANumberOfClosedStreams.toString());
assert.fieldEquals(entityName, id, "totalNumberOfClosedStreams", totalNumberOfClosedStreams.toString());
assert.fieldEquals(entityName, id, "totalNumberOfIndexes", totalNumberOfIndexes.toString());
assert.fieldEquals(entityName, id, "totalNumberOfActiveIndexes", totalNumberOfActiveIndexes.toString());
assert.fieldEquals(entityName, id, "totalSubscriptionsWithUnits", totalSubscriptionsWithUnits.toString());
assert.fieldEquals(entityName, id, "totalApprovedSubscriptions", totalApprovedSubscriptions.toString());
assert.fieldEquals(entityName, id, "totalDeposit", totalDeposit.toString());
+ assert.fieldEquals(entityName, id, "totalCFADeposit", totalCFADeposit.toString());
+ assert.fieldEquals(entityName, id, "totalGDADeposit", totalGDADeposit.toString());
assert.fieldEquals(entityName, id, "totalOutflowRate", totalOutflowRate.toString());
+ assert.fieldEquals(entityName, id, "totalCFAOutflowRate", totalCFAOutflowRate.toString());
+ assert.fieldEquals(entityName, id, "totalGDAOutflowRate", totalGDAOutflowRate.toString());
assert.fieldEquals(entityName, id, "totalAmountStreamed", totalAmountStreamed.toString());
+ assert.fieldEquals(entityName, id, "totalCFAAmountStreamed", totalCFAAmountStreamed.toString());
+ assert.fieldEquals(entityName, id, "totalGDAAmountStreamed", totalGDAAmountStreamed.toString());
assert.fieldEquals(entityName, id, "totalAmountTransferred", totalAmountTransferred.toString());
assert.fieldEquals(entityName, id, "totalAmountDistributed", totalAmountDistributed.toString());
assert.fieldEquals(entityName, id, "totalSupply", totalSupply.toString());
assert.fieldEquals(entityName, id, "token", tokenAddress);
- assert.fieldEquals(entityName, id, "totalNumberOfAccounts", totalNumberOfAccounts.toString());
assert.fieldEquals(entityName, id, "tokenStatistic", tokenAddress);
+ assert.fieldEquals(entityName, id, "totalNumberOfAccounts", totalNumberOfAccounts.toString());
assert.fieldEquals(entityName, id, "totalNumberOfHolders", totalNumberOfHolders.toString());
+}
+
+/**
+ * Asserts that the properties on an "empty" initialized TokenStatistic entity are correct.
+ * @param id the token address
+ * @param event if event is passed, we validate TokenStatisticLog
+ * @param triggeredByEventName if triggeredByEventName is passed, we validate TokenStatisticLog
+ * @param updatedAtTimestamp timestamp retrieved from the event
+ * @param updatedAtBlockNumber block number retrieved from the event
+ * @param totalSupply expected total supply
+ */
+export function assertEmptyTokenStatisticProperties(
+ event: ethereum.Event | null,
+ triggeredByEventName: string | null,
+ id: string,
+ updatedAtTimestamp: BigInt,
+ updatedAtBlockNumber: BigInt,
+ totalSupply: BigInt
+): void {
+ assertTokenStatisticProperties(
+ event,
+ triggeredByEventName,
+ id,
+ updatedAtTimestamp,
+ updatedAtBlockNumber,
+ 0, // totalNumberOfActiveStreams
+ 0, // totalCFANumberOfActiveStreams
+ 0, // totalGDANumberOfActiveStreams
+ 0, // totalNumberOfClosedStreams
+ 0, // totalCFANumberOfClosedStreams
+ 0, // totalGDANumberOfClosedStreams
+ 0, // totalNumberOfIndexes
+ 0, // totalNumberOfActiveIndexes
+ 0, // totalSubscriptionsWithUnits
+ 0, // totalApprovedSubscriptions
+ BIG_INT_ZERO, // totalDeposit
+ BIG_INT_ZERO, // totalCFADeposit
+ BIG_INT_ZERO, // totalGDADeposit
+ BIG_INT_ZERO, // totalOutflowRate
+ BIG_INT_ZERO, // totalCFAOutflowRate
+ BIG_INT_ZERO, // totalGDAOutflowRate
+ BIG_INT_ZERO, // totalAmountStreamedUntilUpdatedAt
+ BIG_INT_ZERO, // totalCFAAmountStreamedUntilUpdatedAt
+ BIG_INT_ZERO, // totalGDAAmountStreamedUntilUpdatedAt
+ BIG_INT_ZERO, // totalAmountTransferredUntilUpdatedAt
+ BIG_INT_ZERO, // totalAmountDistributedUntilUpdatedAt
+ totalSupply, // totalSupply
+ 0, // totalNumberOfAccounts
+ 0 // totalNumberOfHolders
+ )
}
\ No newline at end of file
diff --git a/packages/subgraph/tests/cfav1/hol/cfav1.hol.test.ts b/packages/subgraph/tests/cfav1/hol/cfav1.hol.test.ts
index da836836d1..5beb4a9368 100644
--- a/packages/subgraph/tests/cfav1/hol/cfav1.hol.test.ts
+++ b/packages/subgraph/tests/cfav1/hol/cfav1.hol.test.ts
@@ -1,23 +1,21 @@
import { Address, BigInt } from "@graphprotocol/graph-ts";
+import { assert, beforeEach, clearStore, describe, test } from "matchstick-as/assembly/index";
+import { handleFlowOperatorUpdated } from "../../../src/mappings/cfav1";
import {
- assert,
- beforeEach,
- clearStore,
- describe,
- test,
-} from "matchstick-as/assembly/index";
-import {
- handleFlowOperatorUpdated,
-} from "../../../src/mappings/cfav1";
-import { BIG_INT_ZERO, getAccountTokenSnapshotID, getFlowOperatorID, getStreamID, ZERO_ADDRESS } from "../../../src/utils";
-import {
- assertHigherOrderBaseProperties,
-} from "../../assertionHelpers";
+ BIG_INT_ZERO,
+ getAccountTokenSnapshotID,
+ getFlowOperatorID,
+ getStreamID,
+ ZERO_ADDRESS,
+} from "../../../src/utils";
+import { assertHigherOrderBaseProperties } from "../../assertionHelpers";
import { alice, bob, maticXAddress, maticXName, maticXSymbol } from "../../constants";
import {
- createFlowOperatorUpdatedEvent, getDeposit, modifyFlowAndAssertFlowUpdatedEventProperties,
+ createFlowOperatorUpdatedEvent,
+ getDeposit,
+ modifyFlowAndAssertFlowUpdatedEventProperties,
} from "../cfav1.helper";
-import {mockedApprove} from "../../mockedFunctions";
+import { mockedApprove } from "../../mockedFunctions";
const initialFlowRate = BigInt.fromI32(100);
@@ -57,11 +55,7 @@ describe("ConstantFlowAgreementV1 Higher Order Level Entity Unit Tests", () => {
BIG_INT_ZERO
);
- assert.fieldEquals("Stream", id, "id", id);
- assert.fieldEquals("Stream", id, "createdAtTimestamp", flowUpdatedEvent.block.timestamp.toString());
- assert.fieldEquals("Stream", id, "createdAtBlockNumber", flowUpdatedEvent.block.number.toString());
- assert.fieldEquals("Stream", id, "updatedAtTimestamp", flowUpdatedEvent.block.timestamp.toString());
- assert.fieldEquals("Stream", id, "updatedAtBlockNumber", flowUpdatedEvent.block.number.toString());
+ assertHigherOrderBaseProperties("Stream", id, flowUpdatedEvent);
assert.fieldEquals("Stream", id, "currentFlowRate", flowUpdatedEvent.params.flowRate.toString());
assert.fieldEquals("Stream", id, "deposit", deposit.toString());
assert.fieldEquals("Stream", id, "streamedUntilUpdatedAt", streamedUntilUpdatedAt.toString());
@@ -98,14 +92,7 @@ describe("ConstantFlowAgreementV1 Higher Order Level Entity Unit Tests", () => {
Address.fromString(sender)
);
const atsId = getAccountTokenSnapshotID(Address.fromString(sender), Address.fromString(superToken));
- assertHigherOrderBaseProperties(
- "FlowOperator",
- id,
- flowOperatorUpdatedEvent.block.timestamp,
- flowOperatorUpdatedEvent.block.number,
- flowOperatorUpdatedEvent.block.timestamp,
- flowOperatorUpdatedEvent.block.number
- );
+ assertHigherOrderBaseProperties("FlowOperator", id, flowOperatorUpdatedEvent);
assert.fieldEquals("FlowOperator", id, "permissions", permissions.toString());
assert.fieldEquals("FlowOperator", id, "flowRateAllowanceGranted", flowRateAllowance.toString());
assert.fieldEquals("FlowOperator", id, "flowRateAllowanceRemaining", flowRateAllowance.toString());
@@ -117,7 +104,6 @@ describe("ConstantFlowAgreementV1 Higher Order Level Entity Unit Tests", () => {
});
});
-
/**
* Calculates the streamedUntilUpdatedAt.
* @param streamedSoFar
@@ -132,7 +118,5 @@ function _getStreamedUntilUpdatedAt(
lastUpdatedAtTime: BigInt,
previousOutflowRate: BigInt
): BigInt {
- return streamedSoFar.plus(
- previousOutflowRate.times(currentTime.minus(lastUpdatedAtTime))
- );
+ return streamedSoFar.plus(previousOutflowRate.times(currentTime.minus(lastUpdatedAtTime)));
}
diff --git a/packages/subgraph/tests/constants.ts b/packages/subgraph/tests/constants.ts
index c4582b3b10..1c15feacfc 100644
--- a/packages/subgraph/tests/constants.ts
+++ b/packages/subgraph/tests/constants.ts
@@ -18,21 +18,23 @@ export const bob = "0x70997970c51812dc3a010c7d01b50e0d17dc79c8";
export const charlie = "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc";
export const delta = "0x90f79bf6eb2c4f870365e785982e1f101e93b906";
export const echo = "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65";
+export const superfluidPool = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
// contract addresses (polygon)
export const hostAddress = "0x3e14dc1b13c488a8d5d310918780c983bd5982e7";
export const cfaV1Address = "0x6eee6060f715257b970700bc2656de21dedf074c";
export const idaV1Address = "0xb0aabba4b2783a72c52956cdef62d438eca2d7a1";
export const superTokenFactoryAddress = "0x2c90719f25b10fc5646c82da3240c76fa5bccf34";
-export const superTokenLogicAddress = "0xd15c6953c0a7fcc324e835f397496d53751441e2";
+export const superTokenLogicAddress = "0x1349b5f1006ef0366a7b6ae41fa9155c6cd91e4b";
export const resolverAddress = "0xe0cc76334405ee8b39213e620587d815967af39c";
+// this is not the actual TOGA
export const togaAddress = "0x6aeaee5fd4d05a741723d752d30ee4d72690a8f7";
export const maticXAddress = "0x3ad736904e9e65189c3000c7dd2c8ac8bb7cd4e3";
export const maticXSymbol = "MATICx";
export const maticXName = "Super MATIC";
-export const daiXAddress = "0x1305f6b6df9dc47159d12eb7ac2804d4a33173c2";
+export const daiXAddress = "0x5d8b4c2554aeb7e86f387b4d6c00ac33499ed01f";
export const daiXSymbol = "DAIx";
export const daiXName = "Super DAI (PoS)";
-export const daiAddress = "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063";
+export const daiAddress = "0x15f0ca26781c3852f8166ed2ebce5d18265cceb7";
export const daiSymbol = "DAI";
export const daiName = "(PoS) Dai Stablecoin (DAI)";
diff --git a/packages/subgraph/tests/gdav1/event/gdav1.event.test.ts b/packages/subgraph/tests/gdav1/event/gdav1.event.test.ts
new file mode 100644
index 0000000000..ee21c7b029
--- /dev/null
+++ b/packages/subgraph/tests/gdav1/event/gdav1.event.test.ts
@@ -0,0 +1,260 @@
+import { Address, BigInt } from "@graphprotocol/graph-ts";
+import { assert, beforeEach, clearStore, describe, test } from "matchstick-as/assembly/index";
+import {
+ handleBufferAdjusted,
+ handleFlowDistributionUpdated,
+ handleInstantDistributionUpdated,
+} from "../../../src/mappings/gdav1";
+import { handleDistributionClaimed } from "../../../src/mappings/superfluidPool";
+import { BIG_INT_ZERO, getPoolDistributorID, getPoolMemberID } from "../../../src/utils";
+import { assertEventBaseProperties } from "../../assertionHelpers";
+import { FAKE_INITIAL_BALANCE, FALSE, TRUE, alice, bob, maticXAddress, superfluidPool } from "../../constants";
+import {
+ createBufferAdjustedEvent,
+ createDistributionClaimedEvent,
+ createFlowDistributionUpdatedEvent,
+ createInstantDistributionUpdatedEvent,
+ createPoolAndReturnPoolCreatedEvent
+} from "../gdav1.helper";
+import { mockedGetAppManifest, mockedRealtimeBalanceOf } from "../../mockedFunctions";
+import { updatePoolConnectionAndReturnPoolConnectionUpdatedEvent } from "../gdav1.helper";
+import { updateMemberUnitsAndReturnMemberUnitsUpdatedEvent } from "../gdav1.helper";
+import { stringToBytes } from "../../converters";
+
+const initialFlowRate = BigInt.fromI32(100);
+const superToken = maticXAddress;
+
+describe("GeneralDistributionAgreementV1 Event Entity Unit Tests", () => {
+ beforeEach(() => {
+ clearStore();
+ });
+
+ test("handlePoolCreated() - Should create a new PoolCreatedEvent entity", () => {
+ const admin = bob;
+ const poolCreatedEvent = createPoolAndReturnPoolCreatedEvent(admin, superToken, superfluidPool);
+
+ const id = assertEventBaseProperties(poolCreatedEvent, "PoolCreated");
+ assert.fieldEquals("PoolCreatedEvent", id, "token", superToken);
+ assert.fieldEquals("PoolCreatedEvent", id, "caller", poolCreatedEvent.transaction.from.toHexString());
+ assert.fieldEquals("PoolCreatedEvent", id, "admin", admin);
+ assert.fieldEquals("PoolCreatedEvent", id, "pool", superfluidPool);
+ });
+
+ test("handlePoolConnectionUpdated() - Should create a new handlePoolConnectionUpdatedEvent entity (connected)", () => {
+ const account = bob;
+ const connected = true;
+ const userData = stringToBytes("");
+
+ const poolConnectionUpdatedEvent = updatePoolConnectionAndReturnPoolConnectionUpdatedEvent(
+ superToken,
+ account,
+ superfluidPool,
+ connected,
+ initialFlowRate,
+ userData
+ );
+
+ const poolMemberId = getPoolMemberID(Address.fromString(superfluidPool), Address.fromString(account));
+
+ const id = assertEventBaseProperties(poolConnectionUpdatedEvent, "PoolConnectionUpdated");
+ assert.fieldEquals("PoolConnectionUpdatedEvent", id, "token", superToken);
+ assert.fieldEquals("PoolConnectionUpdatedEvent", id, "connected", TRUE);
+ assert.fieldEquals("PoolConnectionUpdatedEvent", id, "pool", superfluidPool);
+ assert.fieldEquals("PoolConnectionUpdatedEvent", id, "poolMember", poolMemberId);
+ });
+
+ test("handlePoolConnectionUpdated() - Should create a new handlePoolConnectionUpdatedEvent entity (disconnected)", () => {
+ const account = bob;
+ const connected = false;
+ const userData = stringToBytes("");
+
+ const poolConnectionUpdatedEvent = updatePoolConnectionAndReturnPoolConnectionUpdatedEvent(
+ superToken,
+ account,
+ superfluidPool,
+ connected,
+ initialFlowRate,
+ userData
+ );
+
+ const poolMemberId = getPoolMemberID(Address.fromString(superfluidPool), Address.fromString(account));
+
+ const id = assertEventBaseProperties(poolConnectionUpdatedEvent, "PoolConnectionUpdated");
+ assert.fieldEquals("PoolConnectionUpdatedEvent", id, "token", superToken);
+ assert.fieldEquals("PoolConnectionUpdatedEvent", id, "connected", FALSE);
+ assert.fieldEquals("PoolConnectionUpdatedEvent", id, "pool", superfluidPool);
+ assert.fieldEquals("PoolConnectionUpdatedEvent", id, "poolMember", poolMemberId);
+ });
+
+ test("handleBufferAdjusted() - Should create a new handleBufferAdjustedEvent entity", () => {
+ const bufferDelta = BigInt.fromI32(69);
+ const newBufferAmount = BigInt.fromI32(420);
+ const totalBufferAmount = BigInt.fromI32(42069);
+ const poolDistributor = alice;
+
+ const bufferAdjustedEvent = createBufferAdjustedEvent(
+ maticXAddress,
+ superfluidPool,
+ poolDistributor,
+ bufferDelta,
+ newBufferAmount,
+ totalBufferAmount
+ );
+
+ const poolDistributorId = getPoolDistributorID(
+ Address.fromString(superfluidPool),
+ Address.fromString(poolDistributor)
+ );
+
+ handleBufferAdjusted(bufferAdjustedEvent);
+
+ const id = assertEventBaseProperties(bufferAdjustedEvent, "BufferAdjusted");
+ assert.fieldEquals("BufferAdjustedEvent", id, "token", maticXAddress);
+ assert.fieldEquals("BufferAdjustedEvent", id, "pool", superfluidPool);
+ assert.fieldEquals("BufferAdjustedEvent", id, "poolDistributor", poolDistributorId.toString());
+ assert.fieldEquals("BufferAdjustedEvent", id, "bufferDelta", bufferDelta.toString());
+ assert.fieldEquals("BufferAdjustedEvent", id, "newBufferAmount", newBufferAmount.toString());
+ assert.fieldEquals("BufferAdjustedEvent", id, "totalBufferAmount", totalBufferAmount.toString());
+ });
+
+ test("handleInstantDistributionUpdated() - Should create a new handleInstantDistributionUpdatedEvent entity", () => {
+ const operator = alice;
+ const requestedAmount = BigInt.fromI32(69);
+ const actualAmount = BigInt.fromI32(70);
+ const poolDistributor = bob;
+ const userData = stringToBytes("");
+
+ const instantDistributionUpdatedEvent = createInstantDistributionUpdatedEvent(
+ superToken,
+ superfluidPool,
+ poolDistributor,
+ operator,
+ requestedAmount,
+ actualAmount,
+ userData
+ );
+
+ const poolDistributorId = getPoolDistributorID(
+ Address.fromString(superfluidPool),
+ Address.fromString(poolDistributor)
+ );
+
+ handleInstantDistributionUpdated(instantDistributionUpdatedEvent);
+
+ const id = assertEventBaseProperties(instantDistributionUpdatedEvent, "InstantDistributionUpdated");
+ assert.fieldEquals("InstantDistributionUpdatedEvent", id, "token", superToken);
+ assert.fieldEquals("InstantDistributionUpdatedEvent", id, "pool", superfluidPool);
+ assert.fieldEquals("InstantDistributionUpdatedEvent", id, "poolDistributor", poolDistributorId.toString());
+ assert.fieldEquals("InstantDistributionUpdatedEvent", id, "operator", operator);
+ assert.fieldEquals("InstantDistributionUpdatedEvent", id, "requestedAmount", requestedAmount.toString());
+ assert.fieldEquals("InstantDistributionUpdatedEvent", id, "actualAmount", actualAmount.toString());
+ });
+
+ test("handleFlowDistributionUpdated() - Should create a new handleFlowDistributionUpdatedEvent entity", () => {
+ const operator = alice;
+ const oldFlowRate = BigInt.fromI32(69);
+ const newDistributorToPoolFlowRate = BigInt.fromI32(420);
+ const newTotalDistributionFlowRate = BigInt.fromI32(42069);
+ const adjustmentFlowRecipient = alice;
+ const adjustmentFlowRate = BigInt.fromI32(5);
+ const poolDistributor = bob;
+ const userData = stringToBytes("");
+
+ const flowDistributionUpdatedEvent = createFlowDistributionUpdatedEvent(
+ superToken,
+ superfluidPool,
+ poolDistributor,
+ operator,
+ oldFlowRate,
+ newDistributorToPoolFlowRate,
+ newTotalDistributionFlowRate,
+ adjustmentFlowRecipient,
+ adjustmentFlowRate,
+ userData
+ );
+
+ const poolDistributorId = getPoolDistributorID(
+ Address.fromString(superfluidPool),
+ Address.fromString(poolDistributor)
+ );
+
+ handleFlowDistributionUpdated(flowDistributionUpdatedEvent);
+
+ const id = assertEventBaseProperties(flowDistributionUpdatedEvent, "FlowDistributionUpdated");
+ assert.fieldEquals("FlowDistributionUpdatedEvent", id, "token", superToken);
+ assert.fieldEquals("FlowDistributionUpdatedEvent", id, "pool", superfluidPool);
+ assert.fieldEquals("FlowDistributionUpdatedEvent", id, "poolDistributor", poolDistributorId.toString());
+ assert.fieldEquals("FlowDistributionUpdatedEvent", id, "operator", operator);
+ assert.fieldEquals("FlowDistributionUpdatedEvent", id, "oldFlowRate", oldFlowRate.toString());
+ assert.fieldEquals(
+ "FlowDistributionUpdatedEvent",
+ id,
+ "newDistributorToPoolFlowRate",
+ newDistributorToPoolFlowRate.toString()
+ );
+ assert.fieldEquals(
+ "FlowDistributionUpdatedEvent",
+ id,
+ "newTotalDistributionFlowRate",
+ newTotalDistributionFlowRate.toString()
+ );
+ assert.fieldEquals("FlowDistributionUpdatedEvent", id, "adjustmentFlowRecipient", adjustmentFlowRecipient);
+ assert.fieldEquals("FlowDistributionUpdatedEvent", id, "adjustmentFlowRate", adjustmentFlowRate.toString());
+ });
+
+ test("handleDistributionClaimed() - Should create a new DistributionClaimedEvent entity", () => {
+ const poolMember = alice;
+ const claimedAmount = BigInt.fromI32(69);
+ const totalClaimed = BigInt.fromI32(420);
+
+ const distributionClaimedEvent = createDistributionClaimedEvent(
+ superToken,
+ poolMember,
+ claimedAmount,
+ totalClaimed
+ );
+
+ // getOrInitAccountTokenSnapshot(event) => getOrInitAccount(account) => host.try_getAppManifest(account)
+ mockedGetAppManifest(poolMember, false, false, BIG_INT_ZERO);
+
+ // updateATSStreamedAndBalanceUntilUpdatedAt => updateATSBalanceAndUpdatedAt => try_realtimeBalanceOf(poolMember)
+ mockedRealtimeBalanceOf(
+ superToken,
+ poolMember,
+ distributionClaimedEvent.block.timestamp,
+ FAKE_INITIAL_BALANCE.plus(initialFlowRate),
+ initialFlowRate,
+ BIG_INT_ZERO
+ );
+
+ const poolMemberId = getPoolMemberID(distributionClaimedEvent.address, Address.fromString(poolMember));
+
+ handleDistributionClaimed(distributionClaimedEvent);
+
+ const id = assertEventBaseProperties(distributionClaimedEvent, "DistributionClaimed");
+ assert.fieldEquals("DistributionClaimedEvent", id, "token", superToken);
+ assert.fieldEquals("DistributionClaimedEvent", id, "claimedAmount", claimedAmount.toString());
+ assert.fieldEquals("DistributionClaimedEvent", id, "totalClaimed", totalClaimed.toString());
+ assert.fieldEquals("DistributionClaimedEvent", id, "poolMember", poolMemberId.toString());
+ });
+
+ test("handleMemberUnitsUpdated() - Should create a new MemberUnitsUpdatedEvent entity", () => {
+ const oldUnits = BigInt.fromI32(0);
+ const newUnits = BigInt.fromI32(69);
+ const poolMember = bob;
+
+ const memberUnitsUpdatedEvent = updateMemberUnitsAndReturnMemberUnitsUpdatedEvent(
+ superToken,
+ poolMember,
+ oldUnits,
+ newUnits
+ );
+
+ const poolMemberId = getPoolMemberID(memberUnitsUpdatedEvent.address, Address.fromString(poolMember));
+
+ const id = assertEventBaseProperties(memberUnitsUpdatedEvent, "MemberUnitsUpdated");
+ assert.fieldEquals("MemberUnitsUpdatedEvent", id, "token", superToken);
+ assert.fieldEquals("MemberUnitsUpdatedEvent", id, "poolMember", poolMemberId.toString());
+ assert.fieldEquals("MemberUnitsUpdatedEvent", id, "units", newUnits.toString());
+ });
+});
diff --git a/packages/subgraph/tests/gdav1/gdav1.helper.ts b/packages/subgraph/tests/gdav1/gdav1.helper.ts
new file mode 100644
index 0000000000..d170b5b2fa
--- /dev/null
+++ b/packages/subgraph/tests/gdav1/gdav1.helper.ts
@@ -0,0 +1,227 @@
+import { newMockEvent } from "matchstick-as";
+import {
+ BufferAdjusted,
+ FlowDistributionUpdated,
+ InstantDistributionUpdated,
+ PoolConnectionUpdated,
+ PoolCreated,
+} from "../../generated/GeneralDistributionAgreementV1/IGeneralDistributionAgreementV1";
+import {
+ DistributionClaimed,
+ MemberUnitsUpdated,
+} from "../../generated/GeneralDistributionAgreementV1/ISuperfluidPool";
+import { getAddressEventParam, getBigIntEventParam, getBooleanEventParam, getBytesEventParam } from "../converters";
+import { BigInt, Bytes } from "@graphprotocol/graph-ts";
+import { handlePoolConnectionUpdated, handlePoolCreated } from "../../src/mappings/gdav1";
+import { BIG_INT_ZERO } from "../../src/utils";
+import { FAKE_INITIAL_BALANCE } from "../constants";
+import { mockedGetAppManifest, mockedRealtimeBalanceOf } from "../mockedFunctions";
+import { handleMemberUnitsUpdated } from "../../src/mappings/superfluidPool";
+
+export function createPoolAndReturnPoolCreatedEvent(
+ admin: string,
+ superToken: string,
+ superfluidPool: string,
+ initialFlowRate: BigInt = BIG_INT_ZERO
+): PoolCreated {
+ const poolCreatedEvent = createPoolCreatedEvent(superToken, admin, superfluidPool);
+
+ // getOrInitAccountTokenSnapshot(event) => getOrInitAccount(admin) => host.try_getAppManifest(admin)
+ mockedGetAppManifest(admin, false, false, BIG_INT_ZERO);
+
+ // updateATSStreamedAndBalanceUntilUpdatedAt => updateATSBalanceAndUpdatedAt => try_realtimeBalanceOf(admin)
+ mockedRealtimeBalanceOf(
+ superToken,
+ admin,
+ poolCreatedEvent.block.timestamp,
+ FAKE_INITIAL_BALANCE.plus(initialFlowRate),
+ initialFlowRate,
+ BIG_INT_ZERO
+ );
+
+ handlePoolCreated(poolCreatedEvent);
+ return poolCreatedEvent;
+}
+
+export function updatePoolConnectionAndReturnPoolConnectionUpdatedEvent(
+ superToken: string,
+ account: string,
+ superfluidPool: string,
+ connected: boolean,
+ initialFlowRate: BigInt,
+ userData: Bytes
+): PoolConnectionUpdated {
+ const poolConnectionUpdatedEvent = createPoolConnectionUpdatedEvent(
+ superToken,
+ superfluidPool,
+ account,
+ connected,
+ userData
+ );
+
+ // getOrInitAccountTokenSnapshot(event) => getOrInitAccount(account) => host.try_getAppManifest(account)
+ mockedGetAppManifest(account, false, false, BIG_INT_ZERO);
+
+ // updateATSStreamedAndBalanceUntilUpdatedAt => updateATSBalanceAndUpdatedAt => try_realtimeBalanceOf(account)
+ mockedRealtimeBalanceOf(
+ superToken,
+ account,
+ poolConnectionUpdatedEvent.block.timestamp,
+ FAKE_INITIAL_BALANCE.plus(initialFlowRate),
+ initialFlowRate,
+ BIG_INT_ZERO
+ );
+
+ handlePoolConnectionUpdated(poolConnectionUpdatedEvent);
+ return poolConnectionUpdatedEvent;
+}
+
+export function updateMemberUnitsAndReturnMemberUnitsUpdatedEvent(
+ superToken: string,
+ poolMember: string,
+ oldUnits: BigInt,
+ newUnits: BigInt
+): MemberUnitsUpdated {
+ const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent(superToken, poolMember, oldUnits, newUnits);
+
+ handleMemberUnitsUpdated(memberUnitsUpdatedEvent);
+
+ return memberUnitsUpdatedEvent;
+}
+
+// Mock Event Creators
+export function createPoolCreatedEvent(token: string, admin: string, pool: string): PoolCreated {
+ const newPoolCreatedEvent = changetype(newMockEvent());
+ newPoolCreatedEvent.parameters = new Array();
+ newPoolCreatedEvent.parameters.push(getAddressEventParam("token", token));
+ newPoolCreatedEvent.parameters.push(getAddressEventParam("admin", admin));
+ newPoolCreatedEvent.parameters.push(getAddressEventParam("pool", pool));
+
+ return newPoolCreatedEvent;
+}
+
+export function createPoolConnectionUpdatedEvent(
+ token: string,
+ pool: string,
+ poolMember: string,
+ connected: boolean,
+ userData: Bytes
+): PoolConnectionUpdated {
+ const newPoolConnectionUpdatedEvent = changetype(newMockEvent());
+ newPoolConnectionUpdatedEvent.parameters = new Array();
+ newPoolConnectionUpdatedEvent.parameters.push(getAddressEventParam("token", token));
+ newPoolConnectionUpdatedEvent.parameters.push(getAddressEventParam("pool", pool));
+ newPoolConnectionUpdatedEvent.parameters.push(getAddressEventParam("poolMember", poolMember));
+ newPoolConnectionUpdatedEvent.parameters.push(getBooleanEventParam("connected", connected));
+ newPoolConnectionUpdatedEvent.parameters.push(getBytesEventParam("userData", userData));
+
+ return newPoolConnectionUpdatedEvent;
+}
+
+export function createBufferAdjustedEvent(
+ token: string,
+ pool: string,
+ poolDistributor: string,
+ bufferDelta: BigInt,
+ newBufferAmount: BigInt,
+ totalBufferAmount: BigInt
+): BufferAdjusted {
+ const newBufferAdjustedEvent = changetype(newMockEvent());
+ newBufferAdjustedEvent.parameters = new Array();
+ newBufferAdjustedEvent.parameters.push(getAddressEventParam("token", token));
+ newBufferAdjustedEvent.parameters.push(getAddressEventParam("pool", pool));
+ newBufferAdjustedEvent.parameters.push(getAddressEventParam("poolDistributor", poolDistributor));
+ newBufferAdjustedEvent.parameters.push(getBigIntEventParam("bufferDelta", bufferDelta));
+ newBufferAdjustedEvent.parameters.push(getBigIntEventParam("newBufferAmount", newBufferAmount));
+ newBufferAdjustedEvent.parameters.push(getBigIntEventParam("totalBufferAmount", totalBufferAmount));
+
+ return newBufferAdjustedEvent;
+}
+
+export function createInstantDistributionUpdatedEvent(
+ token: string,
+ pool: string,
+ poolDistributor: string,
+ operator: string,
+ requestedAmount: BigInt,
+ actualAmount: BigInt,
+ userData: Bytes
+): InstantDistributionUpdated {
+ const newInstantDistributionUpdatedEvent = changetype(newMockEvent());
+ newInstantDistributionUpdatedEvent.parameters = new Array();
+ newInstantDistributionUpdatedEvent.parameters.push(getAddressEventParam("token", token));
+ newInstantDistributionUpdatedEvent.parameters.push(getAddressEventParam("pool", pool));
+ newInstantDistributionUpdatedEvent.parameters.push(getAddressEventParam("poolDistributor", poolDistributor));
+ newInstantDistributionUpdatedEvent.parameters.push(getAddressEventParam("operator", operator));
+ newInstantDistributionUpdatedEvent.parameters.push(getBigIntEventParam("requestedAmount", requestedAmount));
+ newInstantDistributionUpdatedEvent.parameters.push(getBigIntEventParam("actualAmount", actualAmount));
+ newInstantDistributionUpdatedEvent.parameters.push(getBytesEventParam("userData", userData));
+
+ return newInstantDistributionUpdatedEvent;
+}
+
+export function createFlowDistributionUpdatedEvent(
+ token: string,
+ pool: string,
+ poolDistributor: string,
+ operator: string,
+ oldFlowRate: BigInt,
+ newDistributorToPoolFlowRate: BigInt,
+ newTotalDistributionFlowRate: BigInt,
+ adjustmentFlowRecipient: string,
+ adjustmentFlowRate: BigInt,
+ userData: Bytes
+): FlowDistributionUpdated {
+ const newFlowDistributionUpdatedEvent = changetype(newMockEvent());
+ newFlowDistributionUpdatedEvent.parameters = new Array();
+ newFlowDistributionUpdatedEvent.parameters.push(getAddressEventParam("token", token));
+ newFlowDistributionUpdatedEvent.parameters.push(getAddressEventParam("pool", pool));
+ newFlowDistributionUpdatedEvent.parameters.push(getAddressEventParam("poolDistributor", poolDistributor));
+ newFlowDistributionUpdatedEvent.parameters.push(getAddressEventParam("operator", operator));
+ newFlowDistributionUpdatedEvent.parameters.push(getBigIntEventParam("oldFlowRate", oldFlowRate));
+ newFlowDistributionUpdatedEvent.parameters.push(
+ getBigIntEventParam("newDistributorToPoolFlowRate", newDistributorToPoolFlowRate)
+ );
+ newFlowDistributionUpdatedEvent.parameters.push(
+ getBigIntEventParam("newTotalDistributionFlowRate", newTotalDistributionFlowRate)
+ );
+ newFlowDistributionUpdatedEvent.parameters.push(
+ getAddressEventParam("adjustmentFlowRecipient", adjustmentFlowRecipient)
+ );
+ newFlowDistributionUpdatedEvent.parameters.push(getBigIntEventParam("adjustmentFlowRate", adjustmentFlowRate));
+ newFlowDistributionUpdatedEvent.parameters.push(getBytesEventParam("userData", userData));
+
+ return newFlowDistributionUpdatedEvent;
+}
+
+export function createDistributionClaimedEvent(
+ token: string,
+ poolMember: string,
+ claimedAmount: BigInt,
+ totalClaimed: BigInt
+): DistributionClaimed {
+ const newDistributionClaimedEvent = changetype(newMockEvent());
+ newDistributionClaimedEvent.parameters = new Array();
+ newDistributionClaimedEvent.parameters.push(getAddressEventParam("token", token));
+ newDistributionClaimedEvent.parameters.push(getAddressEventParam("poolMember", poolMember));
+ newDistributionClaimedEvent.parameters.push(getBigIntEventParam("claimedAmount", claimedAmount));
+ newDistributionClaimedEvent.parameters.push(getBigIntEventParam("totalClaimed", totalClaimed));
+
+ return newDistributionClaimedEvent;
+}
+
+export function createMemberUnitsUpdatedEvent(
+ token: string,
+ poolMember: string,
+ oldUnits: BigInt,
+ newUnits: BigInt
+): MemberUnitsUpdated {
+ const newMemberUnitsUpdatedEvent = changetype(newMockEvent());
+ newMemberUnitsUpdatedEvent.parameters = new Array();
+ newMemberUnitsUpdatedEvent.parameters.push(getAddressEventParam("token", token));
+ newMemberUnitsUpdatedEvent.parameters.push(getAddressEventParam("poolMember", poolMember));
+ newMemberUnitsUpdatedEvent.parameters.push(getBigIntEventParam("oldUnits", oldUnits));
+ newMemberUnitsUpdatedEvent.parameters.push(getBigIntEventParam("newUnits", newUnits));
+
+ return newMemberUnitsUpdatedEvent;
+}
diff --git a/packages/subgraph/tests/gdav1/hol/gdav1.hol.test.ts b/packages/subgraph/tests/gdav1/hol/gdav1.hol.test.ts
new file mode 100644
index 0000000000..c0e2cfc3f7
--- /dev/null
+++ b/packages/subgraph/tests/gdav1/hol/gdav1.hol.test.ts
@@ -0,0 +1,693 @@
+import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts";
+import { assert, beforeEach, clearStore, describe, test } from "matchstick-as/assembly/index";
+import { BIG_INT_ONE, BIG_INT_ZERO, getPoolDistributorID, getPoolMemberID, ZERO_ADDRESS } from "../../../src/utils";
+import { assertHigherOrderBaseProperties } from "../../assertionHelpers";
+import { FALSE, TRUE, alice, bob, maticXAddress, superfluidPool } from "../../constants";
+import {
+ createBufferAdjustedEvent,
+ createDistributionClaimedEvent,
+ createFlowDistributionUpdatedEvent,
+ createInstantDistributionUpdatedEvent,
+ createMemberUnitsUpdatedEvent,
+ createPoolAndReturnPoolCreatedEvent,
+ updatePoolConnectionAndReturnPoolConnectionUpdatedEvent,
+} from "../gdav1.helper";
+import {
+ handleBufferAdjusted,
+ handleFlowDistributionUpdated,
+ handleInstantDistributionUpdated,
+} from "../../../src/mappings/gdav1";
+import { updateMemberUnitsAndReturnMemberUnitsUpdatedEvent } from "../gdav1.helper";
+import { handleDistributionClaimed, handleMemberUnitsUpdated } from "../../../src/mappings/superfluidPool";
+import { getOrInitPoolMember } from "../../../src/mappingHelpers";
+import { stringToBytes } from "../../converters";
+
+const initialFlowRate = BigInt.fromI32(100);
+const superToken = maticXAddress;
+const admin = alice;
+
+describe("GeneralDistributionAgreementV1 Higher Order Level Entity Unit Tests", () => {
+ beforeEach(() => {
+ clearStore();
+ });
+
+ test("handlePoolCreated() - Should create a new Pool entity (create)", () => {
+ const poolCreatedEvent = createPoolAndReturnPoolCreatedEvent(admin, superToken, superfluidPool);
+
+ const id = superfluidPool;
+ assertEmptyPoolData(id, poolCreatedEvent, superToken);
+ });
+
+ test("handlePoolConnectionUpdated() - Non-Member (0 units) connection updated: Pool entity is unchanged", () => {
+ createPoolAndReturnPoolCreatedEvent(admin, superToken, superfluidPool);
+
+ const account = bob;
+ const connected = true;
+ const userData = stringToBytes("");
+
+ const poolConnectionUpdatedEvent = updatePoolConnectionAndReturnPoolConnectionUpdatedEvent(
+ superToken,
+ account,
+ superfluidPool,
+ connected,
+ initialFlowRate,
+ userData
+ );
+
+ const id = superfluidPool;
+ assertEmptyPoolData(id, poolConnectionUpdatedEvent, superToken);
+ });
+
+ test("handlePoolConnectionUpdated() - Member (>0 units) connection updated: Pool entity changes", () => {
+ const account = bob;
+ const connected = true;
+ const userData = stringToBytes("");
+
+ const memberUnitsUpdatedEvent = updateMemberUnitsAndReturnMemberUnitsUpdatedEvent(
+ superToken,
+ account,
+ BigInt.fromI32(0),
+ BigInt.fromI32(1)
+ );
+
+ const poolConnectionUpdatedEvent = updatePoolConnectionAndReturnPoolConnectionUpdatedEvent(
+ superToken,
+ account,
+ memberUnitsUpdatedEvent.address.toHexString(),
+ connected,
+ initialFlowRate,
+ userData
+ );
+
+ const id = memberUnitsUpdatedEvent.address.toHexString();
+
+ assertHigherOrderBaseProperties("Pool", id, poolConnectionUpdatedEvent);
+ assert.fieldEquals("Pool", id, "totalUnits", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedUnits", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountInstantlyDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountFlowedDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalMembers", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedMembers", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "adjustmentFlowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "token", ZERO_ADDRESS.toHex());
+ assert.fieldEquals("Pool", id, "admin", ZERO_ADDRESS.toHex());
+ });
+
+ test("handlePoolConnectionUpdated() - Pool Entity: Disconnected member connection updated", () => {
+ const account = bob;
+ const connected = false;
+ const userData = stringToBytes("");
+
+ const memberUnitsUpdatedEvent = updateMemberUnitsAndReturnMemberUnitsUpdatedEvent(
+ superToken,
+ account,
+ BigInt.fromI32(0),
+ BigInt.fromI32(1)
+ );
+
+ const poolConnectionUpdatedEvent = updatePoolConnectionAndReturnPoolConnectionUpdatedEvent(
+ superToken,
+ account,
+ memberUnitsUpdatedEvent.address.toHexString(),
+ connected,
+ initialFlowRate,
+ userData
+ );
+
+ const id = memberUnitsUpdatedEvent.address.toHexString();
+
+ assertHigherOrderBaseProperties("Pool", id, poolConnectionUpdatedEvent);
+ assert.fieldEquals("Pool", id, "totalUnits", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedUnits", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "totalAmountInstantlyDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountFlowedDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalMembers", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedMembers", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "adjustmentFlowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "token", ZERO_ADDRESS.toHex());
+ assert.fieldEquals("Pool", id, "admin", ZERO_ADDRESS.toHex());
+ });
+
+ test("handleBufferAdjusted() - Pool Entity: Total buffer value updated", () => {
+ const distributor = alice;
+
+ const BUFFER = BigInt.fromI32(100);
+
+ const bufferAdjustedEvent = createBufferAdjustedEvent(
+ superToken, // token
+ superfluidPool, // pool
+ distributor, // poolDistributor
+ BUFFER, // bufferDelta
+ BUFFER, // newBufferAmount
+ BUFFER // totalBufferAmount
+ );
+
+ handleBufferAdjusted(bufferAdjustedEvent);
+
+ const id = bufferAdjustedEvent.params.pool.toHexString();
+
+ assertHigherOrderBaseProperties("Pool", id, bufferAdjustedEvent);
+ assert.fieldEquals("Pool", id, "totalUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountInstantlyDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountFlowedDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "adjustmentFlowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalBuffer", BUFFER.toString());
+ assert.fieldEquals("Pool", id, "token", ZERO_ADDRESS.toHex());
+ assert.fieldEquals("Pool", id, "admin", ZERO_ADDRESS.toHex());
+ });
+
+ test("handleFlowDistributionUpdated() - Pool Entity: flow related fields updated:", () => {
+ const distributor = alice;
+ const operator = alice;
+ const emptyFlowRate = BigInt.fromI32(0);
+ const newFlowRate = BigInt.fromI32(100000000);
+ const userData = stringToBytes("");
+ const flowDistributionUpdatedEvent = createFlowDistributionUpdatedEvent(
+ superToken,
+ superfluidPool,
+ distributor,
+ operator,
+ emptyFlowRate, // old flow rate
+ newFlowRate, // new distributor to pool flow rate
+ newFlowRate, // new total distribution flow rate
+ alice, // adjustment flow recipient
+ BigInt.fromI32(0), // adjustment flow rate
+ userData
+ );
+
+ handleFlowDistributionUpdated(flowDistributionUpdatedEvent);
+
+ const id = flowDistributionUpdatedEvent.params.pool.toHexString();
+
+ assertHigherOrderBaseProperties("Pool", id, flowDistributionUpdatedEvent);
+ assert.fieldEquals("Pool", id, "totalUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountInstantlyDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountFlowedDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "adjustmentFlowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "flowRate", newFlowRate.toString());
+ assert.fieldEquals("Pool", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "token", ZERO_ADDRESS.toHex());
+ assert.fieldEquals("Pool", id, "admin", ZERO_ADDRESS.toHex());
+ });
+
+ test("handleInstantDistributionUpdated() - Pool Entity: Total distributed amount updated:", () => {
+ const distributor = alice;
+ const operator = alice;
+ const requestedAmount = BigInt.fromI32(100000000);
+ const userData = stringToBytes("");
+ const instantDistributionUpdatedEvent = createInstantDistributionUpdatedEvent(
+ superToken,
+ superfluidPool,
+ distributor,
+ operator,
+ requestedAmount,
+ requestedAmount,
+ userData
+ );
+
+ handleInstantDistributionUpdated(instantDistributionUpdatedEvent);
+
+ const id = instantDistributionUpdatedEvent.params.pool.toHexString();
+
+ assertHigherOrderBaseProperties("Pool", id, instantDistributionUpdatedEvent);
+ assert.fieldEquals("Pool", id, "totalUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountInstantlyDistributedUntilUpdatedAt", requestedAmount.toString());
+ assert.fieldEquals("Pool", id, "totalAmountFlowedDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountDistributedUntilUpdatedAt", requestedAmount.toString());
+ assert.fieldEquals("Pool", id, "totalMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "adjustmentFlowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "token", ZERO_ADDRESS.toHex());
+ assert.fieldEquals("Pool", id, "admin", ZERO_ADDRESS.toHex());
+ });
+
+ test("handleDistributionClaimed() - Pool Entity: No changes", () => {
+ const poolMember = alice;
+ const claimedAmount = BigInt.fromI32(100000000);
+ const distributionClaimedEvent = createDistributionClaimedEvent(
+ superToken,
+ poolMember,
+ claimedAmount,
+ claimedAmount
+ );
+
+ handleDistributionClaimed(distributionClaimedEvent);
+
+ const id = distributionClaimedEvent.address.toHexString();
+
+ assertHigherOrderBaseProperties("Pool", id, distributionClaimedEvent);
+ assert.fieldEquals("Pool", id, "totalUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountInstantlyDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountFlowedDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "adjustmentFlowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "token", ZERO_ADDRESS.toHex());
+ assert.fieldEquals("Pool", id, "admin", ZERO_ADDRESS.toHex());
+ });
+
+ test("handleMemberUnitsUpdated() - Pool Entity: Units data updated (connected member) 0 to > 0 units", () => {
+ const poolMember = alice;
+ const oldUnits = BigInt.fromI32(0);
+ const newUnits = BigInt.fromI32(100000000);
+ const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent(superToken, poolMember, oldUnits, newUnits);
+ const poolMemberEntity = getOrInitPoolMember(
+ memberUnitsUpdatedEvent,
+ memberUnitsUpdatedEvent.address,
+ Address.fromString(poolMember)
+ );
+ poolMemberEntity.isConnected = true;
+ poolMemberEntity.save();
+
+ handleMemberUnitsUpdated(memberUnitsUpdatedEvent);
+
+ const id = memberUnitsUpdatedEvent.address.toHexString();
+
+ assertHigherOrderBaseProperties("Pool", id, memberUnitsUpdatedEvent);
+ assert.fieldEquals("Pool", id, "totalUnits", newUnits.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedUnits", newUnits.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountInstantlyDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountFlowedDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalMembers", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedMembers", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "adjustmentFlowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "token", ZERO_ADDRESS.toHex());
+ assert.fieldEquals("Pool", id, "admin", ZERO_ADDRESS.toHex());
+ });
+
+ test("handleMemberUnitsUpdated() - Pool Entity: Units data updated (connected member) > 0 to 0 units", () => {
+ const poolMember = alice;
+ const oldUnits = BigInt.fromI32(0);
+ const newUnits = BigInt.fromI32(100000000);
+ const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent(superToken, poolMember, oldUnits, newUnits);
+ const poolMemberEntity = getOrInitPoolMember(
+ memberUnitsUpdatedEvent,
+ memberUnitsUpdatedEvent.address,
+ Address.fromString(poolMember)
+ );
+ poolMemberEntity.isConnected = true;
+ poolMemberEntity.save();
+
+ handleMemberUnitsUpdated(memberUnitsUpdatedEvent);
+
+ const memberUnitsUpdatedEventZeroUnits = createMemberUnitsUpdatedEvent(
+ superToken,
+ poolMember,
+ newUnits,
+ BIG_INT_ZERO
+ );
+
+ handleMemberUnitsUpdated(memberUnitsUpdatedEventZeroUnits);
+
+ const id = memberUnitsUpdatedEvent.address.toHexString();
+
+ assertHigherOrderBaseProperties("Pool", id, memberUnitsUpdatedEvent);
+ assert.fieldEquals("Pool", id, "totalUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountInstantlyDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountFlowedDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "adjustmentFlowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "token", ZERO_ADDRESS.toHex());
+ assert.fieldEquals("Pool", id, "admin", ZERO_ADDRESS.toHex());
+ });
+
+ test("handleMemberUnitsUpdated() - Pool Entity: Units data updated (disconnected member) 0 to > 0 units", () => {
+ const poolMember = alice;
+ const oldUnits = BigInt.fromI32(0);
+ const newUnits = BigInt.fromI32(100000000);
+ const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent(superToken, poolMember, oldUnits, newUnits);
+ const poolMemberEntity = getOrInitPoolMember(
+ memberUnitsUpdatedEvent,
+ memberUnitsUpdatedEvent.address,
+ Address.fromString(poolMember)
+ );
+ poolMemberEntity.save();
+
+ handleMemberUnitsUpdated(memberUnitsUpdatedEvent);
+
+ const id = memberUnitsUpdatedEvent.address.toHexString();
+
+ assertHigherOrderBaseProperties("Pool", id, memberUnitsUpdatedEvent);
+ assert.fieldEquals("Pool", id, "totalUnits", newUnits.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedUnits", newUnits.toString());
+ assert.fieldEquals("Pool", id, "totalAmountInstantlyDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountFlowedDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalMembers", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedMembers", BIG_INT_ONE.toString());
+ assert.fieldEquals("Pool", id, "adjustmentFlowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "token", ZERO_ADDRESS.toHex());
+ assert.fieldEquals("Pool", id, "admin", ZERO_ADDRESS.toHex());
+ });
+
+ test("handleMemberUnitsUpdated() - Pool Entity: Units data updated (connected member) > 0 to 0 units", () => {
+ const poolMember = alice;
+ const oldUnits = BigInt.fromI32(0);
+ const newUnits = BigInt.fromI32(100000000);
+ const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent(superToken, poolMember, oldUnits, newUnits);
+ const poolMemberEntity = getOrInitPoolMember(
+ memberUnitsUpdatedEvent,
+ memberUnitsUpdatedEvent.address,
+ Address.fromString(poolMember)
+ );
+ poolMemberEntity.save();
+
+ handleMemberUnitsUpdated(memberUnitsUpdatedEvent);
+
+ const memberUnitsUpdatedEventZeroUnits = createMemberUnitsUpdatedEvent(
+ superToken,
+ poolMember,
+ newUnits,
+ BIG_INT_ZERO
+ );
+
+ handleMemberUnitsUpdated(memberUnitsUpdatedEventZeroUnits);
+
+ const id = memberUnitsUpdatedEvent.address.toHexString();
+
+ assertHigherOrderBaseProperties("Pool", id, memberUnitsUpdatedEvent);
+ assert.fieldEquals("Pool", id, "totalUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountInstantlyDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountFlowedDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "adjustmentFlowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "token", ZERO_ADDRESS.toHex());
+ assert.fieldEquals("Pool", id, "admin", ZERO_ADDRESS.toHex());
+ });
+
+ test("handlePoolConnectionUpdated - PoolMember Entity: isConnected updated from false to true", () => {
+ const account = bob;
+ const connected = true;
+ const oldUnits = BigInt.fromI32(0);
+ const newUnits = BigInt.fromI32(1);
+ const userData = stringToBytes("");
+
+ const memberUnitsUpdatedEvent = updateMemberUnitsAndReturnMemberUnitsUpdatedEvent(
+ superToken,
+ account,
+ oldUnits,
+ newUnits
+ );
+
+ const poolAddress = memberUnitsUpdatedEvent.address;
+
+ const poolConnectionUpdatedEvent = updatePoolConnectionAndReturnPoolConnectionUpdatedEvent(
+ superToken,
+ account,
+ poolAddress.toHexString(),
+ connected,
+ initialFlowRate,
+ userData
+ );
+ const id = getPoolMemberID(poolAddress, Address.fromString(account));
+
+ assertHigherOrderBaseProperties("PoolMember", id, poolConnectionUpdatedEvent);
+ assert.fieldEquals("PoolMember", id, "units", newUnits.toString());
+ assert.fieldEquals("PoolMember", id, "isConnected", TRUE);
+ assert.fieldEquals("PoolMember", id, "totalAmountClaimed", BIG_INT_ZERO.toString());
+ assert.fieldEquals("PoolMember", id, "account", account);
+ assert.fieldEquals("PoolMember", id, "pool", poolAddress.toHexString());
+ });
+
+ test("handlePoolConnectionUpdated - PoolMember Entity: isConnected updated from true to false", () => {
+ const account = bob;
+ const connected = true;
+ const oldUnits = BigInt.fromI32(0);
+ const newUnits = BigInt.fromI32(1);
+ const userData = stringToBytes("");
+
+ const memberUnitsUpdatedEvent = updateMemberUnitsAndReturnMemberUnitsUpdatedEvent(
+ superToken,
+ account,
+ oldUnits,
+ newUnits
+ );
+
+ const poolAddress = memberUnitsUpdatedEvent.address;
+
+ const poolConnectionUpdatedEvent = updatePoolConnectionAndReturnPoolConnectionUpdatedEvent(
+ superToken,
+ account,
+ poolAddress.toHexString(),
+ connected,
+ initialFlowRate,
+ userData
+ );
+
+ updatePoolConnectionAndReturnPoolConnectionUpdatedEvent(
+ superToken,
+ account,
+ poolAddress.toHexString(),
+ false,
+ initialFlowRate,
+ userData
+ );
+ const id = getPoolMemberID(poolAddress, Address.fromString(account));
+
+ assertHigherOrderBaseProperties("PoolMember", id, poolConnectionUpdatedEvent);
+ assert.fieldEquals("PoolMember", id, "units", newUnits.toString());
+ assert.fieldEquals("PoolMember", id, "isConnected", FALSE);
+ assert.fieldEquals("PoolMember", id, "totalAmountClaimed", BIG_INT_ZERO.toString());
+ assert.fieldEquals("PoolMember", id, "account", account);
+ assert.fieldEquals("PoolMember", id, "pool", poolAddress.toHexString());
+ });
+
+ test("handleDistributionClaimed() - PoolMember Entity: totalAmountClaimed updated", () => {
+ const poolMember = alice;
+ const claimedAmount = BigInt.fromI32(100000000);
+ const distributionClaimedEvent = createDistributionClaimedEvent(
+ superToken,
+ poolMember,
+ claimedAmount,
+ claimedAmount
+ );
+
+ const poolAddress = distributionClaimedEvent.address;
+
+ handleDistributionClaimed(distributionClaimedEvent);
+
+ const id = getPoolMemberID(poolAddress, Address.fromString(poolMember));
+
+ assertHigherOrderBaseProperties("PoolMember", id, distributionClaimedEvent);
+ assert.fieldEquals("PoolMember", id, "units", BIG_INT_ZERO.toString());
+ assert.fieldEquals("PoolMember", id, "isConnected", FALSE);
+ assert.fieldEquals("PoolMember", id, "totalAmountClaimed", claimedAmount.toString());
+ assert.fieldEquals("PoolMember", id, "account", poolMember);
+ assert.fieldEquals("PoolMember", id, "pool", poolAddress.toHexString());
+ });
+
+ test("handleMemberUnitsUpdated() - PoolMember Entity: units updated", () => {
+ const poolMember = alice;
+ const oldUnits = BigInt.fromI32(0);
+ const newUnits = BigInt.fromI32(100000000);
+ const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent(superToken, poolMember, oldUnits, newUnits);
+
+ const poolAddress = memberUnitsUpdatedEvent.address;
+
+ handleMemberUnitsUpdated(memberUnitsUpdatedEvent);
+
+ const id = getPoolMemberID(poolAddress, Address.fromString(poolMember));
+
+ assertHigherOrderBaseProperties("PoolMember", id, memberUnitsUpdatedEvent);
+ assert.fieldEquals("PoolMember", id, "units", newUnits.toString());
+ assert.fieldEquals("PoolMember", id, "isConnected", FALSE);
+ assert.fieldEquals("PoolMember", id, "totalAmountClaimed", BIG_INT_ZERO.toString());
+ assert.fieldEquals("PoolMember", id, "account", poolMember);
+ assert.fieldEquals("PoolMember", id, "pool", poolAddress.toHexString());
+ });
+
+ test("handleBufferAdjusted() - PoolDistributor Entity: totalBufferAmount updated", () => {
+ const distributor = alice;
+
+ const BUFFER = BigInt.fromI32(100);
+
+ const bufferAdjustedEvent = createBufferAdjustedEvent(
+ superToken, // token
+ superfluidPool, // pool
+ distributor, // poolDistributor
+ BUFFER, // bufferDelta
+ BUFFER, // newBufferAmount
+ BUFFER // totalBufferAmount
+ );
+
+ handleBufferAdjusted(bufferAdjustedEvent);
+
+ const id = getPoolDistributorID(Address.fromString(superfluidPool), Address.fromString(distributor));
+
+ assertHigherOrderBaseProperties("PoolDistributor", id, bufferAdjustedEvent);
+ assert.fieldEquals(
+ "PoolDistributor",
+ id,
+ "totalAmountInstantlyDistributedUntilUpdatedAt",
+ BIG_INT_ZERO.toString()
+ );
+ assert.fieldEquals(
+ "PoolDistributor",
+ id,
+ "totalAmountFlowedDistributedUntilUpdatedAt",
+ BIG_INT_ZERO.toString()
+ );
+ assert.fieldEquals("PoolDistributor", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("PoolDistributor", id, "totalBuffer", BUFFER.toString());
+ assert.fieldEquals("PoolDistributor", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("PoolDistributor", id, "account", distributor);
+ assert.fieldEquals("PoolDistributor", id, "pool", superfluidPool);
+ });
+
+ test("handleFlowDistributionUpdated() - PoolDistributor Entity: flowRate updated", () => {
+ const distributor = alice;
+ const operator = alice;
+ const emptyFlowRate = BigInt.fromI32(0);
+ const newFlowRate = BigInt.fromI32(100000000);
+ const userData = stringToBytes("");
+ const flowDistributionUpdatedEvent = createFlowDistributionUpdatedEvent(
+ superToken,
+ superfluidPool,
+ distributor,
+ operator,
+ emptyFlowRate, // old flow rate
+ newFlowRate, // new distributor to pool flow rate
+ newFlowRate, // new total distribution flow rate
+ alice, // adjustment flow recipient
+ BigInt.fromI32(0), // adjustment flow rate
+ userData
+ );
+
+ handleFlowDistributionUpdated(flowDistributionUpdatedEvent);
+
+ const id = getPoolDistributorID(Address.fromString(superfluidPool), Address.fromString(distributor));
+
+ assertHigherOrderBaseProperties("PoolDistributor", id, flowDistributionUpdatedEvent);
+ assert.fieldEquals(
+ "PoolDistributor",
+ id,
+ "totalAmountInstantlyDistributedUntilUpdatedAt",
+ BIG_INT_ZERO.toString()
+ );
+ assert.fieldEquals(
+ "PoolDistributor",
+ id,
+ "totalAmountFlowedDistributedUntilUpdatedAt",
+ BIG_INT_ZERO.toString()
+ );
+ assert.fieldEquals("PoolDistributor", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("PoolDistributor", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("PoolDistributor", id, "flowRate", newFlowRate.toString());
+ assert.fieldEquals("PoolDistributor", id, "account", distributor);
+ assert.fieldEquals("PoolDistributor", id, "pool", superfluidPool);
+ });
+
+ test("handleInstantDistributionUpdated() - PoolDistributor Entity: flowRate updated", () => {
+ const distributor = alice;
+ const operator = alice;
+ const requestedAmount = BigInt.fromI32(100000000);
+ const userData = stringToBytes("");
+ const instantDistributionUpdatedEvent = createInstantDistributionUpdatedEvent(
+ superToken,
+ superfluidPool,
+ distributor,
+ operator,
+ requestedAmount,
+ requestedAmount,
+ userData
+ );
+
+ handleInstantDistributionUpdated(instantDistributionUpdatedEvent);
+
+ const id = getPoolDistributorID(Address.fromString(superfluidPool), Address.fromString(distributor));
+
+ assertHigherOrderBaseProperties("PoolDistributor", id, instantDistributionUpdatedEvent);
+ assert.fieldEquals(
+ "PoolDistributor",
+ id,
+ "totalAmountInstantlyDistributedUntilUpdatedAt",
+ requestedAmount.toString()
+ );
+ assert.fieldEquals(
+ "PoolDistributor",
+ id,
+ "totalAmountFlowedDistributedUntilUpdatedAt",
+ BIG_INT_ZERO.toString()
+ );
+ assert.fieldEquals("PoolDistributor", id, "totalAmountDistributedUntilUpdatedAt", requestedAmount.toString());
+ assert.fieldEquals("PoolDistributor", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("PoolDistributor", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("PoolDistributor", id, "account", distributor);
+ assert.fieldEquals("PoolDistributor", id, "pool", superfluidPool);
+ });
+});
+
+function assertEmptyPoolData(id: string, event: ethereum.Event, token: string): void {
+ assertHigherOrderBaseProperties("Pool", id, event);
+ assert.fieldEquals("Pool", id, "totalUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedUnits", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountInstantlyDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountFlowedDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalAmountDistributedUntilUpdatedAt", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalConnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalDisconnectedMembers", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "adjustmentFlowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "flowRate", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "totalBuffer", BIG_INT_ZERO.toString());
+ assert.fieldEquals("Pool", id, "token", token);
+ assert.fieldEquals("Pool", id, "admin", admin);
+}
diff --git a/packages/subgraph/tests/resolver/resolver.test.ts b/packages/subgraph/tests/resolver/resolver.test.ts
index c7a17b7f10..082e9c8b5c 100644
--- a/packages/subgraph/tests/resolver/resolver.test.ts
+++ b/packages/subgraph/tests/resolver/resolver.test.ts
@@ -1,23 +1,7 @@
import { newMockEvent } from "matchstick-as";
-import {
- assert,
- beforeEach,
- clearStore,
- describe,
- test,
-} from "matchstick-as/assembly/index";
-import {
- DEFAULT_DECIMALS,
- FALSE,
- maticXAddress,
- maticXName,
- maticXSymbol,
- TRUE,
-} from "../constants";
-import {
- assertEventBaseProperties,
- assertHigherOrderBaseProperties,
-} from "../assertionHelpers";
+import { assert, beforeEach, clearStore, describe, test } from "matchstick-as/assembly/index";
+import { DEFAULT_DECIMALS, FALSE, maticXAddress, maticXName, maticXSymbol, TRUE } from "../constants";
+import { assertEventBaseProperties, assertHigherOrderBaseProperties } from "../assertionHelpers";
import { createSetEvent } from "./resolver.helper";
import { handleSet } from "../../src/mappings/resolver";
import { stringToBytes } from "../converters";
@@ -30,10 +14,7 @@ import { Address } from "@graphprotocol/graph-ts";
* @param target the target address
* @returns ResolverEntry id
*/
-function testResolverEntryParams(
- tokenAddress: Address,
- target: Address
-): string {
+function testResolverEntryParams(tokenAddress: Address, target: Address): string {
const name = stringToBytes("supertokens.v1.maticx");
const setEvent = createSetEvent(name, target.toHexString());
const isToken = tokenAddress.equals(Address.zero()) ? FALSE : TRUE;
@@ -42,20 +23,8 @@ function testResolverEntryParams(
handleSet(setEvent);
const resolverEntryId = name.toHex();
- assertHigherOrderBaseProperties(
- "ResolverEntry",
- resolverEntryId,
- setEvent.block.timestamp,
- setEvent.block.number,
- setEvent.block.timestamp,
- setEvent.block.number
- );
- assert.fieldEquals(
- "ResolverEntry",
- resolverEntryId,
- "targetAddress",
- target.toHexString()
- );
+ assertHigherOrderBaseProperties("ResolverEntry", resolverEntryId, setEvent);
+ assert.fieldEquals("ResolverEntry", resolverEntryId, "targetAddress", target.toHexString());
assert.fieldEquals("ResolverEntry", resolverEntryId, "isToken", isToken);
assert.fieldEquals("ResolverEntry", resolverEntryId, "isListed", isListed);
@@ -76,19 +45,9 @@ describe("Resolver Mapper Unit Tests", () => {
handleSet(setEvent);
const id = assertEventBaseProperties(setEvent, "Set");
- assert.fieldEquals(
- "SetEvent",
- id,
- "hashedName",
- name.toHexString()
- );
+ assert.fieldEquals("SetEvent", id, "hashedName", name.toHexString());
assert.fieldEquals("SetEvent", id, "target", target);
- assert.fieldEquals(
- "SetEvent",
- id,
- "resolverEntry",
- name.toHexString()
- );
+ assert.fieldEquals("SetEvent", id, "resolverEntry", name.toHexString());
});
});
@@ -110,15 +69,7 @@ describe("Resolver Mapper Unit Tests", () => {
test("Should create a ResolverEntry entity (token case) - list case", () => {
const mockEvent = newMockEvent();
const token = Address.fromString(maticXAddress);
- createSuperToken(
- token,
- mockEvent.block,
- DEFAULT_DECIMALS,
- maticXName,
- maticXSymbol,
- false,
- Address.zero()
- );
+ createSuperToken(token, mockEvent.block, DEFAULT_DECIMALS, maticXName, maticXSymbol, false, Address.zero());
const target = Address.fromString(maticXAddress);
assert.fieldEquals("Token", maticXAddress, "isListed", FALSE);
// list token on resolver
diff --git a/packages/subgraph/tests/superToken/event/superToken.event.test.ts b/packages/subgraph/tests/superToken/event/superToken.event.test.ts
index a29a9ac385..4a335bd4c0 100644
--- a/packages/subgraph/tests/superToken/event/superToken.event.test.ts
+++ b/packages/subgraph/tests/superToken/event/superToken.event.test.ts
@@ -17,10 +17,7 @@ import {
handleTransfer,
} from "../../../src/mappings/superToken";
import { BIG_INT_ONE, BIG_INT_ZERO, encode, ZERO_ADDRESS } from "../../../src/utils";
-import {
- assertEventBaseProperties,
- assertTokenStatisticProperties,
-} from "../../assertionHelpers";
+import { assertEmptyTokenStatisticProperties, assertEventBaseProperties, assertTokenStatisticProperties } from "../../assertionHelpers";
import { alice, bob, cfaV1Address, charlie, DEFAULT_DECIMALS, delta, FAKE_INITIAL_BALANCE, maticXName, maticXSymbol } from "../../constants";
import { getETHAddress, getETHUnsignedBigInt, stringToBytes } from "../../converters";
import { createStream, createStreamRevision } from "../../mockedEntities";
@@ -273,14 +270,24 @@ describe("SuperToken Mapper Unit Tests", () => {
agreementLiquidatedV2Event.block.timestamp,
agreementLiquidatedV2Event.block.number,
0, // totalNumberOfActiveStreams
+ 0, // totalCFANumberOfActiveStreams
+ 0, // totalGDANumberOfActiveStreams
0, // totalNumberOfClosedStreams
+ 0, // totalCFANumberOfClosedStreams
+ 0, // totalGDANumberOfClosedStreams
0, // totalNumberOfIndexes
0, // totalNumberOfActiveIndexes
0, // totalSubscriptionsWithUnits
0, // totalApprovedSubscriptions
BIG_INT_ZERO, // totalDeposit
+ BIG_INT_ZERO, // totalCFADeposit
+ BIG_INT_ZERO, // totalGDADeposit
BIG_INT_ZERO, // totalOutflowRate
+ BIG_INT_ZERO, // totalCFAOutflowRate
+ BIG_INT_ZERO, // totalGDAOutflowRate
BIG_INT_ZERO, // totalAmountStreamedUntilUpdatedAt
+ BIG_INT_ZERO, // totalCFAAmountStreamedUntilUpdatedAt
+ BIG_INT_ZERO, // totalGDAAmountStreamedUntilUpdatedAt
BIG_INT_ZERO, // totalAmountTransferredUntilUpdatedAt
BIG_INT_ZERO, // totalAmountDistributedUntilUpdatedAt
BigInt.fromI32(1000000), // totalSupply = 100
@@ -358,14 +365,24 @@ describe("SuperToken Mapper Unit Tests", () => {
transferEvent.block.timestamp,
transferEvent.block.number,
0, // totalNumberOfActiveStreams
+ 0, // totalCFANumberOfActiveStreams
+ 0, // totalGDANumberOfActiveStreams
0, // totalNumberOfClosedStreams
+ 0, // totalCFANumberOfClosedStreams
+ 0, // totalGDANumberOfClosedStreams
0, // totalNumberOfIndexes
0, // totalNumberOfActiveIndexes
0, // totalSubscriptionsWithUnits
0, // totalApprovedSubscriptions
BIG_INT_ZERO, // totalDeposit
+ BIG_INT_ZERO, // totalCFADeposit
+ BIG_INT_ZERO, // totalGDADeposit
BIG_INT_ZERO, // totalOutflowRate
+ BIG_INT_ZERO, // totalCFAOutflowRate
+ BIG_INT_ZERO, // totalGDAOutflowRate
BIG_INT_ZERO, // totalAmountStreamedUntilUpdatedAt
+ BIG_INT_ZERO, // totalCFAAmountStreamedUntilUpdatedAt
+ BIG_INT_ZERO, // totalGDAAmountStreamedUntilUpdatedAt
value, // totalAmountTransferredUntilUpdatedAt
BIG_INT_ZERO, // totalAmountDistributedUntilUpdatedAt
BigInt.fromI32(1000000), // totalSupply = 100
@@ -495,14 +512,24 @@ describe("SuperToken Mapper Unit Tests", () => {
transferEvent.block.timestamp,
transferEvent.block.number,
0, // totalNumberOfActiveStreams
+ 0, // totalCFANumberOfActiveStreams
+ 0, // totalGDANumberOfActiveStreams
0, // totalNumberOfClosedStreams
+ 0, // totalCFANumberOfClosedStreams
+ 0, // totalGDANumberOfClosedStreams
0, // totalNumberOfIndexes
0, // totalNumberOfActiveIndexes
0, // totalSubscriptionsWithUnits
0, // totalApprovedSubscriptions
BIG_INT_ZERO, // totalDeposit
+ BIG_INT_ZERO, // totalCFADeposit
+ BIG_INT_ZERO, // totalGDADeposit
BIG_INT_ZERO, // totalOutflowRate
+ BIG_INT_ZERO, // totalCFAOutflowRate
+ BIG_INT_ZERO, // totalGDAOutflowRate
BIG_INT_ZERO, // totalAmountStreamedUntilUpdatedAt
+ BIG_INT_ZERO, // totalCFAAmountStreamedUntilUpdatedAt
+ BIG_INT_ZERO, // totalGDAAmountStreamedUntilUpdatedAt
value, // totalAmountTransferredUntilUpdatedAt
BIG_INT_ZERO, // totalAmountDistributedUntilUpdatedAt
BigInt.fromI32(1000000), // totalSupply = 100
@@ -535,14 +562,24 @@ describe("SuperToken Mapper Unit Tests", () => {
secondTransferEvent.block.timestamp,
secondTransferEvent.block.number,
0, // totalNumberOfActiveStreams
+ 0, // totalCFANumberOfActiveStreams
+ 0, // totalGDANumberOfActiveStreams
0, // totalNumberOfClosedStreams
+ 0, // totalCFANumberOfClosedStreams
+ 0, // totalGDANumberOfClosedStreams
0, // totalNumberOfIndexes
0, // totalNumberOfActiveIndexes
0, // totalSubscriptionsWithUnits
0, // totalApprovedSubscriptions
BIG_INT_ZERO, // totalDeposit
+ BIG_INT_ZERO, // totalCFADeposit
+ BIG_INT_ZERO, // totalGDADeposit
BIG_INT_ZERO, // totalOutflowRate
+ BIG_INT_ZERO, // totalCFAOutflowRate
+ BIG_INT_ZERO, // totalGDAOutflowRate
BIG_INT_ZERO, // totalAmountStreamedUntilUpdatedAt
+ BIG_INT_ZERO, // totalCFAAmountStreamedUntilUpdatedAt
+ BIG_INT_ZERO, // totalGDAAmountStreamedUntilUpdatedAt
value.times(BigInt.fromI32(2)), // totalAmountTransferredUntilUpdatedAt
BIG_INT_ZERO, // totalAmountDistributedUntilUpdatedAt
BigInt.fromI32(1000000), // totalSupply = 100
@@ -573,26 +610,13 @@ describe("SuperToken Mapper Unit Tests", () => {
);
handleBurned(burnedEvent);
- assertTokenStatisticProperties(
+ assertEmptyTokenStatisticProperties(
null,
null,
burnedEvent.address.toHex(),
burnedEvent.block.timestamp,
burnedEvent.block.number,
- 0, // totalNumberOfActiveStreams
- 0, // totalNumberOfClosedStreams
- 0, // totalNumberOfIndexes
- 0, // totalNumberOfActiveIndexes
- 0, // totalSubscriptionsWithUnits
- 0, // totalApprovedSubscriptions
- BIG_INT_ZERO, // totalDeposit
- BIG_INT_ZERO, // totalOutflowRate
- BIG_INT_ZERO, // totalAmountStreamedUntilUpdatedAt
- BIG_INT_ZERO, // totalAmountTransferredUntilUpdatedAt
- BIG_INT_ZERO, // totalAmountDistributedUntilUpdatedAt
- amount.neg(), // totalSupply = -100 (not possible in practice)
- 0, // totalNumberOfAccounts
- 0, // totalNumberOfHolders
+ amount.neg() // totalSupply = -100 (not possible in practice)
);
});
@@ -612,26 +636,13 @@ describe("SuperToken Mapper Unit Tests", () => {
);
handleMinted(mintedEvent);
- assertTokenStatisticProperties(
+ assertEmptyTokenStatisticProperties(
null,
null,
mintedEvent.address.toHex(),
mintedEvent.block.timestamp,
mintedEvent.block.number,
- 0, // totalNumberOfActiveStreams
- 0, // totalNumberOfClosedStreams
- 0, // totalNumberOfIndexes
- 0, // totalNumberOfActiveIndexes
- 0, // totalSubscriptionsWithUnits
- 0, // totalApprovedSubscriptions
- BIG_INT_ZERO, // totalDeposit
- BIG_INT_ZERO, // totalOutflowRate
- BIG_INT_ZERO, // totalAmountStreamedUntilUpdatedAt
- BIG_INT_ZERO, // totalAmountTransferredUntilUpdatedAt
- BIG_INT_ZERO, // totalAmountDistributedUntilUpdatedAt
- amount, // totalSupply = 100
- 0, // totalNumberOfAccounts,
- 0 // totalNumberOfHolders
+ amount // totalSupply = 100
);
});
});
diff --git a/packages/subgraph/tests/superToken/hol/supertoken.hol.test.ts b/packages/subgraph/tests/superToken/hol/supertoken.hol.test.ts
index b71e162e1f..79362bc236 100644
--- a/packages/subgraph/tests/superToken/hol/supertoken.hol.test.ts
+++ b/packages/subgraph/tests/superToken/hol/supertoken.hol.test.ts
@@ -1,19 +1,9 @@
import { assert, beforeEach, clearStore, describe, test } from "matchstick-as";
-import {
- createFlowOperatorUpdatedEvent,
-} from "../../cfav1/cfav1.helper";
-import {
- alice,
- bob,
- maticXAddress,
-} from "../../constants";
-import {
- BIG_INT_ZERO,
- getAccountTokenSnapshotID,
- getFlowOperatorID,
-} from "../../../src/utils";
+import { createFlowOperatorUpdatedEvent } from "../../cfav1/cfav1.helper";
+import { alice, bob, maticXAddress } from "../../constants";
+import { BIG_INT_ZERO, getAccountTokenSnapshotID, getFlowOperatorID } from "../../../src/utils";
import { Address, BigInt } from "@graphprotocol/graph-ts";
-import {mockedApprove, mockedGetAppManifest} from "../../mockedFunctions";
+import { mockedApprove, mockedGetAppManifest } from "../../mockedFunctions";
import { handleFlowOperatorUpdated } from "../../../src/mappings/cfav1";
import { handleApproval } from "../../../src/mappings/superToken";
import { assertHigherOrderBaseProperties } from "../../assertionHelpers";
@@ -53,35 +43,17 @@ describe("SuperToken Higher Order Level Entity Unit Tests", () => {
assert.fieldEquals("FlowOperator", id, "allowance", "0");
// trigger approve event
- const approvalEvent = createApprovalEvent(
- superToken,
- sender,
- flowOperator,
- allowance
- );
+ const approvalEvent = createApprovalEvent(superToken, sender, flowOperator, allowance);
handleApproval(approvalEvent);
- const atsId = getAccountTokenSnapshotID(
- Address.fromString(sender),
- Address.fromString(superToken)
- );
- assertHigherOrderBaseProperties(
- "FlowOperator",
- id,
- flowOperatorUpdatedEvent.block.timestamp,
- flowOperatorUpdatedEvent.block.number,
- flowOperatorUpdatedEvent.block.timestamp,
- flowOperatorUpdatedEvent.block.number
- );
- assert.fieldEquals("FlowOperator", id, "permissions", permissions.toString()
- );
+ const atsId = getAccountTokenSnapshotID(Address.fromString(sender), Address.fromString(superToken));
+ assertHigherOrderBaseProperties("FlowOperator", id, flowOperatorUpdatedEvent);
+ assert.fieldEquals("FlowOperator", id, "permissions", permissions.toString());
assert.fieldEquals("FlowOperator", id, "flowRateAllowanceGranted", flowRateAllowance.toString());
- assert.fieldEquals("FlowOperator", id, "flowRateAllowanceRemaining", flowRateAllowance.toString()
- );
+ assert.fieldEquals("FlowOperator", id, "flowRateAllowanceRemaining", flowRateAllowance.toString());
assert.fieldEquals("FlowOperator", id, "flowOperator", flowOperator);
- assert.fieldEquals("FlowOperator", id, "allowance", allowance.toString()
- );
+ assert.fieldEquals("FlowOperator", id, "allowance", allowance.toString());
assert.fieldEquals("FlowOperator", id, "sender", sender);
assert.fieldEquals("FlowOperator", id, "token", superToken);
assert.fieldEquals("FlowOperator", id, "accountTokenSnapshot", atsId);
diff --git a/packages/subgraph/tests/superTokenFactory/superTokenFactory.test.ts b/packages/subgraph/tests/superTokenFactory/superTokenFactory.test.ts
index 9a2e146448..f070724e68 100644
--- a/packages/subgraph/tests/superTokenFactory/superTokenFactory.test.ts
+++ b/packages/subgraph/tests/superTokenFactory/superTokenFactory.test.ts
@@ -10,7 +10,7 @@ import {
handleSuperTokenCreated,
handleSuperTokenLogicCreated,
} from "../../src/mappings/superTokenFactory";
-import { assertEventBaseProperties, assertTokenStatisticProperties } from "../assertionHelpers";
+import { assertEmptyTokenStatisticProperties, assertEventBaseProperties } from "../assertionHelpers";
import {
daiAddress,
daiName,
@@ -227,26 +227,13 @@ describe("SuperTokenFactory Mapper Unit Tests", () => {
handleSuperTokenCreated(SuperTokenCreatedEvent);
// Validate Created TokenStatistic properties
- assertTokenStatisticProperties(
+ assertEmptyTokenStatisticProperties(
SuperTokenCreatedEvent,
"SuperTokenCreated",
maticXAddress,
SuperTokenCreatedEvent.block.timestamp,
SuperTokenCreatedEvent.block.number,
- 0, // totalNumberOfActiveStreams
- 0, // totalNumberOfClosedStreams
- 0, // totalNumberOfIndexes
- 0, // totalNumberOfActiveIndexes
- 0, // totalSubscriptionsWithUnits
- 0, // totalApprovedSubscriptions
- BIG_INT_ZERO, // totalDeposit
- BIG_INT_ZERO, // totalOutflowRate
- BIG_INT_ZERO, // totalAmountStreamedUntilUpdatedAt
- BIG_INT_ZERO, // totalAmountTransferredUntilUpdatedAt
- BIG_INT_ZERO, // totalAmountDistributedUntilUpdatedAt
- FAKE_SUPER_TOKEN_TOTAL_SUPPLY, // totalSupply = 100,
- 0, // totalNumberOfAccounts
- 0 // totalNumberOfHolders
+ FAKE_SUPER_TOKEN_TOTAL_SUPPLY // totalSupply = 100
);
});
diff --git a/yarn.lock b/yarn.lock
index d51fe41433..4cc83819f9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2630,7 +2630,7 @@
"@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.1"
"@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.1"
-"@nomiclabs/hardhat-ethers@^2.2.1", "@nomiclabs/hardhat-ethers@^2.2.3":
+"@nomiclabs/hardhat-ethers@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz#b41053e360c31a32c2640c9a45ee981a7e603fe0"
integrity sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==
@@ -3027,11 +3027,6 @@
find-up "^4.1.0"
fs-extra "^8.1.0"
-"@openzeppelin/contracts@4.8.2":
- version "4.8.2"
- resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.2.tgz#d815ade0027b50beb9bcca67143c6bcc3e3923d6"
- integrity sha512-kEUOgPQszC0fSYWpbh2kT94ltOJwj1qfT2DWo+zVttmGmf97JZ99LspePNaeeaLhCImaHVeBbjaQFZQn7+Zc5g==
-
"@openzeppelin/contracts@4.9.3":
version "4.9.3"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.3.tgz#00d7a8cf35a475b160b3f0293a6403c511099364"
@@ -3524,37 +3519,6 @@
dependencies:
antlr4ts "^0.5.0-alpha.4"
-"@superfluid-finance/ethereum-contracts@1.7.1":
- version "1.7.1"
- resolved "https://registry.yarnpkg.com/@superfluid-finance/ethereum-contracts/-/ethereum-contracts-1.7.1.tgz#e2f08bed42694e94980199607ed9d3222faeb477"
- integrity sha512-MPimKMSbvJOUkbMzGA2oNdGZrKpJnja3OGS4NdsRO+OxBya2XOTvoy55nb3H/u63IFagoKt6L7eSoJapKtjDrA==
- dependencies:
- "@decentral.ee/web3-helpers" "0.5.3"
- "@openzeppelin/contracts" "4.8.2"
- "@superfluid-finance/js-sdk" "0.6.3"
- "@truffle/contract" "4.6.18"
- ethereumjs-tx "2.1.2"
- ethereumjs-util "7.1.5"
- stack-trace "0.0.10"
-
-"@superfluid-finance/metadata@1.1.10":
- version "1.1.10"
- resolved "https://registry.yarnpkg.com/@superfluid-finance/metadata/-/metadata-1.1.10.tgz#980991d60066f21646d29eb01a9080c7fae1493d"
- integrity sha512-IbcpfB/pOwjl/Vam0d1WXNJaeA0bUW/CkQEZlEhUpL+DQh01d6TnxneEjw3VsT9alqamtycKoi6+2uPHAzyvFA==
-
-"@superfluid-finance/sdk-core@0.6.8":
- version "0.6.8"
- resolved "https://registry.yarnpkg.com/@superfluid-finance/sdk-core/-/sdk-core-0.6.8.tgz#9ca45546cab97de47eb7e8f4ea190bd7a62fc440"
- integrity sha512-OoID1Hmu3OJxXmU7P8+VFsU1sFw587Bak27pzwltm64Te7v0lY7HutcOUL4LaHFmqWhaxKoKqwDPNodxU4hVHA==
- dependencies:
- "@nomiclabs/hardhat-ethers" "^2.2.1"
- "@superfluid-finance/ethereum-contracts" "1.7.1"
- "@superfluid-finance/metadata" "1.1.10"
- browserify "^17.0.0"
- graphql-request "^4.3.0"
- lodash "^4.17.21"
- tsify "^5.0.4"
-
"@szmarczak/http-timer@^4.0.5":
version "4.0.6"
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807"
@@ -3764,26 +3728,6 @@
debug "^4.3.1"
glob "^7.1.6"
-"@truffle/contract@4.6.18", "@truffle/contract@^4.0.35", "@truffle/contract@^4.6.18":
- version "4.6.18"
- resolved "https://registry.yarnpkg.com/@truffle/contract/-/contract-4.6.18.tgz#096f82dbc05060acc9ed0bd8bb5811f497b8e3ad"
- integrity sha512-x49EWZI16VMdYV8pH2LYM1AMFM3xAZ6ZFT2dG9Y71nIDZHdh+HKdlPSL40CqFtzpeoEk9UQoSJL99D/DXtpaog==
- dependencies:
- "@ensdomains/ensjs" "^2.1.0"
- "@truffle/blockchain-utils" "^0.1.7"
- "@truffle/contract-schema" "^3.4.13"
- "@truffle/debug-utils" "^6.0.47"
- "@truffle/error" "^0.2.0"
- "@truffle/interface-adapter" "^0.5.31"
- bignumber.js "^7.2.1"
- debug "^4.3.1"
- ethers "^4.0.32"
- web3 "1.8.2"
- web3-core-helpers "1.8.2"
- web3-core-promievent "1.8.2"
- web3-eth-abi "1.8.2"
- web3-utils "1.8.2"
-
"@truffle/contract@4.6.29":
version "4.6.29"
resolved "https://registry.yarnpkg.com/@truffle/contract/-/contract-4.6.29.tgz#c1f0b9f65985ba5d8f35626a612dd31205cfcd6b"
@@ -3804,6 +3748,26 @@
web3-eth-abi "1.10.0"
web3-utils "1.10.0"
+"@truffle/contract@^4.0.35", "@truffle/contract@^4.6.18":
+ version "4.6.18"
+ resolved "https://registry.yarnpkg.com/@truffle/contract/-/contract-4.6.18.tgz#096f82dbc05060acc9ed0bd8bb5811f497b8e3ad"
+ integrity sha512-x49EWZI16VMdYV8pH2LYM1AMFM3xAZ6ZFT2dG9Y71nIDZHdh+HKdlPSL40CqFtzpeoEk9UQoSJL99D/DXtpaog==
+ dependencies:
+ "@ensdomains/ensjs" "^2.1.0"
+ "@truffle/blockchain-utils" "^0.1.7"
+ "@truffle/contract-schema" "^3.4.13"
+ "@truffle/debug-utils" "^6.0.47"
+ "@truffle/error" "^0.2.0"
+ "@truffle/interface-adapter" "^0.5.31"
+ bignumber.js "^7.2.1"
+ debug "^4.3.1"
+ ethers "^4.0.32"
+ web3 "1.8.2"
+ web3-core-helpers "1.8.2"
+ web3-core-promievent "1.8.2"
+ web3-eth-abi "1.8.2"
+ web3-utils "1.8.2"
+
"@truffle/dashboard-message-bus-client@^0.1.10":
version "0.1.10"
resolved "https://registry.yarnpkg.com/@truffle/dashboard-message-bus-client/-/dashboard-message-bus-client-0.1.10.tgz#bd1cef19956f06716d55a327b8ea6f983e41f0b0"
@@ -10604,15 +10568,6 @@ graphql-import-node@^0.0.5:
resolved "https://registry.yarnpkg.com/graphql-import-node/-/graphql-import-node-0.0.5.tgz#caf76a6cece10858b14f27cce935655398fc1bf0"
integrity sha512-OXbou9fqh9/Lm7vwXT0XoRN9J5+WCYKnbiTalgFDvkQERITRmcfncZs6aVABedd5B85yQU5EULS4a5pnbpuI0Q==
-graphql-request@^4.3.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-4.3.0.tgz#b934e08fcae764aa2cdc697d3c821f046cb5dbf2"
- integrity sha512-2v6hQViJvSsifK606AliqiNiijb1uwWp6Re7o0RTyH+uRTv/u7Uqm2g4Fjq/LgZIzARB38RZEvVBFOQOVdlBow==
- dependencies:
- cross-fetch "^3.1.5"
- extract-files "^9.0.0"
- form-data "^3.0.0"
-
graphql-request@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-5.2.0.tgz#a05fb54a517d91bb2d7aefa17ade4523dc5ebdca"