Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions contracts/token/superfluid/ISuperGoodDollar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ interface IGoodDollarCustom {

function isPauser(address _pauser) external view returns (bool);

function isBlocked(address account) external view returns (bool);

function owner() external view returns (address);

// state changing functions
Expand All @@ -57,6 +59,8 @@ interface IGoodDollarCustom {

function adminBurn(address account, uint256 amount) external;

function setBlocked(address[] calldata accounts, bool blocked) external;

function addMinter(address _minter) external;

function renounceMinter() external;
Expand Down
66 changes: 50 additions & 16 deletions contracts/token/superfluid/SuperGoodDollar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ contract SuperGoodDollar is
IGoodDollarCustom // without storage
{
error SUPER_GOODDOLLAR_PAUSED();
error SUPER_GOODDOLLAR_BLOCKED();
error SUPER_GOODDOLLAR_NOT_PAUSER();
error SUPER_GOODDOLLAR_NOT_MINTER();
error SUPER_GOODDOLLAR_CAP_EXCEEDED();
error SUPER_GOODDOLLAR_BURN_EXCEEDS_ALLOWANCE();
error SUPER_GOODDOLLAR_FALLBACK_FAILED();
error SUPER_GOODDOLLAR_FEE_EXCEEDS_BALANCE();

// IMPORTANT! Never change the type (storage size) or order of state variables.
// If a variable isn't needed anymore, leave it as padding (renaming is ok).
Expand All @@ -37,6 +44,8 @@ contract SuperGoodDollar is
IIdentity public identity;
uint256 public cap;
bool public disableHostOperations;
/// @dev accounts (eg. known DEX pools) that can neither send nor receive G$
mapping(address => bool) public isBlocked;
// Append additional state variables here!

// ============== constants and immutables ==============
Expand All @@ -45,6 +54,8 @@ contract SuperGoodDollar is
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

event BlockedUpdated(address indexed account, bool blocked);

event TransferFee(
address from,
address to,
Expand Down Expand Up @@ -199,10 +210,8 @@ contract SuperGoodDollar is
bool res = super._transferFrom(msg.sender, msg.sender, to, netAmount);
emit ERC677.Transfer(msg.sender, to, netAmount, data);
if (isContract(to)) {
require(
contractFallback(to, netAmount, data),
"Contract fallback failed"
);
if (!contractFallback(to, netAmount, data))
revert SUPER_GOODDOLLAR_FALLBACK_FAILED();
}
return res;
}
Expand Down Expand Up @@ -261,6 +270,7 @@ contract SuperGoodDollar is
bytes memory operatorData
) internal virtual override {
_onlyNotPaused();
_onlyNotBlocked(from);
// handing over to the wrapper of SuperToken.transferFrom
super._burn(operator, from, amount, userData, operatorData);
}
Expand All @@ -275,12 +285,10 @@ contract SuperGoodDollar is
uint256 amount
) public override(IGoodDollarCustom) onlyMinter returns (bool) {
_onlyNotPaused();
_onlyNotBlocked(to);

if (cap > 0) {
require(
totalSupply() + amount <= cap,
"Cannot increase supply beyond cap"
);
if (totalSupply() + amount > cap) revert SUPER_GOODDOLLAR_CAP_EXCEEDED();
}
_mint(
msg.sender,
Expand All @@ -296,7 +304,7 @@ contract SuperGoodDollar is

function burnFrom(address account, uint256 amount) public {
uint256 currentAllowance = allowance(account, _msgSender());
require(currentAllowance >= amount, "ERC20: burn amount exceeds allowance");
if (currentAllowance < amount) revert SUPER_GOODDOLLAR_BURN_EXCEEDS_ALLOWANCE();
unchecked {
_approve(account, _msgSender(), currentAllowance - amount);
}
Expand All @@ -321,6 +329,27 @@ contract SuperGoodDollar is
emit IERC20.Transfer(account, address(0), amount);
}

/**
* @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.
* @param accounts the addresses to update
* @param blocked true to block, false to unblock
*/
function setBlocked(address[] calldata accounts, bool blocked) external {
_onlyOwner();
for (uint256 i; i < accounts.length; ) {
address account = accounts[i];
isBlocked[account] = blocked;
emit BlockedUpdated(account, blocked);
unchecked {
++i;
}
}
}

/**
* @dev Gets the current transaction fees
* @return fee senderPays that represents the current transaction fees and bool true if sender pays the fee or receiver
Expand Down Expand Up @@ -356,7 +385,8 @@ contract SuperGoodDollar is
// internal functions

/**
* @dev Sends transactional fees to feeRecipient address from given address
* @dev Enforces the blocklist and sends transactional fees to feeRecipient address from given address.
* Called by every G$ movement (ERC20/ERC677/ERC777 and the superfluid host batch operations).
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
* @param account The account that sends the fees
* @param amount The amount to subtract fees from
* @return an uint256 that represents the given amount minus the transactional fees
Expand All @@ -366,12 +396,12 @@ contract SuperGoodDollar is
address recipient,
uint256 amount
) internal returns (uint256) {
_onlyNotBlocked(account);
_onlyNotBlocked(recipient);
(uint256 txFees, bool senderPays) = getFees(amount, account, recipient);
if (txFees > 0 && !identity.isDAOContract(msg.sender)) {
require(
senderPays == false || amount + txFees <= balanceOf(account),
"Not enough balance to pay TX fee"
);
if (senderPays && amount + txFees > balanceOf(account))
revert SUPER_GOODDOLLAR_FEE_EXCEEDS_BALANCE();
super._transferFrom(account, account, feeRecipient, txFees);
emit TransferFee(account, recipient, amount, txFees, senderPays);
return senderPays ? amount : amount - txFees;
Expand Down Expand Up @@ -408,15 +438,19 @@ contract SuperGoodDollar is
}

function _onlyPauser() internal view {
require(hasRole(PAUSER_ROLE, msg.sender), "not pauser");
if (!hasRole(PAUSER_ROLE, msg.sender)) revert SUPER_GOODDOLLAR_NOT_PAUSER();
}

function _onlyNotPaused() internal view {
if (paused()) revert SUPER_GOODDOLLAR_PAUSED();
}

function _onlyNotBlocked(address account) internal view {
if (isBlocked[account]) revert SUPER_GOODDOLLAR_BLOCKED();
}

modifier onlyMinter() {
require(hasRole(MINTER_ROLE, msg.sender), "not minter");
if (!hasRole(MINTER_ROLE, msg.sender)) revert SUPER_GOODDOLLAR_NOT_MINTER();
_;
}
}
30 changes: 30 additions & 0 deletions scripts/upgrades/blocked-pools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"production-celo": {
"blocked": true,
"pools": [
"0x6619871118D144c1c28eC3b23036FC1f0829ed3a",
"0x059ee811414230d1Fb157878D2b491240F4D8d3B",
"0x991f1AA7e0901f9AB3d583846bf5BE0EbACE1d7f",
"0x3D9e27C04076288eBfdC4815b4f6d81b0ED1b341",
"0x9491d57c5687AB75726423B55AC2d87D1cDa2c3F",
"0x31f9DeE850B4284b81B52b25a3194F2FC8fF18CF",
"0x07F86B39728Be613062bc7413FC2CA7293Eef022",
"0x25878951ae130014E827e6f54fd3B4CCa057a7e8",
"0xCB037f27eB3952222810966e28E0cEB650c65CD9",
"0x8b393470bef8bb27a9a5169531b4eBA5209b0b26",
"0xA0BeF7ff637c10b9Ec67a00687B4d4364A7f1c55",
"0x288dc841A52FCA2707c6947B3A777c5E56cd87BC",
"0xC10eE9031F2a0B84766A86B55a8D90F357910fb4",
"0xb92fe925DC43a0ECdE6c8b1a2709c170Ec4fFf4f",
"0x89c6340B1a1f4b25D36cd8B063D49045caF3f818"
]
},
"staging-celo": {
"blocked": true,
"pools": []
},
"development-celo": {
"blocked": true,
"pools": []
}
}
Loading
Loading