@@ -4,7 +4,7 @@ pragma solidity ^0.8.23;
44
55import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol " ;
66
7- import { ISuperfluid, ISuperfluidGovernance } from "../../interfaces/superfluid/ISuperfluid.sol " ;
7+ import { ISuperfluid, ISuperfluidGovernance, IAccessControl } from "../../interfaces/superfluid/ISuperfluid.sol " ;
88import {
99 BasicParticle,
1010 PDPoolIndex,
@@ -23,8 +23,6 @@ import {
2323} from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol " ;
2424import { SuperfluidUpgradeableBeacon } from "../../upgradability/SuperfluidUpgradeableBeacon.sol " ;
2525import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol " ;
26- import { ISuperToken } from "../../interfaces/superfluid/ISuperToken.sol " ;
27- import { IPoolAdminNFT } from "../../interfaces/agreements/gdav1/IPoolAdminNFT.sol " ;
2826import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol " ;
2927import { SlotsBitmapLibrary } from "../../libs/SlotsBitmapLibrary.sol " ;
3028import { SolvencyHelperLibrary } from "../../libs/SolvencyHelperLibrary.sol " ;
@@ -48,6 +46,12 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
4846
4947 address public constant SUPERFLUID_POOL_DEPLOYER_ADDRESS = address (SuperfluidPoolDeployerLibrary);
5048
49+ // @dev The max number of slots which can be used for connecting pools on behalf of a member (per token)
50+ uint32 public constant MAX_POOL_AUTO_CONNECT_SLOTS = 4 ;
51+
52+ // @dev The ACL role owned by this contract, used to persist autoconnect permissions for accounts
53+ bytes32 constant public ACL_POOL_CONNECT_EXCLUSIVE_ROLE = keccak256 ("ACL_POOL_CONNECT_EXCLUSIVE_ROLE " );
54+
5155 /// @dev Pool member state slot id for storing subs bitmap
5256 uint256 private constant _POOL_SUBS_BITMAP_STATE_SLOT_ID = 1 ;
5357 /// @dev Pool member state slot id starting point for pool connections
@@ -228,7 +232,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
228232 function _createPool (
229233 ISuperfluidToken token ,
230234 address admin ,
231- PoolConfig memory config ,
235+ PoolConfig calldata config ,
232236 PoolERC20Metadata memory poolERC20Metadata
233237 ) internal returns (ISuperfluidPool pool ) {
234238 // @note ensure if token and admin are the same that nothing funky happens with echidna
@@ -245,17 +249,13 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
245249
246250 token.setIsPoolFlag (pool);
247251
248- IPoolAdminNFT poolAdminNFT = IPoolAdminNFT (_getPoolAdminNFTAddress (token));
249-
250- if (address (poolAdminNFT) != address (0 )) {
251- poolAdminNFT.mint (address (pool));
252- }
252+ SuperfluidPoolDeployerLibrary.mintPoolAdminNFT (token, pool);
253253
254254 emit PoolCreated (token, admin, pool);
255255 }
256256
257257 /// @inheritdoc IGeneralDistributionAgreementV1
258- function createPool (ISuperfluidToken token , address admin , PoolConfig memory config )
258+ function createPool (ISuperfluidToken token , address admin , PoolConfig calldata config )
259259 external
260260 override
261261 returns (ISuperfluidPool pool )
@@ -272,7 +272,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
272272 function createPoolWithCustomERC20Metadata (
273273 ISuperfluidToken token ,
274274 address admin ,
275- PoolConfig memory config ,
275+ PoolConfig calldata config ,
276276 PoolERC20Metadata memory poolERC20Metadata
277277 ) external override returns (ISuperfluidPool pool ) {
278278 return _createPool (token, admin, config, poolERC20Metadata);
@@ -305,49 +305,106 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
305305 pool.claimAll (memberAddress);
306306 }
307307
308- // @note setPoolConnection function naming
309- function connectPool (ISuperfluidPool pool , bool doConnect , bytes calldata ctx )
310- public
311- returns (bytes memory newCtx )
308+ /// @inheritdoc IGeneralDistributionAgreementV1
309+ function connectPool (ISuperfluidPool pool , bytes calldata ctx ) external override returns (bytes memory newCtx ) {
310+ newCtx = ctx;
311+ _setPoolConnectionFor (pool, address (0 ), true /* doConnect */ , ctx);
312+ }
313+
314+ /// @inheritdoc IGeneralDistributionAgreementV1
315+ function tryConnectPoolFor (ISuperfluidPool pool , address memberAddr , bytes calldata ctx )
316+ external
317+ override
318+ returns (bool success , bytes memory newCtx )
319+ {
320+ newCtx = ctx;
321+
322+ if (pool.superToken ().isPool (this , memberAddr)) {
323+ revert GDA_CANNOT_CONNECT_POOL ();
324+ }
325+
326+ // check if the member has opted out of autoconnect
327+ IAccessControl simpleACL = ISuperfluid (_host).getSimpleACL ();
328+ if (simpleACL.hasRole (ACL_POOL_CONNECT_EXCLUSIVE_ROLE, memberAddr)) {
329+ success = false ;
330+ } else {
331+ success = _setPoolConnectionFor (pool, memberAddr, true /* doConnect */ , ctx);
332+ }
333+ }
334+
335+ function setConnectPermission (bool allow ) external override {
336+ IAccessControl simpleACL = ISuperfluid (_host).getSimpleACL ();
337+ if (! allow) {
338+ simpleACL.grantRole (ACL_POOL_CONNECT_EXCLUSIVE_ROLE, msg .sender );
339+ } else {
340+ simpleACL.revokeRole (ACL_POOL_CONNECT_EXCLUSIVE_ROLE, msg .sender );
341+ }
342+ }
343+
344+ /// @inheritdoc IGeneralDistributionAgreementV1
345+ function disconnectPool (ISuperfluidPool pool , bytes calldata ctx ) external override returns (bytes memory newCtx ) {
346+ newCtx = ctx;
347+ _setPoolConnectionFor (pool, address (0 ), false /* doConnect */ , ctx);
348+ }
349+
350+ // @note memberAddr has override semantics - if set to address(0), it will be set to the msgSender
351+ function _setPoolConnectionFor (
352+ ISuperfluidPool pool ,
353+ address memberAddr ,
354+ bool doConnect ,
355+ bytes memory ctx
356+ )
357+ internal
358+ returns (bool success )
312359 {
313360 ISuperfluidToken token = pool.superToken ();
314361 ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess (token, ctx);
315- address msgSender = currentContext.msgSender;
316- newCtx = ctx;
317- bool isConnected = token.isPoolMemberConnected (this , pool, msgSender);
318- if (doConnect != isConnected) {
319- assert (
320- SuperfluidPool (address (pool)).operatorConnectMember (
321- msgSender, doConnect, uint32 (currentContext.timestamp)
322- )
323- );
324362
363+ bool autoConnectForOtherMember = false ;
364+ if (memberAddr == address (0 )) {
365+ memberAddr = currentContext.msgSender;
366+ } else {
367+ autoConnectForOtherMember = true ;
368+ }
369+
370+ bool isConnected = token.isPoolMemberConnected (this , pool, memberAddr);
371+
372+ if (doConnect != isConnected) {
325373 if (doConnect) {
326- uint32 poolSlotId =
327- _findAndFillPoolConnectionsBitmap (token, msgSender, bytes32 (uint256 (uint160 (address (pool)))));
374+ if (autoConnectForOtherMember) {
375+ // check if we're below the slot limit for autoconnect
376+ uint256 nUsedSlots = SlotsBitmapLibrary.countUsedSlots (
377+ token, memberAddr, _POOL_SUBS_BITMAP_STATE_SLOT_ID
378+ );
379+ if (nUsedSlots >= MAX_POOL_AUTO_CONNECT_SLOTS) {
380+ return false ;
381+ }
382+ }
383+
384+ uint32 poolSlotId = _findAndFillPoolConnectionsBitmap (
385+ token, memberAddr, bytes32 (uint256 (uint160 (address (pool))))
386+ );
328387
329388 token.createPoolConnectivity
330- (msgSender , GDAv1StorageLib.PoolConnectivity ({ slotId: poolSlotId, pool: pool }));
389+ (memberAddr , GDAv1StorageLib.PoolConnectivity ({ slotId: poolSlotId, pool: pool }));
331390 } else {
332391 (, GDAv1StorageLib.PoolConnectivity memory poolConnectivity ) =
333- token.getPoolConnectivity (this , msgSender , pool);
334- token.deletePoolConnectivity (msgSender , pool);
392+ token.getPoolConnectivity (this , memberAddr , pool);
393+ token.deletePoolConnectivity (memberAddr , pool);
335394
336- _clearPoolConnectionsBitmap (token, msgSender , poolConnectivity.slotId);
395+ _clearPoolConnectionsBitmap (token, memberAddr , poolConnectivity.slotId);
337396 }
338397
339- emit PoolConnectionUpdated (token, pool, msgSender, doConnect, currentContext.userData);
340- }
341- }
398+ assert (
399+ SuperfluidPool (address (pool)).operatorConnectMember (
400+ memberAddr, doConnect, uint32 (currentContext.timestamp)
401+ )
402+ );
342403
343- /// @inheritdoc IGeneralDistributionAgreementV1
344- function connectPool (ISuperfluidPool pool , bytes calldata ctx ) external override returns (bytes memory newCtx ) {
345- return connectPool (pool, true , ctx);
346- }
404+ emit PoolConnectionUpdated (token, pool, memberAddr, doConnect, currentContext.userData);
405+ }
347406
348- /// @inheritdoc IGeneralDistributionAgreementV1
349- function disconnectPool (ISuperfluidPool pool , bytes calldata ctx ) external override returns (bytes memory newCtx ) {
350- return connectPool (pool, false , ctx);
407+ return true ;
351408 }
352409
353410 /// @inheritdoc IGeneralDistributionAgreementV1
@@ -533,21 +590,6 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
533590 }
534591 }
535592
536- function _getPoolAdminNFTAddress (ISuperfluidToken token ) internal view returns (address poolAdminNFTAddress ) {
537- // solhint-disable-next-line avoid-low-level-calls
538- (bool success , bytes memory data ) =
539- address (token).staticcall (abi.encodeWithSelector (ISuperToken.POOL_ADMIN_NFT.selector ));
540-
541- if (success) {
542- // @note We are aware this may revert if a Custom SuperToken's
543- // POOL_ADMIN_NFT does not return data that can be
544- // decoded to an address. This would mean it was intentionally
545- // done by the creator of the Custom SuperToken logic and is
546- // fully expected to revert in that case as the author desired.
547- poolAdminNFTAddress = abi.decode (data, (address ));
548- }
549- }
550-
551593 function _adjustBuffer
552594 (ISuperfluidToken token ,
553595 address pool ,
0 commit comments