From ba1b0674aba6c0297d826ca326b57678d3db4901 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 26 Jun 2025 16:34:40 +0200 Subject: [PATCH 01/68] distinguish between unsettled and claimable balance in pools --- .../gdav1/GeneralDistributionAgreementV1.sol | 2 +- .../agreements/gdav1/SuperfluidPool.sol | 31 +++++++++---- .../gdav1/GeneralDistributionAgreement.t.sol | 46 +++++++++++++++++++ 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index c7c02135ff..a7a7ed69a7 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -142,7 +142,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi _getPoolMemberData(token, account, ISuperfluidPool(pool)); assert(exist); assert(poolMemberData.pool == pool); - fromPools += ISuperfluidPool(pool).getClaimable(account, uint32(time)); + fromPools += SuperfluidPool(pool).getUnsettledValue(account, uint32(time)); } } rtb += fromPools; diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index 60d586a76c..4bbb820f4f 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -374,12 +374,9 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { /// @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) - ); + return GDA.isMemberConnected(ISuperfluidPool(address(this)), memberAddr) + ? int256(0) + : getUnsettledValue(memberAddr, time); } /// @inheritdoc ISuperfluidPool @@ -478,10 +475,23 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { _handlePoolMemberNFT(memberAddr, newUnits); } - function _claimAll(address memberAddr, uint32 time) internal returns (int256 amount) { - amount = getClaimable(memberAddr, time); + function _settle(address memberAddr, int256 amount) internal { assert(GDA.poolSettleClaim(superToken, memberAddr, (amount))); _membersData[memberAddr].claimedValue += amount; + } + + function getUnsettledValue(address memberAddr, uint32 time) public view 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) + ); + } + + function _claimAll(address memberAddr, uint32 time) internal returns (int256 amount) { + amount = getClaimable(memberAddr, time); + _settle(memberAddr, amount); emit DistributionClaimed(superToken, memberAddr, amount, _membersData[memberAddr].claimedValue); } @@ -511,10 +521,11 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { // 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); + int256 settleAmount = getUnsettledValue(memberAddr, time); + _settle(memberAddr, settleAmount); int128 units = uint256(_getUnits(memberAddr)).toInt256().toInt128(); if (doConnect) { - _shiftDisconnectedUnits(Unit.wrap(-units), Value.wrap(claimedAmount), Time.wrap(time)); + _shiftDisconnectedUnits(Unit.wrap(-units), Value.wrap(settleAmount), Time.wrap(time)); } else { _shiftDisconnectedUnits(Unit.wrap(units), Value.wrap(0), Time.wrap(time)); } 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 f7bc8156e7..8b453103a0 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -906,6 +906,52 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste _helperSuperfluidPoolUnitsTransferFrom(freePool, spender, owner, spender, uint256(uint128(transferAmount))); } + function testGetClaimable( + address member, + uint128 units, + uint64 distributionAmount, + bool useForwarder, + PoolConfig memory config + ) public { + vm.assume(member != address(0)); + vm.assume(member != address(freePool)); + vm.assume(units > 0); + vm.assume(distributionAmount > 0); + vm.assume(units < distributionAmount); + vm.assume(distributionAmount < type(uint128).max); + + // Create a pool for testing + ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, useForwarder, config); + _addAccount(member); + + // Step 1: Assign units to disconnected member + _helperUpdateMemberUnits(pool, alice, member, units, _StackVars_UseBools({useForwarder: useForwarder, useGDA: false})); + + (int256 balanceBefore,,,) = superToken.realtimeBalanceOfNow(member); + (int256 claimableBefore,) = pool.getClaimableNow(member); + + // Distribute + _helperDistributeViaGDA(superToken, alice, alice, pool, distributionAmount, useForwarder); + + // Check disconnected member: expect balance unchanged, claimable increased + (int256 balanceAfter1,,,) = superToken.realtimeBalanceOfNow(member); + (int256 claimableAfter1,) = pool.getClaimableNow(member); + + assertEq(balanceAfter1, balanceBefore, "Disconnected member balance should not change"); + assertTrue(claimableAfter1 > claimableBefore, "Disconnected member claimable amount should increase"); + + // Step 2: Connect member and distribute again + _helperConnectPool(member, superToken, pool, useForwarder); + _helperDistributeViaGDA(superToken, alice, alice, pool, distributionAmount, useForwarder); + + (int256 balanceAfter2,,,) = superToken.realtimeBalanceOfNow(member); + (int256 claimableAfter2,) = pool.getClaimableNow(member); + + // Check connected member: balance increased, claimable remains 0 + assertTrue(balanceAfter2 > balanceAfter1, "Connected member balance should increase"); + assertEq(claimableAfter2, 0, "Connected member claimable amount should be 0"); + } + /*////////////////////////////////////////////////////////////////////////// Assertion Functions //////////////////////////////////////////////////////////////////////////*/ From f76ce16b6df8d9415002a9301130aa585da82baf Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 26 Jun 2025 16:43:40 +0200 Subject: [PATCH 02/68] add assertions --- .../test/foundry/FoundrySuperfluidTester.t.sol | 15 +++++++++++++++ .../gdav1/GeneralDistributionAgreement.t.sol | 4 +--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol index b289cace21..7ad994aee7 100644 --- a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol +++ b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol @@ -1278,6 +1278,21 @@ contract FoundrySuperfluidTester is Test { // _assertRealTimeBalances(ISuperToken(address(poolSuperToken))); } + function _helperClaimAll(ISuperfluidPool pool_, address caller_, address member_) internal { + (int256 claimableBefore,) = pool_.getClaimableNow(member_); + (int256 balanceBefore,,,) = pool_.superToken().realtimeBalanceOfNow(member_); + + vm.startPrank(caller_); + pool_.claimAll(member_); + vm.stopPrank(); + + (int256 claimableAfter,) = pool_.getClaimableNow(member_); + (int256 balanceAfter,,,) = pool_.superToken().realtimeBalanceOfNow(member_); + + assertEq(claimableAfter, 0, "GDAv1.t: Member claimable amount should be 0"); + assertEq(balanceAfter, balanceBefore + claimableBefore, "GDAv1.t: Member balance should increase by claimable amount"); + } + function _helperConnectPool(address caller_, ISuperToken superToken_, ISuperfluidPool pool_, bool useForwarder_) internal { 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 8b453103a0..5415d34182 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -995,9 +995,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste 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(); + _helperClaimAll(freePool, user, u4); } else if (action == 3) { bool doConnect = s.v % 2 == 0 ? false : true; emit log_named_string("action", "doConnectPool"); From 9c433c503f23328b393ec553c20bdfc5d5f87c3d Mon Sep 17 00:00:00 2001 From: didi Date: Tue, 15 Jul 2025 18:12:29 +0200 Subject: [PATCH 03/68] use new reader function --- .../contracts/agreements/gdav1/SuperfluidPool.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index 612d353594..472bcd2160 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -375,7 +375,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { /// @inheritdoc ISuperfluidPool function getClaimable(address memberAddr, uint32 time) public view override returns (int256) { - return GDA.isMemberConnected(ISuperfluidPool(address(this)), memberAddr) + return superToken.isPoolMemberConnected(GDA, ISuperfluidPool(address(this)), memberAddr) ? int256(0) : getUnsettledValue(memberAddr, time); } From d6687abec81bfa8f19e6ebd3c55b7a6fb59b4d42 Mon Sep 17 00:00:00 2001 From: didi Date: Wed, 16 Jul 2025 17:19:44 +0200 Subject: [PATCH 04/68] small fix --- packages/ethereum-contracts/ops-scripts/deploy-framework.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/ops-scripts/deploy-framework.js b/packages/ethereum-contracts/ops-scripts/deploy-framework.js index f53b38a014..df893c09ec 100644 --- a/packages/ethereum-contracts/ops-scripts/deploy-framework.js +++ b/packages/ethereum-contracts/ops-scripts/deploy-framework.js @@ -996,7 +996,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( const poolMemberNFTPAddr = await superTokenLogic.POOL_MEMBER_NFT(); let poolMemberNFTLAddr = ZERO_ADDRESS; if (poolMemberNFTPAddr !== ZERO_ADDRESS) { - const poolMemberNFTContract = await PoolMemberNFT.at(poolMemberNFTPAddr); + const poolMemberNFTContract = await UUPSProxiable.at(poolMemberNFTPAddr); poolMemberNFTLAddr = await poolMemberNFTContract.getCodeAddress(); } From 5dad0c3565b9bb9a334ee0cf021bb089b012e802 Mon Sep 17 00:00:00 2001 From: didi Date: Wed, 16 Jul 2025 17:41:57 +0200 Subject: [PATCH 05/68] updated changelog --- packages/ethereum-contracts/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index 0905546b6a..7747002294 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -11,6 +11,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `GDAv1StorageReader` contains getters reading agreement data from the token contract, allowing contracts to get this data without making a call to the GDA contract. - `GDAv1StorageWriter` contains functions for writing agreement data to the token contract. This can only be used by the GDA contract itself. +### Fixed +- `ISuperfluidPool`: `getClaimable` and `getClaimableNow` could previously return non-zero values for connected pools, which was inconsistent with what `claimAll` would actually do in this situation (claim nothing). + ### Breaking - PoolMemberNFT pruning: `IPoolMemberNFT` and `PoolMemberNFT` removed, `POOL_MEMBER_NFT()` removed from `ISuperToken`. From 882f654f56b65e3381167de4411a1c9e48d558b1 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Thu, 17 Jul 2025 11:30:39 +0300 Subject: [PATCH 06/68] limit valid input size for _settle() --- .../agreements/gdav1/SuperfluidPool.sol | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index 472bcd2160..5415c4f85e 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -289,7 +289,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { Value.unwrap( // PDPoolMemberMU(poolIndex, memberData) PDPoolMemberMU(poolIndexDataToPDPoolIndex(_index), _memberDataToPDPoolMember(memberData)).settle( - Time.wrap(uint32(block.timestamp)) + Time.wrap(SafeCast.toUint32(block.timestamp)) ).m._settled_value ) ); @@ -370,7 +370,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { returns (int256 claimableBalance, uint256 timestamp) { timestamp = ISuperfluid(superToken.getHost()).getNow(); - return (getClaimable(memberAddr, uint32(timestamp)), timestamp); + return (getClaimable(memberAddr, SafeCast.toUint32(timestamp)), timestamp); } /// @inheritdoc ISuperfluidPool @@ -427,7 +427,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { if (superToken.isPool(GDA, memberAddr)) revert SUPERFLUID_POOL_NO_POOL_MEMBERS(); if (memberAddr == address(0)) revert SUPERFLUID_POOL_NO_ZERO_ADDRESS(); - uint32 time = uint32(ISuperfluid(superToken.getHost()).getNow()); + uint32 time = SafeCast.toUint32(ISuperfluid(superToken.getHost()).getNow()); Time t = Time.wrap(time); Unit wrappedUnits = toSemanticMoneyUnit(newUnits); @@ -456,8 +456,9 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { emit MemberUnitsUpdated(superToken, memberAddr, oldUnits, newUnits); } - function _settle(address memberAddr, int256 amount) internal { - assert(GDA.poolSettleClaim(superToken, memberAddr, (amount))); + function _settle(address memberAddr, uint32 time) internal returns (int256 amount) { + amount = getUnsettledValue(memberAddr, time); + assert(GDA.poolSettleClaim(superToken, memberAddr, amount)); _membersData[memberAddr].claimedValue += amount; } @@ -471,9 +472,10 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { } function _claimAll(address memberAddr, uint32 time) internal returns (int256 amount) { - amount = getClaimable(memberAddr, time); - _settle(memberAddr, amount); - + // For connected pool, claimable amount is zero; hence, we skip. + if (!superToken.isPoolMemberConnected(GDA, ISuperfluidPool(address(this)), memberAddr)) { + amount = _settle(memberAddr, time); + } emit DistributionClaimed(superToken, memberAddr, amount, _membersData[memberAddr].claimedValue); } @@ -485,7 +487,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { /// @inheritdoc ISuperfluidPool function claimAll(address memberAddr) public returns (bool) { bool isConnected = superToken.isPoolMemberConnected(GDA, this, memberAddr); - uint32 time = uint32(ISuperfluid(superToken.getHost()).getNow()); + uint32 time = SafeCast.toUint32(ISuperfluid(superToken.getHost()).getNow()); int256 claimedAmount = _claimAll(memberAddr, time); if (!isConnected) { _shiftDisconnectedUnits(Unit.wrap(0), Value.wrap(claimedAmount), Time.wrap(time)); @@ -502,8 +504,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { // 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 settleAmount = getUnsettledValue(memberAddr, time); - _settle(memberAddr, settleAmount); + int256 settleAmount = _settle(memberAddr, time); int128 units = uint256(_getUnits(memberAddr)).toInt256().toInt128(); if (doConnect) { _shiftDisconnectedUnits(Unit.wrap(-units), Value.wrap(settleAmount), Time.wrap(time)); From e27cb4181602fa9df8414d267d99c44484f8bdfe Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Thu, 17 Jul 2025 11:33:54 +0300 Subject: [PATCH 07/68] remove some silly type convrersions --- .../contracts/agreements/gdav1/SuperfluidPool.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index 5415c4f85e..6b7c33a895 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -375,7 +375,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { /// @inheritdoc ISuperfluidPool function getClaimable(address memberAddr, uint32 time) public view override returns (int256) { - return superToken.isPoolMemberConnected(GDA, ISuperfluidPool(address(this)), memberAddr) + return superToken.isPoolMemberConnected(GDA, this, memberAddr) ? int256(0) : getUnsettledValue(memberAddr, time); } @@ -440,7 +440,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { PDPoolMemberMU memory mu = PDPoolMemberMU(pdPoolIndex, pdPoolMember); // update pool's disconnected units - if (!superToken.isPoolMemberConnected(GDA, ISuperfluidPool(address(this)), memberAddr)) { + if (!superToken.isPoolMemberConnected(GDA, this, memberAddr)) { _shiftDisconnectedUnits(wrappedUnits - mu.m.owned_units, Value.wrap(0), t); } @@ -473,7 +473,7 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { function _claimAll(address memberAddr, uint32 time) internal returns (int256 amount) { // For connected pool, claimable amount is zero; hence, we skip. - if (!superToken.isPoolMemberConnected(GDA, ISuperfluidPool(address(this)), memberAddr)) { + if (!superToken.isPoolMemberConnected(GDA, this, memberAddr)) { amount = _settle(memberAddr, time); } emit DistributionClaimed(superToken, memberAddr, amount, _membersData[memberAddr].claimedValue); From 5a0fb64cbe9d038a8f401d993f544c0fb1ebebeb Mon Sep 17 00:00:00 2001 From: pavedroad <138004431+pavedroad@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:46:52 +0800 Subject: [PATCH 08/68] chore: remove redundant words in comment (#2092) Signed-off-by: pavedroad --- .../scheduler/contracts/VestingSchedulerV2.sol | 2 +- .../scheduler/contracts/VestingSchedulerV3.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol b/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol index dce469187b..89464102a6 100644 --- a/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol +++ b/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol @@ -441,7 +441,7 @@ contract VestingSchedulerV2 is IVestingSchedulerV2, SuperAppBase { if (!disableClaimCheck && schedule.claimValidityDate != 0) revert ScheduleNotClaimed(); - // Ensure that that the claming date is after the cliff/flow date and before the claim validity date + // Ensure that the claming date is after the cliff/flow date and before the claim validity date if (schedule.cliffAndFlowDate > block.timestamp || _lteDateToExecuteCliffAndFlow(schedule) < block.timestamp) revert TimeWindowInvalid(); diff --git a/packages/automation-contracts/scheduler/contracts/VestingSchedulerV3.sol b/packages/automation-contracts/scheduler/contracts/VestingSchedulerV3.sol index 4170be4fc2..d8445d1478 100644 --- a/packages/automation-contracts/scheduler/contracts/VestingSchedulerV3.sol +++ b/packages/automation-contracts/scheduler/contracts/VestingSchedulerV3.sol @@ -760,7 +760,7 @@ contract VestingSchedulerV3 is IVestingSchedulerV3, IRelayRecipient { revert ScheduleNotClaimed(); } - // Ensure that that the claming date is after the cliff/flow date and before the claim validity date + // Ensure that the claming date is after the cliff/flow date and before the claim validity date if (schedule.cliffAndFlowDate > block.timestamp || _lteDateToExecuteCliffAndFlow(schedule) < block.timestamp) { revert TimeWindowInvalid(); } From b856b8afde71ff7ba6f339eb1ff8c1b641ad186f Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 17 Jul 2025 10:47:08 +0200 Subject: [PATCH 09/68] added GDA function which allows the pool admin to connect pools on behalf of members --- lib/forge-std | 2 +- .../gdav1/GeneralDistributionAgreementV1.sol | 117 +++++++++++++----- .../gdav1/IGeneralDistributionAgreementV1.sol | 11 ++ packages/ethereum-contracts/foundry.toml | 2 +- .../foundry/FoundrySuperfluidTester.t.sol | 55 ++++++-- .../gdav1/GeneralDistributionAgreement.t.sol | 66 +++++++++- 6 files changed, 210 insertions(+), 43 deletions(-) diff --git a/lib/forge-std b/lib/forge-std index 07263d193d..60acb7aaad 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d +Subproject commit 60acb7aaadcce2d68e52986a0a66fe79f07d138f diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 2ecb3e7671..195c61b9b9 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -48,6 +48,9 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi address public constant SUPERFLUID_POOL_DEPLOYER_ADDRESS = address(SuperfluidPoolDeployerLibrary); + // @dev The max number of slots which can be used for connecting pools on behalf of a member (per token) + uint32 public constant MAX_POOL_AUTO_CONNECT_SLOTS = 4; + /// @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 @@ -305,49 +308,86 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi pool.claimAll(memberAddress); } - // @note setPoolConnection function naming - function connectPool(ISuperfluidPool pool, bool doConnect, bytes calldata ctx) - public - returns (bytes memory newCtx) + /// @inheritdoc IGeneralDistributionAgreementV1 + function connectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { + ISuperfluidToken token = pool.superToken(); + ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); + newCtx = ctx; + _setPoolConnection(pool, token, currentContext.msgSender, true, true, currentContext); + } + + /// @inheritdoc IGeneralDistributionAgreementV1 + function tryConnectPoolFor(ISuperfluidPool pool, address memberAddr, bytes calldata ctx) + external + override + returns (bool success, bytes memory newCtx) { ISuperfluidToken token = pool.superToken(); ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); - address msgSender = currentContext.msgSender; + // Only the pool admin is allowed to do this + if (currentContext.msgSender != pool.admin()) { + revert GDA_NOT_POOL_ADMIN(); + } + newCtx = ctx; + success = _setPoolConnection(pool, token, memberAddr, true, false, currentContext); + } + + /// @inheritdoc IGeneralDistributionAgreementV1 + function disconnectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { + ISuperfluidToken token = pool.superToken(); + ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); newCtx = ctx; - bool isConnected = token.isPoolMemberConnected(this, pool, msgSender); - if (doConnect != isConnected) { - assert( - SuperfluidPool(address(pool)).operatorConnectMember( - msgSender, doConnect, uint32(currentContext.timestamp) - ) - ); + _setPoolConnection(pool, token, currentContext.msgSender, false, true /* ignored */, currentContext); + } + + + // @note setPoolConnection function naming + function _setPoolConnection( + ISuperfluidPool pool, + ISuperfluidToken token, + address memberAddr, + bool doConnect, + bool useAllSlots, + ISuperfluid.Context memory currentContext + ) + internal + returns (bool success) + { + bool isConnected = token.isPoolMemberConnected(this, pool, memberAddr); + if (doConnect != isConnected) { if (doConnect) { - uint32 poolSlotId = - _findAndFillPoolConnectionsBitmap(token, msgSender, bytes32(uint256(uint160(address(pool))))); + uint32 poolSlotId = useAllSlots + ? _findAndFillPoolConnectionsBitmap(token, memberAddr, bytes32(uint256(uint160(address(pool))))) + : _tryFindAndFillPoolConnectionsBitmap( + token, memberAddr, bytes32(uint256(uint160(address(pool)))), MAX_POOL_AUTO_CONNECT_SLOTS + ); + + // only if we operate with limited slots, can it fail without revert. + if (poolSlotId == type(uint32).max) { + return false; + } token.createPoolConnectivity - (msgSender, GDAv1StorageLib.PoolConnectivity({ slotId: poolSlotId, pool: pool })); + (memberAddr, GDAv1StorageLib.PoolConnectivity({ slotId: poolSlotId, pool: pool })); } else { (, GDAv1StorageLib.PoolConnectivity memory poolConnectivity) = - token.getPoolConnectivity(this, msgSender, pool); - token.deletePoolConnectivity(msgSender, pool); + token.getPoolConnectivity(this, memberAddr, pool); + token.deletePoolConnectivity(memberAddr, pool); - _clearPoolConnectionsBitmap(token, msgSender, poolConnectivity.slotId); + _clearPoolConnectionsBitmap(token, memberAddr, poolConnectivity.slotId); } - emit PoolConnectionUpdated(token, pool, msgSender, doConnect, currentContext.userData); - } - } + assert( + SuperfluidPool(address(pool)).operatorConnectMember( + memberAddr, doConnect, uint32(currentContext.timestamp) + ) + ); - /// @inheritdoc IGeneralDistributionAgreementV1 - function connectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { - return connectPool(pool, true, ctx); - } + emit PoolConnectionUpdated(token, pool, memberAddr, doConnect, currentContext.userData); + } - /// @inheritdoc IGeneralDistributionAgreementV1 - function disconnectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { - return connectPool(pool, false, ctx); + return true; } /// @inheritdoc IGeneralDistributionAgreementV1 @@ -897,6 +937,27 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ); } + // allow to specify custom slot nr, return type(uint32).max if no slot is found + function _tryFindAndFillPoolConnectionsBitmap( + ISuperfluidToken token, + address poolMember, + bytes32 poolID, + uint32 maxSlots + ) + private + returns (uint32 slotId) + { + (uint32[] memory slotIds, ) = SlotsBitmapLibrary.listData( + token, poolMember, _POOL_SUBS_BITMAP_STATE_SLOT_ID, _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START + ); + if (slotIds.length >= maxSlots) { + return type(uint32).max; + } + 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); } diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol index d3eb2ea33b..4d2773ca01 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol @@ -214,6 +214,17 @@ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement { /// @return newCtx the new context bytes function connectPool(ISuperfluidPool pool, bytes calldata ctx) external virtual returns (bytes memory newCtx); + /// @notice Allows the pool admin to connect a member to the pool. + /// @param pool The pool address + /// @param memberAddr The member address + /// @param ctx Context bytes + /// @return success true if the member was (or remained) connected, false otherwise + /// @return newCtx the new context bytes + function tryConnectPoolFor(ISuperfluidPool pool, address memberAddr, bytes calldata ctx) + external + virtual + returns (bool success, 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 diff --git a/packages/ethereum-contracts/foundry.toml b/packages/ethereum-contracts/foundry.toml index df8b2e2338..2e0330617d 100644 --- a/packages/ethereum-contracts/foundry.toml +++ b/packages/ethereum-contracts/foundry.toml @@ -8,7 +8,7 @@ ignored_error_codes = [ 1699 # assembly { selfdestruct } in contracts/mocks/SuperfluidDestructorMock.sol ] # keep in sync with truffle-config.js -evm_version = 'paris' +evm_version = 'shanghai' optimizer = true optimizer_runs = 200 remappings = [ diff --git a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol index 2af3bc4e5a..4ce476057b 100644 --- a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol +++ b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol @@ -87,7 +87,7 @@ contract FoundrySuperfluidTester is Test { } struct ExpectedPoolMemberData { - bool isConnected; + bool wasConnected; uint128 ownedUnits; int96 flowRate; int96 netFlowRate; @@ -1204,7 +1204,7 @@ contract FoundrySuperfluidTester is Test { vm.assume(newUnits_ < type(uint72).max); ISuperToken poolSuperToken = ISuperToken(address(pool_.superToken())); - (bool isConnected, int256 oldUnits,) = _helperGetMemberPoolState(pool_, member_); + (bool wasConnected, int256 oldUnits,) = _helperGetMemberPoolState(pool_, member_); PoolUnitData memory poolUnitDataBefore = _helperGetPoolUnitsData(pool_); @@ -1220,8 +1220,11 @@ contract FoundrySuperfluidTester is Test { assertEq(pool_.getUnits(member_), newUnits_, "GDAv1.t: Members' units incorrectly set"); - // Assert that pending balance didn't change if user is disconnected - if (!isConnected) { + // Determine the new connection status after the update + bool isConnectedAfter = sf.gda.isMemberConnected(pool_, member_); + + // Assert that pending balance didn't change if user was and remains disconnected + if (!wasConnected && !isConnectedAfter) { (int256 balanceAfter,,,) = poolSuperToken.realtimeBalanceOfNow(member_); assertEq( balanceAfter, balanceBefore, "_helperUpdateMemberUnits: Pending balance changed" @@ -1253,15 +1256,47 @@ contract FoundrySuperfluidTester is Test { poolUnitDataAfter.totalUnits, "_helperUpdateMemberUnits: Pool total units incorrect" ); + + // Calculate expected connected units change based on new behavior + int256 expectedConnectedUnitsDelta; + if (wasConnected && isConnectedAfter) { + // Member was connected and remains connected - units delta applies to connected + expectedConnectedUnitsDelta = unitsDelta; + } else if (!wasConnected && isConnectedAfter) { + // Member was disconnected and is now connected - all new units go to connected + expectedConnectedUnitsDelta = uint256(newUnits_).toInt256(); + } else if (wasConnected && !isConnectedAfter) { + // Member was connected and is now disconnected - all old units move to disconnected + expectedConnectedUnitsDelta = -oldUnits; + } else { + // Member was disconnected and remains disconnected - units delta applies to disconnected + expectedConnectedUnitsDelta = 0; + } + assertEq( - uint256(uint256(poolUnitDataBefore.connectedUnits).toInt256() + (isConnected ? unitsDelta : int128(0))), + uint256(uint256(poolUnitDataBefore.connectedUnits).toInt256() + expectedConnectedUnitsDelta), poolUnitDataAfter.connectedUnits, "_helperUpdateMemberUnits: Pool connected units incorrect" ); + + // Calculate expected disconnected units change based on new behavior + int256 expectedDisconnectedUnitsDelta; + if (wasConnected && isConnectedAfter) { + // Member was connected and remains connected - no change to disconnected + expectedDisconnectedUnitsDelta = 0; + } else if (!wasConnected && isConnectedAfter) { + // Member was disconnected and is now connected - all old units move from disconnected to connected + expectedDisconnectedUnitsDelta = -oldUnits; + } else if (wasConnected && !isConnectedAfter) { + // Member was connected and is now disconnected - all new units go to disconnected + expectedDisconnectedUnitsDelta = uint256(newUnits_).toInt256(); + } else { + // Member was disconnected and remains disconnected - units delta applies to disconnected + expectedDisconnectedUnitsDelta = unitsDelta; + } + assertEq( - uint256( - uint256(poolUnitDataBefore.disconnectedUnits).toInt256() + (isConnected ? int128(0) : unitsDelta) - ), + uint256(uint256(poolUnitDataBefore.disconnectedUnits).toInt256() + expectedDisconnectedUnitsDelta), poolUnitDataAfter.disconnectedUnits, "_helperUpdateMemberUnits: Pool disconnected units incorrect" ); @@ -1690,10 +1725,10 @@ contract FoundrySuperfluidTester is Test { function _helperGetMemberPoolState(ISuperfluidPool pool_, address member_) internal view - returns (bool isConnected, int256 units, int96 flowRate) + returns (bool wasConnected, int256 units, int96 flowRate) { units = uint256(pool_.getUnits(member_)).toInt256(); - isConnected = sf.gda.isMemberConnected(pool_, member_); + wasConnected = sf.gda.isMemberConnected(pool_, member_); flowRate = pool_.getMemberFlowRate(member_); } diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol index 1512a48d2f..880b5b5346 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -694,7 +694,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste assertEq(poolAdjustmentFlowRate, 0, "GDAv1.t: Pool adjustment rate is non-zero"); } - function testDistributeFlowToUnconnectedMembers( + function skip_testDistributeFlowToUnconnectedMembers( uint64[5] memory memberUnits, int32 flowRate, uint16 warpTime, @@ -971,8 +971,10 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste (int256 balanceAfter1,,,) = superToken.realtimeBalanceOfNow(member); (int256 claimableAfter1,) = pool.getClaimableNow(member); - assertEq(balanceAfter1, balanceBefore, "Disconnected member balance should not change"); - assertTrue(claimableAfter1 > claimableBefore, "Disconnected member claimable amount should increase"); + if (!sf.gda.isMemberConnected(pool, member)) { + assertEq(balanceAfter1, balanceBefore, "Disconnected member balance should not change"); + assertTrue(claimableAfter1 > claimableBefore, "Disconnected member claimable amount should increase"); + } // Step 2: Connect member and distribute again _helperConnectPool(member, superToken, pool, useForwarder); @@ -986,6 +988,64 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste assertEq(claimableAfter2, 0, "Connected member claimable amount should be 0"); } + + function testAdminConnect(address member, uint128 units, uint64 distributionAmount) public { + vm.assume(member != address(0)); + vm.assume(member != address(freePool)); + vm.assume(units > 0); + vm.assume(units < distributionAmount); + + uint256 expectedAmount = (distributionAmount / units) * units; + + uint256 balanceBefore = superToken.balanceOf(member); + + vm.startPrank(alice); + // update units to non-zero and connect the pool + freePool.updateMemberUnits(member, units); + sf.host.callAgreement( + sf.gda, + abi.encodeCall(sf.gda.tryConnectPoolFor, (freePool, member, new bytes(0))), + new bytes(0) + ); + assertEq(freePool.getUnits(member), units); + assertEq(sf.gda.isMemberConnected(freePool, member), true, "member should be (auto)connected"); + + // distribute tokens: this is supposed to show up as balance, with claimable amount remaining 0 + superToken.distribute(alice, freePool, distributionAmount); + + assertEq(superToken.balanceOf(member), balanceBefore + expectedAmount, "balance != distributionAmount"); + assertEq(freePool.getClaimable(member, uint32(block.timestamp)), 0, "claimable != 0"); + + // update units to 0, this is supposed to disconnect the pool + freePool.updateMemberUnits(member, 0); + assertEq(freePool.getUnits(member), 0); + + //assertEq(sf.gda.isMemberConnected(freePool, member), false, "member should be (auto)disconnected"); + } + + function testAutoConnectSlotLimit() public { + for (uint256 i = 0; i < sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS() * 2; ++i) { + ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, PoolConfig({ transferabilityForUnitsOwner: false, distributionFromAnyAddress: true })); + vm.startPrank(alice); + // update units to non-zero and connect the pool + pool.updateMemberUnits(bob, 1); + + bytes memory ret = sf.host.callAgreement( + sf.gda, + abi.encodeCall(sf.gda.tryConnectPoolFor, (pool, bob, new bytes(0))), + new bytes(0) + ); + (bool success, ) = abi.decode(ret, (bool, bytes)); + if (i < sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS()) { + assertEq(success, true, "success != true"); + assertEq(sf.gda.isMemberConnected(pool, bob), true, "bob should be (auto)connected"); + } else { + assertEq(success, false, "success != false"); + assertEq(sf.gda.isMemberConnected(pool, bob), false, "bob should not be (auto)connected"); + } + } + } + /*////////////////////////////////////////////////////////////////////////// Assertion Functions //////////////////////////////////////////////////////////////////////////*/ From cd864bdd6d687b7f127f3e28024f18bc0b936683 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 17 Jul 2025 12:50:29 +0200 Subject: [PATCH 10/68] add mechanism for opting out of autoconnect using SimpleACL --- .../gdav1/GeneralDistributionAgreementV1.sol | 23 +++++++++- .../gdav1/IGeneralDistributionAgreementV1.sol | 8 +++- .../contracts/libs/SlotsBitmapLibrary.sol | 4 +- .../SuperfluidFrameworkDeploymentSteps.t.sol | 9 ++++ .../gdav1/GeneralDistributionAgreement.t.sol | 45 +++++++++++++++++-- 5 files changed, 81 insertions(+), 8 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 195c61b9b9..55da7e1938 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.23; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { ISuperfluid, ISuperfluidGovernance } from "../../interfaces/superfluid/ISuperfluid.sol"; +import { ISuperfluid, ISuperfluidGovernance, IAccessControl } from "../../interfaces/superfluid/ISuperfluid.sol"; import { BasicParticle, PDPoolIndex, @@ -51,6 +51,9 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi // @dev The max number of slots which can be used for connecting pools on behalf of a member (per token) uint32 public constant MAX_POOL_AUTO_CONNECT_SLOTS = 4; + bytes32 constant public ACL_POOL_CONNECT_EXCLUSIVE_ROLE = keccak256("ACL_POOL_CONNECT_EXCLUSIVE_ROLE"); + bytes32 constant public ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN = keccak256("ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN"); + /// @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 @@ -329,7 +332,23 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi revert GDA_NOT_POOL_ADMIN(); } newCtx = ctx; - success = _setPoolConnection(pool, token, memberAddr, true, false, currentContext); + + // check if the member has opted out of autoconnect + IAccessControl simpleACL = ISuperfluid(_host).getSimpleACL(); + if (simpleACL.hasRole(ACL_POOL_CONNECT_EXCLUSIVE_ROLE, memberAddr)) { + success = false; + } else { + success = _setPoolConnection(pool, token, memberAddr, true, false, currentContext); + } + } + + function setConnectPermission(bool allow) external override { + IAccessControl simpleACL = ISuperfluid(_host).getSimpleACL(); + if (!allow) { + simpleACL.grantRole(ACL_POOL_CONNECT_EXCLUSIVE_ROLE, msg.sender); + } else { + simpleACL.revokeRole(ACL_POOL_CONNECT_EXCLUSIVE_ROLE, msg.sender); + } } /// @inheritdoc IGeneralDistributionAgreementV1 diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol index 4d2773ca01..51cd843986 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol @@ -214,7 +214,8 @@ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement { /// @return newCtx the new context bytes function connectPool(ISuperfluidPool pool, bytes calldata ctx) external virtual returns (bytes memory newCtx); - /// @notice Allows the pool admin to connect a member to the pool. + /// @notice Allows the pool admin to connect a member to the pool if autoconnect slots are available. + /// "autoconnect slots" are a subset of the slots available to pool members themselves. /// @param pool The pool address /// @param memberAddr The member address /// @param ctx Context bytes @@ -225,6 +226,11 @@ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement { virtual returns (bool success, bytes memory newCtx); + /// @notice Lets accounts deny or allow 3rd parties to connect them to pools. + /// By default, this permission is given (except by accounts which are Super Apps). + /// @param allow true to allow, false to deny + function setConnectPermission(bool allow) external virtual; + /// @notice Disconnects `msg.sender` from `pool`. /// @dev This is used to disconnect a pool from the GDA. /// @param pool The pool address diff --git a/packages/ethereum-contracts/contracts/libs/SlotsBitmapLibrary.sol b/packages/ethereum-contracts/contracts/libs/SlotsBitmapLibrary.sol index 3322b7056e..c5cc9349f9 100644 --- a/packages/ethereum-contracts/contracts/libs/SlotsBitmapLibrary.sol +++ b/packages/ethereum-contracts/contracts/libs/SlotsBitmapLibrary.sol @@ -12,8 +12,8 @@ import {ISuperfluidToken} from "../interfaces/superfluid/ISuperfluidToken.sol"; * - A data slot can be enabled or disabled with the help of bitmap. * - MAX_NUM_SLOTS is 256 in this implementation (using one uint256) * - Superfluid token storage usage: - * - getAgreementStateSlot(bitmapStateSlotId) stores the bitmap of enabled data slots - * - getAgreementStateSlot(dataStateSlotIDStart + stotId) stores the data of the slot + * - updateAgreementStateSlot(bitmapStateSlotId) stores the bitmap of enabled data slots + * - updateAgreementStateSlot(dataStateSlotIDStart + stotId) stores the data of the slot */ library SlotsBitmapLibrary { diff --git a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol index 163713169e..616c2e5b8a 100644 --- a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol +++ b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol @@ -194,6 +194,15 @@ contract SuperfluidFrameworkDeploymentSteps { gdaV1Logic.superfluidPoolBeacon().upgradeTo(address(superfluidPoolLogic)); gdaV1Logic.superfluidPoolBeacon().transferOwnership(address(host)); } + + SimpleACL(address(host.getSimpleACL())).setRoleAdmin( + gdaV1.ACL_POOL_CONNECT_EXCLUSIVE_ROLE(), + gdaV1.ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN() + ); + SimpleACL(address(host.getSimpleACL())).grantRole( + gdaV1.ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN(), + address(gdaV1) + ); } else if (step == 3) {// PERIPHERAL CONTRACTS: NFT Proxy and Logic { poolAdminNFT = PoolAdminNFT(address(ProxyDeployerLibrary.deployUUPSProxy())); 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 880b5b5346..6fc981b589 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.23; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; import "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol"; import "../../FoundrySuperfluidTester.t.sol"; import { @@ -988,7 +989,6 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste assertEq(claimableAfter2, 0, "Connected member claimable amount should be 0"); } - function testAdminConnect(address member, uint128 units, uint64 distributionAmount) public { vm.assume(member != address(0)); vm.assume(member != address(freePool)); @@ -1019,8 +1019,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste // update units to 0, this is supposed to disconnect the pool freePool.updateMemberUnits(member, 0); assertEq(freePool.getUnits(member), 0); - - //assertEq(sf.gda.isMemberConnected(freePool, member), false, "member should be (auto)disconnected"); + vm.stopPrank(); } function testAutoConnectSlotLimit() public { @@ -1043,9 +1042,49 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste assertEq(success, false, "success != false"); assertEq(sf.gda.isMemberConnected(pool, bob), false, "bob should not be (auto)connected"); } + vm.stopPrank(); } } + function testConnectPermissioning() public { + vm.startPrank(bob); + sf.gda.setConnectPermission(false); + vm.stopPrank(); + + vm.startPrank(alice); + freePool.updateMemberUnits(bob, 1); + sf.host.callAgreement( + sf.gda, + abi.encodeCall(sf.gda.tryConnectPoolFor, (freePool, bob, new bytes(0))), + new bytes(0) + ); + assertEq(sf.gda.isMemberConnected(freePool, bob), false, "member should not be (auto)connected"); + + // alice can't revoke the opt-out + IAccessControl simpleACL = sf.host.getSimpleACL(); + bytes32 aclRole = sf.gda.ACL_POOL_CONNECT_EXCLUSIVE_ROLE(); //need this ext call before the expectRevert + vm.expectRevert(); + simpleACL.revokeRole(aclRole, bob); + vm.stopPrank(); + + // bob changes his mind and gives permission + + vm.startPrank(bob); + sf.gda.setConnectPermission(true); + vm.stopPrank(); + + // now alice can connect bob + vm.startPrank(alice); + freePool.updateMemberUnits(bob, 1); + sf.host.callAgreement( + sf.gda, + abi.encodeCall(sf.gda.tryConnectPoolFor, (freePool, bob, new bytes(0))), + new bytes(0) + ); + assertEq(sf.gda.isMemberConnected(freePool, bob), true, "member should not be (auto)connected"); + vm.stopPrank(); + } + /*////////////////////////////////////////////////////////////////////////// Assertion Functions //////////////////////////////////////////////////////////////////////////*/ From 88e17f35973119eaf22213ebf5e38396317ecb65 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 17 Jul 2025 13:05:44 +0200 Subject: [PATCH 11/68] revert forge-std upgrade --- lib/forge-std | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/forge-std b/lib/forge-std index 60acb7aaad..07263d193d 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 60acb7aaadcce2d68e52986a0a66fe79f07d138f +Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d From 8db285b1802b9f84a9813f87b4566e73de369b52 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 17 Jul 2025 13:19:28 +0200 Subject: [PATCH 12/68] simplification --- .../gdav1/GeneralDistributionAgreementV1.sol | 40 +++++-------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 55da7e1938..90683f231d 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -376,17 +376,20 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi if (doConnect != isConnected) { if (doConnect) { - uint32 poolSlotId = useAllSlots - ? _findAndFillPoolConnectionsBitmap(token, memberAddr, bytes32(uint256(uint160(address(pool))))) - : _tryFindAndFillPoolConnectionsBitmap( - token, memberAddr, bytes32(uint256(uint160(address(pool)))), MAX_POOL_AUTO_CONNECT_SLOTS + if (!useAllSlots) { + // check if we're below the slot limit for autoconnect + (uint32[] memory slotIds, ) = SlotsBitmapLibrary.listData( + token, memberAddr, _POOL_SUBS_BITMAP_STATE_SLOT_ID, _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START ); - - // only if we operate with limited slots, can it fail without revert. - if (poolSlotId == type(uint32).max) { - return false; + if (slotIds.length >= MAX_POOL_AUTO_CONNECT_SLOTS) { + return false; + } } + uint32 poolSlotId = _findAndFillPoolConnectionsBitmap( + token, memberAddr, bytes32(uint256(uint160(address(pool)))) + ); + token.createPoolConnectivity (memberAddr, GDAv1StorageLib.PoolConnectivity({ slotId: poolSlotId, pool: pool })); } else { @@ -956,27 +959,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ); } - // allow to specify custom slot nr, return type(uint32).max if no slot is found - function _tryFindAndFillPoolConnectionsBitmap( - ISuperfluidToken token, - address poolMember, - bytes32 poolID, - uint32 maxSlots - ) - private - returns (uint32 slotId) - { - (uint32[] memory slotIds, ) = SlotsBitmapLibrary.listData( - token, poolMember, _POOL_SUBS_BITMAP_STATE_SLOT_ID, _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START - ); - if (slotIds.length >= maxSlots) { - return type(uint32).max; - } - 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); } From 101bdfc10a8abe87b8edc296bd7901081ed14d54 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 17 Jul 2025 22:50:10 +0200 Subject: [PATCH 13/68] don't restrict autoconnect to pool admin, more testing --- .../autowrap/foundry.toml | 2 +- .../scheduler/foundry.toml | 2 +- .../gdav1/GeneralDistributionAgreementV1.sol | 33 +++++++--------- .../foundry/FoundrySuperfluidTester.t.sol | 38 ++++++++++++++----- .../gdav1/GeneralDistributionAgreement.t.sol | 19 +++++----- packages/solidity-semantic-money/foundry.toml | 2 +- 6 files changed, 55 insertions(+), 41 deletions(-) diff --git a/packages/automation-contracts/autowrap/foundry.toml b/packages/automation-contracts/autowrap/foundry.toml index bdf6e6f602..f4f292602f 100644 --- a/packages/automation-contracts/autowrap/foundry.toml +++ b/packages/automation-contracts/autowrap/foundry.toml @@ -3,7 +3,7 @@ root = '../../../' libs = ['lib'] src = 'packages/automation-contracts/autowrap' solc_version = "0.8.23" -evm_version = 'paris' +evm_version = 'shanghai' optimizer = true optimizer_runs = 200 remappings = [ diff --git a/packages/automation-contracts/scheduler/foundry.toml b/packages/automation-contracts/scheduler/foundry.toml index 33fe841026..db88ebe9c4 100644 --- a/packages/automation-contracts/scheduler/foundry.toml +++ b/packages/automation-contracts/scheduler/foundry.toml @@ -3,7 +3,7 @@ root = '../../../' libs = ['lib'] src = 'packages/automation-contracts/scheduler' solc_version = "0.8.23" -evm_version = 'paris' +evm_version = 'shanghai' optimizer = true optimizer_runs = 200 remappings = [ diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 90683f231d..554922cf33 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -313,10 +313,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi /// @inheritdoc IGeneralDistributionAgreementV1 function connectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { - ISuperfluidToken token = pool.superToken(); - ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); newCtx = ctx; - _setPoolConnection(pool, token, currentContext.msgSender, true, true, currentContext); + _setPoolConnection(pool, address(0), true, true, ctx); } /// @inheritdoc IGeneralDistributionAgreementV1 @@ -325,12 +323,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (bool success, bytes memory newCtx) { - ISuperfluidToken token = pool.superToken(); - ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); - // Only the pool admin is allowed to do this - if (currentContext.msgSender != pool.admin()) { - revert GDA_NOT_POOL_ADMIN(); - } newCtx = ctx; // check if the member has opted out of autoconnect @@ -338,7 +330,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi if (simpleACL.hasRole(ACL_POOL_CONNECT_EXCLUSIVE_ROLE, memberAddr)) { success = false; } else { - success = _setPoolConnection(pool, token, memberAddr, true, false, currentContext); + success = _setPoolConnection(pool, memberAddr, true, false, ctx); } } @@ -353,30 +345,33 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi /// @inheritdoc IGeneralDistributionAgreementV1 function disconnectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { - ISuperfluidToken token = pool.superToken(); - ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); newCtx = ctx; - _setPoolConnection(pool, token, currentContext.msgSender, false, true /* ignored */, currentContext); + _setPoolConnection(pool, address(0), false, true /* ignored */, ctx); } - - // @note setPoolConnection function naming + // @note memberAddr has override semantics - if set to address(0), it will be set to the msgSender function _setPoolConnection( ISuperfluidPool pool, - ISuperfluidToken token, address memberAddr, bool doConnect, - bool useAllSlots, - ISuperfluid.Context memory currentContext + bool onlyAutoConnectSlots, + bytes memory ctx ) internal returns (bool success) { + ISuperfluidToken token = pool.superToken(); + ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); + + if (memberAddr == address(0)) { + memberAddr = currentContext.msgSender; + } + bool isConnected = token.isPoolMemberConnected(this, pool, memberAddr); if (doConnect != isConnected) { if (doConnect) { - if (!useAllSlots) { + if (onlyAutoConnectSlots) { // check if we're below the slot limit for autoconnect (uint32[] memory slotIds, ) = SlotsBitmapLibrary.listData( token, memberAddr, _POOL_SUBS_BITMAP_STATE_SLOT_ID, _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START diff --git a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol index 4ce476057b..fc7e28cabf 100644 --- a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol +++ b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol @@ -1324,20 +1324,38 @@ contract FoundrySuperfluidTester is Test { function _helperConnectPool(address caller_, ISuperToken superToken_, ISuperfluidPool pool_, bool useForwarder_) internal { - (bool isConnectedBefore, int256 oldUnits, int96 oldFlowRate) = _helperGetMemberPoolState(pool_, caller_); + _helperConnectPoolFor(caller_, caller_, superToken_, pool_, useForwarder_); + } + + function _helperConnectPoolFor(address member_, address caller_, ISuperToken superToken_, ISuperfluidPool pool_, bool useForwarder_) + internal + { + (bool isConnectedBefore, int256 oldUnits, int96 oldFlowRate) = _helperGetMemberPoolState(pool_, member_); PoolUnitData memory poolUnitDataBefore = _helperGetPoolUnitsData(pool_); PoolFlowRateData memory poolFlowRateDataBefore = _helperGetPoolFlowRatesData(pool_); vm.startPrank(caller_); if (useForwarder_) { - sf.gdaV1Forwarder.connectPool(pool_, ""); + if (caller_ == member_) { + sf.gdaV1Forwarder.connectPool(pool_, ""); + } else { + revert("autoconnect not supported by forwarder"); + } } else { - sf.host.callAgreement( - sf.gda, - abi.encodeWithSelector(IGeneralDistributionAgreementV1.connectPool.selector, pool_, ""), - new bytes(0) - ); + if (caller_ == member_) { + sf.host.callAgreement( + sf.gda, + abi.encodeWithSelector(IGeneralDistributionAgreementV1.connectPool.selector, pool_, ""), + new bytes(0) + ); + } else { + sf.host.callAgreement( + sf.gda, + abi.encodeWithSelector(IGeneralDistributionAgreementV1.tryConnectPoolFor.selector, pool_, member_, ""), + new bytes(0) + ); + } } vm.stopPrank(); @@ -1345,12 +1363,12 @@ contract FoundrySuperfluidTester is Test { PoolFlowRateData memory poolFlowRateDataAfter = _helperGetPoolFlowRatesData(pool_); { - _helperTakeBalanceSnapshot(superToken_, caller_); + _helperTakeBalanceSnapshot(superToken_, member_); } bool isMemberConnected = useForwarder_ - ? sf.gdaV1Forwarder.isMemberConnected(pool_, caller_) - : sf.gda.isMemberConnected(pool_, caller_); + ? sf.gdaV1Forwarder.isMemberConnected(pool_, member_) + : sf.gda.isMemberConnected(pool_, member_); assertEq(isMemberConnected, true, "GDAv1.t: Member not connected"); // Assert connected units delta for the pool 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 6fc981b589..b260bf2190 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -989,7 +989,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste assertEq(claimableAfter2, 0, "Connected member claimable amount should be 0"); } - function testAdminConnect(address member, uint128 units, uint64 distributionAmount) public { + function testAutoConnect(address member, uint128 units, uint64 distributionAmount) public { vm.assume(member != address(0)); vm.assume(member != address(freePool)); vm.assume(units > 0); @@ -1015,10 +1015,6 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste assertEq(superToken.balanceOf(member), balanceBefore + expectedAmount, "balance != distributionAmount"); assertEq(freePool.getClaimable(member, uint32(block.timestamp)), 0, "claimable != 0"); - - // update units to 0, this is supposed to disconnect the pool - freePool.updateMemberUnits(member, 0); - assertEq(freePool.getUnits(member), 0); vm.stopPrank(); } @@ -1046,7 +1042,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste } } - function testConnectPermissioning() public { + function testAutoConnectPermissioning() public { vm.startPrank(bob); sf.gda.setConnectPermission(false); vm.stopPrank(); @@ -1091,8 +1087,8 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste 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 + uint8 a; // action types: 0 update units, 1 distribute flow, 2 freePool claim for, 3 freePool connection, + // 4 distribute, 5: freePool autoconnect uint32 v; // action param uint16 dt; // time delta } @@ -1104,7 +1100,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste emit log_named_string("", ""); emit log_named_uint(">>> STEP", i); PoolUpdateStep memory s = steps[i]; - uint256 action = s.a % 5; + uint256 action = s.a % 6; uint256 u = 1 + s.u % N_MEMBERS; address user = TEST_ACCOUNTS[u]; @@ -1140,6 +1136,11 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste emit log_named_string("action", "distribute"); emit log_named_uint("distributionAmount", s.v); _helperDistributeViaGDA(superToken, user, user, freePool, uint256(s.v), useBools_.useForwarder); + } else if (action == 5) { + address u4 = TEST_ACCOUNTS[1 + (s.v % N_MEMBERS)]; + emit log_named_string("action", "tryConnectPoolFor"); + emit log_named_address("connect for", u4); + _helperConnectPoolFor(user, u4, superToken, freePool, false); } else { assert(false); } diff --git a/packages/solidity-semantic-money/foundry.toml b/packages/solidity-semantic-money/foundry.toml index f7b6d1bbd8..d26228c096 100644 --- a/packages/solidity-semantic-money/foundry.toml +++ b/packages/solidity-semantic-money/foundry.toml @@ -4,7 +4,7 @@ src = 'packages/solidity-semantic-money/src' out = 'packages/solidity-semantic-money/out/default' cache_path = 'packages/solidity-semantic-money/out/default.cache' solc_version = '0.8.26' -evm_version = 'paris' # no PUSH0 for now +evm_version = 'shanghai' deny_warnings = true optimizer = true optimizer_runs = 200 From 701ea337fa16c3b5dee9d4f78bf3cbadf5497276 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 17 Jul 2025 22:59:57 +0200 Subject: [PATCH 14/68] fix stack too deep? --- packages/solidity-semantic-money/foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/solidity-semantic-money/foundry.toml b/packages/solidity-semantic-money/foundry.toml index d26228c096..4a2aedcbfc 100644 --- a/packages/solidity-semantic-money/foundry.toml +++ b/packages/solidity-semantic-money/foundry.toml @@ -4,7 +4,7 @@ src = 'packages/solidity-semantic-money/src' out = 'packages/solidity-semantic-money/out/default' cache_path = 'packages/solidity-semantic-money/out/default.cache' solc_version = '0.8.26' -evm_version = 'shanghai' +evm_version = 'paris' deny_warnings = true optimizer = true optimizer_runs = 200 From 4dce3b740ac9db3706846acae7e4758c965f5ab1 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 17 Jul 2025 23:30:12 +0200 Subject: [PATCH 15/68] fix flag --- .../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 554922cf33..27f3ea141c 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -314,7 +314,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi /// @inheritdoc IGeneralDistributionAgreementV1 function connectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { newCtx = ctx; - _setPoolConnection(pool, address(0), true, true, ctx); + _setPoolConnection(pool, address(0), true, false, ctx); } /// @inheritdoc IGeneralDistributionAgreementV1 @@ -330,7 +330,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi if (simpleACL.hasRole(ACL_POOL_CONNECT_EXCLUSIVE_ROLE, memberAddr)) { success = false; } else { - success = _setPoolConnection(pool, memberAddr, true, false, ctx); + success = _setPoolConnection(pool, memberAddr, true, true, ctx); } } From 5a603b2d9e1955c77dfe2bc9696c7b51721fafa7 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 18 Jul 2025 00:00:58 +0200 Subject: [PATCH 16/68] more shanghai --- packages/ethereum-contracts/hardhat.config.ts | 2 +- packages/ethereum-contracts/truffle-config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ethereum-contracts/hardhat.config.ts b/packages/ethereum-contracts/hardhat.config.ts index 91301ba2d1..2f666ba121 100644 --- a/packages/ethereum-contracts/hardhat.config.ts +++ b/packages/ethereum-contracts/hardhat.config.ts @@ -99,7 +99,7 @@ const config: HardhatUserConfig = { enabled: true, runs: 200, }, - evmVersion: "paris", + evmVersion: "shanghai", }, }, paths: { diff --git a/packages/ethereum-contracts/truffle-config.js b/packages/ethereum-contracts/truffle-config.js index d89b4a0e67..91283d7279 100644 --- a/packages/ethereum-contracts/truffle-config.js +++ b/packages/ethereum-contracts/truffle-config.js @@ -391,7 +391,7 @@ const E = (module.exports = { // see https://docs.soliditylang.org/en/latest/using-the-compiler.html#target-options // we don't switch to "shanghai" or later as long as there's networks // without EIP-3855 support (PUSH0) - evmVersion: "paris", + evmVersion: "shanghai", }, }, }, From d3bfd9559d3789f43feb0ed70bf438e2143db92e Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 18 Jul 2025 12:07:16 +0200 Subject: [PATCH 17/68] updated CHANGELOG --- packages/ethereum-contracts/CHANGELOG.md | 4 ++++ .../contracts/agreements/gdav1/SuperfluidPool.sol | 4 ++-- .../agreements/gdav1/SuperfluidPoolDeployerLibrary.sol | 4 ++-- packages/ethereum-contracts/truffle-config.js | 2 -- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index 7747002294..9cdef08e39 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -5,11 +5,15 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [UNRELEASED] +### 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. + ### Changed - Refactored `GeneralDistributionAgreementV1`: extracted functionality which reads/writes agreement data from/to the token contract into dedicated libraries: - `GDAv1StorageLib` contains data structures and related encoders/decoders. - `GDAv1StorageReader` contains getters reading agreement data from the token contract, allowing contracts to get this data without making a call to the GDA contract. - `GDAv1StorageWriter` contains functions for writing agreement data to the token contract. This can only be used by the GDA contract itself. +- Changed EVM target from `paris` to `shanghai` because now all networks with supported Superfluid deployment support it. ### Fixed - `ISuperfluidPool`: `getClaimable` and `getClaimableNow` could previously return non-zero values for connected pools, which was inconsistent with what `claimAll` would actually do in this situation (claim nothing). diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index 6b7c33a895..83234be488 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -123,8 +123,8 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable { ISuperfluidToken superToken_, bool transferabilityForUnitsOwner_, bool distributionFromAnyAddress_, - string memory erc20Name_, - string memory erc20Symbol_, + string calldata erc20Name_, + string calldata erc20Symbol_, uint8 erc20Decimals_ ) external initializer { admin = admin_; diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol index 5b7682cbba..2ccf4438ac 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol @@ -11,8 +11,8 @@ library SuperfluidPoolDeployerLibrary { address beacon, address admin, ISuperfluidToken token, - PoolConfig memory config, - PoolERC20Metadata memory poolERC20Metadata + PoolConfig calldata config, + PoolERC20Metadata calldata poolERC20Metadata ) external returns (SuperfluidPool pool) { bytes memory initializeCallData = abi.encodeWithSelector( SuperfluidPool.initialize.selector, diff --git a/packages/ethereum-contracts/truffle-config.js b/packages/ethereum-contracts/truffle-config.js index 91283d7279..02207399ec 100644 --- a/packages/ethereum-contracts/truffle-config.js +++ b/packages/ethereum-contracts/truffle-config.js @@ -389,8 +389,6 @@ const E = (module.exports = { runs: 200, }, // see https://docs.soliditylang.org/en/latest/using-the-compiler.html#target-options - // we don't switch to "shanghai" or later as long as there's networks - // without EIP-3855 support (PUSH0) evmVersion: "shanghai", }, }, From d6c913595eac061a7af91f4e9e3bc33ac4f6e026 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 18 Jul 2025 17:59:19 +0200 Subject: [PATCH 18/68] undo disable test --- .../foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b260bf2190..9277f59231 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -695,7 +695,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste assertEq(poolAdjustmentFlowRate, 0, "GDAv1.t: Pool adjustment rate is non-zero"); } - function skip_testDistributeFlowToUnconnectedMembers( + function testDistributeFlowToUnconnectedMembers( uint64[5] memory memberUnits, int32 flowRate, uint16 warpTime, From 2aa5ccdc79e5f0bf97098a3d7cda6135669f3c55 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 18 Jul 2025 18:14:53 +0200 Subject: [PATCH 19/68] added tryConnectPoolFor to SuperTokenV1Library --- .../contracts/apps/SuperTokenV1Library.sol | 18 ++++++++++++++++++ .../foundry/apps/SuperTokenV1Library.t.sol | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index 529ec77d57..3279b4828e 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -1415,6 +1415,24 @@ library SuperTokenV1Library { return true; } + /** + * @dev Connects a pool member to `pool` (aka "autoconnect") if less than 4 connection slots are occupied. + * @param token The Super Token address. + * @param pool The Superfluid Pool to connect. + * @param memberAddress The address of the member to connect. + * @return success indicates whether the connection was successful. + */ + function tryConnectPoolFor(ISuperToken token, ISuperfluidPool pool, address memberAddress) + internal + returns (bool success) + { + (ISuperfluid host, IGeneralDistributionAgreementV1 gda) = _getAndCacheHostAndGDA(token); + bytes memory ret = host.callAgreement( + gda, abi.encodeCall(gda.tryConnectPoolFor, (pool, memberAddress, new bytes(0))), new bytes(0) + ); + (success, ) = abi.decode(ret, (bool, bytes)); + } + /** * @dev Disconnects a pool member from `pool`. * @param token The Super Token address. diff --git a/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol b/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol index a7560519e3..cb464784bb 100644 --- a/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol +++ b/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol @@ -448,6 +448,25 @@ contract SuperTokenV1LibraryTest is FoundrySuperfluidTester { assertFalse(sf.host.isAppJailed(ISuperApp(superAppAddr)), "superApp is jailed"); } + function testTryConnectPoolFor() external { + for (uint256 i = 0; i < sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS() * 2; ++i) { + ISuperfluidPool pool = superToken.createPool(); + pool.updateMemberUnits(bob, 1); + + vm.startPrank(alice); + bool success = superToken.tryConnectPoolFor(pool, bob); + vm.stopPrank(); + + if (i < sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS()) { + assertEq(success, true, "success != true"); + assertEq(sf.gda.isMemberConnected(pool, bob), true, "bob should be (auto)connected"); + } else { + assertEq(success, false, "success != false"); + assertEq(sf.gda.isMemberConnected(pool, bob), false, "bob should not be (auto)connected"); + } + } + } + // HELPER FUNCTIONS ======================================================================================== // direct use of the agreement for assertions From b4e6342b2107f50d8ee4a968a51d0c5a849672da Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 18 Jul 2025 18:34:35 +0200 Subject: [PATCH 20/68] CFASuperAppBase: disable autoconnect in constructor --- .../contracts/apps/CFASuperAppBase.sol | 18 +++++++++++++++++- .../gdav1/IGeneralDistributionAgreementV1.sol | 5 ++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/ethereum-contracts/contracts/apps/CFASuperAppBase.sol b/packages/ethereum-contracts/contracts/apps/CFASuperAppBase.sol index ec91b3e5e4..cbd95cc410 100644 --- a/packages/ethereum-contracts/contracts/apps/CFASuperAppBase.sol +++ b/packages/ethereum-contracts/contracts/apps/CFASuperAppBase.sol @@ -1,7 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity >= 0.8.11; -import { ISuperfluid, ISuperToken, ISuperApp, SuperAppDefinitions } from "../interfaces/superfluid/ISuperfluid.sol"; +import { + ISuperfluid, + ISuperToken, + ISuperApp, + SuperAppDefinitions, + IGeneralDistributionAgreementV1 +} from "../interfaces/superfluid/ISuperfluid.sol"; import { SuperTokenV1Library } from "./SuperTokenV1Library.sol"; /** @@ -39,6 +45,16 @@ abstract contract CFASuperAppBase is ISuperApp { */ constructor(ISuperfluid host_) { HOST = host_; + + // disable autoconnect for GDA pools + IGeneralDistributionAgreementV1 gda = IGeneralDistributionAgreementV1( + address( + ISuperfluid(host_).getAgreementClass( + keccak256("org.superfluid-finance.agreements.GeneralDistributionAgreement.v1") + ) + ) + ); + gda.setConnectPermission(false); } /** diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol index 51cd843986..8f438ccdb0 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol @@ -226,9 +226,8 @@ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement { virtual returns (bool success, bytes memory newCtx); - /// @notice Lets accounts deny or allow 3rd parties to connect them to pools. - /// By default, this permission is given (except by accounts which are Super Apps). - /// @param allow true to allow, false to deny + /// @notice Lets accounts deny or allow 3rd parties to connect them to pools. The default is to allow. + /// @param allow true to allow (only has an effect if it was previously denied), false to deny function setConnectPermission(bool allow) external virtual; /// @notice Disconnects `msg.sender` from `pool`. From c1959234b21801561abd93e52bd19a606ece02cf Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 18 Jul 2025 19:35:50 +0200 Subject: [PATCH 21/68] set admin roles in deploy script --- .../gdav1/GeneralDistributionAgreementV1.sol | 2 +- .../SuperfluidFrameworkDeploymentSteps.t.sol | 5 ++-- .../ops-scripts/deploy-framework.js | 25 +++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 27f3ea141c..a15c4fb017 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -51,8 +51,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi // @dev The max number of slots which can be used for connecting pools on behalf of a member (per token) uint32 public constant MAX_POOL_AUTO_CONNECT_SLOTS = 4; + // @dev The ACL role owned by this contract, used to persist autoconnect permissions for accounts bytes32 constant public ACL_POOL_CONNECT_EXCLUSIVE_ROLE = keccak256("ACL_POOL_CONNECT_EXCLUSIVE_ROLE"); - bytes32 constant public ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN = keccak256("ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN"); /// @dev Pool member state slot id for storing subs bitmap uint256 private constant _POOL_SUBS_BITMAP_STATE_SLOT_ID = 1; diff --git a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol index 616c2e5b8a..4714576620 100644 --- a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol +++ b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.t.sol @@ -195,12 +195,13 @@ contract SuperfluidFrameworkDeploymentSteps { gdaV1Logic.superfluidPoolBeacon().transferOwnership(address(host)); } + bytes32 aclPoolConnectExclusiveRoleAdmin = keccak256("ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN"); SimpleACL(address(host.getSimpleACL())).setRoleAdmin( gdaV1.ACL_POOL_CONNECT_EXCLUSIVE_ROLE(), - gdaV1.ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN() + aclPoolConnectExclusiveRoleAdmin ); SimpleACL(address(host.getSimpleACL())).grantRole( - gdaV1.ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN(), + aclPoolConnectExclusiveRoleAdmin, address(gdaV1) ); } else if (step == 3) {// PERIPHERAL CONTRACTS: NFT Proxy and Logic diff --git a/packages/ethereum-contracts/ops-scripts/deploy-framework.js b/packages/ethereum-contracts/ops-scripts/deploy-framework.js index df893c09ec..194ae71af4 100644 --- a/packages/ethereum-contracts/ops-scripts/deploy-framework.js +++ b/packages/ethereum-contracts/ops-scripts/deploy-framework.js @@ -947,6 +947,31 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( if (gdaNewLogicAddress !== ZERO_ADDRESS) { agreementsToUpdate.push(gdaNewLogicAddress); } + + // check/set ACL role admins + const simpleAcl = await SimpleACL.at(simpleAclAddress); + + const aclSuperappRegistrationRoleAdmin = web3.utils.sha3("ACL_SUPERAPP_REGISTRATION_ROLE_ADMIN"); + const aclSuperappRegistrationRole = web3.utils.sha3("ACL_SUPERAPP_REGISTRATION_ROLE"); + if (! await simpleAcl.hasRole(aclSuperappRegistrationRoleAdmin, deployerAddr)) { + await simpleAcl.setRoleAdmin(aclSuperappRegistrationRole, aclSuperappRegistrationRoleAdmin); + console.log("Set ACL_SUPERAPP_REGISTRATION_ROLE admin to ACL_SUPERAPP_REGISTRATION_ROLE_ADMIN"); + await simpleAcl.grantRole(aclSuperappRegistrationRoleAdmin, deployerAddr); + console.log("Granted ACL_SUPERAPP_REGISTRATION_ROLE_ADMIN to deployerAddr"); + } else { + console.log("ACL_SUPERAPP_REGISTRATION_ROLE_ADMIN already granted to deployerAddr"); + } + + const aclPoolConnectExclusiveRole = web3.utils.sha3("ACL_POOL_CONNECT_EXCLUSIVE_ROLE"); + const aclPoolConnectExclusiveRoleAdmin = web3.utils.sha3("ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN"); + if (! await simpleAcl.hasRole(aclPoolConnectExclusiveRoleAdmin, gdaProxyAddr)) { + await simpleAcl.setRoleAdmin(aclPoolConnectExclusiveRole, aclPoolConnectExclusiveRoleAdmin); + console.log("Set ACL_POOL_CONNECT_EXCLUSIVE_ROLE admin to ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN"); + await simpleAcl.grantRole(aclPoolConnectExclusiveRoleAdmin, gdaProxyAddr); + console.log("Granted ACL_POOL_CONNECT_EXCLUSIVE_ROLE to GDA"); + } else { + console.log("ACL_POOL_CONNECT_EXCLUSIVE_ROLE_ADMIN already granted to GDA"); + } } // deploy new super token factory logic (depends on SuperToken logic, which links to nft deployer library) From bd1d158147eba88b4acb1e9f5e7eb8af2878d410 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 22 Jul 2025 11:29:39 +0300 Subject: [PATCH 22/68] solidity semantic money to shanghai evm --- packages/solidity-semantic-money/foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/solidity-semantic-money/foundry.toml b/packages/solidity-semantic-money/foundry.toml index 4a2aedcbfc..d26228c096 100644 --- a/packages/solidity-semantic-money/foundry.toml +++ b/packages/solidity-semantic-money/foundry.toml @@ -4,7 +4,7 @@ src = 'packages/solidity-semantic-money/src' out = 'packages/solidity-semantic-money/out/default' cache_path = 'packages/solidity-semantic-money/out/default.cache' solc_version = '0.8.26' -evm_version = 'paris' +evm_version = 'shanghai' deny_warnings = true optimizer = true optimizer_runs = 200 From c76c167332439b40c08165316110bc86c77ebda1 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 22 Jul 2025 11:41:58 +0300 Subject: [PATCH 23/68] added countUsedSlots to SlotsBitmapLibrary --- .../contracts/libs/SlotsBitmapLibrary.sol | 63 ++++++++++++------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/packages/ethereum-contracts/contracts/libs/SlotsBitmapLibrary.sol b/packages/ethereum-contracts/contracts/libs/SlotsBitmapLibrary.sol index c5cc9349f9..a38e4eea14 100644 --- a/packages/ethereum-contracts/contracts/libs/SlotsBitmapLibrary.sol +++ b/packages/ethereum-contracts/contracts/libs/SlotsBitmapLibrary.sol @@ -19,13 +19,13 @@ library SlotsBitmapLibrary { uint32 internal constant _MAX_NUM_SLOTS = 256; - function findEmptySlotAndFill( - ISuperfluidToken token, - address account, - uint256 bitmapStateSlotId, - uint256 dataStateSlotIDStart, - bytes32 data - ) + function findEmptySlotAndFill + (ISuperfluidToken token, + address account, + uint256 bitmapStateSlotId, + uint256 dataStateSlotIDStart, + bytes32 data + ) public returns (uint32 slotId) { @@ -55,12 +55,12 @@ library SlotsBitmapLibrary { require(slotId < _MAX_NUM_SLOTS, "SlotBitmap out of bound"); } - function clearSlot( - ISuperfluidToken token, - address account, - uint256 bitmapStateSlotId, - uint32 slotId - ) + function clearSlot + (ISuperfluidToken token, + address account, + uint256 bitmapStateSlotId, + uint32 slotId + ) public { uint256 subsBitmap = uint256(token.getAgreementStateSlot( @@ -78,16 +78,16 @@ library SlotsBitmapLibrary { slotData); } - function listData( - ISuperfluidToken token, - address account, - uint256 bitmapStateSlotId, - uint256 dataStateSlotIDStart - ) + function listData + (ISuperfluidToken token, + address account, + uint256 bitmapStateSlotId, + uint256 dataStateSlotIDStart + ) public view - returns ( - uint32[] memory slotIds, - bytes32[] memory dataList) + returns (uint32[] memory slotIds, + bytes32[] memory dataList + ) { uint256 subsBitmap = uint256(token.getAgreementStateSlot( address(this), @@ -113,4 +113,23 @@ library SlotsBitmapLibrary { mstore(dataList, nSlots) } } + + function countUsedSlots + (ISuperfluidToken token, + address account, + uint256 bitmapStateSlotId + ) + public view + returns (uint256 nUsedSlots) + { + uint256 subsBitmap = uint256(token.getAgreementStateSlot( + address(this), + account, + bitmapStateSlotId, 1)[0]); + + for (uint32 slotId = 0; slotId < _MAX_NUM_SLOTS; ++slotId) { + if ((uint256(subsBitmap >> slotId) & 1) == 0) continue; + ++nUsedSlots; + } + } } From 64484aeced94bf89fba1d5ad7ab1dccc23116e64 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 22 Jul 2025 11:42:14 +0300 Subject: [PATCH 24/68] GDAv1: use SlotsBitmapLibrary --- .../agreements/gdav1/GeneralDistributionAgreementV1.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index a15c4fb017..f04643fd74 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -318,7 +318,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } /// @inheritdoc IGeneralDistributionAgreementV1 - function tryConnectPoolFor(ISuperfluidPool pool, address memberAddr, bytes calldata ctx) + function tryConnectPoolFor(ISuperfluidPool pool, address memberAddr, bytes calldata ctx) external override returns (bool success, bytes memory newCtx) @@ -373,10 +373,10 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi if (doConnect) { if (onlyAutoConnectSlots) { // check if we're below the slot limit for autoconnect - (uint32[] memory slotIds, ) = SlotsBitmapLibrary.listData( - token, memberAddr, _POOL_SUBS_BITMAP_STATE_SLOT_ID, _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START + uint256 nUsedSlots = SlotsBitmapLibrary.countUsedSlots( + token, memberAddr, _POOL_SUBS_BITMAP_STATE_SLOT_ID ); - if (slotIds.length >= MAX_POOL_AUTO_CONNECT_SLOTS) { + if (nUsedSlots > MAX_POOL_AUTO_CONNECT_SLOTS) { return false; } } From d8ea0dc1f27fb1a78df2f8e6c9a30eb75c082e79 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 22 Jul 2025 11:49:12 +0300 Subject: [PATCH 25/68] added some asserts to SlotsBitmapLibraryPropertyTest._listData --- .../test/foundry/libs/SlotsBitmapLibrary.prop.t.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.t.sol b/packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.t.sol index abe99982f8..cbe46abaee 100644 --- a/packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.t.sol +++ b/packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.t.sol @@ -46,6 +46,11 @@ contract SlotsBitmapLibraryPropertyTest is Test { (slotIds, dataList) = SlotsBitmapLibrary.listData( _superToken, _subscriber, _SUBSCRIBER_SUBS_BITMAP_STATE_SLOT_ID, _SUBSCRIBER_SUB_DATA_STATE_SLOT_ID_START ); + uint256 n = SlotsBitmapLibrary.countUsedSlots( + _superToken, _subscriber, _SUBSCRIBER_SUBS_BITMAP_STATE_SLOT_ID + ); + assertEq(slotIds.length, n, "slotsIds.length"); + assertEq(dataList.length, n, "dataList.length"); } /** From 79109ec732bb9c191ebe40cf6c4342ccfe6af642 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 22 Jul 2025 12:03:33 +0300 Subject: [PATCH 26/68] revert solidity semantic money to paris evm --- packages/solidity-semantic-money/foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/solidity-semantic-money/foundry.toml b/packages/solidity-semantic-money/foundry.toml index d26228c096..4a2aedcbfc 100644 --- a/packages/solidity-semantic-money/foundry.toml +++ b/packages/solidity-semantic-money/foundry.toml @@ -4,7 +4,7 @@ src = 'packages/solidity-semantic-money/src' out = 'packages/solidity-semantic-money/out/default' cache_path = 'packages/solidity-semantic-money/out/default.cache' solc_version = '0.8.26' -evm_version = 'shanghai' +evm_version = 'paris' deny_warnings = true optimizer = true optimizer_runs = 200 From d57884275374cb8891a121b89c4bcabe4b77e62b Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 22 Jul 2025 12:07:55 +0300 Subject: [PATCH 27/68] fix testTryConnectPoolFor --- .../test/foundry/apps/SuperTokenV1Library.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol b/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol index cb464784bb..e93d8db0ce 100644 --- a/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol +++ b/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol @@ -362,7 +362,7 @@ contract SuperTokenV1LibraryTest is FoundrySuperfluidTester { ISuperfluidPool pool = superToken.createPool( address(this), PoolConfig({ - transferabilityForUnitsOwner: false, + transferabilityForUnitsOwner: false, distributionFromAnyAddress: true }) ); @@ -457,7 +457,7 @@ contract SuperTokenV1LibraryTest is FoundrySuperfluidTester { bool success = superToken.tryConnectPoolFor(pool, bob); vm.stopPrank(); - if (i < sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS()) { + if (i <= sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS()) { assertEq(success, true, "success != true"); assertEq(sf.gda.isMemberConnected(pool, bob), true, "bob should be (auto)connected"); } else { From dff96ab882686044a4c82b7ee07a1e32c422bfd7 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Tue, 22 Jul 2025 12:48:41 +0300 Subject: [PATCH 28/68] fix testAutoConnectSlotLimit --- .../foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9277f59231..acd83015bb 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -1031,7 +1031,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste new bytes(0) ); (bool success, ) = abi.decode(ret, (bool, bytes)); - if (i < sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS()) { + if (i <= sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS()) { assertEq(success, true, "success != true"); assertEq(sf.gda.isMemberConnected(pool, bob), true, "bob should be (auto)connected"); } else { From c855ecf871d9a2670fff9e65f91bbb9be9c9043c Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Wed, 23 Jul 2025 10:59:16 +0300 Subject: [PATCH 29/68] update flake inputs --- flake.lock | 26 +++++++++++++------------- flake.nix | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/flake.lock b/flake.lock index a5f9725a26..053374114d 100644 --- a/flake.lock +++ b/flake.lock @@ -28,16 +28,16 @@ ] }, "locked": { - "lastModified": 1748682694, - "narHash": "sha256-vun3iVnpk5cU6HZsOjpHRpeo9/ztAQWgFOMLR5IyOHE=", + "lastModified": 1752867797, + "narHash": "sha256-oT129SDSr7SI9ThTd6ZbpmShh5f2tzUH3S4hl6c5/7w=", "owner": "shazow", "repo": "foundry.nix", - "rev": "cefa65c2e3c77e9ee035e2a23188799710bb7cdb", + "rev": "d4445852933ab5bc61ca532cb6c5d3276d89c478", "type": "github" }, "original": { "owner": "shazow", - "ref": "cefa65c", + "ref": "stable", "repo": "foundry.nix", "type": "github" } @@ -68,11 +68,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1746549287, - "narHash": "sha256-lzkSxKv3GwWQ+18zb1YoHCEtFSGIPj0sAMqtQjNLihQ=", + "lastModified": 1753151930, + "narHash": "sha256-XSQy6wRKHhRe//iVY5lS/ZpI/Jn6crWI8fQzl647wCg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f90d0af5db814e870b2ad1aebc4e8924a30c53dd", + "rev": "83e677f31c84212343f4cc553bab85c2efcad60a", "type": "github" }, "original": { @@ -102,11 +102,11 @@ "solc-macos-amd64-list-json": "solc-macos-amd64-list-json" }, "locked": { - "lastModified": 1742758229, - "narHash": "sha256-FrU9rhab/0vOjjeFoQF+Ej43zRLv3enUIYjgLrH3Gd8=", + "lastModified": 1748780655, + "narHash": "sha256-mradCdMvjXwKd7kVFACB/d1CP2LLCyEgUu4vJCSzNLU=", "owner": "hellwolf", "repo": "solc.nix", - "rev": "6885b61bac89da19a6e3c70b89fdd592e2cef884", + "rev": "3b6f3223ace5a7bc400b01a434d86bb1cb2593fb", "type": "github" }, "original": { @@ -118,13 +118,13 @@ "solc-macos-amd64-list-json": { "flake": false, "locked": { - "narHash": "sha256-U5ckttxwKO13gIKggel6iybG5oTDbSidPR5nH3Gs+kY=", + "narHash": "sha256-AvITkfpNYgCypXuLJyqco0li+unVw39BAfdOZvd/SPE=", "type": "file", - "url": "https://github.com/ethereum/solc-bin/raw/30a3695/macosx-amd64/list.json" + "url": "https://github.com/ethereum/solc-bin/raw/26fc3fd/macosx-amd64/list.json" }, "original": { "type": "file", - "url": "https://github.com/ethereum/solc-bin/raw/30a3695/macosx-amd64/list.json" + "url": "https://github.com/ethereum/solc-bin/raw/26fc3fd/macosx-amd64/list.json" } }, "systems": { diff --git a/flake.nix b/flake.nix index bc5db0a3f4..684bac20b9 100644 --- a/flake.nix +++ b/flake.nix @@ -5,7 +5,7 @@ flake-utils.url = "github:numtide/flake-utils"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; foundry = { - url = "github:shazow/foundry.nix/cefa65c"; + url = "github:shazow/foundry.nix/stable"; inputs.flake-utils.follows = "flake-utils"; inputs.nixpkgs.follows = "nixpkgs"; }; From e0ca43f72b138d9c9c9f6eae81ed3646b5672def Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Wed, 23 Jul 2025 11:58:29 +0300 Subject: [PATCH 30/68] solc: 0.8.26 -> 0.8.30 --- .envrc | 2 +- flake.nix | 2 +- packages/ethereum-contracts/foundry.toml | 2 +- packages/ethereum-contracts/hardhat.config.ts | 2 +- packages/ethereum-contracts/truffle-config.js | 2 +- packages/solidity-semantic-money/foundry.toml | 2 +- packages/subgraph/hardhat.config.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.envrc b/.envrc index 49fe8d361f..c9b4271139 100644 --- a/.envrc +++ b/.envrc @@ -4,7 +4,7 @@ dotenv_if_exists || direnv status # https://direnv.net/man/direnv-stdlib.1.html # foundry to use solc.nix provided solc export FOUNDRY_OFFLINE=true -export FOUNDRY_SOLC_VERSION=`which solc-0.8.26` +export FOUNDRY_SOLC_VERSION=`which solc-0.8.30` # use flake shell # Note: diff --git a/flake.nix b/flake.nix index 684bac20b9..4e6f328d99 100644 --- a/flake.nix +++ b/flake.nix @@ -34,7 +34,7 @@ system: let minDevSolcVer = "solc_0_8_11"; # minimum solidity version used for external development - solcVer = "solc_0_8_26"; + solcVer = "solc_0_8_30"; ghcVer92 = "ghc928"; ghcVer94 = "ghc948"; diff --git a/packages/ethereum-contracts/foundry.toml b/packages/ethereum-contracts/foundry.toml index df8b2e2338..df19e26ee9 100644 --- a/packages/ethereum-contracts/foundry.toml +++ b/packages/ethereum-contracts/foundry.toml @@ -2,7 +2,7 @@ root = '../..' src = 'packages/ethereum-contracts/contracts' test = 'packages/ethereum-contracts/test/foundry' -solc_version = "0.8.26" +solc_version = "0.8.30" #deny_warnings = true ignored_error_codes = [ 1699 # assembly { selfdestruct } in contracts/mocks/SuperfluidDestructorMock.sol diff --git a/packages/ethereum-contracts/hardhat.config.ts b/packages/ethereum-contracts/hardhat.config.ts index 91301ba2d1..309fbdc786 100644 --- a/packages/ethereum-contracts/hardhat.config.ts +++ b/packages/ethereum-contracts/hardhat.config.ts @@ -93,7 +93,7 @@ function createNetworkConfig( const config: HardhatUserConfig = { solidity: { - version: "0.8.26", + version: "0.8.30", settings: { optimizer: { enabled: true, diff --git a/packages/ethereum-contracts/truffle-config.js b/packages/ethereum-contracts/truffle-config.js index d89b4a0e67..15a49f4727 100644 --- a/packages/ethereum-contracts/truffle-config.js +++ b/packages/ethereum-contracts/truffle-config.js @@ -381,7 +381,7 @@ const E = (module.exports = { // Fetch exact version from solc-bin (default: truffle's version) // If SOLC environment variable is provided, assuming it is available as "solc", use it instead. // Ref, this maybe possible in the future: https://github.com/trufflesuite/truffle/pull/6007 - version: process.env.SOLC ? "native" : "0.8.26", + version: process.env.SOLC ? "native" : "0.8.30", settings: { // See the solidity docs for advice about optimization and evmVersion optimizer: { diff --git a/packages/solidity-semantic-money/foundry.toml b/packages/solidity-semantic-money/foundry.toml index f7b6d1bbd8..9313afd96e 100644 --- a/packages/solidity-semantic-money/foundry.toml +++ b/packages/solidity-semantic-money/foundry.toml @@ -3,7 +3,7 @@ root = '../..' src = 'packages/solidity-semantic-money/src' out = 'packages/solidity-semantic-money/out/default' cache_path = 'packages/solidity-semantic-money/out/default.cache' -solc_version = '0.8.26' +solc_version = '0.8.30' evm_version = 'paris' # no PUSH0 for now deny_warnings = true optimizer = true diff --git a/packages/subgraph/hardhat.config.ts b/packages/subgraph/hardhat.config.ts index bda0b1e477..0569e12aa2 100644 --- a/packages/subgraph/hardhat.config.ts +++ b/packages/subgraph/hardhat.config.ts @@ -13,7 +13,7 @@ dotenvConfig(); */ const config: HardhatUserConfig = { solidity: { - version: "0.8.26", + version: "0.8.30", settings: { optimizer: { enabled: true, From ae36ff0b360406f1598b6ed24bc8eb7580626d20 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Wed, 23 Jul 2025 12:21:50 +0300 Subject: [PATCH 31/68] ethereum-contracts: update CHANGELOG.md --- packages/ethereum-contracts/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index 7747002294..2fa1f22d73 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `GDAv1StorageLib` contains data structures and related encoders/decoders. - `GDAv1StorageReader` contains getters reading agreement data from the token contract, allowing contracts to get this data without making a call to the GDA contract. - `GDAv1StorageWriter` contains functions for writing agreement data to the token contract. This can only be used by the GDA contract itself. +- bump solc to "0.8.30". ### Fixed - `ISuperfluidPool`: `getClaimable` and `getClaimableNow` could previously return non-zero values for connected pools, which was inconsistent with what `claimAll` would actually do in this situation (claim nothing). From 365b08751768463618b1cfcd547881077c2623f7 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Wed, 23 Jul 2025 12:35:36 +0300 Subject: [PATCH 32/68] update more solc_version to 0.8.30 --- packages/automation-contracts/autowrap/foundry.toml | 2 +- packages/automation-contracts/scheduler/foundry.toml | 2 +- packages/hot-fuzz/foundry.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/automation-contracts/autowrap/foundry.toml b/packages/automation-contracts/autowrap/foundry.toml index bdf6e6f602..e79af10c38 100644 --- a/packages/automation-contracts/autowrap/foundry.toml +++ b/packages/automation-contracts/autowrap/foundry.toml @@ -2,7 +2,7 @@ root = '../../../' libs = ['lib'] src = 'packages/automation-contracts/autowrap' -solc_version = "0.8.23" +solc_version = "0.8.30" evm_version = 'paris' optimizer = true optimizer_runs = 200 diff --git a/packages/automation-contracts/scheduler/foundry.toml b/packages/automation-contracts/scheduler/foundry.toml index 33fe841026..a4e9c41259 100644 --- a/packages/automation-contracts/scheduler/foundry.toml +++ b/packages/automation-contracts/scheduler/foundry.toml @@ -2,7 +2,7 @@ root = '../../../' libs = ['lib'] src = 'packages/automation-contracts/scheduler' -solc_version = "0.8.23" +solc_version = "0.8.30" evm_version = 'paris' optimizer = true optimizer_runs = 200 diff --git a/packages/hot-fuzz/foundry.toml b/packages/hot-fuzz/foundry.toml index 1571e85714..eb05ebf501 100644 --- a/packages/hot-fuzz/foundry.toml +++ b/packages/hot-fuzz/foundry.toml @@ -1,7 +1,7 @@ [profile.default] root = '../..' src = 'packages/hot-fuzz/contracts' -solc_version = "0.8.23" +solc_version = "0.8.30" evm_version = 'shanghai' optimizer = true optimizer_runs = 200 From 7b2e30f7e4bf7d1aeaa94cf9e8ce170b7f9d63e8 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Wed, 23 Jul 2025 13:03:00 +0300 Subject: [PATCH 33/68] revert the wrong fix --- .../agreements/gdav1/GeneralDistributionAgreementV1.sol | 2 +- .../foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol | 2 +- .../test/foundry/apps/SuperTokenV1Library.t.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index f04643fd74..84bf3a1533 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -376,7 +376,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi uint256 nUsedSlots = SlotsBitmapLibrary.countUsedSlots( token, memberAddr, _POOL_SUBS_BITMAP_STATE_SLOT_ID ); - if (nUsedSlots > MAX_POOL_AUTO_CONNECT_SLOTS) { + if (nUsedSlots >= MAX_POOL_AUTO_CONNECT_SLOTS) { return 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 acd83015bb..9277f59231 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -1031,7 +1031,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste new bytes(0) ); (bool success, ) = abi.decode(ret, (bool, bytes)); - if (i <= sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS()) { + if (i < sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS()) { assertEq(success, true, "success != true"); assertEq(sf.gda.isMemberConnected(pool, bob), true, "bob should be (auto)connected"); } else { diff --git a/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol b/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol index e93d8db0ce..8983b1a162 100644 --- a/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol +++ b/packages/ethereum-contracts/test/foundry/apps/SuperTokenV1Library.t.sol @@ -457,7 +457,7 @@ contract SuperTokenV1LibraryTest is FoundrySuperfluidTester { bool success = superToken.tryConnectPoolFor(pool, bob); vm.stopPrank(); - if (i <= sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS()) { + if (i < sf.gda.MAX_POOL_AUTO_CONNECT_SLOTS()) { assertEq(success, true, "success != true"); assertEq(sf.gda.isMemberConnected(pool, bob), true, "bob should be (auto)connected"); } else { From cdf04820c16b627b0f06854b63c7156daa579d9f Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Wed, 23 Jul 2025 13:23:51 +0300 Subject: [PATCH 34/68] small code refactoring to _setPoolConnection --- .../gdav1/GeneralDistributionAgreementV1.sol | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 84bf3a1533..274fa33c50 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -314,7 +314,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi /// @inheritdoc IGeneralDistributionAgreementV1 function connectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { newCtx = ctx; - _setPoolConnection(pool, address(0), true, false, ctx); + _setPoolConnection(pool, address(0), true /* doConnect */, ctx); } /// @inheritdoc IGeneralDistributionAgreementV1 @@ -330,7 +330,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi if (simpleACL.hasRole(ACL_POOL_CONNECT_EXCLUSIVE_ROLE, memberAddr)) { success = false; } else { - success = _setPoolConnection(pool, memberAddr, true, true, ctx); + success = _setPoolConnection(pool, memberAddr, true /* doConnect */, ctx); } } @@ -346,7 +346,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi /// @inheritdoc IGeneralDistributionAgreementV1 function disconnectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { newCtx = ctx; - _setPoolConnection(pool, address(0), false, true /* ignored */, ctx); + _setPoolConnection(pool, address(0), false /* doConnect */, ctx); } // @note memberAddr has override semantics - if set to address(0), it will be set to the msgSender @@ -354,7 +354,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ISuperfluidPool pool, address memberAddr, bool doConnect, - bool onlyAutoConnectSlots, bytes memory ctx ) internal @@ -363,15 +362,18 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ISuperfluidToken token = pool.superToken(); ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx); + bool autoConnectForOtherMember = false; if (memberAddr == address(0)) { memberAddr = currentContext.msgSender; + } else { + autoConnectForOtherMember = true; } bool isConnected = token.isPoolMemberConnected(this, pool, memberAddr); if (doConnect != isConnected) { if (doConnect) { - if (onlyAutoConnectSlots) { + if (autoConnectForOtherMember) { // check if we're below the slot limit for autoconnect uint256 nUsedSlots = SlotsBitmapLibrary.countUsedSlots( token, memberAddr, _POOL_SUBS_BITMAP_STATE_SLOT_ID From 9b4da71f3b94ccad52421c9f62501829934847df Mon Sep 17 00:00:00 2001 From: didi Date: Wed, 23 Jul 2025 17:01:24 +0200 Subject: [PATCH 35/68] fix tests --- .../contracts/apps/SuperTokenV1Library.sol | 7 +++++++ .../agreements/gdav1/IGeneralDistributionAgreementV1.sol | 2 +- .../test/foundry/FoundrySuperfluidTester.t.sol | 2 ++ .../agreements/gdav1/GeneralDistributionAgreement.t.sol | 2 ++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index 3279b4828e..74f35b1a22 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -47,6 +47,7 @@ import { * `expectRevert` expects a revert in the next call. * If a revert is triggered by library code itself (vs by a call), `expectRevert` will thus not _see_ that. * Possible mitigations: + * - if available, use an overloaded variant which allows to explicitly specify the sender * - avoid higher-level library methods which can themselves trigger reverts in tests where this is an issue * - wrap the method invocation into an external helper method which you then invoke with `this.helperMethod()`, * which makes it an external call @@ -1462,6 +1463,9 @@ library SuperTokenV1Library { * @param pool The Superfluid Pool address. * @param requestedAmount The amount of tokens to distribute. * @return actualAmount The amount actually distributed, which is equal or smaller than `requestedAmount` + * NOTE: in foundry tests, you may unexpectedly get `GDA_DISTRIBUTE_FOR_OTHERS_NOT_ALLOWED` reverts + * because of the use of `address(this)` as the `from` argument. You can work around this by using + * `distribute(token, from, pool, requestedAmount)` instead. */ function distribute(ISuperToken token, ISuperfluidPool pool, uint256 requestedAmount) internal @@ -1517,6 +1521,9 @@ library SuperTokenV1Library { * @param requestedFlowRate The flow rate of tokens to distribute. * @return actualFlowRate The flowrate actually set, which is equal or smaller than `requestedFlowRate`, * depending on pool state - see IGeneralDistributionAgreement.estimateFlowDistributionActualFlowRate(). + * NOTE: in foundry tests, you may unexpectedly get `GDA_DISTRIBUTE_FOR_OTHERS_NOT_ALLOWED` reverts + * because of the use of `address(this)` as the `from` argument. You can work around this by using + * `distributeFlow(token, from, pool, requestedFlowRate)` instead. */ function distributeFlow(ISuperToken token, ISuperfluidPool pool, int96 requestedFlowRate) internal diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol index 8f438ccdb0..e90327024a 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol @@ -226,7 +226,7 @@ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement { virtual returns (bool success, bytes memory newCtx); - /// @notice Lets accounts deny or allow 3rd parties to connect them to pools. The default is to allow. + /// @notice Allows accounts to control whether third parties can connect them to pools. By default, they can. /// @param allow true to allow (only has an effect if it was previously denied), false to deny function setConnectPermission(bool allow) external virtual; diff --git a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol index fc7e28cabf..e680cd1748 100644 --- a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol +++ b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol @@ -128,6 +128,8 @@ contract FoundrySuperfluidTester is Test { address internal constant ivan = address(0x429); address[] internal TEST_ACCOUNTS = [admin, alice, bob, carol, dan, eve, frank, grace, heidi, ivan]; + address internal constant MAX_TESTER_ADDRESS = address(0x4ff); + /// @dev Other account addresses added that aren't testers (pools, super apps, smart contracts) EnumerableSet.AddressSet internal OTHER_ACCOUNTS; 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 9277f59231..71033d0b8f 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -950,6 +950,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste ) public { vm.assume(member != address(0)); vm.assume(member != address(freePool)); + vm.assume(member != alice); // alice is the test distributor vm.assume(units > 0); vm.assume(distributionAmount > 0); vm.assume(units < distributionAmount); @@ -992,6 +993,7 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste function testAutoConnect(address member, uint128 units, uint64 distributionAmount) public { vm.assume(member != address(0)); vm.assume(member != address(freePool)); + vm.assume(member != alice); // alice is the test distributor vm.assume(units > 0); vm.assume(units < distributionAmount); From 283402abd05b7919ac72a4bb07a4f329b40f0554 Mon Sep 17 00:00:00 2001 From: didi Date: Wed, 23 Jul 2025 18:53:30 +0200 Subject: [PATCH 36/68] fix flaky test --- lib/forge-std | 2 +- .../test/foundry/apps/CrossStreamSuperApp.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/forge-std b/lib/forge-std index 07263d193d..60acb7aaad 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d +Subproject commit 60acb7aaadcce2d68e52986a0a66fe79f07d138f diff --git a/packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol b/packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol index 3f119fbe6b..7f7cb660d1 100644 --- a/packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol +++ b/packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol @@ -64,7 +64,7 @@ contract CrossStreamSuperAppTest is FoundrySuperfluidTester { _addAccount(address(superApp)); } - function testNoTokensMintedOrBurnedInCrossStreamSuperApp(int96 flowRate, uint32 blockTimestamp) public { + function testNoTokensMintedOrBurnedInCrossStreamSuperApp(int96 flowRate, uint24 blockTimestamp) public { // @note due to clipping, there is precision loss, therefore if the flow rate is too low // tokens will be unrecoverable flowRate = int96(bound(flowRate, 2 ** 31 - 1, 1e14)); From c5fd4d513d02763b60ca3e828c75c9fba64d080e Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 24 Jul 2025 10:47:53 +0200 Subject: [PATCH 37/68] fix scheduler test --- .../automation-contracts/scheduler/test/FlowScheduler.t.sol | 2 +- .../scheduler/test/VestingSchedulerV2.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/automation-contracts/scheduler/test/FlowScheduler.t.sol b/packages/automation-contracts/scheduler/test/FlowScheduler.t.sol index 7c1bb94efd..266eb7016e 100644 --- a/packages/automation-contracts/scheduler/test/FlowScheduler.t.sol +++ b/packages/automation-contracts/scheduler/test/FlowScheduler.t.sol @@ -232,7 +232,7 @@ contract FlowSchedulerTest is FoundrySuperfluidTester { superToken, alice, defaultStartDate, uint32(1000), int96(1000), defaultStartAmount, defaultStartDate + uint32(3600), "", "" ); - vm.expectRevert(0xa3eab6ac); // error CFA_ACL_OPERATOR_NO_CREATE_PERMISSIONS() -> 0xa3eab6ac + vm.expectRevert(bytes4(0xa3eab6ac)); // error CFA_ACL_OPERATOR_NO_CREATE_PERMISSIONS() -> 0xa3eab6ac vm.warp(defaultStartDate + 1000); vm.prank(admin); flowScheduler.executeCreateFlow(superToken, bob,alice,""); diff --git a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol index 2ad00c07b6..7eff7da0c7 100644 --- a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol +++ b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol @@ -200,7 +200,7 @@ contract VestingSchedulerV2Tests is FoundrySuperfluidTester { address superToken, address sender, address receiver - ) public { + ) public view { VestingSchedulerV2.VestingSchedule memory schedule = vestingScheduler.getVestingSchedule(superToken, sender, receiver); VestingSchedulerV2.VestingSchedule memory deletedSchedule; From 088eba38024f1b39181ba28797a9577e6e1801dc Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 24 Jul 2025 14:52:41 +0200 Subject: [PATCH 38/68] renamed to _setPoolConnectionFor --- .../agreements/gdav1/GeneralDistributionAgreementV1.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 274fa33c50..89cfa622cd 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -314,7 +314,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi /// @inheritdoc IGeneralDistributionAgreementV1 function connectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { newCtx = ctx; - _setPoolConnection(pool, address(0), true /* doConnect */, ctx); + _setPoolConnectionFor(pool, address(0), true /* doConnect */, ctx); } /// @inheritdoc IGeneralDistributionAgreementV1 @@ -330,7 +330,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi if (simpleACL.hasRole(ACL_POOL_CONNECT_EXCLUSIVE_ROLE, memberAddr)) { success = false; } else { - success = _setPoolConnection(pool, memberAddr, true /* doConnect */, ctx); + success = _setPoolConnectionFor(pool, memberAddr, true /* doConnect */, ctx); } } @@ -346,11 +346,11 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi /// @inheritdoc IGeneralDistributionAgreementV1 function disconnectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) { newCtx = ctx; - _setPoolConnection(pool, address(0), false /* doConnect */, ctx); + _setPoolConnectionFor(pool, address(0), false /* doConnect */, ctx); } // @note memberAddr has override semantics - if set to address(0), it will be set to the msgSender - function _setPoolConnection( + function _setPoolConnectionFor( ISuperfluidPool pool, address memberAddr, bool doConnect, From 03b81ba3769f7756836fcb040c326cad046b8efa Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 24 Jul 2025 15:10:36 +0200 Subject: [PATCH 39/68] revert if trying to connect a pool for a pool --- .../gdav1/GeneralDistributionAgreementV1.sol | 4 ++++ .../gdav1/IGeneralDistributionAgreementV1.sol | 1 + .../gdav1/GeneralDistributionAgreement.t.sol | 12 ++++++++++++ 3 files changed, 17 insertions(+) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 89cfa622cd..c19798532e 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -325,6 +325,10 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi { newCtx = ctx; + if (pool.superToken().isPool(this, memberAddr)) { + revert GDA_CANNOT_CONNECT_POOL(); + } + // check if the member has opted out of autoconnect IAccessControl simpleACL = ISuperfluid(_host).getSimpleACL(); if (simpleACL.hasRole(ACL_POOL_CONNECT_EXCLUSIVE_ROLE, memberAddr)) { diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol index e90327024a..40d90be9ee 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol @@ -37,6 +37,7 @@ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement { error GDA_NOT_POOL_ADMIN(); // 0x3a87e565 error GDA_NO_ZERO_ADDRESS_ADMIN(); // 0x82c5d837 error GDA_ONLY_SUPER_TOKEN_POOL(); // 0x90028c37 + error GDA_CANNOT_CONNECT_POOL(); // 0x83d98e4c // Events 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 71033d0b8f..0c3050b781 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -1081,6 +1081,18 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste ); assertEq(sf.gda.isMemberConnected(freePool, bob), true, "member should not be (auto)connected"); vm.stopPrank(); + + + // cannot connect a pool + ISuperfluidPool anotherPool = _helperCreatePool(superToken, alice, alice, false, PoolConfig({ transferabilityForUnitsOwner: false, distributionFromAnyAddress: true })); + vm.startPrank(alice); + vm.expectRevert(IGeneralDistributionAgreementV1.GDA_CANNOT_CONNECT_POOL.selector); + sf.host.callAgreement( + sf.gda, + abi.encodeCall(sf.gda.tryConnectPoolFor, (freePool, address(anotherPool), new bytes(0))), + new bytes(0) + ); + vm.stopPrank(); } /*////////////////////////////////////////////////////////////////////////// From 5652c9d685fc4bde63f6b863b3c48dcbb050873f Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 24 Jul 2025 15:16:11 +0200 Subject: [PATCH 40/68] calldata storage location for PoolConfig --- .../agreements/gdav1/GeneralDistributionAgreementV1.sol | 6 +++--- .../agreements/gdav1/IGeneralDistributionAgreementV1.sol | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index c19798532e..a0850b56fc 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -234,7 +234,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi function _createPool( ISuperfluidToken token, address admin, - PoolConfig memory config, + PoolConfig calldata config, PoolERC20Metadata memory poolERC20Metadata ) internal returns (ISuperfluidPool pool) { // @note ensure if token and admin are the same that nothing funky happens with echidna @@ -261,7 +261,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } /// @inheritdoc IGeneralDistributionAgreementV1 - function createPool(ISuperfluidToken token, address admin, PoolConfig memory config) + function createPool(ISuperfluidToken token, address admin, PoolConfig calldata config) external override returns (ISuperfluidPool pool) @@ -278,7 +278,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi function createPoolWithCustomERC20Metadata( ISuperfluidToken token, address admin, - PoolConfig memory config, + PoolConfig calldata config, PoolERC20Metadata memory poolERC20Metadata ) external override returns (ISuperfluidPool pool) { return _createPool(token, admin, config, poolERC20Metadata); diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol index 40d90be9ee..6c7b157eda 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol @@ -180,7 +180,7 @@ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement { /// @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) + function createPool(ISuperfluidToken token, address admin, PoolConfig calldata poolConfig) external virtual returns (ISuperfluidPool pool); @@ -194,7 +194,7 @@ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement { function createPoolWithCustomERC20Metadata( ISuperfluidToken token, address admin, - PoolConfig memory poolConfig, + PoolConfig calldata poolConfig, PoolERC20Metadata memory poolERC20Metadata ) external virtual returns (ISuperfluidPool pool); From 21da9df05c6bc9af607aa654e43ca3603881ba2c Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 24 Jul 2025 16:02:01 +0200 Subject: [PATCH 41/68] squeeze out a few more bytes --- .../gdav1/GeneralDistributionAgreementV1.sol | 25 +++++++++++-------- .../gdav1/SuperfluidPoolDeployerLibrary.sol | 12 +++++---- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index a0850b56fc..518f4b3adf 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -235,7 +235,9 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ISuperfluidToken token, address admin, PoolConfig calldata config, - PoolERC20Metadata memory poolERC20Metadata + string calldata name, + string calldata symbol, + uint8 decimals ) 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(); @@ -244,7 +246,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi pool = ISuperfluidPool( address( SuperfluidPoolDeployerLibrary.deploy( - address(superfluidPoolBeacon), admin, token, config, poolERC20Metadata + address(superfluidPoolBeacon), admin, token, config, name, symbol, decimals ) ) ); @@ -266,12 +268,13 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (ISuperfluidPool pool) { - return _createPool( - token, - admin, - config, - PoolERC20Metadata("", "", 0) // use defaults specified by the implementation contract - ); + string calldata emptyStr; + assembly { + let emptyOffset := calldatasize() + emptyStr.offset := emptyOffset + emptyStr.length := 0 + } + return _createPool(token, admin, config, emptyStr, emptyStr, 0); } /// @inheritdoc IGeneralDistributionAgreementV1 @@ -279,9 +282,11 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ISuperfluidToken token, address admin, PoolConfig calldata config, - PoolERC20Metadata memory poolERC20Metadata + PoolERC20Metadata calldata poolERC20Metadata ) external override returns (ISuperfluidPool pool) { - return _createPool(token, admin, config, poolERC20Metadata); + return _createPool( + token, admin, config, poolERC20Metadata.name, poolERC20Metadata.symbol, poolERC20Metadata.decimals + ); } /// @inheritdoc IGeneralDistributionAgreementV1 diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol index 2ccf4438ac..79a24fb275 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.23; import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol"; import { SuperfluidPool } from "./SuperfluidPool.sol"; -import { PoolConfig, PoolERC20Metadata } from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; +import { PoolConfig } from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; library SuperfluidPoolDeployerLibrary { function deploy( @@ -12,7 +12,9 @@ library SuperfluidPoolDeployerLibrary { address admin, ISuperfluidToken token, PoolConfig calldata config, - PoolERC20Metadata calldata poolERC20Metadata + string calldata name, + string calldata symbol, + uint8 decimals ) external returns (SuperfluidPool pool) { bytes memory initializeCallData = abi.encodeWithSelector( SuperfluidPool.initialize.selector, @@ -20,9 +22,9 @@ library SuperfluidPoolDeployerLibrary { token, config.transferabilityForUnitsOwner, config.distributionFromAnyAddress, - poolERC20Metadata.name, - poolERC20Metadata.symbol, - poolERC20Metadata.decimals + name, + symbol, + decimals ); BeaconProxy superfluidPoolBeaconProxy = new BeaconProxy( beacon, From 56084757299caea80761397d2d119978fd73d361 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 24 Jul 2025 16:15:55 +0200 Subject: [PATCH 42/68] more contract size margin for GDA --- .../gdav1/GeneralDistributionAgreementV1.sol | 23 +----------------- .../gdav1/SuperfluidPoolDeployerLibrary.sol | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 518f4b3adf..98e8cc2722 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -23,8 +23,6 @@ import { } from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; import { SuperfluidUpgradeableBeacon } from "../../upgradability/SuperfluidUpgradeableBeacon.sol"; import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.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"; @@ -253,11 +251,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi token.setIsPoolFlag(pool); - IPoolAdminNFT poolAdminNFT = IPoolAdminNFT(_getPoolAdminNFTAddress(token)); - - if (address(poolAdminNFT) != address(0)) { - poolAdminNFT.mint(address(pool)); - } + SuperfluidPoolDeployerLibrary.mintPoolAdminNFT(token, pool); emit PoolCreated(token, admin, pool); } @@ -601,21 +595,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi } } - 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 _adjustBuffer (ISuperfluidToken token, address pool, diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol index 79a24fb275..e251031abc 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol @@ -5,6 +5,9 @@ import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.so import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol"; import { SuperfluidPool } from "./SuperfluidPool.sol"; import { PoolConfig } from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; +import { ISuperToken } from "../../interfaces/superfluid/ISuperToken.sol"; +import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol"; +import { IPoolAdminNFT } from "../../interfaces/agreements/gdav1/IPoolAdminNFT.sol"; library SuperfluidPoolDeployerLibrary { function deploy( @@ -32,4 +35,25 @@ library SuperfluidPoolDeployerLibrary { ); pool = SuperfluidPool(address(superfluidPoolBeaconProxy)); } + + // This was moved out of GeneralDistributionAgreementV1.sol to reduce the contract size. + function mintPoolAdminNFT(ISuperfluidToken token, ISuperfluidPool pool) external { + 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)); + } + + if (poolAdminNFTAddress != address(0)) { + IPoolAdminNFT(poolAdminNFTAddress).mint(address(pool)); + } + } } From 5a3639d10bf420b4597bdb77586e0238281da88f Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 24 Jul 2025 16:18:02 +0200 Subject: [PATCH 43/68] fix import --- .../agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol index 0b1956df68..cfef124724 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol @@ -14,7 +14,7 @@ import { ISuperAgreement } from "../../../../contracts/interfaces/superfluid/ISu import { GeneralDistributionAgreementV1, PoolConfig, - ISuperfluid, ISuperfluidPool, ISuperToken + ISuperfluid, ISuperfluidPool } from "../../../../contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol"; import { GDAv1StorageLib, GDAv1StorageReader, GDAv1StorageWriter From 31bf0b38e4cd993f0e69ae65270c16279531a222 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 31 Jul 2025 09:11:33 +0200 Subject: [PATCH 44/68] no calldata magic --- .../gdav1/GeneralDistributionAgreementV1.sol | 25 ++++++++----------- .../gdav1/SuperfluidPoolDeployerLibrary.sol | 13 +++++----- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 98e8cc2722..08b23cde33 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -233,9 +233,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ISuperfluidToken token, address admin, PoolConfig calldata config, - string calldata name, - string calldata symbol, - uint8 decimals + PoolERC20Metadata memory poolERC20Metadata ) 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(); @@ -244,7 +242,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi pool = ISuperfluidPool( address( SuperfluidPoolDeployerLibrary.deploy( - address(superfluidPoolBeacon), admin, token, config, name, symbol, decimals + address(superfluidPoolBeacon), admin, token, config, poolERC20Metadata ) ) ); @@ -262,13 +260,12 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi override returns (ISuperfluidPool pool) { - string calldata emptyStr; - assembly { - let emptyOffset := calldatasize() - emptyStr.offset := emptyOffset - emptyStr.length := 0 - } - return _createPool(token, admin, config, emptyStr, emptyStr, 0); + return _createPool( + token, + admin, + config, + PoolERC20Metadata("", "", 0) // use defaults specified by the implementation contract + ); } /// @inheritdoc IGeneralDistributionAgreementV1 @@ -276,11 +273,9 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi ISuperfluidToken token, address admin, PoolConfig calldata config, - PoolERC20Metadata calldata poolERC20Metadata + PoolERC20Metadata memory poolERC20Metadata ) external override returns (ISuperfluidPool pool) { - return _createPool( - token, admin, config, poolERC20Metadata.name, poolERC20Metadata.symbol, poolERC20Metadata.decimals - ); + return _createPool(token, admin, config, poolERC20Metadata); } /// @inheritdoc IGeneralDistributionAgreementV1 diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol index e251031abc..a8025e8269 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol @@ -4,20 +4,19 @@ pragma solidity ^0.8.23; 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"; +import { PoolConfig, PoolERC20Metadata } from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; import { ISuperToken } from "../../interfaces/superfluid/ISuperToken.sol"; import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol"; import { IPoolAdminNFT } from "../../interfaces/agreements/gdav1/IPoolAdminNFT.sol"; + library SuperfluidPoolDeployerLibrary { function deploy( address beacon, address admin, ISuperfluidToken token, PoolConfig calldata config, - string calldata name, - string calldata symbol, - uint8 decimals + PoolERC20Metadata calldata poolERC20Metadata ) external returns (SuperfluidPool pool) { bytes memory initializeCallData = abi.encodeWithSelector( SuperfluidPool.initialize.selector, @@ -25,9 +24,9 @@ library SuperfluidPoolDeployerLibrary { token, config.transferabilityForUnitsOwner, config.distributionFromAnyAddress, - name, - symbol, - decimals + poolERC20Metadata.name, + poolERC20Metadata.symbol, + poolERC20Metadata.decimals ); BeaconProxy superfluidPoolBeaconProxy = new BeaconProxy( beacon, From eee4100efdc70091aa9a3e9646b305f3608528f3 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 1 Aug 2025 16:55:29 +0200 Subject: [PATCH 45/68] migrated ethereum-contracts to OZ5 --- .../agreements/gdav1/PoolNFTBase.sol | 3 +- .../contracts/gov/SuperfluidGovernanceII.sol | 7 ++- .../interfaces/superfluid/ISuperToken.sol | 2 +- .../interfaces/superfluid/ISuperfluid.sol | 2 +- .../contracts/libs/ERC777Helper.sol | 2 +- .../mocks/ERC777SenderRecipientMock.t.sol | 50 ++++++++++++++++--- .../FullUpgradableSuperTokenProxy.sol | 9 +++- .../contracts/superfluid/SuperToken.sol | 25 ++++------ .../upgradability/BeaconProxiable.sol | 2 +- .../SuperfluidUpgradeableBeacon.sol | 2 +- .../contracts/upgradability/UUPSProxiable.sol | 2 +- .../contracts/upgradability/UUPSProxy.sol | 11 +++- .../contracts/utils/ERC2771Forwarder.sol | 2 + .../contracts/utils/Resolver.sol | 4 +- .../contracts/utils/SimpleACL.sol | 2 +- .../contracts/utils/SimpleForwarder.sol | 2 + .../contracts/utils/SuperUpgrader.sol | 9 ++-- .../contracts/utils/TOGA.sol | 4 +- .../contracts/utils/TestGovernance.sol | 2 + .../contracts/utils/TestResolver.sol | 4 +- packages/ethereum-contracts/foundry.toml | 2 +- packages/ethereum-contracts/package.json | 2 +- .../foundry/superfluid/PoolAdminNFT.t.sol | 3 +- .../test/foundry/superfluid/PoolNFTBase.t.sol | 4 +- .../superfluid/Superfluid.BatchCall.t.sol | 8 +-- .../SuperfluidUpgradeableBeacon.t.sol | 3 +- yarn.lock | 5 ++ 27 files changed, 120 insertions(+), 53 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol index 0af81e2919..935a12e8b7 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol @@ -6,7 +6,8 @@ pragma solidity ^0.8.23; // 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 { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; +import { IERC721, IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; import { UUPSProxiable } from "../../upgradability/UUPSProxiable.sol"; import { IGeneralDistributionAgreementV1, ISuperfluid } from "../../interfaces/superfluid/ISuperfluid.sol"; import { ISuperTokenFactory } from "../../interfaces/superfluid/ISuperTokenFactory.sol"; diff --git a/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceII.sol b/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceII.sol index 541c24254d..9dd27942d2 100644 --- a/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceII.sol +++ b/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceII.sol @@ -13,7 +13,9 @@ import { ISuperfluid } from "../interfaces/superfluid/ISuperfluid.sol"; * IMPORTANT! Make sure the inheritance order remains in sync with the logic contract (Ownable first)! */ // solhint-disable-next-line no-empty-blocks -contract SuperfluidGovernanceIIProxy is Ownable, UUPSProxy { } +contract SuperfluidGovernanceIIProxy is Ownable, UUPSProxy { + constructor() Ownable(_msgSender()) {} +} contract SuperfluidGovernanceII is Ownable, @@ -21,6 +23,9 @@ contract SuperfluidGovernanceII is SuperfluidGovernanceBase { error SF_GOV_II_ONLY_OWNER(); + + constructor() Ownable(_msgSender()) {} + function _requireAuthorised() private view { if (owner() != _msgSender()) revert SF_GOV_II_ONLY_OWNER(); } diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol index 56b2d9f428..60ee321921 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol @@ -5,7 +5,7 @@ import { ISuperfluidToken } from "./ISuperfluidToken.sol"; import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; import { IERC5267 } from "@openzeppelin/contracts/interfaces/IERC5267.sol"; -import { IERC777 } from "@openzeppelin/contracts/token/ERC777/IERC777.sol"; +import { IERC777 } from "@openzeppelin/contracts/interfaces/IERC777.sol"; import { IPoolAdminNFT } from "../agreements/gdav1/IPoolAdminNFT.sol"; /** diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol index b5f9c1a28d..6d6c915cd9 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol @@ -17,7 +17,7 @@ import { /// Super token related interfaces: /// Note: CustomSuperTokenBase is not included for people building CustomSuperToken. import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { IERC777 } from "@openzeppelin/contracts/token/ERC777/IERC777.sol"; +import { IERC777 } from "@openzeppelin/contracts/interfaces/IERC777.sol"; import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; import { ISuperfluidToken } from "./ISuperfluidToken.sol"; import { ISuperToken } from "./ISuperToken.sol"; diff --git a/packages/ethereum-contracts/contracts/libs/ERC777Helper.sol b/packages/ethereum-contracts/contracts/libs/ERC777Helper.sol index 1842629981..5aa1fccef8 100644 --- a/packages/ethereum-contracts/contracts/libs/ERC777Helper.sol +++ b/packages/ethereum-contracts/contracts/libs/ERC777Helper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { IERC1820Registry } from "@openzeppelin/contracts/utils/introspection/IERC1820Registry.sol"; +import { IERC1820Registry } from "@openzeppelin/contracts/interfaces/IERC1820Registry.sol"; /** * @title ERC777 helper library diff --git a/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol b/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol index f6ca024fce..6fe7e3fb62 100644 --- a/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol +++ b/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol @@ -3,17 +3,53 @@ pragma solidity ^0.8.23; import { Context } from "@openzeppelin/contracts/utils/Context.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC777 } from "@openzeppelin/contracts/token/ERC777/IERC777.sol"; -import { IERC777Sender } from "@openzeppelin/contracts/token/ERC777/IERC777Sender.sol"; -import { IERC777Recipient } from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol"; -import { IERC1820Registry } from "@openzeppelin/contracts/utils/introspection/IERC1820Registry.sol"; -import { - ERC1820Implementer, IERC1820Implementer -} from "@openzeppelin/contracts/utils/introspection/ERC1820Implementer.sol"; +import { IERC777 } from "@openzeppelin/contracts/interfaces/IERC777.sol"; +import { IERC777Sender } from "@openzeppelin/contracts/interfaces/IERC777Sender.sol"; +import { IERC777Recipient } from "@openzeppelin/contracts/interfaces/IERC777Recipient.sol"; +import { IERC1820Registry } from "@openzeppelin/contracts/interfaces/IERC1820Registry.sol"; +import { IERC1820Implementer } from "@openzeppelin/contracts/interfaces/IERC1820Implementer.sol"; import { ISuperToken } from "../superfluid/SuperToken.sol"; +// Copy of: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.6/contracts/utils/introspection/ERC1820Implementer.sol +/** + * @dev Implementation of the {IERC1820Implementer} interface. + * + * Contracts may inherit from this and call {_registerInterfaceForAddress} to + * declare their willingness to be implementers. + * {IERC1820Registry-setInterfaceImplementer} should then be called for the + * registration to be complete. + * + * CAUTION: This file is deprecated as of v4.9 and will be removed in the next major release. + */ +contract ERC1820Implementer is IERC1820Implementer { + bytes32 private constant _ERC1820_ACCEPT_MAGIC = keccak256("ERC1820_ACCEPT_MAGIC"); + + mapping(bytes32 => mapping(address => bool)) private _supportedInterfaces; + + /** + * @dev See {IERC1820Implementer-canImplementInterfaceForAddress}. + */ + function canImplementInterfaceForAddress( + bytes32 interfaceHash, + address account + ) public view virtual override returns (bytes32) { + return _supportedInterfaces[interfaceHash][account] ? _ERC1820_ACCEPT_MAGIC : bytes32(0x00); + } + + /** + * @dev Declares the contract as willing to be an implementer of + * `interfaceHash` for `account`. + * + * See {IERC1820Registry-setInterfaceImplementer} and + * {IERC1820Registry-interfaceHash}. + */ + function _registerInterfaceForAddress(bytes32 interfaceHash, address account) internal virtual { + _supportedInterfaces[interfaceHash][account] = true; + } +} + contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient, ERC1820Implementer { event TokensToSendCalled( address operator, diff --git a/packages/ethereum-contracts/contracts/superfluid/FullUpgradableSuperTokenProxy.sol b/packages/ethereum-contracts/contracts/superfluid/FullUpgradableSuperTokenProxy.sol index 5870bd8174..bb3c345c79 100644 --- a/packages/ethereum-contracts/contracts/superfluid/FullUpgradableSuperTokenProxy.sol +++ b/packages/ethereum-contracts/contracts/superfluid/FullUpgradableSuperTokenProxy.sol @@ -29,6 +29,14 @@ contract FullUpgradableSuperTokenProxy is Proxy { } } + /** + * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data + * is empty. + */ + receive() external payable virtual { + _fallback(); + } + function _implementation() internal override view returns (address impl) { ISuperTokenFactory factory; assembly { // solium-disable-line @@ -37,5 +45,4 @@ contract FullUpgradableSuperTokenProxy is Proxy { assert(address(factory) != address(0)); return address(factory.getSuperTokenLogic()); } - } diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol index 9d6e3bf6b4..443a77cdd6 100644 --- a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol +++ b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol @@ -14,11 +14,9 @@ import { import { SuperfluidToken } from "./SuperfluidToken.sol"; import { ERC777Helper } from "../libs/ERC777Helper.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { IERC777Recipient } from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol"; -import { IERC777Sender } from "@openzeppelin/contracts/token/ERC777/IERC777Sender.sol"; -import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { IERC777Recipient } from "@openzeppelin/contracts/interfaces/IERC777Recipient.sol"; +import { IERC777Sender } from "@openzeppelin/contracts/interfaces/IERC777Sender.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; @@ -36,9 +34,7 @@ contract SuperToken is SuperfluidToken, ISuperToken { - using SafeMath for uint256; using SafeCast for uint256; - using Address for address; using ERC777Helper for ERC777Helper.Operators; using SafeERC20 for IERC20; @@ -362,10 +358,9 @@ contract SuperToken is _move(operator, holder, recipient, amount, "", ""); if (spender != holder) { - _approve( - holder, - spender, - _allowances[holder][spender].sub(amount, "SuperToken: transfer amount exceeds allowance")); + require(amount <= _allowances[holder][spender], "SuperToken: transfer amount exceeds allowance"); + // TODO: this triggers an `Approval` event, which shouldn't happen for transfers. + _approve(holder, spender, _allowances[holder][spender] - amount); } return true; @@ -576,7 +571,7 @@ contract SuperToken is if (implementer != address(0)) { IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); } else if (requireReceptionAck) { - if (to.isContract()) revert SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT(); + if (to.code.length > 0) revert SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT(); } } @@ -638,8 +633,8 @@ contract SuperToken is function decreaseAllowance(address spender, uint256 subtractedValue) public virtual override returns (bool) { - _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue, - "SuperToken: decreased allowance below zero")); + require(subtractedValue <= _allowances[msg.sender][spender], "SuperToken: decreased allowance below zero"); + _approve(msg.sender, spender, _allowances[msg.sender][spender] - subtractedValue); return true; } @@ -918,8 +913,8 @@ contract SuperToken is external virtual override onlyHost { - _approve(account, spender, _allowances[account][spender].sub(subtractedValue, - "SuperToken: decreased allowance below zero")); + require(subtractedValue <= _allowances[account][spender], "SuperToken: decreased allowance below zero"); + _approve(account, spender, _allowances[account][spender] - subtractedValue); } function operationTransferFrom( diff --git a/packages/ethereum-contracts/contracts/upgradability/BeaconProxiable.sol b/packages/ethereum-contracts/contracts/upgradability/BeaconProxiable.sol index 43db85e730..b11cf3389f 100644 --- a/packages/ethereum-contracts/contracts/upgradability/BeaconProxiable.sol +++ b/packages/ethereum-contracts/contracts/upgradability/BeaconProxiable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import { Initializable } from "./Initializable.sol"; abstract contract BeaconProxiable is Initializable { diff --git a/packages/ethereum-contracts/contracts/upgradability/SuperfluidUpgradeableBeacon.sol b/packages/ethereum-contracts/contracts/upgradability/SuperfluidUpgradeableBeacon.sol index 3dc1bbc9f6..2b93d1171b 100644 --- a/packages/ethereum-contracts/contracts/upgradability/SuperfluidUpgradeableBeacon.sol +++ b/packages/ethereum-contracts/contracts/upgradability/SuperfluidUpgradeableBeacon.sol @@ -11,7 +11,7 @@ contract SuperfluidUpgradeableBeacon is UpgradeableBeacon { error INCOMPATIBLE_LOGIC(); // 0x5af2144c error NO_PROXY_LOOP(); // 0z66750bca - constructor(address implementation_) UpgradeableBeacon(implementation_) {} + constructor(address implementation_) UpgradeableBeacon(implementation_, _msgSender()) {} function upgradeTo(address newImplementation) public override onlyOwner { if (newImplementation == address(0)) { diff --git a/packages/ethereum-contracts/contracts/upgradability/UUPSProxiable.sol b/packages/ethereum-contracts/contracts/upgradability/UUPSProxiable.sol index 13f5fc9036..5371dec35b 100644 --- a/packages/ethereum-contracts/contracts/upgradability/UUPSProxiable.sol +++ b/packages/ethereum-contracts/contracts/upgradability/UUPSProxiable.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; import { UUPSUtils } from "./UUPSUtils.sol"; -import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import { Initializable } from "./Initializable.sol"; /** * @title UUPS (Universal Upgradeable Proxy Standard) Proxiable contract. diff --git a/packages/ethereum-contracts/contracts/upgradability/UUPSProxy.sol b/packages/ethereum-contracts/contracts/upgradability/UUPSProxy.sol index c871838a61..2ea182881e 100644 --- a/packages/ethereum-contracts/contracts/upgradability/UUPSProxy.sol +++ b/packages/ethereum-contracts/contracts/upgradability/UUPSProxy.sol @@ -10,7 +10,7 @@ import { Proxy } from "@openzeppelin/contracts/proxy/Proxy.sol"; * * NOTE: * - Compliant with [Universal Upgradeable Proxy Standard](https://eips.ethereum.org/EIPS/eip-1822) - * - Compiiant with [Standard Proxy Storage Slots](https://eips.ethereum.org/EIPS/eip-1967) + * - Compliant with [Standard Proxy Storage Slots](https://eips.ethereum.org/EIPS/eip-1967) * - Implements delegation of calls to other contracts, with proper forwarding of * return values and bubbling of failures. * - It defines a fallback function that delegates all calls to the implementation. @@ -28,10 +28,17 @@ contract UUPSProxy is Proxy { UUPSUtils.setImplementation(initialAddress); } + /** + * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data + * is empty. + */ + receive() external payable virtual { + _fallback(); + } + /// @dev Proxy._implementation implementation function _implementation() internal virtual override view returns (address) { return UUPSUtils.implementation(); } - } diff --git a/packages/ethereum-contracts/contracts/utils/ERC2771Forwarder.sol b/packages/ethereum-contracts/contracts/utils/ERC2771Forwarder.sol index cc2da07420..33784dee41 100644 --- a/packages/ethereum-contracts/contracts/utils/ERC2771Forwarder.sol +++ b/packages/ethereum-contracts/contracts/utils/ERC2771Forwarder.sol @@ -7,6 +7,8 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; * @title Forwards calls preserving the original msg.sender according to ERC-2771 */ contract ERC2771Forwarder is Ownable { + constructor() Ownable(_msgSender()) {} + /** * @dev Forwards a call passing along the original msg.sender encoded as specified in ERC-2771. * @param target The target contract to call diff --git a/packages/ethereum-contracts/contracts/utils/Resolver.sol b/packages/ethereum-contracts/contracts/utils/Resolver.sol index 36ee7aa156..9f528363eb 100644 --- a/packages/ethereum-contracts/contracts/utils/Resolver.sol +++ b/packages/ethereum-contracts/contracts/utils/Resolver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.11; -import { AccessControlEnumerable } from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; +import { AccessControlEnumerable } from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol"; import { IResolver } from "../interfaces/utils/IResolver.sol"; @@ -20,7 +20,7 @@ contract Resolver is IResolver, AccessControlEnumerable { mapping(string => address) private _registry; constructor() { - _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } function set(string calldata name, address target) external override { diff --git a/packages/ethereum-contracts/contracts/utils/SimpleACL.sol b/packages/ethereum-contracts/contracts/utils/SimpleACL.sol index 05fb553429..36fe30eb7d 100644 --- a/packages/ethereum-contracts/contracts/utils/SimpleACL.sol +++ b/packages/ethereum-contracts/contracts/utils/SimpleACL.sol @@ -5,7 +5,7 @@ import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol" contract SimpleACL is AccessControl { constructor() { - _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } /// Allows the default admin to set the admin role for a given role. diff --git a/packages/ethereum-contracts/contracts/utils/SimpleForwarder.sol b/packages/ethereum-contracts/contracts/utils/SimpleForwarder.sol index 612c9734cd..39d01d1068 100644 --- a/packages/ethereum-contracts/contracts/utils/SimpleForwarder.sol +++ b/packages/ethereum-contracts/contracts/utils/SimpleForwarder.sol @@ -10,6 +10,8 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; * This is necessary for security reasons if the calling account has privileged access anywhere. */ contract SimpleForwarder is Ownable { + constructor() Ownable(_msgSender()) {} + /** * @dev Forwards a call for which msg.sender doesn't matter * @param target The target contract to call diff --git a/packages/ethereum-contracts/contracts/utils/SuperUpgrader.sol b/packages/ethereum-contracts/contracts/utils/SuperUpgrader.sol index ba410beb21..7981912107 100644 --- a/packages/ethereum-contracts/contracts/utils/SuperUpgrader.sol +++ b/packages/ethereum-contracts/contracts/utils/SuperUpgrader.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { AccessControlEnumerable } from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; +import { AccessControlEnumerable } from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { ISuperToken, @@ -29,10 +29,10 @@ contract SuperUpgrader is AccessControlEnumerable { constructor(address adminRole, address[] memory backendAddr) { require(adminRole != address(0), "adminRole is empty"); - _setupRole(DEFAULT_ADMIN_ROLE, adminRole); + _grantRole(DEFAULT_ADMIN_ROLE, adminRole); for (uint256 i = 0; i < backendAddr.length; ++i) { require(backendAddr[i] != address(0), "backend can't be zero"); - _setupRole(BACKEND_ROLE, backendAddr[i]); + _grantRole(BACKEND_ROLE, backendAddr[i]); } } @@ -60,8 +60,7 @@ contract SuperUpgrader is AccessControlEnumerable { IERC20 token = IERC20(superToken.getUnderlyingToken()); uint256 beforeBalance = token.balanceOf(address(this)); token.safeTransferFrom(account, address(this), amount); - token.safeApprove(address(superToken), 0); - token.safeApprove(address(superToken), amount); + token.forceApprove(address(superToken), amount); // upgrade tokens and send back to user superToken.upgradeTo( account, diff --git a/packages/ethereum-contracts/contracts/utils/TOGA.sol b/packages/ethereum-contracts/contracts/utils/TOGA.sol index 30dcf941c5..a19cf02c89 100644 --- a/packages/ethereum-contracts/contracts/utils/TOGA.sol +++ b/packages/ethereum-contracts/contracts/utils/TOGA.sol @@ -7,8 +7,8 @@ import { ISuperfluid, ISuperToken, IConstantFlowAgreementV1 } from "../interfaces/superfluid/ISuperfluid.sol"; -import { IERC1820Registry } from "@openzeppelin/contracts/utils/introspection/IERC1820Registry.sol"; -import { IERC777Recipient } from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol"; +import { IERC1820Registry } from "@openzeppelin/contracts/interfaces/IERC1820Registry.sol"; +import { IERC777Recipient } from "@openzeppelin/contracts/interfaces/IERC777Recipient.sol"; /** * @title TOGA: Transparent Ongoing Auction diff --git a/packages/ethereum-contracts/contracts/utils/TestGovernance.sol b/packages/ethereum-contracts/contracts/utils/TestGovernance.sol index 33ee9c7182..7efa2f1c95 100644 --- a/packages/ethereum-contracts/contracts/utils/TestGovernance.sol +++ b/packages/ethereum-contracts/contracts/utils/TestGovernance.sol @@ -21,6 +21,8 @@ contract TestGovernance is { ISuperfluid private _host; + constructor() Ownable(_msgSender()) {} + function initialize( ISuperfluid host, address rewardAddress, diff --git a/packages/ethereum-contracts/contracts/utils/TestResolver.sol b/packages/ethereum-contracts/contracts/utils/TestResolver.sol index e7b0c46f7d..0e47479715 100644 --- a/packages/ethereum-contracts/contracts/utils/TestResolver.sol +++ b/packages/ethereum-contracts/contracts/utils/TestResolver.sol @@ -9,10 +9,10 @@ import { Resolver } from "./Resolver.sol"; /// @dev Used by the SuperfluidFrameworkDeployer to grant admin privileges to its deployer contract TestResolver is Resolver { constructor(address _additionalAdmin) { - _setupRole(DEFAULT_ADMIN_ROLE, _additionalAdmin); + _grantRole(DEFAULT_ADMIN_ROLE, _additionalAdmin); } function addAdmin(address _additionalAdmin) external { - _setupRole(DEFAULT_ADMIN_ROLE, _additionalAdmin); + _grantRole(DEFAULT_ADMIN_ROLE, _additionalAdmin); } } diff --git a/packages/ethereum-contracts/foundry.toml b/packages/ethereum-contracts/foundry.toml index cc999464ae..a4a4505494 100644 --- a/packages/ethereum-contracts/foundry.toml +++ b/packages/ethereum-contracts/foundry.toml @@ -14,7 +14,7 @@ optimizer_runs = 200 remappings = [ '@superfluid-finance/ethereum-contracts/contracts/=packages/ethereum-contracts/contracts/', '@superfluid-finance/solidity-semantic-money/src/=packages/solidity-semantic-money/src/', - '@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/', + '@openzeppelin/contracts/=packages/ethereum-contracts/node_modules/@openzeppelin/contracts/', 'ds-test/=lib/forge-std/lib/ds-test/src/', 'forge-std/=lib/forge-std/src/'] out = 'packages/ethereum-contracts/build/foundry/default' diff --git a/packages/ethereum-contracts/package.json b/packages/ethereum-contracts/package.json index 72219b4a52..be34aff56f 100644 --- a/packages/ethereum-contracts/package.json +++ b/packages/ethereum-contracts/package.json @@ -5,7 +5,7 @@ "dependencies": { "@decentral.ee/web3-helpers": "0.5.3", "@nomiclabs/hardhat-ethers": "2.2.3", - "@openzeppelin/contracts": "4.9.6", + "@openzeppelin/contracts": "5.4.0", "@truffle/contract": "4.6.31", "ethereumjs-tx": "2.1.2", "ethereumjs-util": "7.1.5", diff --git a/packages/ethereum-contracts/test/foundry/superfluid/PoolAdminNFT.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/PoolAdminNFT.t.sol index 1d33ffbea6..14df4b792a 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/PoolAdminNFT.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/PoolAdminNFT.t.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { IERC165, IERC721, IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; +import { 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"; diff --git a/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTBase.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTBase.t.sol index fc0a513105..cf12d0bf64 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTBase.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTBase.t.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { IERC165, IERC721, IERC721Metadata } from "@openzeppelin/contracts/interfaces/IERC721Metadata.sol"; +import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; +import { IERC721 } from "@openzeppelin/contracts/interfaces/IERC721.sol"; +import { IERC721Metadata } from "@openzeppelin/contracts/interfaces/IERC721Metadata.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol"; import { diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol index 90e3426610..43822e6437 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol @@ -43,6 +43,8 @@ contract TestContract { contract TestContract2771 is TestContract, Ownable, BaseRelayRecipient { error NotOwner(); + constructor() Ownable(_msgSender()) {} + // Expects the msgSender to be encoded in calldata as specified by ERC-2771. // Will revert if relayed for anybody but the contract owner. function privilegedFn() public returns (bool) { @@ -454,7 +456,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { // only the owner of the forwarder shall be allowed to relay vm.startPrank(eve); - vm.expectRevert("Ownable: caller is not the owner"); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, eve)); forwarder.forward2771Call( address(testContract), alice, @@ -619,7 +621,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { // eve isn't allowed to withdraw vm.startPrank(eve); - vm.expectRevert("Ownable: caller is not the owner"); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, eve)); forwarder.withdrawLostNativeTokens(payable(bob)); vm.stopPrank(); @@ -643,7 +645,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { // eve isn't allowed to withdraw vm.startPrank(eve); - vm.expectRevert("Ownable: caller is not the owner"); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, eve)); forwarder.withdrawLostNativeTokens(payable(bob)); vm.stopPrank(); diff --git a/packages/ethereum-contracts/test/foundry/upgradability/SuperfluidUpgradeableBeacon.t.sol b/packages/ethereum-contracts/test/foundry/upgradability/SuperfluidUpgradeableBeacon.t.sol index 5117ad87a5..6f002d00f9 100644 --- a/packages/ethereum-contracts/test/foundry/upgradability/SuperfluidUpgradeableBeacon.t.sol +++ b/packages/ethereum-contracts/test/foundry/upgradability/SuperfluidUpgradeableBeacon.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.23; import { Test } from "forge-std/Test.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { SuperfluidUpgradeableBeacon } from "../../../contracts/upgradability/SuperfluidUpgradeableBeacon.sol"; @@ -32,7 +33,7 @@ contract SuperfluidUpgradeableBeaconTest is Test { function testRevertNonOwnerUpgrade() public { ProxiableBeacon proxiableBeacon = new ProxiableBeacon(); - vm.expectRevert("Ownable: caller is not the owner"); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(this))); beacon.upgradeTo(address(proxiableBeacon)); } diff --git a/yarn.lock b/yarn.lock index 33de3e8eb2..10d627a54e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3037,6 +3037,11 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.6.tgz#2a880a24eb19b4f8b25adc2a5095f2aa27f39677" integrity sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA== +"@openzeppelin/contracts@5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.4.0.tgz#177594bdb2d86c71f5d1052fe40cb4edb95fb20f" + integrity sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A== + "@openzeppelin/test-helpers@^0.5.16": version "0.5.16" resolved "https://registry.yarnpkg.com/@openzeppelin/test-helpers/-/test-helpers-0.5.16.tgz#2c9054f85069dfbfb5e8cef3ed781e8caf241fb3" From d4cebf3ad5240c1bb8c746b3abe4bc47495f6470 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 1 Aug 2025 17:33:06 +0200 Subject: [PATCH 46/68] switch to OZ5 in all packages --- packages/automation-contracts/autowrap/package.json | 2 +- packages/automation-contracts/scheduler/package.json | 2 +- .../contracts/agreements/gdav1/PoolNFTBase.sol | 2 +- .../contracts/mocks/ERC777SenderRecipientMock.t.sol | 2 +- packages/ethereum-contracts/foundry.toml | 2 +- packages/hot-fuzz/package.json | 2 +- packages/sdk-core/package.json | 2 +- packages/solidity-semantic-money/package.json | 2 +- yarn.lock | 7 +------ 9 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/automation-contracts/autowrap/package.json b/packages/automation-contracts/autowrap/package.json index 210a9aa315..e922e195ab 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": { - "@openzeppelin/contracts": "^4.9.6", + "@openzeppelin/contracts": "^5.4.0", "@superfluid-finance/ethereum-contracts": "^1.13.0", "@superfluid-finance/metadata": "^1.6.0" }, diff --git a/packages/automation-contracts/scheduler/package.json b/packages/automation-contracts/scheduler/package.json index 1d31a2e123..5bdd02278a 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": { - "@openzeppelin/contracts": "^4.9.6", + "@openzeppelin/contracts": "^5.4.0", "@superfluid-finance/ethereum-contracts": "^1.13.0", "@superfluid-finance/metadata": "^1.6.0" }, diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol index 935a12e8b7..098d72ba43 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol @@ -7,7 +7,7 @@ pragma solidity ^0.8.23; // Notes: We use these interfaces in natspec documentation below, grep @inheritdoc // solhint-disable-next-line no-unused-import import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; -import { IERC721, IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { UUPSProxiable } from "../../upgradability/UUPSProxiable.sol"; import { IGeneralDistributionAgreementV1, ISuperfluid } from "../../interfaces/superfluid/ISuperfluid.sol"; import { ISuperTokenFactory } from "../../interfaces/superfluid/ISuperTokenFactory.sol"; diff --git a/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol b/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol index 6fe7e3fb62..7d71aac494 100644 --- a/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol +++ b/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol @@ -12,7 +12,7 @@ import { IERC1820Implementer } from "@openzeppelin/contracts/interfaces/IERC1820 import { ISuperToken } from "../superfluid/SuperToken.sol"; -// Copy of: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.6/contracts/utils/introspection/ERC1820Implementer.sol +// Copy of OpenZeppelin v4.9.6: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.6/ /** * @dev Implementation of the {IERC1820Implementer} interface. * diff --git a/packages/ethereum-contracts/foundry.toml b/packages/ethereum-contracts/foundry.toml index a4a4505494..cc999464ae 100644 --- a/packages/ethereum-contracts/foundry.toml +++ b/packages/ethereum-contracts/foundry.toml @@ -14,7 +14,7 @@ optimizer_runs = 200 remappings = [ '@superfluid-finance/ethereum-contracts/contracts/=packages/ethereum-contracts/contracts/', '@superfluid-finance/solidity-semantic-money/src/=packages/solidity-semantic-money/src/', - '@openzeppelin/contracts/=packages/ethereum-contracts/node_modules/@openzeppelin/contracts/', + '@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/', 'ds-test/=lib/forge-std/lib/ds-test/src/', 'forge-std/=lib/forge-std/src/'] out = 'packages/ethereum-contracts/build/foundry/default' diff --git a/packages/hot-fuzz/package.json b/packages/hot-fuzz/package.json index f375ec2321..a938cf98a6 100644 --- a/packages/hot-fuzz/package.json +++ b/packages/hot-fuzz/package.json @@ -7,7 +7,7 @@ }, "bugs": "https://github.com/superfluid-finance/protocol-monorepo/issues", "dependencies": { - "@openzeppelin/contracts": "4.9.6" + "@openzeppelin/contracts": "5.4.0" }, "devDependencies": { "@superfluid-finance/ethereum-contracts": "^1.13.0" diff --git a/packages/sdk-core/package.json b/packages/sdk-core/package.json index 9e84c7575b..226e326cfa 100644 --- a/packages/sdk-core/package.json +++ b/packages/sdk-core/package.json @@ -55,7 +55,7 @@ "pretest": "yarn testenv:start", "test": "hardhat test --tsconfig \"tsconfig.test.json\"", "dev": "nodemon -e ts -x yarn test", - "clean": "rm -rf node_modules; rm -rf dist; rm -rf src/typechain-types; rm -rf src/typechain; rm -rf src/abi; find . -type f -name '*.generated.ts' -exec rm {} +", + "clean": "rm -rf node_modules; rm -rf dist; rm -rf typechain-types; rm -rf src/typechain; rm -rf src/abi; find . -type f -name '*.generated.ts' -exec rm {} +", "test-coverage": "nyc --reporter=html --reporter=lcov --reporter=json yarn test", "posttest": "yarn testenv:stop", "check-updates": "ncu --target minor --dep prod,dev", diff --git a/packages/solidity-semantic-money/package.json b/packages/solidity-semantic-money/package.json index a83fa185dd..97c4255c2b 100644 --- a/packages/solidity-semantic-money/package.json +++ b/packages/solidity-semantic-money/package.json @@ -4,7 +4,7 @@ "version": "0.1.0", "bugs": "https://github.com/superfluid-finance/protocol-monorepo/issues", "dependencies": { - "@openzeppelin/contracts": "4.9.6" + "@openzeppelin/contracts": "5.4.0" }, "directories": { "src": "src", diff --git a/yarn.lock b/yarn.lock index 10d627a54e..77bc28dc41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3032,12 +3032,7 @@ find-up "^4.1.0" fs-extra "^8.1.0" -"@openzeppelin/contracts@4.9.6", "@openzeppelin/contracts@^4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.6.tgz#2a880a24eb19b4f8b25adc2a5095f2aa27f39677" - integrity sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA== - -"@openzeppelin/contracts@5.4.0": +"@openzeppelin/contracts@5.4.0", "@openzeppelin/contracts@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.4.0.tgz#177594bdb2d86c71f5d1052fe40cb4edb95fb20f" integrity sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A== From b2969374590023666da0de93b5157473fb7643bc Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 1 Aug 2025 18:15:25 +0200 Subject: [PATCH 47/68] add Initializable --- .../contracts/upgradability/Initializable.sol | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 packages/ethereum-contracts/contracts/upgradability/Initializable.sol diff --git a/packages/ethereum-contracts/contracts/upgradability/Initializable.sol b/packages/ethereum-contracts/contracts/upgradability/Initializable.sol new file mode 100644 index 0000000000..baf3f6425e --- /dev/null +++ b/packages/ethereum-contracts/contracts/upgradability/Initializable.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol) + +pragma solidity ^0.8.2; + +/** + * Note: Copy from Openzeppelin v4.9.6. We need to stick with this version due to storage layout changes in v5. + * + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be + * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in + * case an upgrade adds a module that needs to be initialized. + * + * For example: + * + * [.hljs-theme-light.nopadding] + * ```solidity + * contract MyToken is ERC20Upgradeable { + * function initialize() initializer public { + * __ERC20_init("MyToken", "MTK"); + * } + * } + * + * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { + * function initializeV2() reinitializer(2) public { + * __ERC20Permit_init("MyToken"); + * } + * } + * ``` + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + * + * [CAUTION] + * ==== + * Avoid leaving a contract uninitialized. + * + * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation + * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke + * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: + * + * [.hljs-theme-light.nopadding] + * ``` + * /// @custom:oz-upgrades-unsafe-allow constructor + * constructor() { + * _disableInitializers(); + * } + * ``` + * ==== + */ +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + * @custom:oz-retyped-from bool + */ + uint8 private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint8 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. + * + * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a + * constructor. + * + * Emits an {Initialized} event. + */ + modifier initializer() { + bool isTopLevelCall = !_initializing; + require( + (isTopLevelCall && _initialized < 1) || (address(this).code.length == 0 && _initialized == 1), + "Initializable: contract is already initialized" + ); + _initialized = 1; + if (isTopLevelCall) { + _initializing = true; + } + _; + if (isTopLevelCall) { + _initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * A reinitializer may be used after the original initialization step. This is essential to configure modules that + * are added through upgrades and that require initialization. + * + * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` + * cannot be nested. If one is invoked in the context of another, execution will revert. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + * + * WARNING: setting the version to 255 will prevent any future reinitialization. + * + * Emits an {Initialized} event. + */ + modifier reinitializer(uint8 version) { + require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); + _initialized = version; + _initializing = true; + _; + _initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + require(_initializing, "Initializable: contract is not initializing"); + _; + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + * + * Emits an {Initialized} event the first time it is successfully executed. + */ + function _disableInitializers() internal virtual { + require(!_initializing, "Initializable: contract is initializing"); + if (_initialized != type(uint8).max) { + _initialized = type(uint8).max; + emit Initialized(type(uint8).max); + } + } + + /** + * @dev Returns the highest version that has been initialized. See {reinitializer}. + */ + function _getInitializedVersion() internal view returns (uint8) { + return _initialized; + } + + /** + * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. + */ + function _isInitializing() internal view returns (bool) { + return _initializing; + } +} \ No newline at end of file From b012934212d2a0aa25ef08a1dba40c0936e66949 Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 4 Aug 2025 15:07:07 +0200 Subject: [PATCH 48/68] workaround to the Address issue --- packages/sdk-core/tasks/build-types.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/sdk-core/tasks/build-types.sh b/packages/sdk-core/tasks/build-types.sh index f22129c155..6af75ae01f 100755 --- a/packages/sdk-core/tasks/build-types.sh +++ b/packages/sdk-core/tasks/build-types.sh @@ -15,5 +15,11 @@ fi # copy the typechain files over from ethereum-contracts cp -r ../ethereum-contracts/typechain-types ./src/typechain-types +# Remove the Address export from typechain-types to avoid conflict with mappedSubgraphTypes +# OpenZeppelin v5 added a custom error to Address library, giving it a non-empty ABI +# This causes typechain to generate types for it, but it's not needed for the SDK +sed -i '/export type { Address } from ".\/@openzeppelin\/contracts\/utils\/Address";/d' ./src/typechain-types/index.ts +sed -i '/export { Address__factory } from ".\/factories\/@openzeppelin\/contracts\/utils\/Address__factory";/d' ./src/typechain-types/index.ts + # compile the typechain files in sdk-core tsc -p tsconfig.typechain.json From 248f2897a465d895dc75f4fd539aa5c4719edbb2 Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 4 Aug 2025 19:16:24 +0200 Subject: [PATCH 49/68] adjust tests to new (custom) errors --- .../superfluid/SuperToken.NonStandard.test.ts | 6 +++-- .../contracts/utils/SuperUpgrader.test.ts | 26 ++++++++++++------- .../test/utils/expectRevert.ts | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) 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 e5b019a3c4..7930323bb6 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts @@ -141,11 +141,13 @@ describe("SuperToken's Non Standard Functions", function () { it("#2.2 - should not upgrade without enough underlying balance", async () => { const initialBalance = await testToken.balanceOf(alice); console.log("SuperToken.upgrade - bad balance"); - await expectRevertedWith( + await expectCustomError( superToken .connect(aliceSigner) .upgrade(initialBalance.add(toBN(1))), - "ERC20: transfer amount exceeds balance" + testToken, + "ERC20InsufficientBalance", + [alice, initialBalance, initialBalance.add(toBN(1))] ); await t.validateSystemInvariance(); }); diff --git a/packages/ethereum-contracts/test/contracts/utils/SuperUpgrader.test.ts b/packages/ethereum-contracts/test/contracts/utils/SuperUpgrader.test.ts index aaba699bc9..e2eeb72fd7 100644 --- a/packages/ethereum-contracts/test/contracts/utils/SuperUpgrader.test.ts +++ b/packages/ethereum-contracts/test/contracts/utils/SuperUpgrader.test.ts @@ -1,6 +1,6 @@ import {assert} from "chai"; import {ethers, web3} from "hardhat"; -import {expectRevertedWith} from "../../utils/expectRevert"; +import {expectCustomError, expectRevertedWith} from "../../utils/expectRevert"; import TestEnvironment from "../../TestEnvironment"; import {toWad} from "./helpers"; @@ -195,11 +195,13 @@ describe("Superfluid Super Upgrader Contract", function () { const backendSigner = await ethers.getSigner(backend[0]); console.log("upgrader.upgrade"); - await expectRevertedWith( + await expectCustomError( upgrader .connect(backendSigner) .upgrade(superToken.address, alice, 1), - "ERC20: insufficient allowance" + testToken, + "ERC20InsufficientAllowance", + [upgrader.address, 0, 1] ); }); @@ -212,11 +214,13 @@ describe("Superfluid Super Upgrader Contract", function () { .connect(aliceSigner) .approve(upgrader.address, toWad("1")); - await expectRevertedWith( + await expectCustomError( upgrader .connect(backendSigner) .upgrade(superToken.address, alice, "1000000000000000001"), - "ERC20: insufficient allowance" + testToken, + "ERC20InsufficientAllowance", + [upgrader.address, toWad("1"), "1000000000000000001"] ); }); @@ -308,14 +312,18 @@ describe("Superfluid Super Upgrader Contract", function () { "operation not allowed" ); - await expectRevertedWith( + await expectCustomError( upgrader.connect(eveSigner).grantBackendAgent(eve), - `AccessControl: account ${eve.toLowerCase()} is missing role ${DEFAULT_ADMIN_ROLE}` + upgrader, + "AccessControlUnauthorizedAccount", + [eve, DEFAULT_ADMIN_ROLE] ); - await expectRevertedWith( + await expectCustomError( upgrader.connect(eveSigner).revokeBackendAgent(backend[1]), - `AccessControl: account ${eve.toLowerCase()} is missing role ${DEFAULT_ADMIN_ROLE}` + upgrader, + "AccessControlUnauthorizedAccount", + [eve, DEFAULT_ADMIN_ROLE] ); }); diff --git a/packages/ethereum-contracts/test/utils/expectRevert.ts b/packages/ethereum-contracts/test/utils/expectRevert.ts index 0427617c3e..4e0265c9f9 100644 --- a/packages/ethereum-contracts/test/utils/expectRevert.ts +++ b/packages/ethereum-contracts/test/utils/expectRevert.ts @@ -19,7 +19,7 @@ export const expectCustomError = async ( args ? await expect(func) .to.be.revertedWithCustomError(contract, customErrorString) - .withArgs(args) + .withArgs(...args) : await expect(func).to.be.revertedWithCustomError( contract, customErrorString From 253ab3bdc74776b71d9aa00d8725cec92169d75c Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 4 Aug 2025 21:57:56 +0200 Subject: [PATCH 50/68] unbreak single-arg use --- packages/ethereum-contracts/test/utils/expectRevert.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/ethereum-contracts/test/utils/expectRevert.ts b/packages/ethereum-contracts/test/utils/expectRevert.ts index 4e0265c9f9..e432e161d4 100644 --- a/packages/ethereum-contracts/test/utils/expectRevert.ts +++ b/packages/ethereum-contracts/test/utils/expectRevert.ts @@ -16,12 +16,6 @@ export const expectCustomError = async ( customErrorString: string, args?: any ) => { - args - ? await expect(func) - .to.be.revertedWithCustomError(contract, customErrorString) - .withArgs(...args) - : await expect(func).to.be.revertedWithCustomError( - contract, - customErrorString - ); + const expectation = expect(func).to.be.revertedWithCustomError(contract, customErrorString); + args ? await expectation.withArgs(...(Array.isArray(args) ? args : [args])) : await expectation; }; From e34e685230a69928036b193de313b2aa6b7d5491 Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 4 Aug 2025 22:33:46 +0200 Subject: [PATCH 51/68] adjust automation contracts --- .../automation-contracts/autowrap/contracts/Manager.sol | 2 +- .../autowrap/contracts/strategies/StrategyBase.sol | 2 ++ .../automation-contracts/autowrap/test/Manager.t.sol | 9 +++++---- .../autowrap/test/WrapStrategy.t.sol | 7 ++++--- .../scheduler/contracts/VestingSchedulerV2.sol | 6 +----- .../scheduler/contracts/VestingSchedulerV3.sol | 3 +-- .../scheduler/test/VestingSchedulerV2.t.sol | 5 ++--- .../scheduler/test/VestingSchedulerV3.t.sol | 4 ++-- 8 files changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/automation-contracts/autowrap/contracts/Manager.sol b/packages/automation-contracts/autowrap/contracts/Manager.sol index 50e6a3d603..435678ea04 100644 --- a/packages/automation-contracts/autowrap/contracts/Manager.sol +++ b/packages/automation-contracts/autowrap/contracts/Manager.sol @@ -31,7 +31,7 @@ contract Manager is IManager, Ownable { address _cfa, uint64 _minLower, uint64 _minUpper - ) { + ) Ownable(_msgSender()) { if (_cfa == address(0)) revert ZeroAddress(); if (_minLower >= _minUpper) revert WrongLimits(_minLower, _minUpper); diff --git a/packages/automation-contracts/autowrap/contracts/strategies/StrategyBase.sol b/packages/automation-contracts/autowrap/contracts/strategies/StrategyBase.sol index ffd3902005..ae1787fef9 100644 --- a/packages/automation-contracts/autowrap/contracts/strategies/StrategyBase.sol +++ b/packages/automation-contracts/autowrap/contracts/strategies/StrategyBase.sol @@ -14,6 +14,8 @@ abstract contract StrategyBase is IStrategy, Ownable { /// @dev IStrategy.manager implementation. address public override manager; + constructor() Ownable(_msgSender()) { } + /// @dev IStrategy.changeManager implementation. function changeManager(address newManager) external diff --git a/packages/automation-contracts/autowrap/test/Manager.t.sol b/packages/automation-contracts/autowrap/test/Manager.t.sol index ee81cfff0b..c9d244914a 100644 --- a/packages/automation-contracts/autowrap/test/Manager.t.sol +++ b/packages/automation-contracts/autowrap/test/Manager.t.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { ISuperToken } from "@superfluid-finance/ethereum-contracts/contracts/superfluid/SuperToken.sol"; import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; import { FoundrySuperfluidTester } from "@superfluid-finance/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol"; @@ -89,7 +90,7 @@ contract ManagerTests is FoundrySuperfluidTester { new Manager(address(sf.cfa), 2, 1); } - function testDeploymentCheckData() public { + function testDeploymentCheckData() public view { assertEq(address(manager.cfaV1()), address(sf.cfa), "manager.cfaV1 not equal"); assertEq(manager.owner(), admin, "manager.owner not equal"); assertEq(manager.minLower(), MIN_LOWER, "manager.minLower not equal"); @@ -108,7 +109,7 @@ contract ManagerTests is FoundrySuperfluidTester { vm.prank(admin); manager.setLimits(newMinLower, newMinUpper); // non owner can't set new limits - vm.expectRevert(bytes("Ownable: caller is not the owner")); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(this))); manager.setLimits(newMinLower, newMinUpper); } @@ -124,7 +125,7 @@ contract ManagerTests is FoundrySuperfluidTester { manager.addApprovedStrategy(address(wrapStrategy)); vm.stopPrank(); // non owner can't add new strategy - vm.expectRevert(bytes("Ownable: caller is not the owner")); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(this))); manager.addApprovedStrategy(address(wrapStrategy)); bool isStrategyApproved = manager.approvedStrategies(address(wrapStrategy)); assertTrue(isStrategyApproved, "strategy should be register"); @@ -135,7 +136,7 @@ contract ManagerTests is FoundrySuperfluidTester { //add strategy to be removed manager.addApprovedStrategy(address(wrapStrategy)); // non owner can't add new strategy - vm.expectRevert(bytes("Ownable: caller is not the owner")); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(this))); manager.removeApprovedStrategy(address(wrapStrategy)); vm.startPrank(admin); vm.expectEmit(true, true, true, true); diff --git a/packages/automation-contracts/autowrap/test/WrapStrategy.t.sol b/packages/automation-contracts/autowrap/test/WrapStrategy.t.sol index 327c8fca24..57631cc57b 100644 --- a/packages/automation-contracts/autowrap/test/WrapStrategy.t.sol +++ b/packages/automation-contracts/autowrap/test/WrapStrategy.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { ISuperToken } from "@superfluid-finance/ethereum-contracts/contracts/superfluid/SuperToken.sol"; import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; import { FoundrySuperfluidTester } from "@superfluid-finance/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol"; @@ -75,7 +76,7 @@ contract WrapStrategyTests is FoundrySuperfluidTester { function testCannotChangeManagerContractIfNotOwner() public { Manager newManager = new Manager(address(sf.cfa), MIN_LOWER, MIN_UPPER); vm.prank(admin); - vm.expectRevert(bytes("Ownable: caller is not the owner")); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, admin)); wrapStrategy.changeManager(address(newManager)); } @@ -93,14 +94,14 @@ contract WrapStrategyTests is FoundrySuperfluidTester { // SuperToken - function testSupportedSuperToken() public { + function testSupportedSuperToken() public view { assertTrue( wrapStrategy.isSupportedSuperToken(superToken), "SuperToken should be supported" ); } - function testCannotNonSupportedSuperToken() public { + function testCannotNonSupportedSuperToken() public view { assertTrue( !wrapStrategy.isSupportedSuperToken(nativeSuperToken), "Native SuperToken shouldn't be supported" diff --git a/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol b/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol index 89464102a6..aa3c5b963b 100644 --- a/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol +++ b/packages/automation-contracts/scheduler/contracts/VestingSchedulerV2.sol @@ -7,7 +7,6 @@ import { import { SuperAppBase } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperAppBase.sol"; import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; import { IVestingSchedulerV2 } from "./interface/IVestingSchedulerV2.sol"; -import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; contract VestingSchedulerV2 is IVestingSchedulerV2, SuperAppBase { @@ -221,10 +220,7 @@ contract VestingSchedulerV2 is IVestingSchedulerV2, SuperAppBase { remainderAmount: remainderAmount }); } else { - uint256 cliffAmount = SafeMath.mul( - cliffPeriod, - SafeCast.toUint256(flowRate) - ); + uint256 cliffAmount = cliffPeriod * SafeCast.toUint256(flowRate); params = ScheduleCreationParams({ superToken: superToken, sender: sender, diff --git a/packages/automation-contracts/scheduler/contracts/VestingSchedulerV3.sol b/packages/automation-contracts/scheduler/contracts/VestingSchedulerV3.sol index d8445d1478..632f8b8835 100644 --- a/packages/automation-contracts/scheduler/contracts/VestingSchedulerV3.sol +++ b/packages/automation-contracts/scheduler/contracts/VestingSchedulerV3.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; /// @dev OpenZeppelin Imports -import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; /// @dev Superfluid Protocol Imports @@ -532,7 +531,7 @@ contract VestingSchedulerV3 is IVestingSchedulerV3, IRelayRecipient { }); } else { // Linear Default Cliff (calculated based on the overall vesting flow rate) - cliffAmount = SafeMath.mul(cliffPeriod, SafeCast.toUint256(flowRate)); + cliffAmount = cliffPeriod * SafeCast.toUint256(flowRate); params = ScheduleCreationParams({ superToken: superToken, sender: sender, diff --git a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol index 7eff7da0c7..00ef7c18ee 100644 --- a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol +++ b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol @@ -7,7 +7,6 @@ import { IVestingSchedulerV2 } from "./../contracts/interface/IVestingSchedulerV import { VestingSchedulerV2 } from "./../contracts/VestingSchedulerV2.sol"; import { FoundrySuperfluidTester } from "@superfluid-finance/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol"; import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; -import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import "forge-std/console.sol"; @@ -899,7 +898,7 @@ contract VestingSchedulerV2Tests is FoundrySuperfluidTester { ); console.log("Revert with overflow."); - vm.expectRevert("SafeCast: value doesn't fit in 96 bits"); + vm.expectRevert(); // SafeCastOverflowedIntDowncast vestingScheduler.createVestingScheduleFromAmountAndDuration( superToken, bob, @@ -1961,7 +1960,7 @@ contract VestingSchedulerV2Tests is FoundrySuperfluidTester { ); console.log("Revert with overflow."); - vm.expectRevert("SafeCast: value doesn't fit in 96 bits"); + vm.expectRevert(); // SafeCastOverflowedIntDowncast vestingScheduler.createVestingScheduleFromAmountAndDuration( superToken, bob, diff --git a/packages/automation-contracts/scheduler/test/VestingSchedulerV3.t.sol b/packages/automation-contracts/scheduler/test/VestingSchedulerV3.t.sol index ce5282242e..89b3984180 100644 --- a/packages/automation-contracts/scheduler/test/VestingSchedulerV3.t.sol +++ b/packages/automation-contracts/scheduler/test/VestingSchedulerV3.t.sol @@ -1105,7 +1105,7 @@ contract VestingSchedulerV3Tests is FoundrySuperfluidTester { ); console.log("Revert with overflow."); - vm.expectRevert("SafeCast: value doesn't fit in 96 bits"); + vm.expectRevert(); // SafeCastOverflowedIntDowncast vestingScheduler.createVestingScheduleFromAmountAndDuration( superToken, bob, @@ -2189,7 +2189,7 @@ contract VestingSchedulerV3Tests is FoundrySuperfluidTester { ); console.log("Revert with overflow."); - vm.expectRevert("SafeCast: value doesn't fit in 96 bits"); + vm.expectRevert(); // SafeCastOverflowedIntDowncast vestingScheduler.createVestingScheduleFromAmountAndDuration( superToken, bob, From 9fde69266c9395cecca55ed7772228be45722a74 Mon Sep 17 00:00:00 2001 From: didi Date: Tue, 5 Aug 2025 10:29:42 +0200 Subject: [PATCH 52/68] updated changelog --- packages/ethereum-contracts/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index 30ab0e7116..99623afbc4 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `ISuperfluidPool`: `getClaimable` and `getClaimableNow` could previously return non-zero values for connected pools, which was inconsistent with what `claimAll` would actually do in this situation (claim nothing). ### Breaking +- Updated OpenZeppelin library from v.4.9.6 to v5.4.0 - PoolMemberNFT pruning: `IPoolMemberNFT` and `PoolMemberNFT` removed, `POOL_MEMBER_NFT()` removed from `ISuperToken`. ## [v1.13.0] From aa88d2db394666f512326641aeff627b27a6139c Mon Sep 17 00:00:00 2001 From: didi Date: Wed, 6 Aug 2025 18:09:08 +0200 Subject: [PATCH 53/68] added openzeppelin-contracts submodule --- .gitmodules | 4 ++++ packages/ethereum-contracts/lib/openzeppelin-contracts | 1 + 2 files changed, 5 insertions(+) create mode 160000 packages/ethereum-contracts/lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index 888d42dcd9..f232d9c3ae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "packages/ethereum-contracts/lib/openzeppelin-contracts"] + path = packages/ethereum-contracts/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts.git + branch = release-v5.4 diff --git a/packages/ethereum-contracts/lib/openzeppelin-contracts b/packages/ethereum-contracts/lib/openzeppelin-contracts new file mode 160000 index 0000000000..c64a1edb67 --- /dev/null +++ b/packages/ethereum-contracts/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit c64a1edb67b6e3f4a15cca8909c9482ad33a02b0 From 33aac3ebbc551633fe029fb8c72611e12c019dc5 Mon Sep 17 00:00:00 2001 From: didi Date: Wed, 6 Aug 2025 18:09:49 +0200 Subject: [PATCH 54/68] hardhat config for lib in root --- packages/ethereum-contracts/hardhat.config.ts | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/packages/ethereum-contracts/hardhat.config.ts b/packages/ethereum-contracts/hardhat.config.ts index 80b4c605b1..3b39c73d3c 100644 --- a/packages/ethereum-contracts/hardhat.config.ts +++ b/packages/ethereum-contracts/hardhat.config.ts @@ -7,12 +7,13 @@ import "@nomiclabs/hardhat-ethers"; import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS, TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, + TASK_COMPILE_GET_REMAPPINGS, } from "hardhat/builtin-tasks/task-names"; import "solidity-coverage"; import {config as dotenvConfig} from "dotenv"; import {NetworkUserConfig} from "hardhat/types"; import "solidity-docgen"; -import {relative} from "path"; +import {relative, resolve} from "path"; try { dotenvConfig(); @@ -53,6 +54,33 @@ subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction( } ); +// New subtask to define remappings +subtask(TASK_COMPILE_GET_REMAPPINGS).setAction( + async (_, { config }, runSuper) => { + console.log("=== TASK_COMPILE_GET_REMAPPINGS START ==="); + + // Get default remappings (if any) + const remappings = await runSuper(); + + console.log("Type of remappings:", typeof remappings); + console.log("Remappings object:", remappings); + + // Add hardcoded remapping for OpenZeppelin + const openzeppelinPath = "../../../lib/openzeppelin-contracts/contracts"; + console.log("Adding OpenZeppelin remapping:", `@openzeppelin/contracts -> ${openzeppelinPath}`); + + // Return the remappings object with our custom mapping + const newRemappings = { + ...remappings, + "@openzeppelin/contracts/": "lib/openzeppelin-contracts/contracts/", + }; + + console.log("New remappings object:", newRemappings); + + return newRemappings; + } +); + const chainIds = { "eth-mainnet": 1, "eth-sepolia": 11155111, @@ -99,11 +127,15 @@ const config: HardhatUserConfig = { enabled: true, runs: 200, }, - evmVersion: "shanghai", + evmVersion: "cancun", }, }, paths: { - artifacts: "build/hardhat", + root: "../../", + sources: "packages/ethereum-contracts/contracts", + artifacts: "packages/ethereum-contracts/build/hardhat", + tests: "packages/ethereum-contracts/test", + cache: "packages/ethereum-contracts/cache", }, networks: { "bsc-mainnet": { @@ -173,7 +205,10 @@ const config: HardhatUserConfig = { ) : undefined, }, - typechain: {target: "ethers-v5"}, + typechain: { + outDir: "packages/ethereum-contracts/typechain-types", + target: "ethers-v5" + }, }; export default config; From 9bb6b92f67d29731ab42a99690d3c3e7c431dc58 Mon Sep 17 00:00:00 2001 From: didi Date: Wed, 6 Aug 2025 21:56:25 +0200 Subject: [PATCH 55/68] install openzeppelin-contracts a submodule instead of npm package --- .gitmodules | 4 +-- .../lib => lib}/openzeppelin-contracts | 0 package.json | 1 + .../autowrap/foundry.toml | 4 +-- .../autowrap/package.json | 1 - .../scheduler/foundry.toml | 4 +-- .../scheduler/package.json | 1 - packages/ethereum-contracts/foundry.toml | 4 +-- packages/ethereum-contracts/hardhat.config.ts | 36 ++----------------- packages/ethereum-contracts/package.json | 1 - packages/ethereum-contracts/truffle-config.js | 2 +- packages/hot-fuzz/foundry.toml | 4 +-- packages/hot-fuzz/package.json | 3 -- packages/solidity-semantic-money/foundry.toml | 2 +- packages/solidity-semantic-money/package.json | 3 -- yarn.lock | 5 --- 16 files changed, 15 insertions(+), 60 deletions(-) rename {packages/ethereum-contracts/lib => lib}/openzeppelin-contracts (100%) diff --git a/.gitmodules b/.gitmodules index f232d9c3ae..e1e7d6c2c5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,7 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "packages/ethereum-contracts/lib/openzeppelin-contracts"] - path = packages/ethereum-contracts/lib/openzeppelin-contracts +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts.git branch = release-v5.4 diff --git a/packages/ethereum-contracts/lib/openzeppelin-contracts b/lib/openzeppelin-contracts similarity index 100% rename from packages/ethereum-contracts/lib/openzeppelin-contracts rename to lib/openzeppelin-contracts diff --git a/package.json b/package.json index dc0dc21352..ca6dfaf142 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ }, "scripts": { "prepare": "husky && npm run git-submodule:init", + "postinstall": "ln -s ../../lib/openzeppelin-contracts/contracts node_modules/@openzeppelin/contracts", "lint": "run-s -l lint:*", "lint:syncpack": "syncpack lint", "lint:shellcheck": "tasks/shellcheck-all-tasks.sh", diff --git a/packages/automation-contracts/autowrap/foundry.toml b/packages/automation-contracts/autowrap/foundry.toml index 1f6a37e505..1d924b5a1a 100644 --- a/packages/automation-contracts/autowrap/foundry.toml +++ b/packages/automation-contracts/autowrap/foundry.toml @@ -3,13 +3,13 @@ root = '../../../' libs = ['lib'] src = 'packages/automation-contracts/autowrap' solc_version = "0.8.30" -evm_version = 'shanghai' +evm_version = 'cancun' optimizer = true optimizer_runs = 200 remappings = [ '@superfluid-finance/solidity-semantic-money/src/=packages/solidity-semantic-money/src/', '@superfluid-finance/ethereum-contracts/=packages/ethereum-contracts/', - '@openzeppelin/=node_modules/@openzeppelin/', + '@openzeppelin/=lib/openzeppelin-contracts/', 'ds-test/=lib/forge-std/lib/ds-test/src/', 'forge-std/=lib/forge-std/src/'] out = 'packages/automation-contracts/autowrap/out/default' diff --git a/packages/automation-contracts/autowrap/package.json b/packages/automation-contracts/autowrap/package.json index e922e195ab..aa2f8489d7 100644 --- a/packages/automation-contracts/autowrap/package.json +++ b/packages/automation-contracts/autowrap/package.json @@ -3,7 +3,6 @@ "description": "Open contracts that allow upgrading underlying token to supertokens based on running stream", "version": "0.3.0", "devDependencies": { - "@openzeppelin/contracts": "^5.4.0", "@superfluid-finance/ethereum-contracts": "^1.13.0", "@superfluid-finance/metadata": "^1.6.0" }, diff --git a/packages/automation-contracts/scheduler/foundry.toml b/packages/automation-contracts/scheduler/foundry.toml index 102964fdb5..3c03f037d7 100644 --- a/packages/automation-contracts/scheduler/foundry.toml +++ b/packages/automation-contracts/scheduler/foundry.toml @@ -3,13 +3,13 @@ root = '../../../' libs = ['lib'] src = 'packages/automation-contracts/scheduler' solc_version = "0.8.30" -evm_version = 'shanghai' +evm_version = 'cancun' optimizer = true optimizer_runs = 200 remappings = [ '@superfluid-finance/solidity-semantic-money/src/=packages/solidity-semantic-money/src/', '@superfluid-finance/ethereum-contracts/=packages/ethereum-contracts/', - '@openzeppelin/=node_modules/@openzeppelin/', + '@openzeppelin/=lib/openzeppelin-contracts/', 'ds-test/=lib/forge-std/lib/ds-test/src/', 'forge-std/=lib/forge-std/src/'] out = 'packages/automation-contracts/scheduler/out/default' diff --git a/packages/automation-contracts/scheduler/package.json b/packages/automation-contracts/scheduler/package.json index 5bdd02278a..2d61476077 100644 --- a/packages/automation-contracts/scheduler/package.json +++ b/packages/automation-contracts/scheduler/package.json @@ -3,7 +3,6 @@ "description": "Open contracts that allow scheduling streams and vestings onchain", "version": "1.3.0", "devDependencies": { - "@openzeppelin/contracts": "^5.4.0", "@superfluid-finance/ethereum-contracts": "^1.13.0", "@superfluid-finance/metadata": "^1.6.0" }, diff --git a/packages/ethereum-contracts/foundry.toml b/packages/ethereum-contracts/foundry.toml index cc999464ae..c9d600cf79 100644 --- a/packages/ethereum-contracts/foundry.toml +++ b/packages/ethereum-contracts/foundry.toml @@ -8,13 +8,13 @@ ignored_error_codes = [ 1699 # assembly { selfdestruct } in contracts/mocks/SuperfluidDestructorMock.sol ] # keep in sync with truffle-config.js -evm_version = 'shanghai' +evm_version = 'cancun' optimizer = true optimizer_runs = 200 remappings = [ '@superfluid-finance/ethereum-contracts/contracts/=packages/ethereum-contracts/contracts/', '@superfluid-finance/solidity-semantic-money/src/=packages/solidity-semantic-money/src/', - '@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/', + '@openzeppelin/=lib/openzeppelin-contracts/', 'ds-test/=lib/forge-std/lib/ds-test/src/', 'forge-std/=lib/forge-std/src/'] out = 'packages/ethereum-contracts/build/foundry/default' diff --git a/packages/ethereum-contracts/hardhat.config.ts b/packages/ethereum-contracts/hardhat.config.ts index 3b39c73d3c..17f753646d 100644 --- a/packages/ethereum-contracts/hardhat.config.ts +++ b/packages/ethereum-contracts/hardhat.config.ts @@ -13,7 +13,7 @@ import "solidity-coverage"; import {config as dotenvConfig} from "dotenv"; import {NetworkUserConfig} from "hardhat/types"; import "solidity-docgen"; -import {relative, resolve} from "path"; +import {relative} from "path"; try { dotenvConfig(); @@ -54,33 +54,6 @@ subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction( } ); -// New subtask to define remappings -subtask(TASK_COMPILE_GET_REMAPPINGS).setAction( - async (_, { config }, runSuper) => { - console.log("=== TASK_COMPILE_GET_REMAPPINGS START ==="); - - // Get default remappings (if any) - const remappings = await runSuper(); - - console.log("Type of remappings:", typeof remappings); - console.log("Remappings object:", remappings); - - // Add hardcoded remapping for OpenZeppelin - const openzeppelinPath = "../../../lib/openzeppelin-contracts/contracts"; - console.log("Adding OpenZeppelin remapping:", `@openzeppelin/contracts -> ${openzeppelinPath}`); - - // Return the remappings object with our custom mapping - const newRemappings = { - ...remappings, - "@openzeppelin/contracts/": "lib/openzeppelin-contracts/contracts/", - }; - - console.log("New remappings object:", newRemappings); - - return newRemappings; - } -); - const chainIds = { "eth-mainnet": 1, "eth-sepolia": 11155111, @@ -131,11 +104,7 @@ const config: HardhatUserConfig = { }, }, paths: { - root: "../../", - sources: "packages/ethereum-contracts/contracts", - artifacts: "packages/ethereum-contracts/build/hardhat", - tests: "packages/ethereum-contracts/test", - cache: "packages/ethereum-contracts/cache", + artifacts: "build/hardhat", }, networks: { "bsc-mainnet": { @@ -206,7 +175,6 @@ const config: HardhatUserConfig = { : undefined, }, typechain: { - outDir: "packages/ethereum-contracts/typechain-types", target: "ethers-v5" }, }; diff --git a/packages/ethereum-contracts/package.json b/packages/ethereum-contracts/package.json index be34aff56f..292013ced5 100644 --- a/packages/ethereum-contracts/package.json +++ b/packages/ethereum-contracts/package.json @@ -5,7 +5,6 @@ "dependencies": { "@decentral.ee/web3-helpers": "0.5.3", "@nomiclabs/hardhat-ethers": "2.2.3", - "@openzeppelin/contracts": "5.4.0", "@truffle/contract": "4.6.31", "ethereumjs-tx": "2.1.2", "ethereumjs-util": "7.1.5", diff --git a/packages/ethereum-contracts/truffle-config.js b/packages/ethereum-contracts/truffle-config.js index aaadfb4f1b..c439111780 100644 --- a/packages/ethereum-contracts/truffle-config.js +++ b/packages/ethereum-contracts/truffle-config.js @@ -389,7 +389,7 @@ const E = (module.exports = { runs: 200, }, // see https://docs.soliditylang.org/en/latest/using-the-compiler.html#target-options - evmVersion: "shanghai", + evmVersion: "cancun", }, }, }, diff --git a/packages/hot-fuzz/foundry.toml b/packages/hot-fuzz/foundry.toml index eb05ebf501..ff15f271c1 100644 --- a/packages/hot-fuzz/foundry.toml +++ b/packages/hot-fuzz/foundry.toml @@ -2,13 +2,13 @@ root = '../..' src = 'packages/hot-fuzz/contracts' solc_version = "0.8.30" -evm_version = 'shanghai' +evm_version = 'cancun' optimizer = true optimizer_runs = 200 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/', + '@openzeppelin/=lib/openzeppelin-contracts/', 'ds-test/=lib/forge-std/lib/ds-test/src/', 'forge-std/=lib/forge-std/src/'] out = 'packages/hot-fuzz/build/foundry/out' diff --git a/packages/hot-fuzz/package.json b/packages/hot-fuzz/package.json index a938cf98a6..7037b37cbd 100644 --- a/packages/hot-fuzz/package.json +++ b/packages/hot-fuzz/package.json @@ -6,9 +6,6 @@ "hot-fuzz": "./hot-fuzz" }, "bugs": "https://github.com/superfluid-finance/protocol-monorepo/issues", - "dependencies": { - "@openzeppelin/contracts": "5.4.0" - }, "devDependencies": { "@superfluid-finance/ethereum-contracts": "^1.13.0" }, diff --git a/packages/solidity-semantic-money/foundry.toml b/packages/solidity-semantic-money/foundry.toml index 9313afd96e..a9efc578bc 100644 --- a/packages/solidity-semantic-money/foundry.toml +++ b/packages/solidity-semantic-money/foundry.toml @@ -11,7 +11,7 @@ optimizer_runs = 200 via_ir = false remappings = [ '@superfluid-finance/solidity-semantic-money/src/=packages/solidity-semantic-money/src/', - '@openzeppelin/=node_modules/@openzeppelin/', + '@openzeppelin/=lib/openzeppelin-contracts/', 'ds-test/=lib/forge-std/lib/ds-test/src/', 'forge-std/=lib/forge-std/src/'] diff --git a/packages/solidity-semantic-money/package.json b/packages/solidity-semantic-money/package.json index 97c4255c2b..e7298a9834 100644 --- a/packages/solidity-semantic-money/package.json +++ b/packages/solidity-semantic-money/package.json @@ -3,9 +3,6 @@ "description": "Semantic money implementation in solidity.", "version": "0.1.0", "bugs": "https://github.com/superfluid-finance/protocol-monorepo/issues", - "dependencies": { - "@openzeppelin/contracts": "5.4.0" - }, "directories": { "src": "src", "test": "test" diff --git a/yarn.lock b/yarn.lock index 77bc28dc41..0feed6c052 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3032,11 +3032,6 @@ find-up "^4.1.0" fs-extra "^8.1.0" -"@openzeppelin/contracts@5.4.0", "@openzeppelin/contracts@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.4.0.tgz#177594bdb2d86c71f5d1052fe40cb4edb95fb20f" - integrity sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A== - "@openzeppelin/test-helpers@^0.5.16": version "0.5.16" resolved "https://registry.yarnpkg.com/@openzeppelin/test-helpers/-/test-helpers-0.5.16.tgz#2c9054f85069dfbfb5e8cef3ed781e8caf241fb3" From 116fe416ec09a6dee3207a5fe6591e50d8de3dfd Mon Sep 17 00:00:00 2001 From: didi Date: Wed, 6 Aug 2025 22:13:29 +0200 Subject: [PATCH 56/68] change path to openzeppelin-v5 --- .../contracts/agreements/AgreementLibrary.sol | 2 +- .../agreements/ConstantFlowAgreementV1.sol | 2 +- .../agreements/InstantDistributionAgreementV1.sol | 2 +- .../agreements/gdav1/GDAv1StorageLayout.sol | 2 +- .../gdav1/GeneralDistributionAgreementV1.sol | 2 +- .../contracts/agreements/gdav1/PoolAdminNFT.sol | 2 +- .../contracts/agreements/gdav1/PoolNFTBase.sol | 4 ++-- .../contracts/agreements/gdav1/SuperfluidPool.sol | 4 ++-- .../gdav1/SuperfluidPoolDeployerLibrary.sol | 2 +- .../contracts/gov/SuperfluidGovernanceII.sol | 2 +- .../interfaces/agreements/gdav1/IPoolNFTBase.sol | 2 +- .../agreements/gdav1/ISuperfluidPool.sol | 2 +- .../interfaces/superfluid/ISuperToken.sol | 8 ++++---- .../interfaces/superfluid/ISuperTokenFactory.sol | 2 +- .../interfaces/superfluid/ISuperfluid.sol | 6 +++--- .../contracts/libs/ERC777Helper.sol | 2 +- .../contracts/mocks/AgreementMock.t.sol | 2 +- .../mocks/ERC777SenderRecipientMock.t.sol | 14 +++++++------- .../contracts/mocks/SuperTokenMock.t.sol | 2 +- .../superfluid/FullUpgradableSuperTokenProxy.sol | 2 +- .../contracts/superfluid/SuperToken.sol | 10 +++++----- .../contracts/superfluid/SuperTokenFactory.sol | 4 ++-- .../contracts/superfluid/Superfluid.sol | 2 +- .../contracts/superfluid/SuperfluidToken.sol | 4 ++-- .../contracts/tokens/PureSuperToken.sol | 2 +- .../upgradability/SuperfluidUpgradeableBeacon.sol | 2 +- .../contracts/upgradability/UUPSProxy.sol | 2 +- .../contracts/utils/BatchLiquidator.sol | 2 +- .../contracts/utils/ERC2771Forwarder.sol | 2 +- .../contracts/utils/Resolver.sol | 2 +- .../contracts/utils/SimpleACL.sol | 2 +- .../contracts/utils/SimpleForwarder.sol | 2 +- .../contracts/utils/SuperUpgrader.sol | 4 ++-- .../utils/SuperfluidFrameworkDeployer.t.sol | 4 ++-- .../ethereum-contracts/contracts/utils/TOGA.sol | 6 +++--- .../contracts/utils/TestGovernance.sol | 2 +- .../contracts/utils/TestToken.sol | 2 +- packages/ethereum-contracts/foundry.toml | 2 +- .../test/foundry/FoundrySuperfluidTester.t.sol | 4 ++-- .../gdav1/GeneralDistributionAgreement.t.sol | 6 +++--- .../GeneralDistributionAgreementV1.prop.t.sol | 2 +- .../test/foundry/superfluid/ERC721.t.sol | 2 +- .../test/foundry/superfluid/PoolAdminNFT.t.sol | 6 +++--- .../test/foundry/superfluid/PoolNFTBase.t.sol | 8 ++++---- .../test/foundry/superfluid/PoolNFTMock.t.sol | 2 +- .../foundry/superfluid/Superfluid.BatchCall.t.sol | 2 +- .../SuperfluidUpgradeableBeacon.t.sol | 2 +- .../test/foundry/utils/TOGA.t.sol | 4 ++-- 48 files changed, 80 insertions(+), 80 deletions(-) diff --git a/packages/ethereum-contracts/contracts/agreements/AgreementLibrary.sol b/packages/ethereum-contracts/contracts/agreements/AgreementLibrary.sol index 6d7324c214..22d4e66ddb 100644 --- a/packages/ethereum-contracts/contracts/agreements/AgreementLibrary.sol +++ b/packages/ethereum-contracts/contracts/agreements/AgreementLibrary.sol @@ -9,7 +9,7 @@ import { } from "../interfaces/superfluid/ISuperfluid.sol"; import { ISuperfluidToken } from "../interfaces/superfluid/ISuperfluidToken.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; /** diff --git a/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol index bf225b729b..ec7a840200 100644 --- a/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol @@ -13,7 +13,7 @@ import { SuperfluidGovernanceConfigs } from "../interfaces/superfluid/ISuperfluid.sol"; import { AgreementBase } from "./AgreementBase.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; import { AgreementLibrary } from "./AgreementLibrary.sol"; import { SolvencyHelperLibrary } from "../libs/SolvencyHelperLibrary.sol"; diff --git a/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol index 701852a2c4..1481e1d8f1 100644 --- a/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; import { IInstantDistributionAgreementV1, ISuperfluidToken diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol index ea768eb77c..7e4ba559d7 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GDAv1StorageLayout.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; // open-zeppelin -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; // semantic-money import { BasicParticle, diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index 08b23cde33..c79357eea3 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -2,7 +2,7 @@ // solhint-disable not-rely-on-time pragma solidity ^0.8.23; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; import { ISuperfluid, ISuperfluidGovernance, IAccessControl } from "../../interfaces/superfluid/ISuperfluid.sol"; import { diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/PoolAdminNFT.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolAdminNFT.sol index f75fdad8cf..a93c3b61ea 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/PoolAdminNFT.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolAdminNFT.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import { IERC721Metadata } from "@openzeppelin-v5/contracts/token/ERC721/extensions/IERC721Metadata.sol"; import { IPoolAdminNFT } from "../../interfaces/agreements/gdav1/IPoolAdminNFT.sol"; import { PoolNFTBase } from "./PoolNFTBase.sol"; import { IGeneralDistributionAgreementV1, ISuperfluid } from "../../interfaces/superfluid/ISuperfluid.sol"; diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol index 098d72ba43..ee431389a4 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/PoolNFTBase.sol @@ -6,8 +6,8 @@ pragma solidity ^0.8.23; // Notes: We use these interfaces in natspec documentation below, grep @inheritdoc // solhint-disable-next-line no-unused-import -import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { IERC165 } from "@openzeppelin-v5/contracts/interfaces/IERC165.sol"; +import { IERC721 } from "@openzeppelin-v5/contracts/token/ERC721/IERC721.sol"; import { UUPSProxiable } from "../../upgradability/UUPSProxiable.sol"; import { IGeneralDistributionAgreementV1, ISuperfluid } from "../../interfaces/superfluid/ISuperfluid.sol"; import { ISuperTokenFactory } from "../../interfaces/superfluid/ISuperTokenFactory.sol"; diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol index 83234be488..1a12c5edb6 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.23; // Notes: We use these interfaces in natspec documentation below, grep @inheritdoc // solhint-disable-next-line no-unused-import -import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { IERC20, IERC20Metadata } from "@openzeppelin-v5/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; import { BasicParticle, SemanticMoney, diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol index a8025e8269..486c031c90 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPoolDeployerLibrary.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; +import { BeaconProxy } from "@openzeppelin-v5/contracts/proxy/beacon/BeaconProxy.sol"; import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol"; import { SuperfluidPool } from "./SuperfluidPool.sol"; import { PoolConfig, PoolERC20Metadata } from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; diff --git a/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceII.sol b/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceII.sol index 9dd27942d2..f34badd014 100644 --- a/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceII.sol +++ b/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceII.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; import { UUPSProxy } from "../upgradability/UUPSProxy.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Ownable } from "@openzeppelin-v5/contracts/access/Ownable.sol"; import { UUPSProxiable } from "../upgradability/UUPSProxiable.sol"; import { SuperfluidGovernanceBase } from "./SuperfluidGovernanceBase.sol"; import { ISuperfluid } from "../interfaces/superfluid/ISuperfluid.sol"; diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol index 54230b61e8..5d17b00429 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity >=0.8.4; -import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import { IERC721Metadata } from "@openzeppelin-v5/contracts/token/ERC721/extensions/IERC721Metadata.sol"; interface IPoolNFTBase is IERC721Metadata { error POOL_NFT_APPROVE_TO_CALLER(); // 0x9212b333 diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol index b36e1f633f..9c58986c26 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/gdav1/ISuperfluidPool.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity >=0.8.4; -import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { IERC20, IERC20Metadata } from "@openzeppelin-v5/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { ISuperfluidToken } from "../../superfluid/ISuperfluidToken.sol"; /** diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol index 60ee321921..267aec9a8e 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol @@ -2,10 +2,10 @@ pragma solidity >= 0.8.11; import { ISuperfluidToken } from "./ISuperfluidToken.sol"; -import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; -import { IERC5267 } from "@openzeppelin/contracts/interfaces/IERC5267.sol"; -import { IERC777 } from "@openzeppelin/contracts/interfaces/IERC777.sol"; +import { IERC20, IERC20Metadata } from "@openzeppelin-v5/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { IERC20Permit } from "@openzeppelin-v5/contracts/token/ERC20/extensions/IERC20Permit.sol"; +import { IERC5267 } from "@openzeppelin-v5/contracts/interfaces/IERC5267.sol"; +import { IERC777 } from "@openzeppelin-v5/contracts/interfaces/IERC777.sol"; import { IPoolAdminNFT } from "../agreements/gdav1/IPoolAdminNFT.sol"; /** diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol index 9796eb6240..04034b584c 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >= 0.8.11; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { IERC20Metadata } from "@openzeppelin-v5/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { ISuperToken } from "./ISuperToken.sol"; /** * @title Super token factory interface diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol index 6d6c915cd9..a68f25be7c 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol @@ -16,9 +16,9 @@ import { } from "./Definitions.sol"; /// Super token related interfaces: /// Note: CustomSuperTokenBase is not included for people building CustomSuperToken. -import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { IERC777 } from "@openzeppelin/contracts/interfaces/IERC777.sol"; -import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; +import { IERC20, IERC20Metadata } from "@openzeppelin-v5/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { IERC777 } from "@openzeppelin-v5/contracts/interfaces/IERC777.sol"; +import { IAccessControl } from "@openzeppelin-v5/contracts/access/IAccessControl.sol"; import { ISuperfluidToken } from "./ISuperfluidToken.sol"; import { ISuperToken } from "./ISuperToken.sol"; import { ISuperTokenFactory } from "./ISuperTokenFactory.sol"; diff --git a/packages/ethereum-contracts/contracts/libs/ERC777Helper.sol b/packages/ethereum-contracts/contracts/libs/ERC777Helper.sol index 5aa1fccef8..50175fc3fc 100644 --- a/packages/ethereum-contracts/contracts/libs/ERC777Helper.sol +++ b/packages/ethereum-contracts/contracts/libs/ERC777Helper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { IERC1820Registry } from "@openzeppelin/contracts/interfaces/IERC1820Registry.sol"; +import { IERC1820Registry } from "@openzeppelin-v5/contracts/interfaces/IERC1820Registry.sol"; /** * @title ERC777 helper library diff --git a/packages/ethereum-contracts/contracts/mocks/AgreementMock.t.sol b/packages/ethereum-contracts/contracts/mocks/AgreementMock.t.sol index bcfac48017..241dc0a77e 100644 --- a/packages/ethereum-contracts/contracts/mocks/AgreementMock.t.sol +++ b/packages/ethereum-contracts/contracts/mocks/AgreementMock.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; import { ISuperfluid, diff --git a/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol b/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol index 7d71aac494..29602e77ce 100644 --- a/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol +++ b/packages/ethereum-contracts/contracts/mocks/ERC777SenderRecipientMock.t.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { Context } from "@openzeppelin/contracts/utils/Context.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC777 } from "@openzeppelin/contracts/interfaces/IERC777.sol"; -import { IERC777Sender } from "@openzeppelin/contracts/interfaces/IERC777Sender.sol"; -import { IERC777Recipient } from "@openzeppelin/contracts/interfaces/IERC777Recipient.sol"; -import { IERC1820Registry } from "@openzeppelin/contracts/interfaces/IERC1820Registry.sol"; -import { IERC1820Implementer } from "@openzeppelin/contracts/interfaces/IERC1820Implementer.sol"; +import { Context } from "@openzeppelin-v5/contracts/utils/Context.sol"; +import { IERC20 } from "@openzeppelin-v5/contracts/token/ERC20/IERC20.sol"; +import { IERC777 } from "@openzeppelin-v5/contracts/interfaces/IERC777.sol"; +import { IERC777Sender } from "@openzeppelin-v5/contracts/interfaces/IERC777Sender.sol"; +import { IERC777Recipient } from "@openzeppelin-v5/contracts/interfaces/IERC777Recipient.sol"; +import { IERC1820Registry } from "@openzeppelin-v5/contracts/interfaces/IERC1820Registry.sol"; +import { IERC1820Implementer } from "@openzeppelin-v5/contracts/interfaces/IERC1820Implementer.sol"; import { ISuperToken } from "../superfluid/SuperToken.sol"; diff --git a/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.t.sol b/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.t.sol index d511f2dc8a..166929543f 100644 --- a/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.t.sol +++ b/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { SafeERC20 } from "@openzeppelin-v5/contracts/token/ERC20/utils/SafeERC20.sol"; import { ISuperfluid, IERC20, IPoolAdminNFT diff --git a/packages/ethereum-contracts/contracts/superfluid/FullUpgradableSuperTokenProxy.sol b/packages/ethereum-contracts/contracts/superfluid/FullUpgradableSuperTokenProxy.sol index bb3c345c79..27db1af84a 100644 --- a/packages/ethereum-contracts/contracts/superfluid/FullUpgradableSuperTokenProxy.sol +++ b/packages/ethereum-contracts/contracts/superfluid/FullUpgradableSuperTokenProxy.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; import { ISuperTokenFactory } from "../interfaces/superfluid/ISuperTokenFactory.sol"; -import { Proxy } from "@openzeppelin/contracts/proxy/Proxy.sol"; +import { Proxy } from "@openzeppelin-v5/contracts/proxy/Proxy.sol"; /** diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol index 443a77cdd6..fa9d4af9ad 100644 --- a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol +++ b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol @@ -13,11 +13,11 @@ import { } from "../interfaces/superfluid/ISuperfluid.sol"; import { SuperfluidToken } from "./SuperfluidToken.sol"; import { ERC777Helper } from "../libs/ERC777Helper.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { IERC777Recipient } from "@openzeppelin/contracts/interfaces/IERC777Recipient.sol"; -import { IERC777Sender } from "@openzeppelin/contracts/interfaces/IERC777Sender.sol"; -import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { SafeERC20 } from "@openzeppelin-v5/contracts/token/ERC20/utils/SafeERC20.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; +import { IERC777Recipient } from "@openzeppelin-v5/contracts/interfaces/IERC777Recipient.sol"; +import { IERC777Sender } from "@openzeppelin-v5/contracts/interfaces/IERC777Sender.sol"; +import { ECDSA } from "@openzeppelin-v5/contracts/utils/cryptography/ECDSA.sol"; // placeholder type needed as an intermediate step before complete removal diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol b/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol index 943ca4c56b..94d2ba8833 100644 --- a/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol +++ b/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IERC20Metadata } from "@openzeppelin-v5/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { Ownable } from "@openzeppelin-v5/contracts/access/Ownable.sol"; import { ISuperTokenFactory, ISuperToken diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol index 7df746f332..cbd88198b6 100644 --- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol +++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; import { UUPSProxiable } from "../upgradability/UUPSProxiable.sol"; import { UUPSProxy } from "../upgradability/UUPSProxy.sol"; diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol b/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol index cb5218676e..c75fc48f69 100644 --- a/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol +++ b/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { IERC20 } from "@openzeppelin-v5/contracts/token/ERC20/IERC20.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; import { ISuperfluid, ISuperAgreement, diff --git a/packages/ethereum-contracts/contracts/tokens/PureSuperToken.sol b/packages/ethereum-contracts/contracts/tokens/PureSuperToken.sol index 0deeb3dcff..d0466d4006 100644 --- a/packages/ethereum-contracts/contracts/tokens/PureSuperToken.sol +++ b/packages/ethereum-contracts/contracts/tokens/PureSuperToken.sol @@ -8,7 +8,7 @@ import { from "../interfaces/superfluid/CustomSuperTokenBase.sol"; import { IPureSuperTokenCustom } from "../interfaces/tokens/IPureSuperToken.sol"; import { UUPSProxy } from "../upgradability/UUPSProxy.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin-v5/contracts/token/ERC20/IERC20.sol"; /** diff --git a/packages/ethereum-contracts/contracts/upgradability/SuperfluidUpgradeableBeacon.sol b/packages/ethereum-contracts/contracts/upgradability/SuperfluidUpgradeableBeacon.sol index 2b93d1171b..9860d3a106 100644 --- a/packages/ethereum-contracts/contracts/upgradability/SuperfluidUpgradeableBeacon.sol +++ b/packages/ethereum-contracts/contracts/upgradability/SuperfluidUpgradeableBeacon.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.23; import { UpgradeableBeacon -} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; +} from "@openzeppelin-v5/contracts/proxy/beacon/UpgradeableBeacon.sol"; import { BeaconProxiable } from "./BeaconProxiable.sol"; contract SuperfluidUpgradeableBeacon is UpgradeableBeacon { diff --git a/packages/ethereum-contracts/contracts/upgradability/UUPSProxy.sol b/packages/ethereum-contracts/contracts/upgradability/UUPSProxy.sol index 2ea182881e..d28793be17 100644 --- a/packages/ethereum-contracts/contracts/upgradability/UUPSProxy.sol +++ b/packages/ethereum-contracts/contracts/upgradability/UUPSProxy.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; import { UUPSUtils } from "./UUPSUtils.sol"; -import { Proxy } from "@openzeppelin/contracts/proxy/Proxy.sol"; +import { Proxy } from "@openzeppelin-v5/contracts/proxy/Proxy.sol"; /** diff --git a/packages/ethereum-contracts/contracts/utils/BatchLiquidator.sol b/packages/ethereum-contracts/contracts/utils/BatchLiquidator.sol index d8d2ccd01e..04ff928a85 100644 --- a/packages/ethereum-contracts/contracts/utils/BatchLiquidator.sol +++ b/packages/ethereum-contracts/contracts/utils/BatchLiquidator.sol @@ -5,7 +5,7 @@ import { ISuperfluid, ISuperAgreement, ISuperToken, ISuperfluidPool, IConstantFlowAgreementV1, IGeneralDistributionAgreementV1 } from "../interfaces/superfluid/ISuperfluid.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { ERC20 } from "@openzeppelin-v5/contracts/token/ERC20/ERC20.sol"; /** * @title Batch liquidator contract diff --git a/packages/ethereum-contracts/contracts/utils/ERC2771Forwarder.sol b/packages/ethereum-contracts/contracts/utils/ERC2771Forwarder.sol index 33784dee41..f0b8fe9bdd 100644 --- a/packages/ethereum-contracts/contracts/utils/ERC2771Forwarder.sol +++ b/packages/ethereum-contracts/contracts/utils/ERC2771Forwarder.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Ownable } from "@openzeppelin-v5/contracts/access/Ownable.sol"; /** * @title Forwards calls preserving the original msg.sender according to ERC-2771 diff --git a/packages/ethereum-contracts/contracts/utils/Resolver.sol b/packages/ethereum-contracts/contracts/utils/Resolver.sol index 9f528363eb..63416e7dbe 100644 --- a/packages/ethereum-contracts/contracts/utils/Resolver.sol +++ b/packages/ethereum-contracts/contracts/utils/Resolver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.11; -import { AccessControlEnumerable } from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol"; +import { AccessControlEnumerable } from "@openzeppelin-v5/contracts/access/extensions/AccessControlEnumerable.sol"; import { IResolver } from "../interfaces/utils/IResolver.sol"; diff --git a/packages/ethereum-contracts/contracts/utils/SimpleACL.sol b/packages/ethereum-contracts/contracts/utils/SimpleACL.sol index 36fe30eb7d..3ea1568989 100644 --- a/packages/ethereum-contracts/contracts/utils/SimpleACL.sol +++ b/packages/ethereum-contracts/contracts/utils/SimpleACL.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; +import { AccessControl } from "@openzeppelin-v5/contracts/access/AccessControl.sol"; contract SimpleACL is AccessControl { constructor() { diff --git a/packages/ethereum-contracts/contracts/utils/SimpleForwarder.sol b/packages/ethereum-contracts/contracts/utils/SimpleForwarder.sol index 39d01d1068..6b6cde8e50 100644 --- a/packages/ethereum-contracts/contracts/utils/SimpleForwarder.sol +++ b/packages/ethereum-contracts/contracts/utils/SimpleForwarder.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Ownable } from "@openzeppelin-v5/contracts/access/Ownable.sol"; /** * @title Forwards arbitrary calls diff --git a/packages/ethereum-contracts/contracts/utils/SuperUpgrader.sol b/packages/ethereum-contracts/contracts/utils/SuperUpgrader.sol index 7981912107..3398369daa 100644 --- a/packages/ethereum-contracts/contracts/utils/SuperUpgrader.sol +++ b/packages/ethereum-contracts/contracts/utils/SuperUpgrader.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { AccessControlEnumerable } from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { AccessControlEnumerable } from "@openzeppelin-v5/contracts/access/extensions/AccessControlEnumerable.sol"; +import { SafeERC20 } from "@openzeppelin-v5/contracts/token/ERC20/utils/SafeERC20.sol"; import { ISuperToken, IERC20 diff --git a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.t.sol b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.t.sol index ce98d91d71..4fe858cd10 100644 --- a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.t.sol +++ b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.11; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20Metadata } from "@openzeppelin-v5/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { IERC20 } from "@openzeppelin-v5/contracts/token/ERC20/IERC20.sol"; import { SuperfluidFrameworkDeploymentSteps, TokenDeployerLibrary } from "./SuperfluidFrameworkDeploymentSteps.t.sol"; import { ISuperTokenFactory } from "../superfluid/SuperTokenFactory.sol"; import { SuperToken } from "../superfluid/SuperToken.sol"; diff --git a/packages/ethereum-contracts/contracts/utils/TOGA.sol b/packages/ethereum-contracts/contracts/utils/TOGA.sol index a19cf02c89..c83755e549 100644 --- a/packages/ethereum-contracts/contracts/utils/TOGA.sol +++ b/packages/ethereum-contracts/contracts/utils/TOGA.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; import { ISuperfluid, ISuperToken, IConstantFlowAgreementV1 } from "../interfaces/superfluid/ISuperfluid.sol"; -import { IERC1820Registry } from "@openzeppelin/contracts/interfaces/IERC1820Registry.sol"; -import { IERC777Recipient } from "@openzeppelin/contracts/interfaces/IERC777Recipient.sol"; +import { IERC1820Registry } from "@openzeppelin-v5/contracts/interfaces/IERC1820Registry.sol"; +import { IERC777Recipient } from "@openzeppelin-v5/contracts/interfaces/IERC777Recipient.sol"; /** * @title TOGA: Transparent Ongoing Auction diff --git a/packages/ethereum-contracts/contracts/utils/TestGovernance.sol b/packages/ethereum-contracts/contracts/utils/TestGovernance.sol index 7efa2f1c95..20fdf38d63 100644 --- a/packages/ethereum-contracts/contracts/utils/TestGovernance.sol +++ b/packages/ethereum-contracts/contracts/utils/TestGovernance.sol @@ -7,7 +7,7 @@ import { } from "../interfaces/superfluid/ISuperfluid.sol"; import { SuperfluidGovernanceBase } from "../gov/SuperfluidGovernanceBase.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Ownable } from "@openzeppelin-v5/contracts/access/Ownable.sol"; /** diff --git a/packages/ethereum-contracts/contracts/utils/TestToken.sol b/packages/ethereum-contracts/contracts/utils/TestToken.sol index cea59f442f..55fe727b8c 100644 --- a/packages/ethereum-contracts/contracts/utils/TestToken.sol +++ b/packages/ethereum-contracts/contracts/utils/TestToken.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.11; -import { ERC20, ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +import { ERC20, ERC20Permit } from "@openzeppelin-v5/contracts/token/ERC20/extensions/ERC20Permit.sol"; /** * @title Test token contract diff --git a/packages/ethereum-contracts/foundry.toml b/packages/ethereum-contracts/foundry.toml index c9d600cf79..350cd10e1c 100644 --- a/packages/ethereum-contracts/foundry.toml +++ b/packages/ethereum-contracts/foundry.toml @@ -14,7 +14,7 @@ optimizer_runs = 200 remappings = [ '@superfluid-finance/ethereum-contracts/contracts/=packages/ethereum-contracts/contracts/', '@superfluid-finance/solidity-semantic-money/src/=packages/solidity-semantic-money/src/', - '@openzeppelin/=lib/openzeppelin-contracts/', + '@openzeppelin-v5/=lib/openzeppelin-contracts/', 'ds-test/=lib/forge-std/lib/ds-test/src/', 'forge-std/=lib/forge-std/src/'] out = 'packages/ethereum-contracts/build/foundry/default' diff --git a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol index e680cd1748..06f0a346f2 100644 --- a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol +++ b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.t.sol @@ -3,8 +3,8 @@ pragma solidity >=0.8.11; import "forge-std/Test.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; +import { EnumerableSet } from "@openzeppelin-v5/contracts/utils/structs/EnumerableSet.sol"; import { ERC1820RegistryCompiled } from "../../contracts/libs/ERC1820RegistryCompiled.sol"; import { SuperfluidFrameworkDeployer } from "../../contracts/utils/SuperfluidFrameworkDeployer.t.sol"; import { Superfluid } from "../../contracts/superfluid/Superfluid.sol"; 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 0c3050b781..b79dc04a4c 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; +import { EnumerableSet } from "@openzeppelin-v5/contracts/utils/structs/EnumerableSet.sol"; +import { SafeCast } from "@openzeppelin-v5/contracts/utils/math/SafeCast.sol"; +import { IAccessControl } from "@openzeppelin-v5/contracts/access/IAccessControl.sol"; import "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol"; import "../../FoundrySuperfluidTester.t.sol"; import { diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol index cfef124724..b29f40b8d2 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; import "forge-std/Test.sol"; -import { IBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; +import { IBeacon } from "@openzeppelin-v5/contracts/proxy/beacon/UpgradeableBeacon.sol"; import "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol"; import { ERC1820RegistryCompiled } from "../../../../contracts/libs/ERC1820RegistryCompiled.sol"; diff --git a/packages/ethereum-contracts/test/foundry/superfluid/ERC721.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/ERC721.t.sol index d59e9756cb..7fd88ddcd2 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/ERC721.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/ERC721.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { IERC721Metadata } from "@openzeppelin/contracts/interfaces/IERC721Metadata.sol"; +import { IERC721Metadata } from "@openzeppelin-v5/contracts/interfaces/IERC721Metadata.sol"; import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.t.sol"; import { PoolAdminNFTMock } from "./PoolNFTMock.t.sol"; import { TestToken } from "../../../contracts/utils/TestToken.sol"; diff --git a/packages/ethereum-contracts/test/foundry/superfluid/PoolAdminNFT.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/PoolAdminNFT.t.sol index 14df4b792a..fa408d06ec 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/PoolAdminNFT.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/PoolAdminNFT.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; -import { IERC721, IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { IERC165 } from "@openzeppelin-v5/contracts/interfaces/IERC165.sol"; +import { IERC721, IERC721Metadata } from "@openzeppelin-v5/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import { Strings } from "@openzeppelin-v5/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"; diff --git a/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTBase.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTBase.t.sol index cf12d0bf64..db2f8bb02e 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTBase.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTBase.t.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.23; -import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; -import { IERC721 } from "@openzeppelin/contracts/interfaces/IERC721.sol"; -import { IERC721Metadata } from "@openzeppelin/contracts/interfaces/IERC721Metadata.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { IERC165 } from "@openzeppelin-v5/contracts/interfaces/IERC165.sol"; +import { IERC721 } from "@openzeppelin-v5/contracts/interfaces/IERC721.sol"; +import { IERC721Metadata } from "@openzeppelin-v5/contracts/interfaces/IERC721Metadata.sol"; +import { Strings } from "@openzeppelin-v5/contracts/utils/Strings.sol"; import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol"; import { PoolNFTBaseStorageLayoutMock, diff --git a/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTMock.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTMock.t.sol index b58f105d03..6d1ac8b3b2 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTMock.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/PoolNFTMock.t.sol @@ -2,7 +2,7 @@ // solhint-disable reason-string pragma solidity ^0.8.23; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { Strings } from "@openzeppelin-v5/contracts/utils/Strings.sol"; import { IGeneralDistributionAgreementV1, ISuperfluid diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol index 43822e6437..e270d2c572 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol @@ -12,7 +12,7 @@ import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library import { SuperAppMock } from "../../../contracts/mocks/SuperAppMocks.t.sol"; import { SimpleForwarder } from "../../../contracts/utils/SimpleForwarder.sol"; import { ERC2771Forwarder } from "../../../contracts/utils/ERC2771Forwarder.sol"; -import { Ownable } from '@openzeppelin/contracts/access/Ownable.sol'; +import { Ownable } from '@openzeppelin-v5/contracts/access/Ownable.sol'; import { BaseRelayRecipient } from "../../../contracts/libs/BaseRelayRecipient.sol"; // A mock for an arbitrary external contract diff --git a/packages/ethereum-contracts/test/foundry/upgradability/SuperfluidUpgradeableBeacon.t.sol b/packages/ethereum-contracts/test/foundry/upgradability/SuperfluidUpgradeableBeacon.t.sol index 6f002d00f9..00029d76cf 100644 --- a/packages/ethereum-contracts/test/foundry/upgradability/SuperfluidUpgradeableBeacon.t.sol +++ b/packages/ethereum-contracts/test/foundry/upgradability/SuperfluidUpgradeableBeacon.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; import { Test } from "forge-std/Test.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Ownable } from "@openzeppelin-v5/contracts/access/Ownable.sol"; import { SuperfluidUpgradeableBeacon } from "../../../contracts/upgradability/SuperfluidUpgradeableBeacon.sol"; diff --git a/packages/ethereum-contracts/test/foundry/utils/TOGA.t.sol b/packages/ethereum-contracts/test/foundry/utils/TOGA.t.sol index 51c7bda1e5..cf0fe99b8e 100644 --- a/packages/ethereum-contracts/test/foundry/utils/TOGA.t.sol +++ b/packages/ethereum-contracts/test/foundry/utils/TOGA.t.sol @@ -5,8 +5,8 @@ import "forge-std/Test.sol"; import { FoundrySuperfluidTester, SuperTokenV1Library } from "../FoundrySuperfluidTester.t.sol"; import { ISuperToken } from "../../../contracts/superfluid/SuperToken.sol"; import { TOGA } from "../../../contracts/utils/TOGA.sol"; -import { IERC1820Registry } from "@openzeppelin/contracts/interfaces/IERC1820Registry.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC1820Registry } from "@openzeppelin-v5/contracts/interfaces/IERC1820Registry.sol"; +import { IERC20 } from "@openzeppelin-v5/contracts/token/ERC20/IERC20.sol"; import { TestToken } from "../../../contracts/utils/TestToken.sol"; /** From 6b53d0004b0efab3b0f5389633c0661af224d941 Mon Sep 17 00:00:00 2001 From: didi Date: Wed, 6 Aug 2025 22:27:03 +0200 Subject: [PATCH 57/68] adjust symlink path --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ca6dfaf142..d773f2e872 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ }, "scripts": { "prepare": "husky && npm run git-submodule:init", - "postinstall": "ln -s ../../lib/openzeppelin-contracts/contracts node_modules/@openzeppelin/contracts", + "postinstall": "ln -fs ../lib/openzeppelin-contracts node_modules/@openzeppelin-v5", "lint": "run-s -l lint:*", "lint:syncpack": "syncpack lint", "lint:shellcheck": "tasks/shellcheck-all-tasks.sh", From deef97096f18d437b32cd2f539b5cd2a635d3ad3 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 7 Aug 2025 09:21:26 +0200 Subject: [PATCH 58/68] adjust workaround to new path --- packages/sdk-core/tasks/build-types.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sdk-core/tasks/build-types.sh b/packages/sdk-core/tasks/build-types.sh index 6af75ae01f..b7eb72fe55 100755 --- a/packages/sdk-core/tasks/build-types.sh +++ b/packages/sdk-core/tasks/build-types.sh @@ -18,8 +18,8 @@ cp -r ../ethereum-contracts/typechain-types ./src/typechain-types # Remove the Address export from typechain-types to avoid conflict with mappedSubgraphTypes # OpenZeppelin v5 added a custom error to Address library, giving it a non-empty ABI # This causes typechain to generate types for it, but it's not needed for the SDK -sed -i '/export type { Address } from ".\/@openzeppelin\/contracts\/utils\/Address";/d' ./src/typechain-types/index.ts -sed -i '/export { Address__factory } from ".\/factories\/@openzeppelin\/contracts\/utils\/Address__factory";/d' ./src/typechain-types/index.ts +sed -i '/export type { Address } from ".\/@openzeppelin-v5\/contracts\/utils\/Address";/d' ./src/typechain-types/index.ts +sed -i '/export { Address__factory } from ".\/factories\/@openzeppelin-v5\/contracts\/utils\/Address__factory";/d' ./src/typechain-types/index.ts # compile the typechain files in sdk-core tsc -p tsconfig.typechain.json From db9cefee5c1637b2273d9ec7da6dc4d13498d8b3 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 7 Aug 2025 10:04:06 +0200 Subject: [PATCH 59/68] more path adjustments --- packages/ethereum-contracts/test/test-solc-compatibility.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-contracts/test/test-solc-compatibility.sh b/packages/ethereum-contracts/test/test-solc-compatibility.sh index 116f3317ec..c4a397d019 100755 --- a/packages/ethereum-contracts/test/test-solc-compatibility.sh +++ b/packages/ethereum-contracts/test/test-solc-compatibility.sh @@ -31,12 +31,12 @@ fi # from here - don't forget to add 0x to our generated sha256 # workaround to make solc to find OZ library -ln -sf ../../node_modules/@openzeppelin . +ln -s ../../lib/openzeppelin-contracts @openzeppelin-v5 # verify they are compatible with the minimum version of the SOLC we support find contracts/{interfaces/,apps/} -name '*.sol' | while read i;do - $SOLC --allow-paths '@openzeppelin' $i + $SOLC --allow-paths '@openzeppelin-v5' $i done echo SUCCESS -rm -f @openzeppelin +rm -f @openzeppelin-v5 From 288b660b65ba66976fba188cc798ce7269c778f4 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 7 Aug 2025 10:18:06 +0200 Subject: [PATCH 60/68] don't allow deployment of SimpleACL in upgrade path --- .../ops-scripts/deploy-framework.js | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/packages/ethereum-contracts/ops-scripts/deploy-framework.js b/packages/ethereum-contracts/ops-scripts/deploy-framework.js index 194ae71af4..582d8caac7 100644 --- a/packages/ethereum-contracts/ops-scripts/deploy-framework.js +++ b/packages/ethereum-contracts/ops-scripts/deploy-framework.js @@ -826,25 +826,6 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( return newAddress !== ZERO_ADDRESS ? newAddress : prevAddr; } - async function getOrDeployHelper( - Contract, - getPrevAddrFn, - outputKey - ) { - let prevAddr = await getPrevAddrFn().catch(_err => { - console.error(`### Error getting ${Contract.contractName} address, likely not yet deployed`); - return ZERO_ADDRESS; - }); - - if (prevAddr !== ZERO_ADDRESS) { - return prevAddr; - } - - const instance = await web3tx(Contract.new, `${Contract.contractName}.new`)(); - output += `${outputKey}=${instance.address}\n`; - return instance.address; - } - const simpleForwarderAddress = await getOrDeployForwarder( superfluid, SimpleForwarder, @@ -859,11 +840,9 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( "ERC2771_FORWARDER" ); - const simpleAclAddress = await getOrDeployHelper( - SimpleACL, - () => superfluid.getSimpleACL(), - "SIMPLE_ACL" - ); + // SimpleACL has now been deployed on all networks. + // It shall never be deployed in the upgrade path in order to eliminate the risk of accidental state loss. + const simpleAclAddress = (await superfluid.getSimpleACL()).address; console.log("SimpleACL address", simpleAclAddress); // get previous callback gas limit, make sure we don't decrease it From b3c6a8ce4295960a82bf1cb4870eb6b9ed50ea91 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 7 Aug 2025 12:19:14 +0200 Subject: [PATCH 61/68] tryConnectPool: don't allow zero address for member argument --- .../gdav1/GeneralDistributionAgreementV1.sol | 2 +- .../gdav1/GeneralDistributionAgreement.t.sol | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol index c79357eea3..ff864bbf30 100644 --- a/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol @@ -319,7 +319,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi { newCtx = ctx; - if (pool.superToken().isPool(this, memberAddr)) { + if (memberAddr == address(0) || pool.superToken().isPool(this, memberAddr)) { revert GDA_CANNOT_CONNECT_POOL(); } 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 b79dc04a4c..36a55bc3a4 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -1093,6 +1093,16 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste new bytes(0) ); vm.stopPrank(); + + // cannot connect the zero address + vm.startPrank(alice); + vm.expectRevert(IGeneralDistributionAgreementV1.GDA_CANNOT_CONNECT_POOL.selector); + sf.host.callAgreement( + sf.gda, + abi.encodeCall(sf.gda.tryConnectPoolFor, (freePool, address(0), new bytes(0))), + new bytes(0) + ); + vm.stopPrank(); } /*////////////////////////////////////////////////////////////////////////// From a24ef82d49df42eea56bbc6ceb19ac3b36cd28c3 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 7 Aug 2025 12:36:18 +0200 Subject: [PATCH 62/68] fix deploy script --- packages/ethereum-contracts/ops-scripts/deploy-framework.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/ops-scripts/deploy-framework.js b/packages/ethereum-contracts/ops-scripts/deploy-framework.js index 582d8caac7..ea3b9d60e3 100644 --- a/packages/ethereum-contracts/ops-scripts/deploy-framework.js +++ b/packages/ethereum-contracts/ops-scripts/deploy-framework.js @@ -842,7 +842,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( // SimpleACL has now been deployed on all networks. // It shall never be deployed in the upgrade path in order to eliminate the risk of accidental state loss. - const simpleAclAddress = (await superfluid.getSimpleACL()).address; + const simpleAclAddress = await superfluid.getSimpleACL(); console.log("SimpleACL address", simpleAclAddress); // get previous callback gas limit, make sure we don't decrease it From 67415dd4323b9e01e9d4474b16b0f505534705d0 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 7 Aug 2025 13:22:22 +0200 Subject: [PATCH 63/68] back to shanghai (where ganache is stuck) --- packages/automation-contracts/autowrap/foundry.toml | 2 +- packages/automation-contracts/scheduler/foundry.toml | 2 +- packages/ethereum-contracts/foundry.toml | 2 +- packages/ethereum-contracts/hardhat.config.ts | 2 +- packages/ethereum-contracts/truffle-config.js | 2 +- packages/hot-fuzz/foundry.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/automation-contracts/autowrap/foundry.toml b/packages/automation-contracts/autowrap/foundry.toml index 1d924b5a1a..8c76c06185 100644 --- a/packages/automation-contracts/autowrap/foundry.toml +++ b/packages/automation-contracts/autowrap/foundry.toml @@ -3,7 +3,7 @@ root = '../../../' libs = ['lib'] src = 'packages/automation-contracts/autowrap' solc_version = "0.8.30" -evm_version = 'cancun' +evm_version = 'shanghai' optimizer = true optimizer_runs = 200 remappings = [ diff --git a/packages/automation-contracts/scheduler/foundry.toml b/packages/automation-contracts/scheduler/foundry.toml index 3c03f037d7..16af7c6127 100644 --- a/packages/automation-contracts/scheduler/foundry.toml +++ b/packages/automation-contracts/scheduler/foundry.toml @@ -3,7 +3,7 @@ root = '../../../' libs = ['lib'] src = 'packages/automation-contracts/scheduler' solc_version = "0.8.30" -evm_version = 'cancun' +evm_version = 'shanghai' optimizer = true optimizer_runs = 200 remappings = [ diff --git a/packages/ethereum-contracts/foundry.toml b/packages/ethereum-contracts/foundry.toml index 350cd10e1c..d0e6a3f967 100644 --- a/packages/ethereum-contracts/foundry.toml +++ b/packages/ethereum-contracts/foundry.toml @@ -8,7 +8,7 @@ ignored_error_codes = [ 1699 # assembly { selfdestruct } in contracts/mocks/SuperfluidDestructorMock.sol ] # keep in sync with truffle-config.js -evm_version = 'cancun' +evm_version = 'shanghai' optimizer = true optimizer_runs = 200 remappings = [ diff --git a/packages/ethereum-contracts/hardhat.config.ts b/packages/ethereum-contracts/hardhat.config.ts index 17f753646d..4c7dc47789 100644 --- a/packages/ethereum-contracts/hardhat.config.ts +++ b/packages/ethereum-contracts/hardhat.config.ts @@ -100,7 +100,7 @@ const config: HardhatUserConfig = { enabled: true, runs: 200, }, - evmVersion: "cancun", + evmVersion: "shanghai", }, }, paths: { diff --git a/packages/ethereum-contracts/truffle-config.js b/packages/ethereum-contracts/truffle-config.js index c439111780..aaadfb4f1b 100644 --- a/packages/ethereum-contracts/truffle-config.js +++ b/packages/ethereum-contracts/truffle-config.js @@ -389,7 +389,7 @@ const E = (module.exports = { runs: 200, }, // see https://docs.soliditylang.org/en/latest/using-the-compiler.html#target-options - evmVersion: "cancun", + evmVersion: "shanghai", }, }, }, diff --git a/packages/hot-fuzz/foundry.toml b/packages/hot-fuzz/foundry.toml index ff15f271c1..1db8b96b13 100644 --- a/packages/hot-fuzz/foundry.toml +++ b/packages/hot-fuzz/foundry.toml @@ -2,7 +2,7 @@ root = '../..' src = 'packages/hot-fuzz/contracts' solc_version = "0.8.30" -evm_version = 'cancun' +evm_version = 'shanghai' optimizer = true optimizer_runs = 200 remappings = [ From 91f26a7571f6f65d72a5ed2ec4b571acb98a2c37 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 7 Aug 2025 13:28:22 +0200 Subject: [PATCH 64/68] add comment --- .../contracts/upgradability/Initializable.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/ethereum-contracts/contracts/upgradability/Initializable.sol b/packages/ethereum-contracts/contracts/upgradability/Initializable.sol index baf3f6425e..6a3607234b 100644 --- a/packages/ethereum-contracts/contracts/upgradability/Initializable.sol +++ b/packages/ethereum-contracts/contracts/upgradability/Initializable.sol @@ -84,6 +84,9 @@ abstract contract Initializable { modifier initializer() { bool isTopLevelCall = !_initializing; require( + // NOTE (Superfluid): + // The original version used `!(Address.isContract(address(this)))` here. + // That method is nomore available in OZ v5, thus the logic behind it (code.length check) was inlined. (isTopLevelCall && _initialized < 1) || (address(this).code.length == 0 && _initialized == 1), "Initializable: contract is already initialized" ); From dd78939c5043bab7a9b0aeed79576d07b05f02a8 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 7 Aug 2025 13:31:24 +0200 Subject: [PATCH 65/68] updated changelog --- packages/ethereum-contracts/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index 99623afbc4..fe84c7dd39 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -20,7 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `ISuperfluidPool`: `getClaimable` and `getClaimableNow` could previously return non-zero values for connected pools, which was inconsistent with what `claimAll` would actually do in this situation (claim nothing). ### Breaking -- Updated OpenZeppelin library from v.4.9.6 to v5.4.0 +- Updated OpenZeppelin library from v.4.9.6 to v5.4.0. The import path now includes the major version, making it easier for contracts integrating with this protocol to use a different major version of OpenZeppelin. - PoolMemberNFT pruning: `IPoolMemberNFT` and `PoolMemberNFT` removed, `POOL_MEMBER_NFT()` removed from `ISuperToken`. ## [v1.13.0] From 075a1c0cd9e9297293f08f3c41066b15ffb88360 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 7 Aug 2025 13:36:58 +0200 Subject: [PATCH 66/68] adjust foundry mappings --- packages/automation-contracts/autowrap/foundry.toml | 1 + packages/automation-contracts/scheduler/foundry.toml | 1 + packages/hot-fuzz/foundry.toml | 1 + 3 files changed, 3 insertions(+) diff --git a/packages/automation-contracts/autowrap/foundry.toml b/packages/automation-contracts/autowrap/foundry.toml index 8c76c06185..a724979827 100644 --- a/packages/automation-contracts/autowrap/foundry.toml +++ b/packages/automation-contracts/autowrap/foundry.toml @@ -9,6 +9,7 @@ optimizer_runs = 200 remappings = [ '@superfluid-finance/solidity-semantic-money/src/=packages/solidity-semantic-money/src/', '@superfluid-finance/ethereum-contracts/=packages/ethereum-contracts/', + '@openzeppelin-v5/=lib/openzeppelin-contracts/', '@openzeppelin/=lib/openzeppelin-contracts/', 'ds-test/=lib/forge-std/lib/ds-test/src/', 'forge-std/=lib/forge-std/src/'] diff --git a/packages/automation-contracts/scheduler/foundry.toml b/packages/automation-contracts/scheduler/foundry.toml index 16af7c6127..d430bfbe9f 100644 --- a/packages/automation-contracts/scheduler/foundry.toml +++ b/packages/automation-contracts/scheduler/foundry.toml @@ -9,6 +9,7 @@ optimizer_runs = 200 remappings = [ '@superfluid-finance/solidity-semantic-money/src/=packages/solidity-semantic-money/src/', '@superfluid-finance/ethereum-contracts/=packages/ethereum-contracts/', + '@openzeppelin-v5/=lib/openzeppelin-contracts/', '@openzeppelin/=lib/openzeppelin-contracts/', 'ds-test/=lib/forge-std/lib/ds-test/src/', 'forge-std/=lib/forge-std/src/'] diff --git a/packages/hot-fuzz/foundry.toml b/packages/hot-fuzz/foundry.toml index 1db8b96b13..2d630772b8 100644 --- a/packages/hot-fuzz/foundry.toml +++ b/packages/hot-fuzz/foundry.toml @@ -8,6 +8,7 @@ optimizer_runs = 200 remappings = [ '@superfluid-finance/ethereum-contracts/contracts/=packages/ethereum-contracts/contracts/', '@superfluid-finance/solidity-semantic-money/src/=packages/solidity-semantic-money/src/', + '@openzeppelin-v5/=lib/openzeppelin-contracts/', '@openzeppelin/=lib/openzeppelin-contracts/', 'ds-test/=lib/forge-std/lib/ds-test/src/', 'forge-std/=lib/forge-std/src/'] From a7686ff1cb0db8de9f1ef1aba3ea787577bb1cbc Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 7 Aug 2025 20:59:39 +0200 Subject: [PATCH 67/68] more detailled changelog, specify OZ as peer dependency --- .../autowrap/hardhat.config.js | 12 ++++++++++++ .../scheduler/hardhat.config.js | 12 ++++++++++++ packages/ethereum-contracts/CHANGELOG.md | 19 ++++++++++++++++++- packages/ethereum-contracts/hardhat.config.ts | 1 - packages/ethereum-contracts/package.json | 3 ++- 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/automation-contracts/autowrap/hardhat.config.js b/packages/automation-contracts/autowrap/hardhat.config.js index 923ae77b0e..5a81333b34 100644 --- a/packages/automation-contracts/autowrap/hardhat.config.js +++ b/packages/automation-contracts/autowrap/hardhat.config.js @@ -4,10 +4,22 @@ require("@nomiclabs/hardhat-etherscan"); require("hardhat-deploy"); require("hardhat/config"); require("./script/addStrategy"); +const {TASK_COMPILE_GET_REMAPPINGS} = require("hardhat/builtin-tasks/task-names"); // You need to export an object to set up your config // Go to https://hardhat.org/config/ to learn more +// Remapping for OpenZeppelin contracts +subtask(TASK_COMPILE_GET_REMAPPINGS).setAction( + async (_, __, runSuper) => { + const remappings = await runSuper(); + return { + ...remappings, + "@openzeppelin/contracts/": "@openzeppelin-v5/contracts/", + }; + } +); + /** * @type import('hardhat/config').HardhatUserConfig */ diff --git a/packages/automation-contracts/scheduler/hardhat.config.js b/packages/automation-contracts/scheduler/hardhat.config.js index 8db0889983..a9e469ef6e 100644 --- a/packages/automation-contracts/scheduler/hardhat.config.js +++ b/packages/automation-contracts/scheduler/hardhat.config.js @@ -3,10 +3,22 @@ require("@nomiclabs/hardhat-ethers"); require("@nomiclabs/hardhat-etherscan"); require("hardhat-deploy"); require("hardhat/config"); +const {TASK_COMPILE_GET_REMAPPINGS} = require("hardhat/builtin-tasks/task-names"); // You need to export an object to set up your config // Go to https://hardhat.org/config/ to learn more +// Remapping for OpenZeppelin contracts +subtask(TASK_COMPILE_GET_REMAPPINGS).setAction( + async (_, __, runSuper) => { + const remappings = await runSuper(); + return { + ...remappings, + "@openzeppelin/contracts/": "@openzeppelin-v5/contracts/", + }; + } +); + /** * @type import('hardhat/config').HardhatUserConfig */ diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index fe84c7dd39..dbaa27f513 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -20,7 +20,24 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `ISuperfluidPool`: `getClaimable` and `getClaimableNow` could previously return non-zero values for connected pools, which was inconsistent with what `claimAll` would actually do in this situation (claim nothing). ### Breaking -- Updated OpenZeppelin library from v.4.9.6 to v5.4.0. The import path now includes the major version, making it easier for contracts integrating with this protocol to use a different major version of OpenZeppelin. +- Updated OpenZeppelin library from v.4.9.6 to v5.4.0. +The import path now includes the major version, making it easier for contracts integrating with this protocol to use a different major version of OpenZeppelin. +Projects using Superfluid contracts as a dependency need to configure a mapping: + - Foundry: add this to remappings: `'@openzeppelin-v5/=lib/openzeppelin-contracts/',` + - Hardhat (>=v2.17.2): add `@openzeppelin/contracts` as a project dependency and a subtask in your hardhat config: +``` +import { TASK_COMPILE_GET_REMAPPINGS } from "hardhat/builtin-tasks/task-names"; + +subtask(TASK_COMPILE_GET_REMAPPINGS).setAction( + async (_, __, runSuper) => { + const remappings = await runSuper(); + return { + ...remappings, + "@openzeppelin-v5/contracts/": "@openzeppelin/contracts/", + }; + } +); +``` - PoolMemberNFT pruning: `IPoolMemberNFT` and `PoolMemberNFT` removed, `POOL_MEMBER_NFT()` removed from `ISuperToken`. ## [v1.13.0] diff --git a/packages/ethereum-contracts/hardhat.config.ts b/packages/ethereum-contracts/hardhat.config.ts index 4c7dc47789..fb8b2d98a3 100644 --- a/packages/ethereum-contracts/hardhat.config.ts +++ b/packages/ethereum-contracts/hardhat.config.ts @@ -7,7 +7,6 @@ import "@nomiclabs/hardhat-ethers"; import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS, TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, - TASK_COMPILE_GET_REMAPPINGS, } from "hardhat/builtin-tasks/task-names"; import "solidity-coverage"; import {config as dotenvConfig} from "dotenv"; diff --git a/packages/ethereum-contracts/package.json b/packages/ethereum-contracts/package.json index 292013ced5..8ea4b955fe 100644 --- a/packages/ethereum-contracts/package.json +++ b/packages/ethereum-contracts/package.json @@ -49,7 +49,8 @@ "license": "AGPL-3.0-or-later OR MIT", "main": "./dev-scripts/index.js", "peerDependencies": { - "ethers": "^5.7.2" + "ethers": "^5.7.2", + "@openzeppelin/contracts": "^5.0.0" }, "repository": { "type": "git", From e6ffae8615f316308aa295e48ee67c4b6f9a41a5 Mon Sep 17 00:00:00 2001 From: Olexandr88 Date: Wed, 13 Aug 2025 09:03:28 +0300 Subject: [PATCH 68/68] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7a2a27f42..58d9cad140 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

Welcome to superfluid protocol-monorepo 👋

- + npm