Skip to content

feat: AdminBurnExecutor — auditable one-shot scheme for burning illegitimate G$ - #304

Closed
blueogin wants to merge 2 commits into
masterfrom
feat/admin-burn-executor
Closed

feat: AdminBurnExecutor — auditable one-shot scheme for burning illegitimate G$#304
blueogin wants to merge 2 commits into
masterfrom
feat/admin-burn-executor

Conversation

@blueogin

@blueogin blueogin commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Description

Adds the on-chain executor and the operational scripts for running an adminBurn
sweep against a list of addresses holding illegitimate G$, plus the USD refund
ledger the sweep produces.

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

Enable auditable one-shot administrative sweeps that burn illegitimate G$ and produce an off-chain USD refund ledger.

New Features:

  • Add owner-controlled adminBurn support to destroy illegitimate G$ balances, including while paused and without holder cooperation.
  • Add a one-shot, immutable executor that burns an audited address list through Controller permissions and records per-account USD refund obligations.
  • Add deployment, registration, execution, cancellation, preflight, verification, and refund-ledger scripts for operating administrative burn sweeps.

Enhancements:

  • Remove the token’s generic recovery function while adding auditable safeguards around administrative burns, including balance and total-burn verification and automatic scheme deregistration.

Tests:

  • Add coverage for successful administrative burns, owner authorization, paused-token operation, and insufficient-balance failures.

Copilot AI lite review requested due to automatic review settings September 9, 2026 16:21
@blueogin blueogin closed this Sep 9, 2026

@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 security issue, and 2 other issues

Security issues:

  • Detected calls to child_process from a function argument address. This could lead to a command injection if the input is user controllable. Try to avoid calls to child_process, and if it is needed ensure user input is correctly sanitized or sandboxed. (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="scripts/upgrades/admin-burn-executor-deploy.ts" line_range="203-204" />
<code_context>
+    totalRefundUSD: ethers.utils.formatEther(onChainUSD),
+    owner: await executor.owner()
+  });
+  if (!onChainGD.eq(totalGD) || !onChainUSD.eq(totalUSD) || !count.eq(entries.length))
+    throw new Error("deployed executor does not match the local burn list");
+
+  // not registered yet, so canExecute is expected to be false on the scheme check
</code_context>
<issue_to_address>
**issue (broader_impact):** When `EXECUTOR` points to an existing executor, the script verifies only the aggregate G$ amount, aggregate refund amount, and entry count. A different executor with the same totals and count passes validation, so guardians can register and execute an unaudited address/amount list while the displayed burn list and resulting refund ledger refer to different accounts.

**Triggers:** When the operator uses `EXECUTOR=0x..` to skip deployment and the existing executor has different tuples but matching totals and length.

**Suggested fix:** Fetch and compare every deployed `(account, gdAmount, refundUSD)` entry against the local burn list, or require an explicit hash of the audited list.
</issue_to_address>

### Comment 2
<location path="contracts/utils/AdminBurnExecutor.sol" line_range="68" />
<code_context>
+		require(_entries.length > 0, "empty burn list");
+
+		controller = _controller;
+		token = _token;
+		owner = _owner;
+
+		uint256 gd;
</code_context>
<issue_to_address>
**issue (bug_risk):** The constructor accepts a zero `_owner`, leaving the executor permanently unable to execute or cancel because both functions require `msg.sender == owner`; if the contract is registered as a scheme, its permission cannot be removed through the executor and remains stranded until an external governance action unregisters it.

**Triggers:** When deployment is accidentally supplied with `address(0)` as `_owner`.

**Suggested fix:** Require `_owner != address(0)` in the constructor.

```suggestion
		require(_owner != address(0), "owner required");
		require(_entries.length > 0, "empty burn list");
```
</issue_to_address>

### Comment 3
<location path="scripts/upgrades/admin-burn-executor-deploy.ts" line_range="300" />
<code_context>
    execSync(cmd, { stdio: "inherit" });
</code_context>
<issue_to_address>
**security (javascript.lang.security.detect-child-process):** Detected calls to child_process from a function argument `address`. This could lead to a command injection if the input is user controllable. Try to avoid calls to child_process, and if it is needed ensure user input is correctly sanitized or sandboxed.

*Source: opengrep*
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 3 findings to address first, and if the fixed list or authorization flow is wrong, the executor can irreversibly destroy listed holders' G$ balances, while the recorded USD refunds do not restore those tokens and reverting the deployment cannot undo a completed burn. The operation is bounded and requires owner execution plus guardian scheme registration, but any mistaken burn would require separate compensation or minting rather than a rerun.

Blocking findings: scripts/upgrades/admin-burn-executor-deploy.ts:204, contracts/utils/AdminBurnExecutor.sol:68, scripts/upgrades/admin-burn-executor-deploy.ts:300


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

