Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
ba1b067
distinguish between unsettled and claimable balance in pools
d10r Jun 26, 2025
f76ce16
add assertions
d10r Jun 26, 2025
62697d9
Merge branch 'dev' into 2025-06-fix-claimable
d10r Jun 30, 2025
ab01e42
Merge branch 'dev' into 2025-06-fix-claimable
d10r Jul 3, 2025
ea53653
merge dev
d10r Jul 15, 2025
9c433c5
use new reader function
d10r Jul 15, 2025
b56c0f3
Merge branch 'dev' into 2025-06-fix-claimable
d10r Jul 16, 2025
d6687ab
small fix
d10r Jul 16, 2025
5dad0c3
updated changelog
d10r Jul 16, 2025
882f654
limit valid input size for _settle()
hellwolf Jul 17, 2025
e27cb41
remove some silly type convrersions
hellwolf Jul 17, 2025
5a0fb64
chore: remove redundant words in comment (#2092)
pavedroad Jul 17, 2025
b856b8a
added GDA function which allows the pool admin to connect pools on be…
d10r Jul 17, 2025
cd864bd
add mechanism for opting out of autoconnect using SimpleACL
d10r Jul 17, 2025
88e17f3
revert forge-std upgrade
d10r Jul 17, 2025
8db285b
simplification
d10r Jul 17, 2025
101bdfc
don't restrict autoconnect to pool admin, more testing
d10r Jul 17, 2025
701ea33
fix stack too deep?
d10r Jul 17, 2025
4dce3b7
fix flag
d10r Jul 17, 2025
5a603b2
more shanghai
d10r Jul 17, 2025
77e3f9d
Merge branch 'dev' into 2025-07-autoconnect
d10r Jul 18, 2025
d3bfd95
updated CHANGELOG
d10r Jul 18, 2025
d6c9135
undo disable test
d10r Jul 18, 2025
2aa5ccd
added tryConnectPoolFor to SuperTokenV1Library
d10r Jul 18, 2025
b4e6342
CFASuperAppBase: disable autoconnect in constructor
d10r Jul 18, 2025
c195923
set admin roles in deploy script
d10r Jul 18, 2025
bd1d158
solidity semantic money to shanghai evm
hellwolf Jul 22, 2025
c76c167
added countUsedSlots to SlotsBitmapLibrary
hellwolf Jul 22, 2025
64484ae
GDAv1: use SlotsBitmapLibrary
hellwolf Jul 22, 2025
d8ea0dc
added some asserts to SlotsBitmapLibraryPropertyTest._listData
hellwolf Jul 22, 2025
79109ec
revert solidity semantic money to paris evm
hellwolf Jul 22, 2025
d578842
fix testTryConnectPoolFor
hellwolf Jul 22, 2025
dff96ab
fix testAutoConnectSlotLimit
hellwolf Jul 22, 2025
c855ecf
update flake inputs
hellwolf Jul 23, 2025
e0ca43f
solc: 0.8.26 -> 0.8.30
hellwolf Jul 23, 2025
4ca4195
Merge branch 'update-solc' into 2025-07-autoconnect
hellwolf Jul 23, 2025
ae36ff0
ethereum-contracts: update CHANGELOG.md
hellwolf Jul 23, 2025
365b087
update more solc_version to 0.8.30
hellwolf Jul 23, 2025
5ec58cd
Merge branch 'update-solc' into 2025-07-autoconnect
hellwolf Jul 23, 2025
7b2e30f
revert the wrong fix
hellwolf Jul 23, 2025
7aef0ee
Merge branch 'dev' into 2025-07-autoconnect
hellwolf Jul 23, 2025
cdf0482
small code refactoring to _setPoolConnection
hellwolf Jul 23, 2025
9b4da71
fix tests
d10r Jul 23, 2025
283402a
fix flaky test
d10r Jul 23, 2025
c5fd4d5
fix scheduler test
d10r Jul 24, 2025
088eba3
renamed to _setPoolConnectionFor
d10r Jul 24, 2025
03b81ba
revert if trying to connect a pool for a pool
d10r Jul 24, 2025
5652c9d
calldata storage location for PoolConfig
d10r Jul 24, 2025
21da9df
squeeze out a few more bytes
d10r Jul 24, 2025
5608475
more contract size margin for GDA
d10r Jul 24, 2025
5a3639d
fix import
d10r Jul 24, 2025
a8264c0
Update VestingSchedulerV2.sol
crStiv Jul 27, 2025
0d29e7a
Update VestingSchedulerV3.sol
crStiv Jul 27, 2025
1588c53
Update FlowScheduler.t.sol
crStiv Jul 27, 2025
7935a0e
Update InstantDistributionAgreementV1-Non-Callback.test.ts
crStiv Jul 27, 2025
027a203
Update FlowSchedulerResolver.t.sol
crStiv Jul 27, 2025
2ab51c9
Update VestingSchedulerV2.t.sol
crStiv Jul 27, 2025
31bf0b3
no calldata magic
d10r Jul 31, 2025
d182827
Merge branch 'dev' into 2025-07-autoconnect
d10r Jul 31, 2025
ce3c48a
forge-std: v1.9.1 -> v1.10.0
hellwolf Aug 6, 2025
47f155e
Merge branch 'typ' into update-forge-std
hellwolf Aug 6, 2025
4eb4dd0
Merge branch 'update-forge-std' into 2025-07-autoconnect
hellwolf Aug 6, 2025
71918b4
fix automation contracts
hellwolf Aug 6, 2025
c6cbeb5
Merge branch 'update-forge-std' into 2025-07-autoconnect
hellwolf Aug 6, 2025
5e4332c
Merge branch 'dev' into 2025-07-autoconnect
hellwolf Aug 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/automation-contracts/autowrap/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
2 changes: 1 addition & 1 deletion packages/automation-contracts/scheduler/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
4 changes: 4 additions & 0 deletions packages/ethereum-contracts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -48,6 +48,12 @@ 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;

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
Expand Down Expand Up @@ -305,49 +311,100 @@ 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) {
newCtx = ctx;
_setPoolConnection(pool, address(0), true, false, ctx);
}

