Skip to content

Commit 7cc9961

Browse files
authored
[ETHEREUM-CONTRACTS] 1.14.1: backport GDA patch, adjust truffle verification pipeline vor etherscan v2 (#2121)
1 parent 9a40912 commit 7cc9961

File tree

16 files changed

+130
-110
lines changed

16 files changed

+130
-110
lines changed

packages/automation-contracts/autowrap/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"description": "Open contracts that allow upgrading underlying token to supertokens based on running stream",
44
"version": "0.3.0",
55
"devDependencies": {
6-
"@superfluid-finance/ethereum-contracts": "^1.14.0",
6+
"@superfluid-finance/ethereum-contracts": "^1.14.1",
77
"@superfluid-finance/metadata": "^1.6.2"
88
},
99
"license": "MIT",

packages/automation-contracts/scheduler/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"description": "Open contracts that allow scheduling streams and vestings onchain",
44
"version": "1.3.0",
55
"devDependencies": {
6-
"@superfluid-finance/ethereum-contracts": "^1.14.0",
6+
"@superfluid-finance/ethereum-contracts": "^1.14.1",
77
"@superfluid-finance/metadata": "^1.6.2"
88
},
99
"license": "MIT",

packages/ethereum-contracts/CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ All notable changes to the ethereum-contracts will be documented in this file.
33

44
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
55

6-
## [v1.14.0]
6+
## [v1.14.1]
77

88
### Added
99
- 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.
@@ -15,7 +15,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1515
- `GDAv1StorageWriter` contains functions for writing agreement data to the token contract. This can only be used by the GDA contract itself.
1616
- bump solc to "0.8.30".
1717
- Changed EVM target from `paris` to `shanghai` because now all networks with supported Superfluid deployment support it.
18-
- Emit ERC20 `Transfer` events (with amount 0) on CFA and GDA actions potentially leading to account balance changes. This shall help indexers to keep track of SuperToken holders and account balances.
1918
- Don't emit ERC20 `Approval` events on `transferFrom` operations. This is consistent with the OpenZeppelin ERC20 implementation from v5 onwards. Change effective only for SuperTokens using the latest logic.
2019

2120
### Fixed
@@ -47,6 +46,10 @@ subtask(TASK_COMPILE_GET_REMAPPINGS).setAction(
4746
- `CFASuperAppBase`: added `flowRate` argument to `onFlowCreated` and `onFlowUpdated`.
4847
- PoolMemberNFT pruning: `IPoolMemberNFT` and `PoolMemberNFT` removed, `POOL_MEMBER_NFT()` removed from `ISuperToken`.
4948

49+
## [v1.14.0]
50+
51+
Defect release, don't use!
52+
5053
## [v1.13.0]
5154

5255
### Added

packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -453,8 +453,6 @@ contract ConstantFlowAgreementV1 is
453453
ctx, currentContext);
454454
}
455455

456-
flowVars.token.emitPseudoTransfer(flowVars.sender, flowVars.receiver);
457-
458456
_requireAvailableBalance(flowVars.token, flowVars.sender, currentContext);
459457
}
460458

@@ -595,7 +593,6 @@ contract ConstantFlowAgreementV1 is
595593
}
596594
}
597595

598-
flowVars.token.emitPseudoTransfer(flowVars.sender, flowVars.receiver);
599596
}
600597

