Skip to content

feat: implement pausable streams in SuperGoodDollar contract - #303

Open
blueogin wants to merge 12 commits into
masterfrom
fix/supergooddollar-pause-streams
Open

feat: implement pausable streams in SuperGoodDollar contract#303
blueogin wants to merge 12 commits into
masterfrom
fix/supergooddollar-pause-streams

Conversation

@blueogin

@blueogin blueogin commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Description

pause() did not stop streaming. The guard sat on createAgreement, which the CFA never calls when a flow is opened — it writes all flow state through updateAgreementData. Anyone could open, increase, or keep running a G$ stream while the token was paused.

This moves the guard to the path the CFA actually uses, and gates it so that opening or increasing a flow is blocked while paused, but closing, decreasing and liquidating stay available.

The bug

Verified against the installed @superfluid-finance/ethereum-contracts@1.8.1. ConstantFlowAgreementV1 calls, on the token:

  • updateAgreementData — for create, update and delete (line 1402, plus the ACL paths at 862/1079)
  • updateAgreementStateSlot — account flow state (line 1104)
  • settleBalance — line 1097

It never calls createAgreement, and never calls terminateAgreement. A flow is deleted by writing the same agreement data back with flowRate = 0.

So the existing _onlyNotPaused() on createAgreement protected nothing on the streaming path. (It is not dead code — the IDA calls createAgreement for index and subscription creation — so the guard is kept, with a corrected comment.)

The fix

Because create and delete share one code path, a blanket pause on updateAgreementData would also block shutting malicious streams down — the opposite of what is needed during an incident. The guard therefore compares the new flow rate against the stored one and rejects only an increase:

action new vs old rate while paused
open flow +n > 0 blocked
increase flow +2n > +n blocked
decrease flow +n/2 < +n allowed
close flow 0 < +n allowed
liquidation (deleteFlow) 0 < +n allowed

signextend(11, ...) sign-extends from bit 95, discarding the timestamp packed at bits 224+ and recovering the int96 flow rate. An unset slot reads 0, so a fresh flow is +n > 0 and is blocked.

About # (link your issue here)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes.

Checklist:

  • PR title matches follow: (Feature|Bug|Chore) Task Name
  • My code follows the style guidelines of this project
  • I have followed all the instructions described in the initial task (check Definitions of Done)
  • I have performed a self-review of my own code
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have added reference to a related issue in the repository
  • I have added a detailed description of the changes proposed in the pull request. I am as descriptive as possible, assisting reviewers as much as possible.
  • I have added screenshots related to my pull request (for frontend tasks)
  • I have pasted a gif showing the feature.
  • @mentions of the person or team responsible for reviewing proposed changes

Summary by Sourcery

Enforce pausing on new and increased SuperGoodDollar streams while keeping stream shutdown operations available during incidents.

New Features:

  • Prevent opening or increasing SuperGoodDollar streams while the token is paused while preserving stream reduction, closure, and liquidation operations.
  • Add regression coverage for paused CFA streams, flow operator permissions, and IDA index creation.

Bug Fixes:

  • Correct the pause enforcement path so it applies to CFA flow updates, which handle stream creation, modification, and deletion.

Enhancements:

  • Replace several revert strings with custom errors for authorization, cap, balance, fallback, allowance, and pause failures.

Tests:

  • Test paused-stream behavior for opening, increasing, decreasing, closing, and third-party liquidation, along with paused flow-operator and IDA operations.

Chores:

  • Improve Celo fork test stability by increasing the default block lag from the chain head.

Copilot AI lite review requested due to automatic review settings September 8, 2026 16:29

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="contracts/token/superfluid/SuperGoodDollar.sol" line_range="132" />
<code_context>
+			// the CFA packs int96 flowRate at bits [128,224) of the first word
+			bool increased;
+			assembly {
+				let newRate := signextend(11, shr(128, calldataload(data.offset)))
+				let oldRate := signextend(11, shr(128, sload(slot)))
+				increased := sgt(newRate, oldRate)
+			}
+			if (increased) revert SUPER_GOODDOLLAR_PAUSED();
</code_context>
<issue_to_address>
**issue (bug_risk):** `oldRate` is loaded from `sload(slot)`, but `FixedSizeData.storeData` stores the array length at `slot` and stores the first agreement-data word at `slot + 1`. For an existing CFA flow, `oldRate` is therefore the data length (normally `2`), so decreasing a normal positive flow to another positive rate is treated as an increase and reverts while paused.

**Triggers:** When an existing flow is decreased, rather than closed, while the token is paused.

**Suggested fix:** Load the first stored agreement-data word with `sload(add(slot, 1))` before extracting the packed flow rate.

```suggestion
				let oldRate := signextend(11, shr(128, sload(add(slot, 1))))
```
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and while paused, this changes whether Superfluid can create or increase token streams, and a faulty calldata/storage decoding or agreement-slot assumption could either leave transfers running during an incident or block needed flows. Reverting restores the gate, but any tokens transferred by an incorrectly permitted stream may not be recoverable.

Blocking findings: contracts/token/superfluid/SuperGoodDollar.sol:132


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread contracts/token/superfluid/SuperGoodDollar.sol Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new pause guard decodes newRate incorrectly because it reads the calldata array length instead of data[0], so it won’t reliably block opening/increasing streams.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes SuperGoodDollar’s pause enforcement for Superfluid Constant Flow Agreements (CFA) by moving the pause guard onto the updateAgreementData path that CFA actually uses, while preserving the ability to decrease/close/liquidate existing streams during an incident.

Changes:

  • Add a SuperfluidToken hook (_beforeAgreementDataUpdate) invoked from updateAgreementData before writing agreement data.
  • Implement pause-aware CFA flow-rate gating in SuperGoodDollar by comparing the newly written flow rate vs the stored one and rejecting only increases while paused.
  • Add tests to ensure new streams and stream increases revert while paused, but closing streams remains possible.
File summaries
File Description
test/token/SuperGoodDollar.test.ts Adds regression tests for pausing behavior across create/update/delete flow operations.
contracts/token/superfluid/SuperGoodDollar.sol Implements the pause guard on agreement-data updates by decoding and comparing flow rates.
contracts/token/superfluid/SuperfluidToken.sol Introduces a reusable pre-write hook for updateAgreementData to enable token-specific gating.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread contracts/token/superfluid/SuperGoodDollar.sol Outdated
@blueogin
blueogin requested a review from sirpy September 9, 2026 18:17

@sirpy sirpy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can the DAO also have permissions to delete a flow?

if (paused() && msg.sender == _cfaV1()) {
_onlyNotIncreasingFlow(slot, data);
}
FixedSizeData.storeData(slot, data);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why not use super.updateAgreementData?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@hellwolf
SuperfluidToken.updateAgreemenData is external, so super. can't reach them.
If you are ok, I can make it public but there is another reason:
Using public + super costs more contract size which I do not want

@blueogin

Copy link
Copy Markdown
Collaborator Author

@sirpy
this pr is not related to permission.
DAO can pause/unpause token, can not delete flow directly

* 2. added allowHostOperations to disable host actions by G$ governance in case of security issues
* 3. made updateAgreementData virtual (so SuperGoodDollar can gate streams while paused)
*/
abstract contract SuperfluidToken is ISuperfluidToken {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I am just curious here: Does the codebase keep tracking the upstream code? Since I am wary of divergence of code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants