Skip to content

[VPD-1036] add Safe EBrake TX generator and fork simulation tooling#63

Merged
fred-venus merged 21 commits into
developfrom
feat/VPD-1036
Apr 21, 2026
Merged

[VPD-1036] add Safe EBrake TX generator and fork simulation tooling#63
fred-venus merged 21 commits into
developfrom
feat/VPD-1036

Conversation

@Debugger022
Copy link
Copy Markdown
Contributor

Summary

  • Adds an interactive CLI to generate Gnosis Safe TX Builder JSON for EBrake emergency actions, covering all supported operations (pause, CF decrease, cap changes, flash loan controls)
  • Adds a Mocha fork simulation script that replays the generated batch against an archive node, with an ACM pre-flight authorization check and per-operation state assertions
  • Extends hardhat config with a --fork <network> task flag and hardfork history for all Venus Protocol chains to support hardhat_reset on non-mainnet chains

Changes

helpers/generateSafeEBrakeJson.ts (new)

  • Interactive CLI: prompts for network, operation, markets, parameters, and Safe address
  • Generates safeEBrakeTxBuilder.json (import into Gnosis Safe UI) and safeEBrakeTxMetadata.json (audit trail)
  • Supports all EBrake operations across IL and BSC Diamond comptrollers

scripts/simulateSafeEBrakeTx.ts (new)

  • Reads the generated JSON pair and forks at the recorded block number
  • ACM pre-flight: checks isAllowedToCall for every function in the batch before impersonating the Safe — fails fast if unauthorized
  • Executes all transactions via typed EBrake contract calls with readable Unauthorized error decoding
  • Generates individual named it() blocks per market/action/cap assertion for clear ✔ / ✘ output

hardhat.config.ts

  • --fork <network> override on TASK_TEST sets FORKED_NETWORK env var
  • chains config with cancun: 0 for BSC, opBNB, Arbitrum, OP, Base, Unichain (mainnet + testnet)

Test plan

  • Generate a batch: npx hardhat run helpers/generateSafeEBrakeJson.ts --network bsctestnet
  • Simulate it: npx hardhat test scripts/simulateSafeEBrakeTx.ts --fork bsctestnet
  • Verify each assertion shows as a named ✔ line in Mocha output
  • To test the ACM guard: use a Safe address that lacks permissions and confirm the suite aborts before execution with a clear error

Adds a CLI tool to generate Gnosis Safe TX Builder JSON for EBrake
emergency actions, and a Mocha fork test to simulate and verify the
batch against an archive node. The simulation includes an ACM pre-flight
check that rejects unauthorized callers before any execution attempt.
Replaces the single aggregated state test with dynamically generated
individual it() blocks so each market/action check gets its own named
✔ / ✘ line in Mocha output, making failures immediately identifiable
without digging into assertion messages.
@Debugger022 Debugger022 self-assigned this Apr 15, 2026
- Generator now accepts multiple operations in one run (upfront multi-select
  like `1,2,5`) and emits a single Safe batch. Metadata schema upgraded to
  `operations: string[]`; simulator reads both new and legacy shapes.
- Per-market value input (CF/caps) gets a 3-option UX: uniform, per-market
  CLI, or JSON file (auto-generates a template pre-filled with current
  on-chain values on first file-mode run).
- Market selection redesigned: 2-option menu (manual CLI or pick-by-symbol
  from `helpers/data/markets.json`); markets file is refetched on every run
  so it always matches the active network.
- Simulator relocated from `scripts/` to `tests/hardhat/Fork/` so it runs
  under the project's standard hardhat test path.
- Terminal colorization in the generator: red (errors), yellow (warnings),
  green (success), cyan (section headers), bold (final CTA). Zero-dep,
  respects `NO_COLOR` and non-TTY.
- Dead code removed: unused EBRAKE_ABI entries, duplicate OUTPUT JSDoc,
  unused `ExportResult.label`, dead `receipt.status === 1` assertion,
  incorrect `Unauthorized` error contract-address source.
