Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0d481be
feat: implement pausable streams in SuperGoodDollar contract
blueogin Sep 8, 2026
9dfa902
feat: enhance SuperGoodDollar with error handling and stream manageme…
blueogin Sep 9, 2026
527e4b4
fix: update minting logic to use custom error for cap exceeded in Sup…
blueogin Sep 9, 2026
5b25fb7
feat: add custom error for fee exceeding balance in SuperGoodDollar
blueogin Sep 9, 2026
b7a1fc5
fix: update IdentityV4 test to avoid restoring block timestamp after …
blueogin Sep 10, 2026
7b4cbc8
fix: update test command and adjust Celo fork block lag in BuyGDClone…
blueogin Sep 10, 2026
18bda18
Merge branch 'master' into fix/supergooddollar-pause-streams
blueogin Sep 10, 2026
b1cf30c
fix: increase CELO fork block lag to prevent historical state errors
blueogin Sep 10, 2026
48a2510
fix: add missing closing bracket in SuperGoodDollar test suite
blueogin Sep 10, 2026
3926686
fix: update Celo fork block handling to prevent historical state errors
blueogin Sep 10, 2026
7910cb3
Revert "fix: update Celo fork block handling to prevent historical st…
blueogin Sep 10, 2026
7a0d2cf
fix: enhance swap tests with TWAP minimum amount bounding logic
blueogin Sep 10, 2026
3562960
fix: update updateAgreementData visibility to public in SuperfluidTok…
blueogin Sep 14, 2026
3c0c7d1
fix: simplify flow validation in SuperGoodDollar and update visibilit…
blueogin Sep 14, 2026
9e9a368
Merge branch 'master' into fix/supergooddollar-pause-streams
sirpy Sep 29, 2026
6f4d0fc
add: reduce size
sirpy Sep 29, 2026
d03e702
add: temp update to fix total supply on celo
sirpy Sep 29, 2026
3011b5c
fix: await increaseTime in UBISchemeCycle tests for proper async hand…
blueogin Sep 29, 2026
6c59e3f
feat: introduce SuperGoodDollarFlows library to manage stream pause l…
blueogin Sep 29, 2026
243e49e
add: cover more sf agreements when paused/blocked
sirpy Oct 11, 2026
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
25 changes: 25 additions & 0 deletions contracts/mocks/RevertingTokensSenderMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8;

/// test: ERC1820 implementer whose ERC777 tokensToSend always reverts
contract RevertingTokensSenderMock {
bytes32 private constant _ACCEPT_MAGIC = keccak256("ERC1820_ACCEPT_MAGIC");

function canImplementInterfaceForAddress(
bytes32,
address
) external pure returns (bytes32) {
return _ACCEPT_MAGIC;
}

function tokensToSend(
address,
address,
address,
uint256,
bytes calldata,
bytes calldata
) external pure {
revert("holder blocks burn");
}
}
88 changes: 54 additions & 34 deletions contracts/token/superfluid/SuperGoodDollar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { ERC777Helper } from "@superfluid-finance/ethereum-contracts/contracts/libs/ERC777Helper.sol";
import { FixedSizeData } from "@superfluid-finance/ethereum-contracts/contracts/libs/FixedSizeData.sol";

import { IGoodDollarCustom } from "./ISuperGoodDollar.sol";
import { SuperToken } from "./SuperToken.sol";
import { SuperGoodDollarFlows } from "./SuperGoodDollarFlows.sol";
import "../ERC677.sol";
import "../IFeesFormula.sol";
import "../../Interfaces.sol";
Expand Down Expand Up @@ -90,10 +90,6 @@ contract SuperGoodDollar is
identity = _identity;
formula = _formula;
cap = _cap;
_setNFTProxyContracts(
IPoolAdminNFT(address(0)),
IPoolMemberNFT(address(0))
);
}

// ============ SuperFluid ============
Expand All @@ -112,8 +108,10 @@ contract SuperGoodDollar is
UUPSProxiable._updateCodeAddress(newAddress);
}

/// override Superfluid agreement function in order to make it pausable
/// that is, no new streams can be started when the contract is paused
/// override Superfluid agreement function in order to make it pausable.
/// NOTE: the CFA never calls this, it writes all flow data through
/// updateAgreementData. This guard therefore covers IDA index / subscription
/// creation and GDA pool connections, not streams - see the overrides below.
function createAgreement(
bytes32 id,
bytes32[] calldata data
Expand All @@ -123,6 +121,46 @@ contract SuperGoodDollar is
super.createAgreement(id, data);
}

