From ab9316263f457c8c3435cd0f3ad7ff494e29dba0 Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 10 Nov 2025 17:16:49 +0100 Subject: [PATCH 01/14] allow pseudo Transfer event call to fail (token contracts not guaranteed to have it) --- .../contracts/agreements/ConstantFlowAgreementV1.sol | 10 ++++++++-- .../gdav1/GeneralDistributionAgreementV1.sol | 5 ++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol index 0daabe2234..4d39695a81 100644 --- a/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol @@ -453,7 +453,10 @@ contract ConstantFlowAgreementV1 is ctx, currentContext); } - flowVars.token.emitPseudoTransfer(flowVars.sender, flowVars.receiver); + // solhint-disable-next-line no-empty-blocks + try flowVars.token.emitPseudoTransfer(flowVars.sender, flowVars.receiver) {} + // solhint-disable-next-line no-empty-blocks + catch {} _requireAvailableBalance(flowVars.token, flowVars.sender, currentContext); } @@ -595,7 +598,10 @@ contract ConstantFlowAgreementV1 is } } - flowVars.token.emitPseudoTransfer(flowVars.sender, flowVars.receiver); + // solhint-disable-next-line no-empty-blocks + try flowVars.token.emitPseudoTransfer(flowVars.sender, flowVars.receiver) {} + // solhint-disable-next-line no-empty-blocks + catch {} } /************************************************************************** diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index a9e762d17c..dd2d416531 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -806,7 +806,10 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi revert GDA_ONLY_SUPER_TOKEN_POOL(); } - superToken.emitPseudoTransfer(from, to); + // solhint-disable-next-line no-empty-blocks + try superToken.emitPseudoTransfer(from, to) {} + // solhint-disable-next-line no-empty-blocks + catch {} } ////////////////////////////////////////////////////////////////////////////////////////////////////// From 7d607ff4239c43f98ae5d738ee6df706b2bf190d Mon Sep 17 00:00:00 2001 From: didi Date: Tue, 9 Sep 2025 17:51:22 +0200 Subject: [PATCH 02/14] port GDA patch --- .../gdav1/GeneralDistributionAgreementV1.sol | 3 ++ .../gdav1/GeneralDistributionAgreement.t.sol | 34 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index dd2d416531..14231df8fc 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -358,6 +358,9 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi returns (bool success) { ISuperfluidToken token = pool.superToken(); + if (!token.isPool(this, address(pool))) { + revert GDA_ONLY_SUPER_TOKEN_POOL(); + } ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); bool autoConnectForOtherMember = false; diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol index 36a55bc3a4..fef14a3314 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -1105,6 +1105,19 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste vm.stopPrank(); } + function testConnectUnauthorizedPool() public { + FakePool pool = new FakePool(address(superToken)); + + vm.startPrank(eve); + vm.expectRevert(IGeneralDistributionAgreementV1.GDA_ONLY_SUPER_TOKEN_POOL.selector); + sf.host.callAgreement( + sf.gda, + abi.encodeWithSelector(IGeneralDistributionAgreementV1.connectPool.selector, pool, ""), + new bytes(0) + ); + vm.stopPrank(); + } + /*////////////////////////////////////////////////////////////////////////// Assertion Functions //////////////////////////////////////////////////////////////////////////*/ @@ -1213,3 +1226,24 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste assertEq(flowRatesSum, 0, "GDAv1.t: flowRatesSum != 0"); } } + +/// Fake pool with `operatorConnectMember` not calling back into the GDA as expected +contract FakePool { + address internal immutable _SUPER_TOKEN; + + constructor(address superToken_) { + _SUPER_TOKEN = superToken_; + } + + function superToken() external view returns (address) { + return _SUPER_TOKEN; + } + + function getClaimable(address, uint32) public pure returns (int256) { + return type(int256).max; + } + + function operatorConnectMember(address, bool, uint32) external pure returns (bool) { + return true; + } +} \ No newline at end of file From ed8b8e934c6d3ce054a651b005ff0b3b340adc8c Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 10 Nov 2025 16:27:49 +0100 Subject: [PATCH 03/14] keep verification with truffle tooling working (etherscan v2) --- packages/ethereum-contracts/package.json | 2 +- packages/ethereum-contracts/truffle-config.js | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/ethereum-contracts/package.json b/packages/ethereum-contracts/package.json index 7a4250b40a..2af9cf5219 100644 --- a/packages/ethereum-contracts/package.json +++ b/packages/ethereum-contracts/package.json @@ -11,7 +11,7 @@ "hardhat": "2.26.1" }, "devDependencies": { - "@d10r/truffle-plugin-verify": "^0.6.11", + "@d10r/truffle-plugin-verify": "^0.7.2", "@nomiclabs/hardhat-truffle5": "^2.1.0", "@safe-global/safe-core-sdk": "^3.3.5", "@safe-global/safe-service-client": "^2.0.3", diff --git a/packages/ethereum-contracts/truffle-config.js b/packages/ethereum-contracts/truffle-config.js index aaadfb4f1b..cc53fc2335 100644 --- a/packages/ethereum-contracts/truffle-config.js +++ b/packages/ethereum-contracts/truffle-config.js @@ -297,7 +297,7 @@ const E = (module.exports = { ...createNetworkDefaultConfiguration("degenchain"), network_id: 666666666, maxPriorityFeePerGas: 1e6, // 0.001 gwei - maxFeePerGas: 100e9, // 100 gwei + maxFeePerGas: 120e9, // 120 gwei }, // @@ -397,6 +397,9 @@ const E = (module.exports = { contracts_build_directory: "./build/truffle", api_keys: { + // used by the truffle-plugin-verify v0.7+ + etherscan_v2: process.env.ETHERSCAN_API_V2_KEY, + // the rest is now legacy and should likely be removed etherscan: process.env.ETHERSCAN_API_KEY, polygonscan: process.env.POLYGONSCAN_API_KEY, snowtrace: process.env.SNOWTRACE_API_KEY, From a15f4c1730f29bd0d32c4055c0138ba6fdc58231 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 6 Nov 2025 18:51:38 +0100 Subject: [PATCH 04/14] update foundry to 1.3.6 --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 053374114d..cafe70968a 100644 --- a/flake.lock +++ b/flake.lock @@ -28,11 +28,11 @@ ] }, "locked": { - "lastModified": 1752867797, - "narHash": "sha256-oT129SDSr7SI9ThTd6ZbpmShh5f2tzUH3S4hl6c5/7w=", + "lastModified": 1758100230, + "narHash": "sha256-sARl8NpG4ifzhd7j5D04A5keJIf0zkP1XYIuDEkzXb4=", "owner": "shazow", "repo": "foundry.nix", - "rev": "d4445852933ab5bc61ca532cb6c5d3276d89c478", + "rev": "e632b06dc759e381ef04f15ff9541f889eda6013", "type": "github" }, "original": { From db5550a36414f6bf6d70871b1b611205ee0eb49e Mon Sep 17 00:00:00 2001 From: didi Date: Tue, 11 Nov 2025 13:17:37 +0100 Subject: [PATCH 05/14] version bump --- packages/automation-contracts/autowrap/package.json | 2 +- packages/automation-contracts/scheduler/package.json | 2 +- packages/ethereum-contracts/CHANGELOG.md | 6 +++++- packages/ethereum-contracts/package.json | 2 +- packages/hot-fuzz/package.json | 4 ++-- packages/js-sdk/package.json | 2 +- packages/sdk-core/package.json | 2 +- 7 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/automation-contracts/autowrap/package.json b/packages/automation-contracts/autowrap/package.json index 5ceb249f05..17b4cdcb9a 100644 --- a/packages/automation-contracts/autowrap/package.json +++ b/packages/automation-contracts/autowrap/package.json @@ -3,7 +3,7 @@ "description": "Open contracts that allow upgrading underlying token to supertokens based on running stream", "version": "0.3.0", "devDependencies": { - "@superfluid-finance/ethereum-contracts": "^1.14.0", + "@superfluid-finance/ethereum-contracts": "^1.14.1", "@superfluid-finance/metadata": "^1.6.2" }, "license": "MIT", diff --git a/packages/automation-contracts/scheduler/package.json b/packages/automation-contracts/scheduler/package.json index b6e7c979d9..ce967d575b 100644 --- a/packages/automation-contracts/scheduler/package.json +++ b/packages/automation-contracts/scheduler/package.json @@ -3,7 +3,7 @@ "description": "Open contracts that allow scheduling streams and vestings onchain", "version": "1.3.0", "devDependencies": { - "@superfluid-finance/ethereum-contracts": "^1.14.0", + "@superfluid-finance/ethereum-contracts": "^1.14.1", "@superfluid-finance/metadata": "^1.6.2" }, "license": "MIT", diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index 9b49b92c4a..73134e744c 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to the ethereum-contracts will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [v1.14.0] +## [v1.14.1] ### Added - GDA _autoconnect_ feature: now any account can connect pool members using `tryConnectPoolFor()` as long as they have less than 4 connection slots occupied for that Super Token. This allows for smoother onboarding of new users, allowing Apps to make sure tokens distributed via GDA immediately show up in user's wallets. Accounts can opt out of this by using `setConnectPermission()`, this is mainly supposed to be used by contracts. @@ -47,6 +47,10 @@ subtask(TASK_COMPILE_GET_REMAPPINGS).setAction( - `CFASuperAppBase`: added `flowRate` argument to `onFlowCreated` and `onFlowUpdated`. - PoolMemberNFT pruning: `IPoolMemberNFT` and `PoolMemberNFT` removed, `POOL_MEMBER_NFT()` removed from `ISuperToken`. +## [v1.14.0] + +Defect release, don't use! + ## [v1.13.0] ### Added diff --git a/packages/ethereum-contracts/package.json b/packages/ethereum-contracts/package.json index 2af9cf5219..8bb5cea3d0 100644 --- a/packages/ethereum-contracts/package.json +++ b/packages/ethereum-contracts/package.json @@ -1,7 +1,7 @@ { "name": "@superfluid-finance/ethereum-contracts", "description": " Ethereum contracts implementation for the Superfluid Protocol", - "version": "1.14.0", + "version": "1.14.1", "dependencies": { "@decentral.ee/web3-helpers": "0.5.3", "@nomiclabs/hardhat-ethers": "2.2.3", diff --git a/packages/hot-fuzz/package.json b/packages/hot-fuzz/package.json index deb60e28f4..856acbc889 100644 --- a/packages/hot-fuzz/package.json +++ b/packages/hot-fuzz/package.json @@ -7,13 +7,13 @@ }, "bugs": "https://github.com/superfluid-finance/protocol-monorepo/issues", "devDependencies": { - "@superfluid-finance/ethereum-contracts": "^1.14.0" + "@superfluid-finance/ethereum-contracts": "^1.14.1" }, "homepage": "https://github.com/superfluid-finance/protocol-monorepo#readme", "license": "AGPL-3.0", "main": "index.js", "peerDependencies": { - "@superfluid-finance/ethereum-contracts": "1.14.0" + "@superfluid-finance/ethereum-contracts": "1.14.1" }, "repository": { "type": "git", diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 395138a7b9..1577bcba8a 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -13,7 +13,7 @@ "node-fetch": "2.7.0" }, "devDependencies": { - "@superfluid-finance/ethereum-contracts": "^1.14.0", + "@superfluid-finance/ethereum-contracts": "^1.14.1", "chai-as-promised": "^8.0.0", "webpack": "^5.94.0", "webpack-bundle-analyzer": "^4.10.2", diff --git a/packages/sdk-core/package.json b/packages/sdk-core/package.json index 12f4b6f2a7..1d1e694c93 100644 --- a/packages/sdk-core/package.json +++ b/packages/sdk-core/package.json @@ -4,7 +4,7 @@ "version": "0.9.0", "bugs": "https://github.com/superfluid-finance/protocol-monorepo/issues", "dependencies": { - "@superfluid-finance/ethereum-contracts": "1.14.0", + "@superfluid-finance/ethereum-contracts": "1.14.1", "@superfluid-finance/metadata": "^1.6.2", "graphql-request": "6.1.0", "lodash": "4.17.21", From b9f03442f9d0546a51378983cbd68ff0fc928b35 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Thu, 13 Nov 2025 15:15:49 +0200 Subject: [PATCH 06/14] revert flake.lock change --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index cafe70968a..053374114d 100644 --- a/flake.lock +++ b/flake.lock @@ -28,11 +28,11 @@ ] }, "locked": { - "lastModified": 1758100230, - "narHash": "sha256-sARl8NpG4ifzhd7j5D04A5keJIf0zkP1XYIuDEkzXb4=", + "lastModified": 1752867797, + "narHash": "sha256-oT129SDSr7SI9ThTd6ZbpmShh5f2tzUH3S4hl6c5/7w=", "owner": "shazow", "repo": "foundry.nix", - "rev": "e632b06dc759e381ef04f15ff9541f889eda6013", + "rev": "d4445852933ab5bc61ca532cb6c5d3276d89c478", "type": "github" }, "original": { From 57979a33220147a6fcc3de599c4aeef5e46f0d59 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Thu, 13 Nov 2025 16:49:08 +0200 Subject: [PATCH 07/14] some notes in GDA during review --- .../gdav1/GeneralDistributionAgreementV1.sol | 59 ++++++++++++++----- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 14231df8fc..9eeb1cc505 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -279,34 +279,44 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } /// @inheritdoc IGeneralDistributionAgreementV1 - function updateMemberUnits(ISuperfluidPool pool, address memberAddress, uint128 newUnits, bytes calldata ctx) + function updateMemberUnits(ISuperfluidPool untrustedPool, address memberAddress, uint128 newUnits, bytes calldata ctx) external override returns (bytes memory newCtx) { + ISuperfluidToken token = untrustedPool.superToken(); + address msgSender = AgreementLibrary.authorizeTokenAccess(token, ctx).msgSender; + // Only the admin can update member units here - if (AgreementLibrary.authorizeTokenAccess(pool.superToken(), ctx).msgSender != pool.admin()) { + if (msgSender != untrustedPool.admin()) { revert GDA_NOT_POOL_ADMIN(); } newCtx = ctx; - pool.updateMemberUnits(memberAddress, newUnits); + // NOTE: In GDA.appendIndexUpdateByPool, it checks whether pool is created by the token. + untrustedPool.updateMemberUnits(memberAddress, newUnits); } /// @inheritdoc IGeneralDistributionAgreementV1 - function claimAll(ISuperfluidPool pool, address memberAddress, bytes calldata ctx) + function claimAll(ISuperfluidPool untrustedPool, address memberAddress, bytes calldata ctx) external override returns (bytes memory newCtx) { - AgreementLibrary.authorizeTokenAccess(pool.superToken(), ctx); + ISuperfluidToken token = untrustedPool.superToken(); + AgreementLibrary.authorizeTokenAccess(token, ctx); newCtx = ctx; + // NOTE: In GDA.poolSettleClaim, it checks whether pool is created by the token. pool.claimAll(memberAddress); } /// @inheritdoc IGeneralDistributionAgreementV1 - function connectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { + function connectPool(ISuperfluidPool pool, bytes calldata ctx) + external + override + returns (bytes memory newCtx) + { newCtx = ctx; _setPoolConnectionFor(pool, address(0), true /* doConnect */, ctx); } @@ -319,6 +329,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi { newCtx = ctx; + // NOTE: We do not allow a pool to connect to another pool. if (memberAddr == address(0) || pool.superToken().isPool(this, memberAddr)) { revert GDA_CANNOT_CONNECT_POOL(); } @@ -354,6 +365,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi bool doConnect, bytes memory ctx ) + // _poolIsTrustedByItsSuperToken(pool) // TODO internal returns (bool success) { @@ -404,6 +416,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ) ); + // NOTE: similar to Transfer, we cannot tell if it is done through tryConnect or regular connect. emit PoolConnectionUpdated(token, pool, memberAddr, doConnect, currentContext.userData); } @@ -411,7 +424,14 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } /// @inheritdoc IGeneralDistributionAgreementV1 - function isMemberConnected(ISuperfluidPool pool, address member) external view override returns (bool) { + function isMemberConnected(ISuperfluidPool pool, address member) + external view override + returns (bool) + { + // NOTE: this function is complete, in that even for invalid pools, it will always return false. + // + // Retrospectively, it may be more helpful to the developers if this function is non-complete, and always revert + // on invalid pool. return pool.superToken().isPoolMemberConnected(this, pool, member); } @@ -427,7 +447,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi newCtx = ctx; - if (token.isPool(this, address(pool)) == false || + if (token.isPool(this, address(pool)) == false || // TODO: with _poolIsTrustedByItsSuperToken // Note: we do not support multi-tokens pools pool.superToken() != token) { revert GDA_ONLY_SUPER_TOKEN_POOL(); @@ -493,7 +513,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi int96 requestedFlowRate, bytes calldata ctx ) external override returns (bytes memory newCtx) { - if (token.isPool(this, address(pool)) == false || + if (token.isPool(this, address(pool)) == false || // TODO _poolIsTrustedByItsSuperTokne // Note: we do not support multi-tokens pools pool.superToken() != token) { revert GDA_ONLY_SUPER_TOKEN_POOL(); @@ -602,6 +622,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ) internal { + // NOTE: the caller to guarantee that the token and pool are mutually trusted. + // 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()); @@ -781,14 +803,17 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) external + // TODO _poolIsTrustedByItsSuperToken(msg.sender) returns (bool) { - if (token.isPool(this, msg.sender) == false) { + ISuperfluidPool untrustedPool = msg.sender; + + if (token.isPool(this, untrustedPool) == 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); + _setUIndex(eff, msg.sender, _getUIndex(eff, untrustedPool).mappend(p)); + _setPoolAdjustmentFlowRate(eff, untrustedPool, true, /* doShift? */ p.flow_rate(), t); return true; } @@ -796,16 +821,20 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi external returns (bool) { - if (superToken.isPool(this, msg.sender) == false) { + ISuperfluidPool untrustedPool = msg.sender; + + if (superToken.isPool(this, untrustedPool) == false) { revert GDA_ONLY_SUPER_TOKEN_POOL(); } - _doShift(abi.encode(superToken), msg.sender, claimRecipient, Value.wrap(amount)); + _doShift(abi.encode(superToken), untrustedPool, claimRecipient, Value.wrap(amount)); return true; } function tokenEmitPseudoTransfer(ISuperfluidToken superToken, address from, address to) external { - if (superToken.isPool(this, msg.sender) == false) { + ISuperfluidPool untrustedPool = msg.sender; + + if (superToken.isPool(this, untrustedPool) == false) { revert GDA_ONLY_SUPER_TOKEN_POOL(); } From 91da5b270aa5b3933efccdb06a055ffb2e76dfcf Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 14 Nov 2025 10:27:55 +0100 Subject: [PATCH 08/14] updated yarn.lock (truffle plugin) --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index eee43b1e84..bf5d0e3caf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -641,10 +641,10 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@d10r/truffle-plugin-verify@^0.6.11": - version "0.6.11" - resolved "https://registry.yarnpkg.com/@d10r/truffle-plugin-verify/-/truffle-plugin-verify-0.6.11.tgz#d10d1923b1344f14b513c0d7ce831c5cfa1407ba" - integrity sha512-jWn2WpiA2fas05XqjJtTLXcOJ4XRL+etf2A/in86AtqN88tImYbcoPIg+S8TpDp/Qum+PGr9edDof/ui1Q/jCw== +"@d10r/truffle-plugin-verify@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@d10r/truffle-plugin-verify/-/truffle-plugin-verify-0.7.2.tgz#fca925564b505b1787c0dcecf8ebdf594b64012a" + integrity sha512-CNdJ9kPVaiKFIRuiaPpA+9xeZ5oqdUrL3wVEadKBZEr+YFABuOC8Ne1e9/TgQFwlHU3JS30YtIhwGMISrtm+0g== dependencies: "@truffle/resolver" "^9.0.35" axios "^0.26.1" From 3e8fe51bfddc628e133f721bdf93753ce5b4bbf2 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 14 Nov 2025 10:41:34 +0100 Subject: [PATCH 09/14] add missing type conversions --- .../gdav1/GeneralDistributionAgreementV1.sol | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 9eeb1cc505..dd7aac9d18 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -279,7 +279,12 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } /// @inheritdoc IGeneralDistributionAgreementV1 - function updateMemberUnits(ISuperfluidPool untrustedPool, address memberAddress, uint128 newUnits, bytes calldata ctx) + function updateMemberUnits( + ISuperfluidPool untrustedPool, + address memberAddress, + uint128 newUnits, + bytes calldata ctx + ) external override returns (bytes memory newCtx) @@ -308,7 +313,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi newCtx = ctx; // NOTE: In GDA.poolSettleClaim, it checks whether pool is created by the token. - pool.claimAll(memberAddress); + untrustedPool.claimAll(memberAddress); } /// @inheritdoc IGeneralDistributionAgreementV1 @@ -806,14 +811,14 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi // TODO _poolIsTrustedByItsSuperToken(msg.sender) returns (bool) { - ISuperfluidPool untrustedPool = msg.sender; + ISuperfluidPool untrustedPool = ISuperfluidPool(msg.sender); - if (token.isPool(this, untrustedPool) == false) { + if (token.isPool(this, address(untrustedPool)) == false) { revert GDA_ONLY_SUPER_TOKEN_POOL(); } bytes memory eff = abi.encode(token); - _setUIndex(eff, msg.sender, _getUIndex(eff, untrustedPool).mappend(p)); - _setPoolAdjustmentFlowRate(eff, untrustedPool, true, /* doShift? */ p.flow_rate(), t); + _setUIndex(eff, msg.sender, _getUIndex(eff, address(untrustedPool)).mappend(p)); + _setPoolAdjustmentFlowRate(eff, address(untrustedPool), true, /* doShift? */ p.flow_rate(), t); return true; } @@ -821,20 +826,20 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi external returns (bool) { - ISuperfluidPool untrustedPool = msg.sender; + ISuperfluidPool untrustedPool = ISuperfluidPool(msg.sender); - if (superToken.isPool(this, untrustedPool) == false) { + if (superToken.isPool(this, address(untrustedPool)) == false) { revert GDA_ONLY_SUPER_TOKEN_POOL(); } - _doShift(abi.encode(superToken), untrustedPool, claimRecipient, Value.wrap(amount)); + _doShift(abi.encode(superToken), address(untrustedPool), claimRecipient, Value.wrap(amount)); return true; } function tokenEmitPseudoTransfer(ISuperfluidToken superToken, address from, address to) external { - ISuperfluidPool untrustedPool = msg.sender; + ISuperfluidPool untrustedPool = ISuperfluidPool(msg.sender); - if (superToken.isPool(this, untrustedPool) == false) { + if (superToken.isPool(this, address(untrustedPool)) == false) { revert GDA_ONLY_SUPER_TOKEN_POOL(); } From c991c33d24e45ef3849cffc0bcede4274ec24a8f Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 20 Nov 2025 20:23:40 +0100 Subject: [PATCH 10/14] modifier for pool-only functions --- .../gdav1/GeneralDistributionAgreementV1.sol | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index dd7aac9d18..af71882ad3 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -808,41 +808,32 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) external - // TODO _poolIsTrustedByItsSuperToken(msg.sender) + senderIsTrustedPool(token) returns (bool) { - ISuperfluidPool untrustedPool = ISuperfluidPool(msg.sender); + address poolAddress = msg.sender; - if (token.isPool(this, address(untrustedPool)) == false) { - revert GDA_ONLY_SUPER_TOKEN_POOL(); - } bytes memory eff = abi.encode(token); - _setUIndex(eff, msg.sender, _getUIndex(eff, address(untrustedPool)).mappend(p)); - _setPoolAdjustmentFlowRate(eff, address(untrustedPool), true, /* doShift? */ p.flow_rate(), t); + _setUIndex(eff, msg.sender, _getUIndex(eff, poolAddress).mappend(p)); + _setPoolAdjustmentFlowRate(eff, poolAddress, true, /* doShift? */ p.flow_rate(), t); return true; } - function poolSettleClaim(ISuperfluidToken superToken, address claimRecipient, int256 amount) + function poolSettleClaim(ISuperfluidToken token, address claimRecipient, int256 amount) external + senderIsTrustedPool(token) returns (bool) { - ISuperfluidPool untrustedPool = ISuperfluidPool(msg.sender); - - if (superToken.isPool(this, address(untrustedPool)) == false) { - revert GDA_ONLY_SUPER_TOKEN_POOL(); - } + address poolAddress = msg.sender; - _doShift(abi.encode(superToken), address(untrustedPool), claimRecipient, Value.wrap(amount)); + _doShift(abi.encode(token), poolAddress, claimRecipient, Value.wrap(amount)); return true; } - function tokenEmitPseudoTransfer(ISuperfluidToken superToken, address from, address to) external { - ISuperfluidPool untrustedPool = ISuperfluidPool(msg.sender); - - if (superToken.isPool(this, address(untrustedPool)) == false) { - revert GDA_ONLY_SUPER_TOKEN_POOL(); - } - + function tokenEmitPseudoTransfer(ISuperfluidToken superToken, address from, address to) + external + senderIsTrustedPool(superToken) + { // solhint-disable-next-line no-empty-blocks try superToken.emitPseudoTransfer(from, to) {} // solhint-disable-next-line no-empty-blocks @@ -1000,4 +991,12 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi token, subscriber, _POOL_SUBS_BITMAP_STATE_SLOT_ID, _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START ); } + + modifier senderIsTrustedPool(ISuperfluidToken token) { + address untrustedPoolAddress = msg.sender; + if (token.isPool(this, untrustedPoolAddress) == false) { + revert GDA_ONLY_SUPER_TOKEN_POOL(); + } + _; + } } From 776cf649c29815f8fa933218310d1794223e8ff0 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 21 Nov 2025 16:22:49 +0100 Subject: [PATCH 11/14] remove pseudo transfer event --- packages/ethereum-contracts/CHANGELOG.md | 1 - .../agreements/ConstantFlowAgreementV1.sol | 9 ---- .../gdav1/GeneralDistributionAgreementV1.sol | 10 ---- .../agreements/gdav1/SuperfluidPool.sol | 4 -- .../superfluid/ISuperfluidToken.sol | 7 --- .../contracts/superfluid/SuperfluidToken.sol | 4 -- .../test/foundry/superfluid/SuperToken.t.sol | 50 ------------------- 7 files changed, 85 deletions(-) diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index 73134e744c..71058b745b 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -15,7 +15,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `GDAv1StorageWriter` contains functions for writing agreement data to the token contract. This can only be used by the GDA contract itself. - bump solc to "0.8.30". - Changed EVM target from `paris` to `shanghai` because now all networks with supported Superfluid deployment support it. -- Emit ERC20 `Transfer` events (with amount 0) on CFA and GDA actions potentially leading to account balance changes. This shall help indexers to keep track of SuperToken holders and account balances. - Don't emit ERC20 `Approval` events on `transferFrom` operations. This is consistent with the OpenZeppelin ERC20 implementation from v5 onwards. Change effective only for SuperTokens using the latest logic. ### Fixed diff --git a/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol index 4d39695a81..3a8dfab57b 100644 --- a/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol @@ -453,11 +453,6 @@ contract ConstantFlowAgreementV1 is ctx, currentContext); } - // solhint-disable-next-line no-empty-blocks - try flowVars.token.emitPseudoTransfer(flowVars.sender, flowVars.receiver) {} - // solhint-disable-next-line no-empty-blocks - catch {} - _requireAvailableBalance(flowVars.token, flowVars.sender, currentContext); } @@ -598,10 +593,6 @@ contract ConstantFlowAgreementV1 is } } - // solhint-disable-next-line no-empty-blocks - try flowVars.token.emitPseudoTransfer(flowVars.sender, flowVars.receiver) {} - // solhint-disable-next-line no-empty-blocks - catch {} } /************************************************************************** diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index af71882ad3..75babd1ed1 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -830,16 +830,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi return true; } - function tokenEmitPseudoTransfer(ISuperfluidToken superToken, address from, address to) - external - senderIsTrustedPool(superToken) - { - // solhint-disable-next-line no-empty-blocks - try superToken.emitPseudoTransfer(from, to) {} - // solhint-disable-next-line no-empty-blocks - catch {} - } - ////////////////////////////////////////////////////////////////////////////////////////////////////// // TokenMonad interface ////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index 1a36d38752..1395398733 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -454,10 +454,6 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { assert(GDA.appendIndexUpdateByPool(superToken, p, t)); } - if ((oldUnits == 0 || newUnits == 0) && oldUnits != newUnits) { - GDA.tokenEmitPseudoTransfer(superToken, address(this), memberAddr); - } - emit MemberUnitsUpdated(superToken, memberAddr, oldUnits, newUnits); } diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidToken.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidToken.sol index d9bcd86e61..36e06fa123 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidToken.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidToken.sol @@ -431,11 +431,4 @@ interface ISuperfluidToken { uint256 rewardAmount, uint256 bailoutAmount ); - - /** - * @dev Emit an ERC20.Transfer event with zero amount, helps indexers track token holders. - * @param from The address from which the transfer is happening - * @param to The address to which the transfer is happening - */ - function emitPseudoTransfer(address from, address to) external; } diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol b/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol index f2c3c6d8f4..c75fc48f69 100644 --- a/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol +++ b/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol @@ -376,10 +376,6 @@ abstract contract SuperfluidToken is ISuperfluidToken ); } - function emitPseudoTransfer(address from, address to) external onlyAgreement { - emit IERC20.Transfer(from, to, 0); - } - /************************************************************************** * Modifiers *************************************************************************/ diff --git a/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol index 4ce9d93d01..e9ffd91641 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol @@ -259,54 +259,4 @@ contract SuperTokenIntegrationTest is FoundrySuperfluidTester { assertEq(localSuperToken.nonces(permitSigner), 1, "Nonce should be incremented"); assertEq(localSuperToken.allowance(permitSigner, spender), amount, "Allowance should be set"); } - - // Verify zero Transfer events being emitted by CFA and GDA actions - function testEmitPseudoTransferEvent() public { - vm.startPrank(admin); - - // case 1: create flow - vm.expectEmit(address(superToken)); - emit IERC20.Transfer(admin, alice, 0); - superToken.createFlow(alice, 1); - - // case 2: delete flow - vm.expectEmit(address(superToken)); - emit IERC20.Transfer(admin, alice, 0); - superToken.deleteFlow(admin, alice); - - // create a pool for the next tests - ISuperfluidPool pool = superToken.createPool( - admin, - PoolConfig({ - transferabilityForUnitsOwner: true, - distributionFromAnyAddress: true - }) - ); - - // case 3: assign pool units - vm.expectEmit(address(superToken)); - emit IERC20.Transfer(address(pool), alice, 0); - pool.updateMemberUnits(alice, 1); - - vm.stopPrank(); - - // case 4: test pool token transfer - vm.startPrank(alice); - // This emits 2 Transfer events, because the sender's units toggle to 0 and the receiver units from 0 - vm.expectEmit(address(superToken)); - emit IERC20.Transfer(address(pool), alice, 0); - vm.expectEmit(address(superToken)); - emit IERC20.Transfer(address(pool), bob, 0); - IERC20(pool).transfer(bob, 1); - - vm.stopPrank(); - - // case 5: remove pool units - vm.startPrank(admin); - vm.expectEmit(address(superToken)); - emit IERC20.Transfer(address(pool), bob, 0); - pool.updateMemberUnits(bob, 0); - - vm.stopPrank(); - } } From 0db855df590a1115ed4031e094d6c3bf93780eaf Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 21 Nov 2025 19:02:17 +0100 Subject: [PATCH 12/14] modifier doesn't need an arg --- .../gdav1/GeneralDistributionAgreementV1.sol | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 75babd1ed1..a7c3e41318 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -808,7 +808,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) external - senderIsTrustedPool(token) + senderIsTrustedPool returns (bool) { address poolAddress = msg.sender; @@ -821,7 +821,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi function poolSettleClaim(ISuperfluidToken token, address claimRecipient, int256 amount) external - senderIsTrustedPool(token) + senderIsTrustedPool returns (bool) { address poolAddress = msg.sender; @@ -982,9 +982,17 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ); } - modifier senderIsTrustedPool(ISuperfluidToken token) { - address untrustedPoolAddress = msg.sender; - if (token.isPool(this, untrustedPoolAddress) == false) { + // This check passing means that either the pool is legitimate, or the associated token is not legitimate. + // if the token is legitimate, `token.isPool()` can return true only if the pool was created by this agreement. + // The following "false positives" could occur if the associated token: + // 1. is lying (claims the pool was registered by this agreement when it was not) + // or + // 2. is not associated to the same host (and agreements). + // In both cases, pre-conditions are not met and no state this agreement is responsible for can be manipulated. + modifier senderIsTrustedPool() { + ISuperfluidPool pool = ISuperfluidPool(msg.sender); + + if (pool.superToken().isPool(this, address(pool)) == false) { revert GDA_ONLY_SUPER_TOKEN_POOL(); } _; From 731273108fec2ede9bf9afd97b94e9530306465a Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 24 Nov 2025 12:53:32 +0100 Subject: [PATCH 13/14] rollback modified, more verbose comments instead. More robust argument checking for pool-only methods. --- .../gdav1/GeneralDistributionAgreementV1.sol | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index a7c3e41318..ea88066315 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -370,11 +370,11 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi bool doConnect, bytes memory ctx ) - // _poolIsTrustedByItsSuperToken(pool) // TODO internal returns (bool success) { ISuperfluidToken token = pool.superToken(); + // TODO: convert to modifier `poolIsTrustedByItsSuperToken(pool)` if (!token.isPool(this, address(pool))) { revert GDA_ONLY_SUPER_TOKEN_POOL(); } @@ -452,9 +452,12 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi newCtx = ctx; - if (token.isPool(this, address(pool)) == false || // TODO: with _poolIsTrustedByItsSuperToken + // TODO: convert to modifier `poolIsTrustedByItsSuperToken(pool)` + if ( + token.isPool(this, address(pool)) == false || // Note: we do not support multi-tokens pools - pool.superToken() != token) { + pool.superToken() != token) + { revert GDA_ONLY_SUPER_TOKEN_POOL(); } @@ -518,9 +521,12 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi int96 requestedFlowRate, bytes calldata ctx ) external override returns (bytes memory newCtx) { - if (token.isPool(this, address(pool)) == false || // TODO _poolIsTrustedByItsSuperTokne + // TODO: convert to modifier `poolIsTrustedByItsSuperToken(pool)` + if ( + token.isPool(this, address(pool)) == false || // Note: we do not support multi-tokens pools - pool.superToken() != token) { + pool.superToken() != token) + { revert GDA_ONLY_SUPER_TOKEN_POOL(); } if (requestedFlowRate < 0) { @@ -804,28 +810,49 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi // // Pool-only operations - // + // Can only be called (`msg.sender`) by legitimate pool contracts. + // If `token` is legitimate, `token.isPool()` can return true only if the pool was created by this agreement. + // "false positives" (does not revert for illegitimate caller) could occur if `token`: + // 1. is lying (claims the pool was registered by this agreement when it was not) + // or + // 2. is not associated to the same host (and agreements). + // In both cases, pre-conditions are not met and no state this agreement is responsible for can be manipulated. function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t) external - senderIsTrustedPool returns (bool) { address poolAddress = msg.sender; + // TODO: convert to modifier `poolIsTrustedByItsSuperToken(pool)` + if ( + token.isPool(this, msg.sender) == false || + ISuperfluidPool(poolAddress).superToken() != token + ) { + revert GDA_ONLY_SUPER_TOKEN_POOL(); + } + bytes memory eff = abi.encode(token); _setUIndex(eff, msg.sender, _getUIndex(eff, poolAddress).mappend(p)); _setPoolAdjustmentFlowRate(eff, poolAddress, true, /* doShift? */ p.flow_rate(), t); return true; } + // succeeds only if `msg.sender` is a pool trusted by `token` function poolSettleClaim(ISuperfluidToken token, address claimRecipient, int256 amount) external - senderIsTrustedPool returns (bool) { address poolAddress = msg.sender; + // TODO: convert to modifier `poolIsTrustedByItsSuperToken(pool)` + if ( + token.isPool(this, msg.sender) == false || + ISuperfluidPool(poolAddress).superToken() != token + ) { + revert GDA_ONLY_SUPER_TOKEN_POOL(); + } + _doShift(abi.encode(token), poolAddress, claimRecipient, Value.wrap(amount)); return true; } @@ -981,20 +1008,4 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi token, subscriber, _POOL_SUBS_BITMAP_STATE_SLOT_ID, _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START ); } - - // This check passing means that either the pool is legitimate, or the associated token is not legitimate. - // if the token is legitimate, `token.isPool()` can return true only if the pool was created by this agreement. - // The following "false positives" could occur if the associated token: - // 1. is lying (claims the pool was registered by this agreement when it was not) - // or - // 2. is not associated to the same host (and agreements). - // In both cases, pre-conditions are not met and no state this agreement is responsible for can be manipulated. - modifier senderIsTrustedPool() { - ISuperfluidPool pool = ISuperfluidPool(msg.sender); - - if (pool.superToken().isPool(this, address(pool)) == false) { - revert GDA_ONLY_SUPER_TOKEN_POOL(); - } - _; - } } From e4638ac72bc3eab9811ae767580456825c969f46 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 24 Nov 2025 15:25:20 +0200 Subject: [PATCH 14/14] small typo fixes --- .../agreements/gdav1/GeneralDistributionAgreementV1.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index ea88066315..59dc3c0d61 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -433,9 +433,9 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi external view override returns (bool) { - // NOTE: this function is complete, in that even for invalid pools, it will always return false. + // NOTE: this function is total, in that even for invalid pools, it will always return false. // - // Retrospectively, it may be more helpful to the developers if this function is non-complete, and always revert + // Retrospectively, it may be more helpful to the developers if this function is non-total, and always revert // on invalid pool. return pool.superToken().isPoolMemberConnected(this, pool, member); }