- Generated artifacts moved into a single gitignored `helpers/data/` folder;
  no per-network prefix, no history archive — operator-local only.
- New `README-EBrake.md` — operator guide with quickstart, end-to-end
  walkthrough, operation cheatsheet, Safe UI signing steps, troubleshooting,
  and network support matrix.
…t glob

- Relocate scripts/simulateSafeEBrakeTx.ts (was tests/hardhat/Fork/...)
  Reason: paths.tests: "./tests" means anything under that dir runs on
  `yarn test` / CI lint; the simulator needs operator-generated JSON
  artifacts (gitignored) and would always fail there
- Fix path resolution: ../../../helpers/data → ../helpers/data
- Update the generator's end-of-run "Simulate:" hint
- Update README-EBrake.md (Quickstart + walkthrough + ASCII diagram)
@Debugger022 Debugger022 marked this pull request as ready for review April 15, 2026 13:34
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

Comment thread helpers/generateSafeEBrakeJson.ts
Comment thread scripts/simulateSafeEBrakeTx.ts Outdated
Comment thread hardhat.config.ts
Comment thread scripts/simulateSafeEBrakeTx.ts
Comment thread ReadMe_EBrake_Safe_TX Generator.md Outdated
Comment thread ReadMe_EBrake_Safe_TX Generator.md Outdated
Comment thread ReadMe_EBrake_Safe_TX Generator.md Outdated
Comment thread ReadMe_EBrake_Safe_TX Generator.md Outdated
Comment thread ReadMe_EBrake_Safe_TX Generator.md Outdated

---

## 4. Install
Copy link
Copy Markdown
Contributor

@fred-venus fred-venus Apr 17, 2026

Choose a reason for hiding this comment

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

no way we will need this to install it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

8b77fe0 Removed

Comment thread ReadMe_EBrake_Safe_TX Generator.md Outdated
@fred-venus
Copy link
Copy Markdown
Contributor

fred-venus commented Apr 17, 2026

also i prefer

  1. we only load the markets 1 time (here in screenshot its 3), further loading let's use cache and go with existing file
  2. except Found 44 listed market(s) Markets saved to /home/node/venus-repos/venus-periphery/helpers/data/markets.json (44 markets), let display the markets name-addr list so that ppl dont bother to open that file
image

@fred-venus
Copy link
Copy Markdown
Contributor

CleanShot 2026-04-17 at 15 44 21@2x

possible we catch it and re-prompt to allow user specify the file path here, just like when i put an error num

@fred-venus
Copy link
Copy Markdown
Contributor

CleanShot 2026-04-17 at 15 53 17@2x

can consider list all the available isolated pools otherwise user will need to check one by one, what i thinking:

if 5 or 8 (pool id level cf setting or borrow-pause ) being included in the list, load all the available isolated pool and check the assets list for each, after user input its target markets to set, we filter out those unrelated and allow user to input pool id with necessary info

…oling

- Default Safe address (0xCCa5...) shown as prompt default; Enter accepts it
- Remove broken ACM pre-flight check — isAllowedToCall requires msg.sender to
  be the protected contract, not an external caller; ACM_ABI cleaned up too
- Clarify simulateSafeEBrakeTx.ts is optional vs Safe Wallet native simulation
- Set both FORKED_NETWORK and HARDHAT_FORK_NETWORK when --fork flag is used so
  Hardhat's built-in forking config is also activated
- Production commands + prerequisites moved to top
- Remove testnet quickstart, install, flow diagram, generate detail,
  simulate detail, sign detail, and operation cheatsheet sections
- Move 'What is EBrake?' to bottom as reference
- Remove stale ACM check failed troubleshooting entry
- Align safety principle #1 with optional simulation stance
@Debugger022
Copy link
Copy Markdown
Contributor Author

Debugger022 commented Apr 17, 2026