/// while paused, block IDA writes and opening or increasing a CFA stream, see SuperGoodDollarFlows
function updateAgreementData(
bytes32 id,
bytes32[] calldata data
) public override(ISuperfluidToken, SuperfluidToken) {
if (paused()) SuperGoodDollarFlows.onlyNotIncreasingFlow(_host, id, data);
// the write itself stays with SuperfluidToken, so this override can not drift
// from the upstream implementation
super.updateAgreementData(id, data);
}

/// while paused, or for a blocked account, block the agreement writes that move value
/// out of the account, see SuperGoodDollarFlows
function updateAgreementStateSlot(
address account,
uint256 slotId,
bytes32[] calldata slotData
) public override(ISuperfluidToken, SuperfluidToken) {
bool isPaused = paused();
if (isPaused || isBlocked[account]) {
SuperGoodDollarFlows.onlyNotIncreasingOutflow(
_host,
isPaused,
account,
slotId,
slotData
);
}
super.updateAgreementStateSlot(account, slotId, slotData);
}

/// a blocked account can not pay IDA distributions
function settleBalance(
address account,
int256 delta
) public override(ISuperfluidToken, SuperfluidToken) {
if (delta < 0 && isBlocked[account]) SuperGoodDollarFlows.onlyNotIDA(_host);
super.settleBalance(account, delta);
}

/// failsafe in case we don't want to trust superfluid host for batch operations
function allowHostOperations()
internal
Expand Down Expand Up @@ -290,6 +328,7 @@ contract SuperGoodDollar is
if (cap > 0) {
if (totalSupply() + amount > cap) revert SUPER_GOODDOLLAR_CAP_EXCEEDED();
}

_mint(
msg.sender,
to,
Expand All @@ -304,7 +343,9 @@ contract SuperGoodDollar is

function burnFrom(address account, uint256 amount) public {
uint256 currentAllowance = allowance(account, _msgSender());
if (currentAllowance < amount) revert SUPER_GOODDOLLAR_BURN_EXCEEDS_ALLOWANCE();
if (currentAllowance < amount)
revert SUPER_GOODDOLLAR_BURN_EXCEEDS_ALLOWANCE();

unchecked {
_approve(account, _msgSender(), currentAllowance - amount);
}
Expand All @@ -323,18 +364,17 @@ contract SuperGoodDollar is
*/
function adminBurn(address account, uint256 amount) external override {
_onlyOwner();
// low level burn: no ERC777 hooks, no pause check, no fees
SuperfluidToken._burn(account, amount);
emit Burned(msg.sender, account, amount, new bytes(0), new bytes(0));
emit IERC20.Transfer(account, address(0), amount);
// low level burn: no ERC777 hooks, no pause or blocklist check, no fees
_burnWithoutHooks(msg.sender, account, amount, new bytes(0), new bytes(0));
}

/**
* @dev Blocks/unblocks accounts (eg. known liquidity pools) from sending or receiving G$.
* Owner only.
* Note: this covers the ERC20/ERC677/ERC777 surface (incl. the host batch operations).
* Superfluid streams settle via the agreement layer and are not covered - a stream can still
* credit a blocked account, but that account will not be able to move the funds out.
* On the agreement layer a blocked account can not open or increase CFA/GDA outflows, make
* GDA instant distributions or use the IDA, see SuperGoodDollarFlows. It can still be
* credited by agreements, and outflows opened before blocking keep running.
* @param accounts the addresses to update
* @param blocked true to block, false to unblock
*/
Expand Down Expand Up @@ -409,26 +449,6 @@ contract SuperGoodDollar is
return amount;
}

/**************************************************************************
* ERC20x-specific Functions
*************************************************************************/

function setNFTProxyContracts(
IPoolAdminNFT _poolAdminNFT,
IPoolMemberNFT _poolMemberNFT
) public {
_onlyOwner();
_setNFTProxyContracts(_poolAdminNFT, _poolMemberNFT);
}

function _setNFTProxyContracts(
IPoolAdminNFT _poolAdminNFT,
IPoolMemberNFT _poolMemberNFT
) internal {
poolAdminNFT = _poolAdminNFT;
poolMemberNFT = _poolMemberNFT;
}

/**************************************************************************
* Modifiers
*************************************************************************/
Expand Down
Loading
Loading