601598
/**************************************************************************

packages/ethereum-contracts/contracts/agreements/gdav1/GeneralDistributionAgreementV1.sol

Lines changed: 75 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -279,34 +279,49 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
279279
}
280280

281281
/// @inheritdoc IGeneralDistributionAgreementV1
282-
function updateMemberUnits(ISuperfluidPool pool, address memberAddress, uint128 newUnits, bytes calldata ctx)
282+
function updateMemberUnits(
283+
ISuperfluidPool untrustedPool,
284+
address memberAddress,
285+
uint128 newUnits,
286+
bytes calldata ctx
287+
)
283288
external
284289
override
285290
returns (bytes memory newCtx)
286291
{
292+
ISuperfluidToken token = untrustedPool.superToken();
293+
address msgSender = AgreementLibrary.authorizeTokenAccess(token, ctx).msgSender;
294+
287295
// Only the admin can update member units here
288-
if (AgreementLibrary.authorizeTokenAccess(pool.superToken(), ctx).msgSender != pool.admin()) {
296+
if (msgSender != untrustedPool.admin()) {
289297
revert GDA_NOT_POOL_ADMIN();
290298
}
291299
newCtx = ctx;
292300

293-
pool.updateMemberUnits(memberAddress, newUnits);
301+
// NOTE: In GDA.appendIndexUpdateByPool, it checks whether pool is created by the token.
302+
untrustedPool.updateMemberUnits(memberAddress, newUnits);
294303
}
295304

296305
/// @inheritdoc IGeneralDistributionAgreementV1
297-
function claimAll(ISuperfluidPool pool, address memberAddress, bytes calldata ctx)
306+
function claimAll(ISuperfluidPool untrustedPool, address memberAddress, bytes calldata ctx)
298307
external
299308
override
300309
returns (bytes memory newCtx)
301310
{
302-
AgreementLibrary.authorizeTokenAccess(pool.superToken(), ctx);
311+
ISuperfluidToken token = untrustedPool.superToken();
312+
AgreementLibrary.authorizeTokenAccess(token, ctx);
303313
newCtx = ctx;
304314

305-
pool.claimAll(memberAddress);
315+
// NOTE: In GDA.poolSettleClaim, it checks whether pool is created by the token.
316+
untrustedPool.claimAll(memberAddress);
306317
}
307318

308319
/// @inheritdoc IGeneralDistributionAgreementV1
309-
function connectPool(ISuperfluidPool pool, bytes calldata ctx) external override returns (bytes memory newCtx) {
320+
function connectPool(ISuperfluidPool pool, bytes calldata ctx)
321+
external
322+
override
323+
returns (bytes memory newCtx)
324+
{
310325
newCtx = ctx;
311326
_setPoolConnectionFor(pool, address(0), true /* doConnect */, ctx);
312327
}
@@ -319,6 +334,7 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
319334
{
320335
newCtx = ctx;
321336

337+
// NOTE: We do not allow a pool to connect to another pool.
322338
if (memberAddr == address(0) || pool.superToken().isPool(this, memberAddr)) {
323339
revert GDA_CANNOT_CONNECT_POOL();
324340
}
@@ -358,6 +374,10 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
358374
returns (bool success)
359375
{
360376
ISuperfluidToken token = pool.superToken();
377+
// TODO: convert to modifier `poolIsTrustedByItsSuperToken(pool)`
378+
if (!token.isPool(this, address(pool))) {
379+
revert GDA_ONLY_SUPER_TOKEN_POOL();
380+
}
361381
ISuperfluid.Context memory currentContext = AgreementLibrary.authorizeTokenAccess(token, ctx);
362382

363383
bool autoConnectForOtherMember = false;
@@ -401,14 +421,22 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
401421
)
402422
);
403423

424+
// NOTE: similar to Transfer, we cannot tell if it is done through tryConnect or regular connect.
404425
emit PoolConnectionUpdated(token, pool, memberAddr, doConnect, currentContext.userData);
405426
}
406427

407428
return true;
408429
}
409430

