Skip to content

feat: support contract ops task layout#177

Open
0xth4nh wants to merge 10 commits into
mainfrom
feat-reorg-contracts-ops
Open

feat: support contract ops task layout#177
0xth4nh wants to merge 10 commits into
mainfrom
feat-reorg-contracts-ops

Conversation

@0xth4nh

@0xth4nh 0xth4nh commented Jun 20, 2026

Copy link
Copy Markdown

Summary

This updates task-signing-tool for the new Contract Ops active EVM task layout.

The tool now discovers one or more active EVM task folders and keeps task, network, and signer profile selection explicit in the UI:

  • EVM root: active/evm
  • task folders: active/evm/tasks/<task-id>
  • validation configs: active/evm/tasks/<task-id>/config/<network>/validations
  • task-origin signed directory: active/evm/tasks/<task-id>/config/<network>
  • task-origin signatures: active/evm/tasks/<task-id>/config/<network>/signatures

What changed

Task discovery

  • Removed old root-level network/task discovery.
  • Added discovery for active task folders under active/evm/tasks/<task-id>.
  • getUpgradeOptions(network) now returns active tasks that have validation configs for the selected network.
  • Task display metadata is read from active/evm/tasks/<task-id>/config/<network>/README.md, active/evm/tasks/<task-id>/README.md, or active/evm/README.md when present.

Signer flow

  • Added an explicit network selection step after task selection.
  • The signer flow is now: select task, select network, select user profile, review/validate, Ledger sign, confirmation.
  • The task list groups ready-to-sign network configs by task, then the network step shows the networks available for that selected task.
  • upgradeId remains part of the app contract because multiple active tasks can exist at the same time.

Validation flow

  • Validation now keys off upgradeId, network, and selected user config.
  • Validation configs are loaded from active/evm/tasks/<task-id>/config/<network>/validations/<user>.json.
  • Simulations run from active/evm, so validation cmd values should be relative to that directory.
  • Task-origin validation covers the selected network config directory: active/evm/tasks/<task-id>/config/<network>.
  • Signature files are read from active/evm/tasks/<task-id>/config/<network>/signatures.
  • The nested signatures/ folder is excluded from deterministic tarballs so generated signatures do not alter the signed payload.

API changes

  • /api/upgrades?readyToSign=true returns ready-to-sign active task/network pairs.
  • /api/upgrade-config accepts network and upgradeId.
  • /api/validate accepts upgradeId, network, and userType.
  • /api/install-deps accepts network and upgradeId, verifies the selected task exists, and runs make deps from active/evm.

Docs and scripts

  • Updated README examples to document the active/evm/tasks/<task-id>/config/<network> layout.
  • Updated task-origin signing examples for per-task, per-network signatures.
  • Updated validation generation help/examples to write configs under the active task layout.

Tests

  • Updated route and deployment tests for the active task layout.
  • Added coverage for multiple active task folders.
  • Added coverage that old root-level task directories are no longer discovered.
  • Updated task-origin tarball tests to ensure nested signatures/ directories are excluded.

Verification

  • npm test
  • npm run lint
  • npm run build

All checks passed locally.

Co-authored-by: Codex <codex-noreply@coinbase.com>
@cb-heimdall

cb-heimdall commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

0xth4nh and others added 9 commits June 20, 2026 11:20
Co-authored-by: Codex <codex-noreply@coinbase.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>

# Conflicts:
#	__tests__/genTaskOriginSig.test.ts
assertWithinDir(upgradePath, CONTRACT_DEPLOYMENTS_ROOT);

): Promise<{ cfg: TaskConfig; scriptPath: string; taskOriginDir: string; signatureDir: string }> {
const scriptPath = assertWithinDir(

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.

task-origin validation no longer attests to the code that is actually executed. This signs/verifies active/evm/tasks/<task>/config/<network>, but cfg.cmd runs from shared active/evm, leaving script/, foundry.toml, Makefile, lib/, and other execution-time inputs outside the signed payload. A post-signature change to those files can affect or fake validation output while task-origin signatures still pass. Please either include all execution-affecting inputs in the signed payload, execute only from signed per-task content, or verify a signed manifest for the shared code/deps before simulation.

Comment thread src/lib/state-diff.ts

const { command, args, env: envAssignments } = this.extractCommandDetails(forgeCmdParts);
const spawnEnv = { ...process.env, ...envAssignments };
const spawnEnv = { ...process.env, ...envAssignments, RECORD_STATE_DIFF: 'true' };

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.

With this PR, every validation in a checkout runs from active/evm, while StateDiffClient still reads and deletes a fixed stateDiff.json under that workdir. Even if the operational model is one branch/check-out per task, concurrent validations in the same signer-tool instance, for example two tabs, retries, profiles, or networks, can overwrite or delete each other’s diff and cause one request to parse another request’s output. Please use a unique state-diff output path per validation, isolate each validation in a temp workdir, or enforce a process-level validation lock.

return {
cfg: parsedConfig.config,
scriptPath,
taskOriginDir: networkConfigDir,

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.

Because the signed task-origin directory is now config/<network>, createDeterministicTarball will derive names like mainnet.tar / sepolia.tar in process.cwd(). Multiple tasks on the same network can collide during signing or verification, especially concurrently. Please create tarballs in a unique temp file/dir, ideally with cleanup after use.

);
const configFileName = `${opts.taskConfigFileName}.json`;
const configPath = path.join(upgradePath, 'validations', configFileName);
const configPath = path.join(configDir, configFileName);

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.

taskConfigFileName is request-controlled via /api/validate, and this only asserts the resulting path is inside the repo root. A value containing ../ can escape the selected validations directory while still remaining under CONTRACT_DEPLOYMENTS_ROOT, decoupling the validation config being executed from the selected task/network signatures. Please reject path separators/.. for upgradeId and userType, and assert configPath is within configDir rather than only within the repo root.

continue;
}

grouped.set(upgrade.id, {

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.

Grouping by upgrade.id keeps the first Upgrade object and only appends later networks. Since the API can populate metadata from config/<network>/README.md, selecting a different network can still show the first network's title/description/status/date in the task card, summary, and confirmation. Please store metadata per network and swap selectedUpgrade after network selection, or render only task-level metadata before the network is selected.

onChange?: () => void;
}

const formatNetworkName = (network: NetworkType) =>

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.

Nit: this formatNetworkName helper is duplicated in the new network components and deployments.ts. If this formatting is now shared UI behavior, consider moving it to one helper to avoid drift.

Comment thread src/app/page.tsx
};

const handleUpgradeSelection = (upgrade: Upgrade) => {
const handleUpgradeSelection = (upgrade: Upgrade, networks: NetworkType[]) => {

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.

Test gap: the split task/network flow has no frontend regression coverage. Please add tests for grouping by task id, preserving the correct network list, clearing downstream state when task/network changes, and ensuring validation receives the selected upgradeId and network.

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.

3 participants