Skip to content

Align precompile execution with the current call frame - #3130

Merged
IntiTechnologies merged 5 commits into
mainfrom
chore/precompile-frame-alignment
Aug 29, 2026
Merged

Align precompile execution with the current call frame#3130
IntiTechnologies merged 5 commits into
mainfrom
chore/precompile-frame-alignment

Conversation

@unarbos

@unarbos unarbos commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Require the precompile code_address to match the current EVM frame address before execute, except for stateless cryptographic precompiles.
  • Brings the set in line with Frontier's existing call-mode check.

Test plan

  • cargo test -p subtensor-precompiles precompile_set_
  • Existing direct precompile calls still succeed

Made with Cursor

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
subtensor Ready Ready Preview Aug 29, 2026 1:15pm

Request Review

@github-actions github-actions 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.

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment thread precompiles/src/lib.rs Outdated
fn execute(&self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
match handle.code_address() {
let code_address = handle.code_address();
if !accepts_foreign_frame(code_address) && code_address != handle.context().address {

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.

[HIGH] Frame guard intercepts non-precompile delegate calls

PrecompileSet::execute must return None for addresses outside this set. Because this check precedes the address match, any ordinary contract reached through DELEGATECALL or CALLCODE has a mismatched frame and returns Some(Err(...)); the EVM consequently treats it as a handled precompile failure instead of executing the contract. This can disable proxy contracts and make contract-controlled assets inaccessible. First establish that code_address belongs to this precompile set, as Frontier's fragment implementation does, and only then apply the frame restriction.

Suggested change
if !accepts_foreign_frame(code_address) && code_address != handle.context().address {
if Self::used_addresses().contains(&code_address)
&& !accepts_foreign_frame(code_address)
&& code_address != handle.context().address
{

@github-actions

github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

🛡️ AI Review — Skeptic (security review)

VERDICT: SAFE

MEDIUM scrutiny: 5-month-old account, mitigated by repository write access and substantial merged history; no Gittensor association found. Branch targets main.

The frame-alignment guard now covers every currently registered caller-signed precompile while retaining foreign-frame access only for cryptographic and read-only precompiles. No security vulnerability or malicious behavior was found.

Findings

No findings.

Prior-comment reconciliation

  • 8c975161: addressed — The guard now rejects foreign-frame execution for all currently registered signed-dispatch precompiles, with regression coverage enumerating those addresses.

Conclusion

The previously exposed signed-dispatch paths are now protected against DELEGATECALL/CALLCODE; the branch is safe from the Skeptic perspective.


📜 Previous run (superseded)
Sev File Finding Status
HIGH precompiles/src/lib.rs:170 Protect every caller-signed precompile ✅ Addressed
The guard now rejects foreign-frame execution for all currently registered signed-dispatch precompiles, with regression coverage enumerating those addresses.

🔍 AI Review — Auditor (domain review)

VERDICT: 👍

Gittensor association: UNKNOWN; established contributor with repository write access and extensive prior contributions.

Description discrepancy

The summary says only stateless cryptographic precompiles may execute in a borrowed frame, while the implementation also permits read-only precompiles. It should say “read-only and cryptographic precompiles.”

Quick preflight: git diff --check passed. cargo fmt --check --all could not run because rustup attempted to write under the read-only /home/runner/.rustup; TypeScript formatting was skipped because the existing ts-tests/node_modules environment was unavailable. No files were modified.

Findings

No findings.

Prior-comment reconciliation

  • 801a3919: addressed — All signed-dispatch precompiles are now excluded from accepts_foreign_frame, and the added regression test covers the complete signed-dispatch set.

Conclusion

The revised allowlist now restricts foreign-frame execution to cryptographic and read-only precompiles, addressing the prior signed-dispatch concern. The implementation and regression coverage are ready to merge.


📜 Previous run (superseded)
Sev File Finding Status
HIGH precompiles/src/lib.rs:177 Protect every signed-dispatch precompile ✅ Addressed
All signed-dispatch precompiles are now excluded from accepts_foreign_frame, and the added regression test covers the complete signed-dispatch set.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions 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.

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment thread precompiles/src/lib.rs Outdated
fn accepts_foreign_frame(address: H160) -> bool {
const PURE_MATH: &[u64] = &[1, 2, 3, 4, 5, 7, 8, 9, 1024, 1025];
PURE_MATH.iter().any(|&index| address == hash(index))
|| address == hash(Ed25519Verify::<[u8; 32]>::INDEX)

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.

[MEDIUM] Test the foreign-frame exception path

The tests cover rejection of a stateful precompile, but none execute an allowlisted cryptographic precompile with code_address != context.address. Add a test for at least one standard cryptographic precompile and one signature-verification precompile so an address-list typo cannot silently break the documented DELEGATECALL compatibility.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: SAFE Auditor: 👍

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions 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.

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment thread precompiles/src/lib.rs Outdated
Comment on lines 170 to 177
fn requires_direct_call(address: H160) -> bool {
address == hash(6)
|| address == hash(BalanceTransferPrecompile::<R>::INDEX)
|| address == hash(StakingPrecompile::<R>::INDEX)
|| address == hash(StakingPrecompileV2::<R>::INDEX)
}

pub fn used_addresses() -> [H160; 33] {

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.

[HIGH] Protect every signed-dispatch precompile

This list omits Subnet, Neuron, Alpha, Crowdloan, Leasing, VotingPower, Proxy, and Balance, although each can dispatch a runtime call using an origin derived from context.caller. A contract reached by an EOA can therefore use a foreign frame to invoke those operations as that EOA—the same confused-deputy path this check is intended to close. Restrict every precompile that dispatches as the caller; leave foreign-frame execution only for genuinely read-only or cryptographic precompiles.

Suggested change
fn requires_direct_call(address: H160) -> bool {
address == hash(6)
|| address == hash(BalanceTransferPrecompile::<R>::INDEX)
|| address == hash(StakingPrecompile::<R>::INDEX)
|| address == hash(StakingPrecompileV2::<R>::INDEX)
}
pub fn used_addresses() -> [H160; 33] {
/// Precompiles that dispatch pallet calls as `context.caller`.
/// Direct CALL only.
fn requires_direct_call(address: H160) -> bool {
address == hash(6)
|| address == hash(BalanceTransferPrecompile::<R>::INDEX)
|| address == hash(StakingPrecompile::<R>::INDEX)
|| address == hash(StakingPrecompileV2::<R>::INDEX)
|| address == hash(SubnetPrecompile::<R>::INDEX)
|| address == hash(NeuronPrecompile::<R>::INDEX)
|| address == hash(AlphaPrecompile::<R>::INDEX)
|| address == hash(CrowdloanPrecompile::<R>::INDEX)
|| address == hash(LeasingPrecompile::<R>::INDEX)
|| address == hash(VotingPowerPrecompile::<R>::INDEX)
|| address == hash(ProxyPrecompile::<R>::INDEX)
|| address == hash(BalancePrecompile::<R>::INDEX)
}

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: SAFE Auditor: 👎

@github-actions github-actions 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.

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment thread precompiles/src/lib.rs Outdated
Comment on lines +168 to +170
/// Dispatch (`0x06`) signs pallet calls as `context.caller`. Direct CALL only.
fn requires_direct_call(address: H160) -> bool {
address == hash(6)

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.

[HIGH] Protect every caller-signed precompile

Restricting this guard to address 0x06 leaves other state-mutating precompiles—including balance transfer, staking, subnet, neuron, alpha, crowdloan, leasing, voting-power, proxy, and balance operations—able to dispatch runtime calls as context.caller from a foreign frame. A contract reached by a user can invoke these through DELEGATECALL/CALLCODE, causing the runtime dispatch to be signed as that user. Require a matching frame for every precompile that derives authority from context.caller, while exempting only genuinely read-only or cryptographic precompiles.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

Co-authored-by: Cursor <cursoragent@cursor.com>
@IntiTechnologies
IntiTechnologies merged commit 4c59503 into main Aug 29, 2026
47 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: SAFE Auditor: 👍

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