#63 (comment)
@fred-venus two concerns with caching markets.json:

  1. Cross-network mix-up. The file is a flat {symbol: address} map with no network field. If an operator runs for bsctestnet, then later for bscmainnet without deleting the file, we'd silently load testnet addresses into a mainnet batch. In an emergency that's the worst possible footgun.

  2. Stale cache hides new markets. If a market is listed on-chain after the file was generated (could be weeks old), a cached run would never surface it — the operator wouldn't know it's missing unless they already knew to look.

On point printing the name↔address list in-terminal: I tried this earlier and with 40+ markets the output got long and cluttered, which felt off to me. Opening markets.json once seemed cleaner.

Happy to revisit either if you still prefer — could do per-network filenames (markets_<network>.json) or embed a network field and validate on load to remove the mix-up risk, and print the list if you'd rather have it inline. Let me know which direction you want.


(edit) fixed: a992515

- Add marketsCache and bscPoolsCache on StepContext
- Populate cache on first step that needs markets or pool layout
- Subsequent steps reuse in-memory data, skipping chain calls
- Scope is per-run: next invocation refetches, so new markets surface
- No persisted state, so cross-network mix-ups remain impossible
- Print "Available markets: ..." inline after fetch so operators don't open markets.json
Comment thread helpers/generateSafeEBrakeJson.ts Outdated
Comment on lines +474 to +478
if (!ethers.utils.isAddress(entered)) {
console.error(red("Invalid address!"));
rl.close();
process.exit(1);
}
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.

should ask again ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed: c26a769

Comment thread helpers/generateSafeEBrakeJson.ts Outdated

// ─── Network helpers ─────────────────────────────────────────────────────────