410431
/// @inheritdoc IGeneralDistributionAgreementV1
411-
function isMemberConnected(ISuperfluidPool pool, address member) external view override returns (bool) {
432+
function isMemberConnected(ISuperfluidPool pool, address member)
433+
external view override
434+
returns (bool)
435+
{
436+
// NOTE: this function is total, in that even for invalid pools, it will always return false.
437+
//
438+
// Retrospectively, it may be more helpful to the developers if this function is non-total, and always revert
439+
// on invalid pool.
412440
return pool.superToken().isPoolMemberConnected(this, pool, member);
413441
}
414442

@@ -424,9 +452,12 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
424452

425453
newCtx = ctx;
426454

427-
if (token.isPool(this, address(pool)) == false ||
455+
// TODO: convert to modifier `poolIsTrustedByItsSuperToken(pool)`
456+
if (
457+
token.isPool(this, address(pool)) == false ||
428458
// Note: we do not support multi-tokens pools
429-
pool.superToken() != token) {
459+
pool.superToken() != token)
460+
{
430461
revert GDA_ONLY_SUPER_TOKEN_POOL();
431462
}
432463

@@ -490,9 +521,12 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
490521
int96 requestedFlowRate,
491522
bytes calldata ctx
492523
) external override returns (bytes memory newCtx) {
493-
if (token.isPool(this, address(pool)) == false ||
524+
// TODO: convert to modifier `poolIsTrustedByItsSuperToken(pool)`
525+
if (
526+
token.isPool(this, address(pool)) == false ||
494527
// Note: we do not support multi-tokens pools
495-
pool.superToken() != token) {
528+
pool.superToken() != token)
529+
{
496530
revert GDA_ONLY_SUPER_TOKEN_POOL();
497531
}
498532
if (requestedFlowRate < 0) {
@@ -599,6 +633,8 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
599633
)
600634
internal
601635
{
636+
// NOTE: the caller to guarantee that the token and pool are mutually trusted.
637+
602638
// not using oldFlowRate in this model
603639
// surprising effect: reducing flow rate may require more buffer when liquidation_period adjusted upward
604640
ISuperfluidGovernance gov = ISuperfluidGovernance(ISuperfluid(_host).getGovernance());
@@ -774,39 +810,51 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
774810

775811
//
776812
// Pool-only operations
777-
//
813+
// Can only be called (`msg.sender`) by legitimate pool contracts.
814+
// If `token` is legitimate, `token.isPool()` can return true only if the pool was created by this agreement.
815+
// "false positives" (does not revert for illegitimate caller) could occur if `token`:
816+
// 1. is lying (claims the pool was registered by this agreement when it was not)
817+
// or
818+
// 2. is not associated to the same host (and agreements).
819+
// In both cases, pre-conditions are not met and no state this agreement is responsible for can be manipulated.
778820

779821
function appendIndexUpdateByPool(ISuperfluidToken token, BasicParticle memory p, Time t)
780822
external
781823
returns (bool)
782824
{
783-
if (token.isPool(this, msg.sender) == false) {
825+
address poolAddress = msg.sender;
826+
827+
// TODO: convert to modifier `poolIsTrustedByItsSuperToken(pool)`
828+
if (
829+
token.isPool(this, msg.sender) == false ||
830+
ISuperfluidPool(poolAddress).superToken() != token
831+
) {
784832
revert GDA_ONLY_SUPER_TOKEN_POOL();
785833
}
834+
786835
bytes memory eff = abi.encode(token);
787-
_setUIndex(eff, msg.sender, _getUIndex(eff, msg.sender).mappend(p));
788-
_setPoolAdjustmentFlowRate(eff, msg.sender, true, /* doShift? */ p.flow_rate(), t);
836+
_setUIndex(eff, msg.sender, _getUIndex(eff, poolAddress).mappend(p));
837+
_setPoolAdjustmentFlowRate(eff, poolAddress, true, /* doShift? */ p.flow_rate(), t);
789838
return true;
790839
}
791840

792-
function poolSettleClaim(ISuperfluidToken superToken, address claimRecipient, int256 amount)
841+
// succeeds only if `msg.sender` is a pool trusted by `token`
842+
function poolSettleClaim(ISuperfluidToken token, address claimRecipient, int256 amount)
793843
external
794844
returns (bool)
795845
{
796-
if (superToken.isPool(this, msg.sender) == false) {
797-
revert GDA_ONLY_SUPER_TOKEN_POOL();
798-
}
846+
address poolAddress = msg.sender;
799847

800-
_doShift(abi.encode(superToken), msg.sender, claimRecipient, Value.wrap(amount));
801-
return true;
802-
}
803-
804-
function tokenEmitPseudoTransfer(ISuperfluidToken superToken, address from, address to) external {
805-
if (superToken.isPool(this, msg.sender) == false) {
848+
// TODO: convert to modifier `poolIsTrustedByItsSuperToken(pool)`
849+
if (
850+
token.isPool(this, msg.sender) == false ||
851+
ISuperfluidPool(poolAddress).superToken() != token
852+
) {
806853
revert GDA_ONLY_SUPER_TOKEN_POOL();
807854
}
808855

809-
superToken.emitPseudoTransfer(from, to);
856+
_doShift(abi.encode(token), poolAddress, claimRecipient, Value.wrap(amount));
857+
return true;
810858
}
811859

812860
//////////////////////////////////////////////////////////////////////////////////////////////////////

packages/ethereum-contracts/contracts/agreements/gdav1/SuperfluidPool.sol

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -454,10 +454,6 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable {
454454
assert(GDA.appendIndexUpdateByPool(superToken, p, t));
455455
}
456456

457-
if ((oldUnits == 0 || newUnits == 0) && oldUnits != newUnits) {
458-
GDA.tokenEmitPseudoTransfer(superToken, address(this), memberAddr);
459-
}
460-
461457
emit MemberUnitsUpdated(superToken, memberAddr, oldUnits, newUnits);
462458
}
463459

packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidToken.sol

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -431,11 +431,4 @@ interface ISuperfluidToken {
431431
uint256 rewardAmount,
432432
uint256 bailoutAmount
433433
);
434-
435-
/**
436-
* @dev Emit an ERC20.Transfer event with zero amount, helps indexers track token holders.
437-
* @param from The address from which the transfer is happening
438-
* @param to The address to which the transfer is happening
439-
*/
440-
function emitPseudoTransfer(address from, address to) external;
441434
}

packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -376,10 +376,6 @@ abstract contract SuperfluidToken is ISuperfluidToken
376376
);
377377
}
378378

379-
function emitPseudoTransfer(address from, address to) external onlyAgreement {
380-
emit IERC20.Transfer(from, to, 0);
381-
}
382-
383379
/**************************************************************************
384380
* Modifiers
385381
*************************************************************************/

packages/ethereum-contracts/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@superfluid-finance/ethereum-contracts",
33
"description": " Ethereum contracts implementation for the Superfluid Protocol",
4-
"version": "1.14.0",
4+
"version": "1.14.1",
55
"dependencies": {
66
"@decentral.ee/web3-helpers": "0.5.3",
77
"@nomiclabs/hardhat-ethers": "2.2.3",
@@ -11,7 +11,7 @@
1111
"hardhat": "2.26.1"
1212
},
1313
"devDependencies": {
14-
"@d10r/truffle-plugin-verify": "^0.6.11",
14+
"@d10r/truffle-plugin-verify": "^0.7.2",
1515
"@nomiclabs/hardhat-truffle5": "^2.1.0",
1616
"@safe-global/safe-core-sdk": "^3.3.5",
1717
"@safe-global/safe-service-client": "^2.0.3",

packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,19 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste
11051105
vm.stopPrank();
11061106
}
11071107

1108+
function testConnectUnauthorizedPool() public {
1109+
FakePool pool = new FakePool(address(superToken));
1110+
1111+
vm.startPrank(eve);
1112+
vm.expectRevert(IGeneralDistributionAgreementV1.GDA_ONLY_SUPER_TOKEN_POOL.selector);
1113+
sf.host.callAgreement(
1114+
sf.gda,
1115+
abi.encodeWithSelector(IGeneralDistributionAgreementV1.connectPool.selector, pool, ""),
1116+
new bytes(0)
1117+
);
1118+
vm.stopPrank();
1119+
}
1120+
11081121
/*//////////////////////////////////////////////////////////////////////////
11091122
Assertion Functions
11101123
//////////////////////////////////////////////////////////////////////////*/
@@ -1213,3 +1226,24 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste
12131226
assertEq(flowRatesSum, 0, "GDAv1.t: flowRatesSum != 0");
12141227
}
12151228
}
1229+
1230+
/// Fake pool with `operatorConnectMember` not calling back into the GDA as expected
1231+
contract FakePool {
1232+
address internal immutable _SUPER_TOKEN;
1233+
1234+
constructor(address superToken_) {
1235+
_SUPER_TOKEN = superToken_;
1236+
}
1237+
1238+
function superToken() external view returns (address) {
1239+
return _SUPER_TOKEN;
1240+
}
1241+
1242+
function getClaimable(address, uint32) public pure returns (int256) {
1243+
return type(int256).max;
1244+
}
1245+
1246+
function operatorConnectMember(address, bool, uint32) external pure returns (bool) {
1247+
return true;
1248+
}
1249+
}

0 commit comments

Comments
 (0)