Skip to content

Contract: Re-entrancy guard analysis#742

Open
wheval wants to merge 1 commit into
SoroLabs:mainfrom
wheval:fix/issue-699-reentrancy-guard-analysis
Open

Contract: Re-entrancy guard analysis#742
wheval wants to merge 1 commit into
SoroLabs:mainfrom
wheval:fix/issue-699-reentrancy-guard-analysis

Conversation

@wheval

@wheval wheval commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds docs/security/REENTRANCY_ANALYSIS.md: analyzes execute()/execute_internal for re-entrancy risk against arbitrary target/protocol_address contracts. Finds the code is not checks-effects-interactions ordered (the cross-contract call happens before the last_run state update), so the existing enter_security_guard/exit_security_guard global mutex is load-bearing, not redundant - confirmed necessary and sufficient rather than reordering writes.
  • Bug found while tracing every guarded call path: execute_internal's yield-strategy branch called the guarded public execute_yield_strategy() for any task with a yield_strategy configured. Since execute() already holds the lock at that point, this immediately re-entered enter_security_guard and panicked - meaning any task with a yield_strategy set could never execute successfully, and this had zero test coverage (no existing test ever registered a task with yield_strategy: Some(_) and called execute()).
  • Fixed the same way execute/execute_internal are already split: extracted a guard-free execute_yield_strategy_internal; the public execute_yield_strategy still guards itself as a standalone entry point, execute_internal now calls the internal helper directly so it shares execute()'s existing lock instead of trying to acquire a second one.
  • Added test_execute_with_yield_strategy_does_not_trip_reentrancy_guard, exercising a yield-strategy task through the public execute() entry point end-to-end (real token, real keeper fee payment) - fails before the fix, passes after.

Closes #699

Test plan

  • cargo test --lib in contract/ - all 136 tests pass, including the new regression test

docs/security/REENTRANCY_ANALYSIS.md analyzes execute()/execute_internal
for re-entrancy: the state update happens after the cross-contract call
(not checks-effects-interactions ordered), so the global enter_security_guard
mutex is load-bearing, not redundant - confirmed necessary and sufficient.

While tracing every guarded call path, found that execute_internal calls
the *guarded* public execute_yield_strategy() for any task with a
yield_strategy configured, which re-enters enter_security_guard while
execute()'s own guard is still held and panics unconditionally - so any
task with a yield_strategy could never execute. This had no test coverage.

Fixes it the same way execute()/execute_internal are already split:
extracted execute_yield_strategy_internal (guard-free), with the public
execute_yield_strategy still guarding itself as a standalone entry point,
and execute_internal now calling the internal helper directly.

Adds a regression test exercising a yield-strategy task through the public
execute() entry point end-to-end, including the real keeper fee payment.

Closes SoroLabs#699
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.

Contract: Re-entrancy guard analysis

1 participant