const getEBrakeAddress = async (networkName: string): Promise<string> => {
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.

I think the proxy contract address is unlikely to change. Would it be better to define it as a constant (or an array) in the config file and reference it from there?

Copy link
Copy Markdown
Contributor Author

@Debugger022 Debugger022 Apr 17, 2026

Choose a reason for hiding this comment

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

Both approaches work here. The deployment artifact (deployments/<network>/EBrake.json) is already the canonical source of truth in this repo — BSC mainnet and testnet auto-resolve today, and future networks will too without a code change. That said, adding a EBRAKE_ADDRESSES constant map at the top as the primary lookup (with artifact as fallback, manual prompt as last resort) has a real benefit: known addresses are visible at a glance without digging into files. Happy to go either way — what's your preference?

Comment thread helpers/generateSafeEBrakeJson.ts Outdated
Comment thread helpers/generateSafeEBrakeJson.ts Outdated
const filePath = entered.length > 0 ? entered : defaultPath;

if (!fs.existsSync(filePath)) {
console.log(`\n${filePath} not found — generating template with current on-chain ${cfg.kind} values...`);
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.

The user might provide an incorrect path by mistake. Should we re-prompt them to enter the path again in that case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Both approaches make sense. Most operators will use the default path — if they type a custom path, it's likely intentional, so auto-generating a template there is fine. But a quick y/n confirmation before generating adds a safety net for the rare typo case without much friction. 659af81

Comment thread ReadMe_EBrake_Safe_TX Generator.md
@fred-venus
Copy link
Copy Markdown
Contributor

CleanShot 2026-04-20 at 14 16 00@2x

can see for aave, there are 2 pools on bsc (core and isolated), but i was prompted only once here, this makes option 2 "Decrease collateral factor — all pools (decreaseCF)" and option5 "Decrease CF — specific pool (decreaseCF with poolId)" making no difference

@fred-venus
Copy link
Copy Markdown
Contributor

#63 (comment) @fred-venus two concerns with caching markets.json:

  1. Cross-network mix-up. The file is a flat {symbol: address} map with no network field. If an operator runs for bsctestnet, then later for bscmainnet without deleting the file, we'd silently load testnet addresses into a mainnet batch. In an emergency that's the worst possible footgun.
  2. Stale cache hides new markets. If a market is listed on-chain after the file was generated (could be weeks old), a cached run would never surface it — the operator wouldn't know it's missing unless they already knew to look.

On point printing the name↔address list in-terminal: I tried this earlier and with 40+ markets the output got long and cluttered, which felt off to me. Opening markets.json once seemed cleaner.

Happy to revisit either if you still prefer — could do per-network filenames (markets_<network>.json) or embed a network field and validate on load to remove the mix-up risk, and print the list if you'd rather have it inline. Let me know which direction you want.

(edit) fixed: a992515

yeah, i am not saying caching it across session, i am saying in the same session we could reuse it instead of loading before every step

decrease_cf_pool prompted once per market and fanned the same cf to every
pool the market was listed in, producing the same end state as decrease_cf
(all pools). a market in N pools now yields N prompts with independent cf
values, so the per-pool option can express per-pool divergence.
@fred-venus
Copy link
Copy Markdown
Contributor

still the case for market level borrow , there is no option like which pool id to pause, it's pausing all by default

CleanShot 2026-04-20 at 17 12 39@2x

@fred-venus
Copy link
Copy Markdown
Contributor

also dont see the template

CleanShot 2026-04-20 at 17 15 38@2x

markets.json was written on first fetch but never read back by the
generator. the artifact only created a cross-session footgun (stale cache
silently loaded into a new run) without providing any runtime benefit.
in-session reuse via ctx.marketsCache / ctx.bscPoolsCache is untouched.
@github-actions
Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Health
contracts 100% 100%
contracts.DeviationSentinel 100% 100%
contracts.DeviationSentinel.Oracles 93% 90%
contracts.EmergencyBrake 0% 0%
contracts.Interfaces 100% 100%
contracts.LeverageManager 94% 80%
contracts.Libraries 29% 33%
contracts.PositionSwapper 0% 0%
contracts.RelativePositionManager 91% 64%
contracts.SwapHelper 100% 100%
contracts.SwapRouter 79% 55%
contracts.pendle-pt-fixed-rate-vault 0% 0%
contracts.pendle-pt-fixed-rate-vault.interfaces 100% 100%
contracts.pendle-pt-fixed-rate-vault.test 0% 0%
Summary 63% (898 / 1427) 48% (436 / 902)

@Debugger022
Copy link
Copy Markdown
Contributor Author

#63 (comment) can see for aave, there are 2 pools on bsc (core and isolated), but i was prompted only once here, this makes option 2 "Decrease collateral factor — all pools (decreaseCF)" and option 5 "Decrease CF — specific pool (decreaseCF with poolId)" making no difference

Good catch — root cause was decrease_cf_pool reusing gatherPerMarketValues, which prompted once per market and fanned the same CF to every pool the market was listed in. End state was identical to decrease_cf (all pools), so the per-pool option carried no extra expressiveness.

Fixed across two commits:

  • c12f9cc — added gatherPerPoolValues() keyed on (market, poolId). A market in N pools now produces N independent prompts (CLI, single-value, or file mode). File-mode template format is { symbol: { poolId: value } }. Step state carries a new newCFsByPool: Map<address, Map<poolId, value>> and commandsForStep fans out one tx per (market, pool) pair with its own CF.
  • fa6a44b — added pickPoolIds(sym, available) so the operator selects which pool IDs each market acts on (typing all or a comma-separated list). Single-pool markets skip the prompt. End-of-selection prints the fan-out plan + total tx count and asks for confirmation before generating the batch.

So for vAAVE (pools [0, 6] on bsc): option 2 still pushes one CF to both pools; option 5 now prompts twice and lets you set, say, 0 for pool 0 and 7e17 for pool 6.

@Debugger022
Copy link
Copy Markdown
Contributor Author

#63 (comment) yeah, i am not saying caching it across session, i am saying in the same session we could reuse it instead of loading before every step

Got it — misread the original comment, apologies. cddbb25 removes the markets.json disk artifact entirely; in-session reuse via ctx.marketsCache and ctx.bscPoolsCache is unchanged, so within a single run we still hit the chain only once for markets and once per BSC pool. The "select by name" prompt now reads Select by name (auto-fetch all listed markets on first use, cached for this run) to make the in-session-only behaviour explicit. Removes the cross-session footgun (testnet addresses leaking into a mainnet batch / stale cache hiding new markets) without losing the runtime benefit you asked for.

@Debugger022
Copy link
Copy Markdown
Contributor Author

Debugger022 commented Apr 20, 2026

#63 (comment) still the case for market level borrow, there is no option like which pool id to pause, it's pausing all by default

Fixed in fa6a44b.

@Debugger022
Copy link
Copy Markdown
Contributor Author

#63 (comment) also dont see the template

Fixed in c9c21f2. Four changes to the file-mode flow:

  • Expected file shape (with example JSON) is printed before the path prompt, so the format is visible up front rather than discovered by trial-and-error.
  • When the file is missing or stale, the template is generated and the prompt pauses for edit-then-Enter to resume — no more process.exit(0) requiring a full re-run. Type q to quit instead.
  • Sticky path: the entered path is remembered, so after template generation, hitting Enter on the next prompt reuses the same file.
  • Stale file (missing entries for the current selection) auto-regenerates the template and backs up the prior content to <file>.bak first.

Applies to both gatherPerMarketValues (CF / caps) and gatherPerPoolValues (per-pool CF).

@Debugger022 Debugger022 requested a review from fred-venus April 20, 2026 12:36
@fred-venus
Copy link
Copy Markdown
Contributor

lgtm now

@fred-venus
Copy link
Copy Markdown
Contributor

will merge first, retrospective comment is welcomed

@fred-venus fred-venus merged commit cd360b3 into develop Apr 21, 2026
4 checks passed
Debugger022 pushed a commit that referenced this pull request May 14, 2026
## 1.2.0-dev.7 (2026-04-21)

* Merge pull request #63 from VenusProtocol/feat/VPD-1036 ([cd360b3](cd360b3)), closes [#63](#63)
* Update helpers/generateSafeEBrakeJson.ts ([df290e7](df290e7))
* refactor: cache markets and pool data, show market list inline ([a992515](a992515))
* refactor(ebrake): drop markets.json disk cache, keep in-session reuse ([cddbb25](cddbb25))
* refactor(ebrake): extract pure validators and export helpers for testing ([f7f6f91](f7f6f91))
* refactor(simulation): expand state checks into per-assertion it() blocks ([7b41669](7b41669))
* feat: add --fork task flag and Venus chain hardfork config to hardhat ([42a1789](42a1789))
* feat: add Safe EBrake TX generator and fork simulation script ([e9d91d6](e9d91d6))
* feat(ebrake): file-mode UX — format hint, sticky path, auto-regen stale ([c9c21f2](c9c21f2))
* feat(ebrake): multi-op batching, per-market value inputs, operator docs ([b72c764](b72c764))
* feat(generateSafeEBrakeJson): enhance pool and market selection ([abd2088](abd2088))
* fix: prompt user before generating template for missing file path ([659af81](659af81))
* fix: resolve yarn lint formatting ([6fe4067](6fe4067))
* fix(ebrake): address PR review comments on generator and simulator tooling ([5e2211e](5e2211e))
* fix(ebrake): collect per-pool cf for decrease_cf_pool ([c12f9cc](c12f9cc))
* fix(ebrake): move simulator back to scripts/ to keep it out of CI test glob ([d552c57](d552c57))
* fix(ebrake): pool picker + fan-out confirm for bsc fan-out ops ([fa6a44b](fa6a44b))
* fix(gatherPerMarketValues): improve file path validation and error handling ([d176750](d176750))
* fix(getEBrakeAddress): improve address validation loop for user input ([c26a769](c26a769))
* docs: rename README-EBrake.md to ReadMe_EBrake_Safe_TX Generator.m ([50d9bd8](50d9bd8))
* docs(ebrake): drop opbnb/op/unichain from network matrix ([2c3e795](2c3e795))
* docs(ebrake): restructure README for emergency-first use ([8b77fe0](8b77fe0)), closes [#1](#1)
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