/// @inheritdoc IGeneralDistributionAgreementV1
function tryConnectPoolFor(ISuperfluidPool pool, address memberAddr, bytes calldata ctx)
external
override
returns (bool success, bytes memory newCtx)
{
newCtx = ctx;

// 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, memberAddr, true, true, ctx);
}
}

function setConnectPermission(bool allow) external override {
IAccessControl simpleACL = ISuperfluid(_host).getSimpleACL();
if (!allow) {
simpleACL.grantRole(ACL_POOL_CONNECT_EXCLUSIVE_ROLE, msg.sender);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who is the admins of simpleACL??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still the GH agent, but after deploying this it should be handed over to SF gov or SF gov owner.

} else {
simpleACL.revokeRole(ACL_POOL_CONNECT_EXCLUSIVE_ROLE, msg.sender);
}
}

/// @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);
}

// @note memberAddr has override semantics - if set to address(0), it will be set to the msgSender
function _setPoolConnection(
ISuperfluidPool pool,
address memberAddr,
bool doConnect,
bool onlyAutoConnectSlots,
bytes memory ctx
)
internal
returns (bool success)
{
ISuperfluidToken token = pool.superToken();
ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx);
address msgSender = currentContext.msgSender;
newCtx = ctx;
bool isConnected = token.isPoolMemberConnected(this, pool, msgSender);
if (doConnect != isConnected) {
assert(
SuperfluidPool(address(pool)).operatorConnectMember(
msgSender, doConnect, uint32(currentContext.timestamp)
)
);

if (memberAddr == address(0)) {
memberAddr = currentContext.msgSender;
}

bool isConnected = token.isPoolMemberConnected(this, pool, memberAddr);

if (doConnect != isConnected) {
if (doConnect) {
uint32 poolSlotId =
_findAndFillPoolConnectionsBitmap(token, msgSender, bytes32(uint256(uint160(address(pool)))));
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
);
if (slotIds.length >= MAX_POOL_AUTO_CONNECT_SLOTS) {
return false;
}
}

uint32 poolSlotId = _findAndFillPoolConnectionsBitmap(
token, memberAddr, bytes32(uint256(uint160(address(pool))))
);

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without adding anything, is there an easy way to know if the connection was an "auto-connect" from event logs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be an issue. An option is to supplement with a new event type.

}

/// @inheritdoc IGeneralDistributionAgreementV1
function disconnectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) {
return connectPool(pool, false, ctx);
return true;
}

/// @inheritdoc IGeneralDistributionAgreementV1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,23 @@ 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 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
/// @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 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
2 changes: 1 addition & 1 deletion packages/ethereum-contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
2 changes: 1 addition & 1 deletion packages/ethereum-contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const config: HardhatUserConfig = {
enabled: true,
runs: 200,
},
evmVersion: "paris",
evmVersion: "shanghai",
},
},
paths: {
Expand Down
Loading
Loading