Skip to content

feat(ethexe/contracts): use ERC-2612 for many actions with ERC20 token, add requestCodeValidationOnBehalf#5362

Open
StackOverflowExcept1on wants to merge 11 commits intomasterfrom
av/ethexe-contracts-permit
Open

feat(ethexe/contracts): use ERC-2612 for many actions with ERC20 token, add requestCodeValidationOnBehalf#5362
StackOverflowExcept1on wants to merge 11 commits intomasterfrom
av/ethexe-contracts-permit

Conversation

@StackOverflowExcept1on
Copy link
Copy Markdown
Member

Resolves #5357
Resolves #4506

@StackOverflowExcept1on StackOverflowExcept1on added A0-pleasereview PR is ready to be reviewed by the team D8-ethexe ethexe-related PR labels Apr 20, 2026
@semanticdiff-com
Copy link
Copy Markdown

semanticdiff-com Bot commented Apr 20, 2026

Review changes with  SemanticDiff

Changed Files
File Status
  ethexe/contracts/.vscode/settings.json  98% smaller
  ethexe/ethereum/abi/Gear.json  95% smaller
  ethexe/ethereum/abi/DemoCaller.json  93% smaller
  ethexe/service/src/tests/utils/env.rs  29% smaller
  ethexe/ethereum/src/mirror/mod.rs  26% smaller
  ethexe/ethereum/src/abi/mod.rs  19% smaller
  ethexe/ethereum/src/router/mod.rs  2% smaller
  ethexe/cli/src/commands/tx.rs Unsupported file format
  ethexe/contracts/foundry.lock Unsupported file format
  ethexe/contracts/script/Deployment.s.sol Unsupported file format
  ethexe/contracts/script/LookupGenesisHash.s.sol Unsupported file format
  ethexe/contracts/script/MirrorProxy.s.sol Unsupported file format
  ethexe/contracts/script/MirrorProxySmall.s.sol Unsupported file format
  ethexe/contracts/script/actions/Pause.s.sol Unsupported file format
  ethexe/contracts/script/actions/Unpause.s.sol Unsupported file format
  ethexe/contracts/script/upgrades/Mirror.s.sol Unsupported file format
  ethexe/contracts/script/upgrades/POAMiddleware.s.sol Unsupported file format
  ethexe/contracts/script/upgrades/Router.s.sol Unsupported file format
  ethexe/contracts/script/upgrades/WrappedVara.s.sol Unsupported file format
  ethexe/contracts/src/BatchMulticall.sol Unsupported file format
  ethexe/contracts/src/ICallbacks.sol Unsupported file format
  ethexe/contracts/src/IMiddleware.sol Unsupported file format
  ethexe/contracts/src/IMirror.sol Unsupported file format
  ethexe/contracts/src/IPOAMiddleware.sol Unsupported file format
  ethexe/contracts/src/IRouter.sol Unsupported file format
  ethexe/contracts/src/IWrappedVara.sol Unsupported file format
  ethexe/contracts/src/Middleware.sol Unsupported file format
  ethexe/contracts/src/Mirror.sol Unsupported file format
  ethexe/contracts/src/MirrorProxy.sol Unsupported file format
  ethexe/contracts/src/MirrorProxySmall.sol Unsupported file format
  ethexe/contracts/src/POAMiddleware.sol Unsupported file format
  ethexe/contracts/src/Router.sol Unsupported file format
  ethexe/contracts/src/WrappedVara.sol Unsupported file format
  ethexe/contracts/src/libraries/Clones.sol Unsupported file format
  ethexe/contracts/src/libraries/ClonesSmall.sol Unsupported file format
  ethexe/contracts/src/libraries/Gear.sol Unsupported file format
  ethexe/contracts/src/libraries/MapWithTimeData.sol Unsupported file format
  ethexe/contracts/src/libraries/SSTORE2.sol Unsupported file format
  ethexe/contracts/test/Base.t.sol Unsupported file format
  ethexe/contracts/test/DemoCaller.sol Unsupported file format
  ethexe/contracts/test/IDemoCallbacks.sol Unsupported file format
  ethexe/contracts/test/Middleware.t.sol Unsupported file format
  ethexe/contracts/test/POC.t.sol Unsupported file format
  ethexe/contracts/test/Router.t.sol Unsupported file format
  ethexe/ethereum/abi/BatchMulticall.json Unsupported file format
  ethexe/ethereum/abi/Middleware.json Unsupported file format
  ethexe/ethereum/abi/Mirror.json Unsupported file format
  ethexe/ethereum/abi/POAMiddleware.json Unsupported file format
  ethexe/ethereum/abi/Router.json Unsupported file format
  ethexe/ethereum/abi/WrappedVara.json Unsupported file format
  ethexe/ethereum/src/lib.rs Unsupported file format
  ethexe/ethereum/src/wvara/mod.rs  0% smaller
  ethexe/scripts/update-clones-sol.rs  0% smaller
  ethexe/sdk/src/mirror.rs  0% smaller
  ethexe/sdk/src/router.rs  0% smaller
  ethexe/sdk/src/wvara.rs  0% smaller

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the ethexe contracts and CLI by integrating ERC-2612 permit support, which streamlines user interactions by reducing the number of required transactions. It also introduces new functionality for code validation on behalf of other users and allows for setting an initial executable balance during program creation. These changes improve gas efficiency and user experience while maintaining robust security through EIP-712 signature verification.