Comment on lines +203 to +204
if (!onChainGD.eq(totalGD) || !onChainUSD.eq(totalUSD) || !count.eq(entries.length))
throw new Error("deployed executor does not match the local burn list");

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.

issue (broader_impact): When EXECUTOR points to an existing executor, the script verifies only the aggregate G$ amount, aggregate refund amount, and entry count. A different executor with the same totals and count passes validation, so guardians can register and execute an unaudited address/amount list while the displayed burn list and resulting refund ledger refer to different accounts.

Triggers: When the operator uses EXECUTOR=0x.. to skip deployment and the existing executor has different tuples but matching totals and length.

Suggested fix: Fetch and compare every deployed (account, gdAmount, refundUSD) entry against the local burn list, or require an explicit hash of the audited list.

) {
require(address(_controller) != address(0), "controller required");
require(address(_token) != address(0), "token required");
require(_entries.length > 0, "empty burn list");

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.

issue (bug_risk): The constructor accepts a zero _owner, leaving the executor permanently unable to execute or cancel because both functions require msg.sender == owner; if the contract is registered as a scheme, its permission cannot be removed through the executor and remains stranded until an external governance action unregisters it.

Triggers: When deployment is accidentally supplied with address(0) as _owner.

Suggested fix: Require _owner != address(0) in the constructor.

Suggested change
require(_entries.length > 0, "empty burn list");
require(_owner != address(0), "owner required");
require(_entries.length > 0, "empty burn list");

const cmd = `yarn hardhat verify --contract contracts/utils/AdminBurnExecutor.sol:AdminBurnExecutor --constructor-args ${argsFile} ${address} --network ${network.name}`;
console.log("\n=== verifying ===\n" + cmd);
try {
execSync(cmd, { stdio: "inherit" });

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.

security (javascript.lang.security.detect-child-process): Detected calls to child_process from a function argument address. This could lead to a command injection if the input is user controllable. Try to avoid calls to child_process, and if it is needed ensure user input is correctly sanitized or sandboxed.

Source: opengrep

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 operational scripts include confirmed runtime-breaking issues (BigNumber underflow in supply delta reporting and incorrect fork-network detection) that should be fixed before approval.

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

Pull request overview

This PR introduces an auditable, one-shot on-chain scheme (AdminBurnExecutor) and supporting operational scripts to burn illegitimate G$ balances via a new owner-only adminBurn function on SuperGoodDollar, and to output an off-chain USD refund ledger.

Changes:

  • Add adminBurn(address,uint256) to SuperGoodDollar (owner-only, works while paused, bypasses ERC777 hooks) and expose it via ISuperGoodDollar.
  • Add AdminBurnExecutor scheme contract plus deploy/execute scripts (and a sample burn-list JSON) for guarded, one-time burn sweeps with an emitted refund ledger.
  • Add tests covering adminBurn behavior (authorization, paused operation, supply reduction, insufficient balance).
File summaries
File Description
test/token/SuperGoodDollar.test.ts Adds unit tests for adminBurn authorization and supply/balance effects.
scripts/upgrades/admin-burn-list.example.json Provides an example input format for burn/refund entries.
scripts/upgrades/admin-burn-executor-execute.ts Adds an execution script to run the executor and write the refund ledger.
scripts/upgrades/admin-burn-executor-deploy.ts Adds a deploy+registration proposal script for the executor.
contracts/utils/AdminBurnExecutor.sol Implements one-shot scheme that burns a fixed audited list and emits refund data.
contracts/token/superfluid/SuperGoodDollar.sol Adds adminBurn and removes the token recovery helper.
contracts/token/superfluid/ISuperGoodDollar.sol Exposes adminBurn in the interface.
Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 3
  • Review effort level: Lite

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

Comment on lines +31 to +35
let { name: networkName } = network;
networkName = networkName.replace("-fork", "");

const isSimulation = ["hardhat", "fork", "localhost"].includes(network.name);

Comment on lines +140 to +143
supplyBefore: ethers.utils.formatEther(supplyBefore),
supplyAfter: ethers.utils.formatEther(supplyAfter),
supplyDelta: ethers.utils.formatEther(supplyAfter.sub(supplyBefore)),
stillRegistered: await ctrl.isSchemeRegistered(executorAddress, release.Avatar),
Comment on lines +23 to +27
* Flow:
* 1. deploy with the full list
* 2. guardians register this address as a scheme (genericCall permission)
* 3. anyone (or `owner`, see `execute`) calls `execute()`
* 4. the contract unregisters itself, permanently disarming it
@blueogin
blueogin deleted the feat/admin-burn-executor branch September 10, 2026 13:10
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.

2 participants