Highlights

  • ERC-2612 Permit Support: Added support for ERC-2612 permit signatures across multiple actions, including executable balance top-ups and program creation, allowing users to perform these actions without a separate approval transaction.
  • New Validation Mechanism: Introduced requestCodeValidationOnBehalf to allow code validation requests to be made on behalf of another user, including fee handling and signature verification.
  • Contract Initialization Updates: Updated initialize functions in Mirror and Router to support setting an initial executable balance during program creation.
  • License and Dependency Updates: Updated license headers to GPL-3.0-or-later and bumped Solidity compiler version to 0.8.33.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/check.yml
  • Ignored by pattern: ethexe/ethereum/abi/*.json (8)
    • ethexe/ethereum/abi/BatchMulticall.json
    • ethexe/ethereum/abi/DemoCaller.json
    • ethexe/ethereum/abi/Gear.json
    • ethexe/ethereum/abi/Middleware.json
    • ethexe/ethereum/abi/Mirror.json
    • ethexe/ethereum/abi/POAMiddleware.json
    • ethexe/ethereum/abi/Router.json
    • ethexe/ethereum/abi/WrappedVara.json
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

//
// We might also need to update `ERC1967Utils.IMPLEMENTATION_SLOT` in all `Mirror`-s that are set to
// `isSmall = true` to the new `Mirror` address so that this method is visible on Etherscan.
function executableBalanceTopUpWithPermit(uint128 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

need to this on-chain (migration)

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements initial executable balance transfers during program creation and integrates EIP-2612 permit support for code validation and balance top-ups. It updates the Router and Mirror contracts, CLI, SDK, and Ethereum service layer to support these new flows and associated fees. Review feedback identifies a high-severity issue in the EIP-712 signature implementation for blob hashes where dynamic arrays must be hashed according to the standard to avoid signature mismatches. Additionally, it is recommended to remove try-catch blocks around permit calls to ensure that specific revert reasons are not masked by generic transfer failures.

Comment thread ethexe/contracts/src/Router.sol
Comment thread ethexe/contracts/src/Router.sol
@StackOverflowExcept1on
Copy link
Copy Markdown
Member Author

@claude opus review

@claude
Copy link
Copy Markdown

claude Bot commented Apr 20, 2026

Claude finished @StackOverflowExcept1on's task in 5m 14s —— View job


Claude Opus Review

Summary. The PR adds ERC-2612 permit flows across three call paths (executableBalanceTopUpWithPermit, createProgramWithValue/AndAbiInterface, and requestCodeValidation), plus a new relayer-style requestCodeValidationOnBehalf. It also threads initial executable balance through Mirror.initialize, wires the new call paths into the Rust SDK/CLI, and bumps licensing/solc. The integration with EIP-712 on the Router and the permit + transferFrom pattern look well-reasoned; below are the issues I'd address before shipping.

Disclosure up-front on a point already in the thread: the try { permit(...) } catch {} pattern around an immediately-following transferFrom is intentional and correct — it's the standard mitigation for permit-frontrunning (an attacker landing a replay of the same (owner, nonce) would otherwise grief the caller). The author's pushback on Gemini on that point stands; I'm not repeating that feedback.


severity: high — EIP-712 encoding of bytes32[] blobHashes is non-compliant

File: ethexe/contracts/src/Router.sol lines 590-592

bytes32 structHash = keccak256(
    abi.encode(REQUEST_CODE_VALIDATION_ON_BEHALF_TYPEHASH, _requester, _codeId, _blobHashes, _deadline)
);

Per EIP-712, dynamic arrays must contribute keccak256(abi.encodePacked(<contents>)) to the struct hash, not the raw ABI encoding. The current code passes _blobHashes directly to abi.encode, which emits an offset/length/tail layout — a different hash from what any compliant signer (alloy's SolStruct::eip712_signing_hash, MetaMask's eth_signTypedData_v4, ethers' _signTypedData) will produce.

The POC test in ethexe/contracts/test/POC.t.sol:180 also uses the same non-standard abi.encode(..., blobHashes, ...), which is why the test passes end-to-end — both sides agree on the wrong encoding. An off-chain client using any standard EIP-712 library will never produce a recoverable signature.

Fix:

bytes32 blobHashesHash = keccak256(abi.encodePacked(_blobHashes));
bytes32 structHash = keccak256(
    abi.encode(REQUEST_CODE_VALIDATION_ON_BEHALF_TYPEHASH, _requester, _codeId, blobHashesHash, _deadline)
);

And update the POC test to the same. Ideally add a second test that constructs the signature from an off-chain reference (e.g. alloy's SolStruct on the Rust side) to catch future drift.

Fix this →


severity: medium_requester signature in requestCodeValidationOnBehalf is ceremonial on-chain

File: ethexe/contracts/src/Router.sol lines 564-610, ethexe/contracts/src/IRouter.sol line 146

The relayer (msg.sender) pays the entire baseFee + extraFee via permit/transferFrom. The _requester signature is verified but never stored, never emitted (CodeValidationRequested(_codeId) doesn't include _requester), and no state transition is keyed on it. If off-chain validators rely on _requester for attribution/accounting, they can't learn it from the chain — they'd need to decode calldata of each tx, which is fragile. Consider either:

  • emitting _requester in a dedicated event (e.g. CodeValidationRequestedOnBehalf(codeId, requester)), or
  • removing the parameter entirely if attribution isn't needed (simpler API, one fewer signature to collect).

If the intent is future-proofing for slashing / quota enforcement per requester, a comment explaining that would help reviewers.


severity: medium — No governance setter for requestCodeValidationBaseFee / requestCodeValidationExtraFee

File: ethexe/contracts/src/Router.sol lines 42-43, 106-107, 438-448

Fees are set to DEFAULT_* × 10^decimals in initialize/reinitialize and are otherwise immutable. Protocol parameters that denominate in WVARA typically need to be tunable as token value/cost structure shifts — otherwise the only lever is a full reinitialize (version bump + redeploy path), which is heavy for a fee tweak. If keeping these hardcoded is intentional (to reduce governance surface area), document that explicitly; otherwise add setRequestCodeValidationBaseFee(uint256) / ...ExtraFee(uint256) guarded by onlyOwner.


severity: medium — Signature replay window for requestCodeValidationOnBehalf after invalid commitment

File: ethexe/contracts/src/Router.sol lines 564-610 and _commitCodes at lines 872-877

When a code commitment is invalid, _commitCodes does delete router.protocolData.codes[_commitment.id] — the state returns to Unknown. Until _deadline passes, the original _requester signature can be re-submitted for the same _codeId. The permit is protected by WVARA's own nonce (so a second (v2,r2,s2) replay is blocked), but the _requester signature has no nonce and is freely replayable during that window. The financial exposure sits with the replaying relayer (they pay the fee), so the direct harm is bounded — but there's still value in including a uint256 nonce per-requester or per-codeId in the typehash to make the semantic intent clearer and eliminate the replay entirely. Worth considering given the EIP-712 encoding already needs a fix.


severity: low_blobHashes.length is not checked against actual blob count

File: ethexe/contracts/src/Router.sol lines 583-586

for (uint256 i = 0; i < _blobHashes.length; i++) {
    require(_blobHashes[i] == blobhash(i), ...);
}

If the relayer attaches more blobs than _blobHashes.length, the excess blobs are not verified against the requester's consent. Because off-chain validators resolve the blob matching _codeId specifically, this is probably not exploitable — but if the off-chain side ever iterates all attached blobs (e.g. for a future multi-code flow), this becomes load-bearing. Cheap guard: require(blobhash(_blobHashes.length) == bytes32(0), ExtraBlobAttached()).


severity: low — 120s permit deadline in SDK may be tight

File: ethexe/ethereum/src/lib.rs line ~378

let deadline = AlloyU256::from(timestamp.checked_add(120).expect("infallible"));

120s after the latest-block timestamp is fine on fast chains but can expire under L1 congestion or if the user signs then waits to broadcast. Bumping to 300–600s, or making it configurable on EthereumBuilder, would be more resilient.


severity: low — Redundant IsSmallAlreadySet check in Mirror.initialize

File: ethexe/contracts/src/Mirror.sol lines 368-370

require(initializer == address(0), InitializerAlreadySet());
require(!isSmall, IsSmallAlreadySet());

The initializer == address(0) check already prevents re-initialization; isSmall can only be set via this function. The second require cannot fire independently. Not a bug, just dead code — safe to remove.


severity: low — Old MirrorProxy contracts won't expose executableBalanceTopUpWithPermit

File: ethexe/contracts/src/MirrorProxy.sol lines 76-83

The author already flagged this in the code comment and in the PR review (need to [do] this on-chain (migration)). Re-surfacing for tracking: the migration plan for already-deployed small-mirror programs is the open item. One option worth considering — since MirrorProxy proxies via delegatecall to the current Mirror impl, a user can call the new selector directly on the proxy address and the delegate will dispatch it, if MirrorProxy's fallback forwards unknown selectors. Looking at MirrorProxy.fallback() { _delegate(); } — it does. So old proxies can already route executableBalanceTopUpWithPermit(...) via fallback; the explicit shim exists only for Etherscan's "Write Contract" UI discoverability. That's worth documenting so users aren't blocked on the migration for functional usage.


Nits

  • ethexe/ethereum/src/router/mod.rs: HUGE_GAS_LIMIT = 10_000_000TX_GAS_LIMIT_CAP = 16_777_216 (2^24). The rename + bump is unrelated to ERC-2612 work; could be split into its own commit for clean history.
  • ethexe/ethereum/src/lib.rs:390: let v = (signature.v() as u8) + 27; — alloy Parity::YParity returns 0/1, so + 27 is correct for legacy ECDSA v. A comment pointing this out would save the next reader a check.
  • Several files flipped from SPDX-License-Identifier: UNLICENSEDGPL-3.0-or-later WITH Classpath-exception-2.0. Confirm this is the intended license for IMirror.sol, IWrappedVara.sol, libraries/Gear.sol, POC.t.sol, Router.t.sol — most other files in the contracts tree already use this license, so this looks like a cleanup.

Checklist

  • Analyze PR changes (Router, Mirror, MirrorProxy, CLI, SDK)
  • Review EIP-712 / ERC-2612 permit integration
  • Review requestCodeValidationOnBehalf flow
  • Review initial executable balance wiring
  • Post final review
    • Branch: av/ethexe-contracts-permit

@StackOverflowExcept1on
Copy link
Copy Markdown
Member Author

@claude opus review

@claude
Copy link
Copy Markdown

claude Bot commented Apr 21, 2026

Claude finished @StackOverflowExcept1on's task in 4m 1s —— View job


Claude Opus Review

Summary. The "fix review" commit correctly addressed the earlier high-severity EIP-712 encoding issue (now uses keccak256(abi.encodePacked(_blobHashes)) at Router.sol:621 and the matching client encoding at POC.t.sol:190) and added a full _blobHashes.length guard at Router.sol:599-607. The POC test passes because both sides agree on the correct encoding. Two new governance setters were added for the validation fees — but one of them is missing access control, which is a blocker.


severity: criticalsetRequestCodeValidationExtraFee has no onlyOwner modifier

File: ethexe/contracts/src/Router.sol lines 488-490

function setRequestCodeValidationExtraFee(uint256 newExtraFee) external {
    _router().protocolData.requestCodeValidationExtraFee = newExtraFee;
}

Compare the sibling setter immediately above it (line 480):

function setRequestCodeValidationBaseFee(uint256 newBaseFee) external onlyOwner {

Anyone can call setRequestCodeValidationExtraFee(0) to completely bypass the extra fee charged for requestCodeValidationOnBehalf, or set it to type(uint256).max to brick that call path (every relayer-assisted validation would revert on the baseFee + extraFee addition, or on the subsequent transferFrom if the overflow doesn't fire first). Since this was clearly added in the "fix review" commit alongside the onlyOwner base-fee setter, this reads as an obvious oversight. Add the modifier.

Fix this →


severity: medium — Signature for _requester has no nonce; replayable within deadline after invalid commitment

File: ethexe/contracts/src/Router.sol lines 564-642, typehash at line 46

The EIP-712 typehash is:

RequestCodeValidationOnBehalf(address requester,bytes32 codeId,bytes32[] blobHashes,uint256 deadline)

No nonce. If a code commitment comes back invalid, _commitCodes at line 908 does delete router.protocolData.codes[_commitment.id] — state goes back to Unknown. Until _deadline passes, anyone holding (v1,r1,s1) can resubmit on _requester's behalf. The WVARA permit is protected by the token's own nonce (the attacker would need a fresh permit signature), but the _requester signature itself is freely replayable.

The financial harm is bounded — whoever replays has to pay the baseFee + extraFee themselves — so this is medium, not high. But the semantics are clearer with a per-requester nonce (or a (requester, codeId) consumed-marker), and given the typehash is already non-trivial to change later once clients are in the wild, adding uint256 nonce to the typehash now is worth the cost. Example:

mapping(address => uint256) public requesterNonces;
// ...
bytes32 structHash = keccak256(abi.encode(
    REQUEST_CODE_VALIDATION_ON_BEHALF_TYPEHASH,
    _requester, _codeId, keccak256(abi.encodePacked(_blobHashes)),
    requesterNonces[_requester]++, _deadline
));

severity: medium_requester never appears in any event

File: ethexe/contracts/src/Router.sol line 641, IRouter.sol event declaration

emit CodeValidationRequested(_codeId);

The relayer (msg.sender) pays the fee, but the on-chain record never ties the _codeId back to _requester. Off-chain validators / attribution / future-slashing would have to decode calldata per-tx to learn who actually requested the code — fragile. Add indexed requester to the event (or a separate CodeValidationRequestedOnBehalf(codeId, requester) event) so downstream systems have a stable source of truth.

If _requester is truly just ceremonial ("prove the requester consented to this fee"), consider removing the parameter entirely: simpler ABI, one fewer signature to collect off-chain, same on-chain behavior.


severity: lowTX_GAS_LIMIT_CAP is a floor, not a cap

File: ethexe/ethereum/src/router/mod.rs line 74, 533

const TX_GAS_LIMIT_CAP: u64 = 16_777_216;
// ...
let gas_limit =
    Self::TX_GAS_LIMIT_CAP.max(estimated_gas_limit + Self::GEAR_BLOCK_IS_PREDECESSOR_GAS);

u64::max(a, b) returns the larger — so when estimated + GEAR_BLOCK_IS_PREDECESSOR_GAS < 16_777_216, the gas limit becomes 16.7M (the "cap"); when it's larger, the constant is ignored. That's the opposite of a cap. Pre-existing (was already HUGE_GAS_LIMIT in the old name), but the PR renamed it to CAP which makes the mismatch glaring. Either rename to TX_GAS_LIMIT_FLOOR / MIN_TX_GAS_LIMIT, or switch to .min(...) with an additional lower-bound clamp if both behaviors are actually wanted. Unrelated to the ERC-2612 work — could be split out.


severity: low — Redundant IsSmallAlreadySet guard in Mirror.initialize

File: ethexe/contracts/src/Mirror.sol lines 368-370

require(initializer == address(0), InitializerAlreadySet());
require(!isSmall, IsSmallAlreadySet());

isSmall is only ever written inside initialize, right after the initializer == address(0) check — so once initialize has run once, the first require already reverts. The second require is dead. Safe to remove, minor gas savings on every program creation.


severity: low — Permit deadline of 120s may be tight under congestion

File: ethexe/ethereum/src/lib.rs around line ~378

let deadline = AlloyU256::from(timestamp.checked_add(120).expect("infallible"));

Fine on Anvil / fast chains; tight on mainnet if the signer waits to broadcast. Consider 300–600s, or making it configurable on EthereumBuilder. Non-blocking — the user can always retry.


Nits

  • createProgramWithValue / ...AbiInterfaceAndValue call permit + transferFrom unconditionally, even for _value == 0. A cheap if (_value != 0) guard around both would avoid wasted gas for the degenerate case — though that's what createProgram is for, so low priority.
  • _wrappedVara naming throughout Router.sol uses a leading underscore, but it's a local stack variable, not storage. The existing convention (router) doesn't use it. Non-load-bearing.
  • Several SPDX headers flipped UNLICENSEDGPL-3.0-or-later WITH Classpath-exception-2.0. Looks like intentional cleanup to match the rest of the tree.

Already handled by "fix review" commit (d571d1d)

These items from my first pass are resolved — calling out so the author doesn't re-chase them:

  • EIP-712 encoding of bytes32[] blobHashes — fixed (contract + POC test)
  • _blobHashes.length vs actual blob count not checked — fixed with InvalidBlobHashesLength guard at line 607
  • Fees immutable, no governance setter — base-fee setter added (extra-fee setter present but needs onlyOwner, see critical above)

Stands from my first pass

Not re-reviewed here — pointers:

  • try { permit(...) } catch {}: correct mitigation for frontrunning, author's pushback is right. No action.
  • Old MirrorProxy / MirrorProxySmall fallback routes unknown selectors via delegatecall, so old proxies can already use executableBalanceTopUpWithPermit functionally — the explicit shim at MirrorProxy.sol:81 is only for Etherscan's "Write Contract" tab discoverability. Worth documenting in the TODO so users aren't blocked on a migration for functionality.

Checklist

  • Diff vs origin/master
  • Router.sol (permit flows, EIP-712, new setters, requestCodeValidationOnBehalf)
  • Mirror.sol / MirrorProxy[Small].sol
  • Rust SDK / ethereum crate
  • POC.t.sol tests
  • Post final review
    • Branch: av/ethexe-contracts-permit

Comment thread ethexe/ethereum/src/lib.rs Outdated
Comment thread ethexe/contracts/src/libraries/Gear.sol
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A0-pleasereview PR is ready to be reviewed by the team D8-ethexe ethexe-related PR

Projects

None yet

2 participants