Skip to content

[FLOW-9] Add checks to guard against local registry YieldVault IDs corruption#43

Merged
liobrasil merged 5 commits intomainfrom
mpeter/FLOW-9-guard-against-local-registry-corruption
Jan 29, 2026
Merged

[FLOW-9] Add checks to guard against local registry YieldVault IDs corruption#43
liobrasil merged 5 commits intomainfrom
mpeter/FLOW-9-guard-against-local-registry-corruption

Conversation

@m-Peter
Copy link
Collaborator

@m-Peter m-Peter commented Jan 23, 2026

Closes: #24

@m-Peter m-Peter self-assigned this Jan 23, 2026
@m-Peter m-Peter added Low Low severity security finding ⎈ QuantStamp QuantStamp audit finding labels Jan 23, 2026
@claude
Copy link

claude bot commented Jan 23, 2026

Code Review - PR #43: Guard Against Local Registry Corruption

I've reviewed this PR addressing issue #24. Overall, the changes properly implement the recommended defenses against registry corruption. Here's my detailed feedback:


Strengths

  1. Addresses All Three Vulnerabilities: The PR successfully implements all three recommendations from the security audit:

    • ID binding enforcement for DEPOSIT/WITHDRAW/CLOSE operations
    • Uniqueness guard in _registerYieldVault()
    • Ownership verification in _unregisterYieldVault() before clearing global state
  2. Clear Error Messages: The new custom errors (YieldVaultIdAlreadyRegistered, YieldVaultIdMismatch) provide precise context for debugging.

  3. Defensive Programming: The checks follow a fail-fast approach, preventing corruption before it occurs.


🐛 Critical Bug Found

Location: completeProcessing() at lines 1122-1128

if (request.requestType == RequestType.CREATE_YIELDVAULT) {
    request.yieldVaultId = yieldVaultId;
} else if (request.yieldVaultId != yieldVaultId) {
    revert YieldVaultIdMismatch(request.yieldVaultId, yieldVaultId);
}

request.yieldVaultId = yieldVaultId;  // ⚠️ LINE 1128 - REDUNDANT AND PROBLEMATIC

Issue: Line 1128 unconditionally assigns yieldVaultId to request.yieldVaultId after the validation logic. This is:

  • Redundant for CREATE requests (already assigned at line 1123)
  • Incorrect for DEPOSIT/WITHDRAW/CLOSE requests (overwrites the value we just validated)

Impact: The validation at line 1124-1125 becomes pointless since line 1128 overwrites request.yieldVaultId anyway. This doesn't reintroduce the vulnerability (the _registerYieldVault and _unregisterYieldVault checks still work), but it makes the mismatch check ineffective and could cause confusion in logs/events.

Recommendation: Remove line 1128 entirely. The assignment at line 1123 is sufficient for CREATE operations, and for other operations, request.yieldVaultId should remain unchanged (it was set when the request was created).


⚠️ Test Coverage Gap

Missing Test Cases: No tests verify the new error conditions:

  1. Attempting to register an already-registered YieldVault ID
  2. Calling completeProcessing() with mismatched yieldVaultId for DEPOSIT/WITHDRAW/CLOSE
  3. Attempting to unregister a YieldVault with wrong user ownership

Recommendation: Add unit tests in solidity/test/FlowYieldVaultsRequests.t.sol or integration tests in Cadence to cover these scenarios. This ensures the guards work as intended and prevents future regressions.


🔍 Minor Observations

  1. Sentinel Value Handling: The code doesn't explicitly validate against NO_YIELDVAULT_ID (type(uint64).max) in the new checks. While unlikely to occur naturally, consider whether explicit checks would improve robustness.

  2. Gas Efficiency: The additional checks add minimal gas overhead (~2-3k gas per operation), which is acceptable for the security benefit.

  3. Documentation: The inline comments clearly explain the intent of each guard, which is excellent for maintainability.


📝 Summary

Approval Status: ⚠️ Approve with required fix

The PR successfully addresses the security vulnerability, but line 1128 must be removed before merging. Once fixed:

  • ✅ All three audit recommendations implemented correctly
  • ⚠️ Test coverage should be added (can be follow-up PR)
  • ✅ No other security concerns identified

Suggested Fix:

  if (request.requestType == RequestType.CREATE_YIELDVAULT) {
      request.yieldVaultId = yieldVaultId;
  } else if (request.yieldVaultId != yieldVaultId) {
      revert YieldVaultIdMismatch(request.yieldVaultId, yieldVaultId);
  }
-
- request.yieldVaultId = yieldVaultId;

Great work addressing this security finding! Let me know if you'd like me to elaborate on any of these points.

@claude
Copy link

claude bot commented Jan 28, 2026

Pull Request Review: Guard Against Local Registry YieldVault IDs Corruption

Summary

This PR addresses issue #24 by adding defensive checks to prevent COA operational mistakes from corrupting the EVM-side registry. The changes implement three key safeguards in the completeProcessing() flow and internal registry management functions.


✅ Positive Aspects

1. Security Improvements - Strong

  • ID binding enforcement (lines 1120-1126): Excellent addition that prevents YieldVault ID misbinding for DEPOSIT/WITHDRAW/CLOSE requests. This directly addresses the attack scenario described in issue FLOW-9: Yieldvault Id Misbinding Can Corrupt Local Registry #24.
  • Uniqueness guard (lines 1561-1564): Prevents duplicate registrations that could corrupt ownership mappings.
  • Ownership verification (lines 1591-1594): Ensures _unregisterYieldVault() validates the user owns the vault before clearing global state.

2. Code Quality

  • Clear, descriptive error messages with appropriate parameters
  • Good inline comments explaining the security rationale
  • Follows existing code patterns and conventions from CLAUDE.md

3. Targeted Fix

  • Minimal, surgical changes that directly address the vulnerability without unnecessary refactoring
  • Maintains backward compatibility for all successful flows

🔍 Issues & Concerns

Critical: Missing Test Coverage

The new error cases are not covered by tests. I found no tests for:

  • YieldVaultIdAlreadyRegistered - What happens if COA tries to register the same ID twice?
  • YieldVaultIdMismatch - What happens if COA provides wrong ID for DEPOSIT/WITHDRAW/CLOSE?

Recommendation: Add tests covering:

function test_CompleteProcessing_RevertYieldVaultIdMismatch() public
function test_RegisterYieldVault_RevertAlreadyRegistered() public  
function test_UnregisterYieldVault_RevertInvalidOwnership() public

These should verify the revert occurs and that contract state remains consistent after the revert.

Medium: Potential Edge Case with type(uint64).max

Lines 1122-1126: The check treats CREATE_YIELDVAULT specially, but doesn't explicitly handle the NO_YIELDVAULT_ID sentinel value (type(uint64).max).

Scenario:

  1. CREATE request fails
  2. COA calls completeProcessing(reqId, false, NO_YIELDVAULT_ID, "error")
  3. Code path: request.requestType == RequestType.CREATE_YIELDVAULT → sets request.yieldVaultId = type(uint64).max

This is correct per the documentation, but could be more explicit:

if (request.requestType == RequestType.CREATE_YIELDVAULT) {
    // For CREATE, accept any yieldVaultId (valid ID on success, NO_YIELDVAULT_ID on failure)
    request.yieldVaultId = yieldVaultId;
} else if (request.yieldVaultId != yieldVaultId) {
    revert YieldVaultIdMismatch(request.yieldVaultId, yieldVaultId);
}

Low: Gas Optimization Opportunity

Line 1592-1594: The ownership check in _unregisterYieldVault() uses yieldVaultOwners[yieldVaultId] != user, which will load from storage.

Since line 1596-1597 already retrieves userYieldVaults[user], and line 1619 checks userOwnsYieldVault[user][yieldVaultId], there's a potential for optimization by checking userOwnsYieldVault first (though this would change the error semantics slightly).

Current approach is more explicit and gas difference is minimal, so this is acceptable.


🔐 Security Analysis

Threat Model Coverage:

  • Duplicate registration: Blocked by validYieldVaultIds check
  • ID misbinding on CLOSE: Blocked by ID matching requirement
  • Cross-user corruption: Blocked by ownership verification in _unregisterYieldVault
  • Reentancy: Existing nonReentrant modifier covers all public entry points

Attack Vectors Mitigated:

  1. Malicious/buggy COA providing wrong IDs → Reverts with clear error
  2. Race conditions causing duplicate registrations → First registration wins, subsequent reverts
  3. Accidental unregistration of wrong user's vault → Ownership check prevents

Residual Risks:

  • COA is still a trusted component - if fully compromised, it could still cause issues by:
    • Never completing CREATE requests (DoS)
    • Providing incorrect success flags
    • But it CANNOT corrupt the registry ownership mappings (which this PR fixes ✅)

📊 Performance Considerations

Gas Impact:

  • Registration: +1 SLOAD (~2100 gas) for uniqueness check - negligible
  • Completion: +1 comparison operation for non-CREATE types - negligible
  • Unregistration: +1 SLOAD for ownership check - negligible

All changes are constant-time operations with minimal gas overhead.


📝 Documentation & Style

Strengths:

  • Error definitions clearly describe the problem
  • Inline comments explain the "why"

Minor Suggestion:
Consider adding a NatSpec comment above the new check at line 1120 explaining it prevents the attack from issue #24:

// Guard against COA operational mistakes: ensure yieldVaultId matches the request's
// intended target for DEPOSIT/WITHDRAW/CLOSE operations (prevents issue #24)

🎯 Verdict

Approve with Minor Changes Required

This is a well-targeted security fix that addresses a real vulnerability. The implementation is clean and follows best practices. However, test coverage is essential before merging.

Required Before Merge:

  1. ✅ Add tests for all three new error cases
  2. ✅ Verify tests pass with cd solidity && forge test

Recommended (Optional):

  1. Add NatSpec explaining the security rationale
  2. Consider adding a comment about NO_YIELDVAULT_ID handling in the CREATE branch

Test Recommendations

// Test that duplicate registration is blocked
function test_RegisterYieldVault_RevertAlreadyRegistered() public {
    vm.startPrank(user);
    uint256 reqId = c.createYieldVault{value: 1 ether}(NATIVE_FLOW, 1 ether, VAULT_ID, STRATEGY_ID);
    vm.stopPrank();
    
    vm.startPrank(coa);
    c.startProcessing(reqId);
    c.completeProcessing(reqId, true, 100, "Created");
    
    // Try to register same ID again (simulate COA bug)
    uint256 reqId2 = // ... create another request
    c.startProcessing(reqId2);
    vm.expectRevert(abi.encodeWithSelector(
        FlowYieldVaultsRequests.YieldVaultIdAlreadyRegistered.selector, 
        100
    ));
    c.completeProcessing(reqId2, true, 100, "Duplicate");
    vm.stopPrank();
}

// Test that ID mismatch on DEPOSIT is blocked  
function test_CompleteProcessing_RevertDepositIdMismatch() public {
    // Create vault with ID 100
    // ... setup code ...
    
    // Try to deposit but COA provides wrong ID
    vm.prank(user);
    uint256 depositReq = c.depositToYieldVault{value: 1 ether}(100, NATIVE_FLOW, 1 ether);
    
    vm.startPrank(coa);
    c.startProcessing(depositReq);
    vm.expectRevert(abi.encodeWithSelector(
        FlowYieldVaultsRequests.YieldVaultIdMismatch.selector,
        100,  // expected
        101   // provided (wrong)
    ));
    c.completeProcessing(depositReq, true, 101, "Wrong ID");
    vm.stopPrank();
}

// Similar test for CLOSE_YIELDVAULT ID mismatch
// Test for unregister with wrong user ownership

Great work on this security improvement! 🔒

@claude
Copy link

claude bot commented Jan 28, 2026

Code Review Summary

This PR effectively addresses FLOW-9 by adding defensive checks to prevent YieldVault ID registry corruption. The changes are well-targeted and directly resolve the security concerns outlined in the issue.

✅ Strengths

1. Strong ID Binding (lines 1120-1126)

The new logic enforces that for DEPOSIT/WITHDRAW/CLOSE operations, the yieldVaultId parameter must match the request's stored ID:

if (request.requestType == RequestType.CREATE_YIELDVAULT) {
    request.yieldVaultId = yieldVaultId;
} else if (request.yieldVaultId != yieldVaultId) {
    revert YieldVaultIdMismatch(request.yieldVaultId, yieldVaultId);
}

Impact: Prevents COA mistakes from targeting the wrong vault (e.g., closing Alice's vault when processing Bob's request).

2. Duplicate Registration Guard (lines 1561-1564)

Prevents registering an already-valid YieldVault ID:

if (validYieldVaultIds[yieldVaultId]) {
    revert YieldVaultIdAlreadyRegistered(yieldVaultId);
}

Impact: Blocks duplicate registrations that could corrupt ownership mappings.

3. Owner Validation on Unregister (lines 1591-1594)

Validates ownership before unregistering:

if (yieldVaultOwners[yieldVaultId] != user) {
    revert InvalidYieldVaultId(yieldVaultId, user);
}

Impact: Ensures only the rightful owner's vault can be unregistered, fixing the scenario described in issue #24 where Bob's vault could be incorrectly removed.

4. Comprehensive Test Coverage

Both new tests properly validate the error conditions:

  • test_RegisterYieldVault_RevertAlreadyRegistered - Tests duplicate registration prevention
  • test_CompleteProcessing_RevertDepositIdMismatch - Tests ID mismatch detection

🔍 Analysis

Security Impact: Medium-High

These checks prevent several attack vectors:

  • Registry corruption: COA operational errors can no longer corrupt the ownership registry
  • Cross-user interference: One user's malformed request cannot affect another user's vaults
  • State inconsistency: EVM-side registry stays consistent with Cadence-side reality

Code Quality: Excellent

  • Clear, descriptive error messages with parameter details
  • Logical placement of checks (fail-fast principle)
  • Maintains gas efficiency (single storage reads)
  • Consistent with existing error handling patterns

Potential Edge Cases Covered:

  1. ✅ Duplicate ID registration blocked
  2. ✅ ID mismatch on DEPOSIT/WITHDRAW/CLOSE caught early
  3. ✅ Wrong-user unregistration prevented
  4. ✅ CREATE flow still allows ID assignment (correct behavior)

💡 Minor Observations

1. Event Emission Consistency

The YieldVaultIdMismatch error will prevent state changes, so no events are emitted on mismatch (correct behavior). However, you may want to consider if any monitoring/logging is needed for these security events in production.

2. Gas Optimization (Optional)

Line 1562: The check reads from storage (validYieldVaultIds[yieldVaultId]). This is already optimal since:

  • It's a single SLOAD
  • No cheaper alternative exists for this validation
  • The revert saves gas by preventing invalid state changes

3. Test Enhancement Suggestion (Optional)

Consider adding a test for the WITHDRAW case to complement the DEPOSIT test:

function test_CompleteProcessing_RevertWithdrawIdMismatch() public {
    // Similar to deposit test but for withdraw operation
}

This would complete the test coverage for all non-CREATE operations.

4. CLOSE_YIELDVAULT Edge Case

The fix handles CLOSE correctly through the ID mismatch check. However, the unregister validation at line 1592 adds a second layer of defense. This defense-in-depth approach is good, though it means:

  • If a mismatched ID passes the first check somehow (impossible given current code), the unregister will still fail
  • This is excellent defensive programming

🎯 Recommendations

Ready to Merge

The changes are production-ready with the following minor suggestions:

  1. Consider adding a test for WITHDRAW ID mismatch (similar to the DEPOSIT test) for complete coverage
  2. Ensure monitoring/alerting is configured for the new error types in production, as they indicate COA operational issues that need investigation

No Blocking Issues

  • No security vulnerabilities introduced
  • No performance regressions
  • No breaking changes to existing functionality
  • Backward compatible (only adds restrictions on invalid operations)

📋 Checklist

Excellent work on this security enhancement! The PR directly addresses the identified vulnerability with minimal code changes and maximum defensive benefit.

@m-Peter m-Peter marked this pull request as ready for review January 28, 2026 15:46
@claude
Copy link

claude bot commented Jan 28, 2026

PR Review: Add checks to guard against local registry YieldVault IDs corruption

Summary

This PR addresses issue #24 by adding three defensive checks to prevent YieldVault ID corruption in the EVM-side registry. The changes are well-targeted and directly address the security concerns outlined in the issue.

Code Quality & Best Practices ✅

Strengths:

  • Clear error definitions: The two new custom errors (YieldVaultIdAlreadyRegistered, YieldVaultIdMismatch) follow the established pattern and provide clear information for debugging (solidity/src/FlowYieldVaultsRequests.sol:245-249)
  • Defensive programming: All three guards implement defense-in-depth against COA operational mistakes
  • Code clarity: The validation logic is straightforward and easy to understand
  • Consistent style: Follows existing code conventions (error handling, natdoc comments)

Security Analysis ✅

The three guards work together to prevent the corruption scenarios described in issue #24:

1. ID Mismatch Protection (lines 1120-1126)

Purpose: Prevents COA from accidentally completing a DEPOSIT/WITHDRAW/CLOSE request with a different YieldVault ID than originally specified.

Security impact:

  • ✅ Prevents the "misbound CLOSE" attack scenario from issue FLOW-9: Yieldvault Id Misbinding Can Corrupt Local Registry #24 where Alice's CLOSE request could be completed with Bob's vault ID
  • ✅ Works because DEPOSIT/WITHDRAW/CLOSE requests store the yieldVaultId at creation time (validated via _validateYieldVaultOwnership)
  • ✅ Only allows CREATE_YIELDVAULT to set the ID (appropriate since ID is assigned during Cadence creation)

2. Duplicate Registration Protection (lines 1562-1564)

Purpose: Prevents re-registering an already-valid YieldVault ID.

Security impact:

  • ✅ Prevents ownership overwrites if COA accidentally calls completeProcessing twice with same ID
  • ✅ Maintains invariant that each YieldVault ID has exactly one owner at a time
  • ✅ Simple and efficient (single storage read)

3. Ownership Validation on Unregister (lines 1592-1594)

Purpose: Ensures the vault being unregistered actually belongs to the specified user.

Security impact:

  • ✅ Prevents unregistering Bob's vault when processing Alice's CLOSE request
  • ✅ Complements the ID mismatch check as a second layer of defense
  • ✅ Validates before any state modifications occur

Overall security assessment: These checks effectively address the corruption vectors identified in issue #24. The defense-in-depth approach is appropriate for a cross-VM bridge where operational mistakes could have significant impact.

Test Coverage ✅

The PR adds three comprehensive test cases that cover the main scenarios. Minor suggestions for additional coverage:

  • Could add test for WITHDRAW ID mismatch (currently only tests DEPOSIT)
  • Could add test attempting to close a vault that doesn't exist
  • The third test validates ownership at request creation time rather than directly testing the new guard in _unregisterYieldVault

Performance Considerations ✅

All three checks are extremely efficient:

  • ID mismatch check: simple comparison (negligible gas)
  • Duplicate registration: single SLOAD (~2100 gas cold, 100 gas warm)
  • Ownership validation: single SLOAD (~2100 gas cold, 100 gas warm)

Total overhead: <5000 gas in worst case, negligible compared to typical request processing costs.

Minor Suggestions

  1. Documentation: The inline comment at line 1120 is excellent. Consider adding similar comments at line 1562 explaining why duplicate registration is dangerous.

  2. Edge cases: Consider whether the sentinel value NO_YIELDVAULT_ID (type(uint64).max) needs explicit handling in these guards.

  3. Failed CREATE retry behavior: Confirm that failed CREATE_YIELDVAULT requests can be retried with different yieldVaultId values (current implementation allows this).

Conclusion

Recommendation: APPROVE ✅

This is a well-crafted security fix that directly addresses the identified vulnerability. The implementation is clean, efficient, and follows best practices. The changes are minimal and focused, reducing risk of introducing new bugs.

The PR successfully closes issue #24 and strengthens the integrity of the EVM-side YieldVault registry against operational mistakes from the Cadence worker.

@claude
Copy link

claude bot commented Jan 29, 2026

Pull Request Review: FLOW-9 - Guard Against Local Registry Corruption

Summary

This PR addresses a critical security issue where COA operational mistakes could corrupt the EVM-side registry by adding three defensive checks to prevent YieldVault ID misbinding. The implementation directly addresses all three recommendations from issue #24.


✅ Strengths

  1. Comprehensive Security Fix

    • All three vulnerability vectors from issue FLOW-9: Yieldvault Id Misbinding Can Corrupt Local Registry #24 are properly addressed
    • Strong ID binding prevents misbinding attacks on DEPOSIT/WITHDRAW/CLOSE operations
    • Uniqueness guard prevents duplicate registration corruption
    • Ownership validation in unregister prevents cross-user corruption
  2. Well-Structured Implementation

    • solidity/src/FlowYieldVaultsRequests.sol:1123-1129: The ID binding logic is clear and correctly distinguishes CREATE (assigns ID) vs other operations (validates ID match)
    • solidity/src/FlowYieldVaultsRequests.sol:1564-1567: Uniqueness check is properly placed before any state modifications
    • solidity/src/FlowYieldVaultsRequests.sol:1596-1599: Ownership validation uses existing error type, maintaining consistency
  3. Excellent Test Coverage

    • test_RegisterYieldVault_RevertAlreadyRegistered(): Tests duplicate registration prevention
    • test_CompleteProcessing_RevertDepositIdMismatch(): Tests ID binding enforcement for DEPOSIT
    • test_CompleteProcessing_RevertInvalidYieldVaultId(): Tests ownership validation in closeYieldVault
    • All three attack vectors from the issue are covered
  4. Clear Error Messages

    • New custom errors (YieldVaultIdAlreadyRegistered, YieldVaultIdMismatch) provide precise debugging information
    • Error parameters include both expected and actual values for easy troubleshooting

🔍 Observations & Suggestions

1. Test Coverage Gap - WITHDRAW Request Type

The PR tests DEPOSIT ID mismatch but not WITHDRAW. Consider adding a test for WITHDRAW operations:

function test_CompleteProcessing_RevertWithdrawIdMismatch() public {
    // Setup: create vault with ID 100
    vm.prank(user);
    uint256 createReq = c.createYieldVault{value: 1 ether}(NATIVE_FLOW, 1 ether, VAULT_ID, STRATEGY_ID);
    vm.startPrank(coa);
    c.startProcessing(createReq);
    c.completeProcessing(createReq, true, 100, "Created");
    vm.stopPrank();

    // Deposit to build up balance
    vm.prank(user);
    uint256 depositReq = c.depositToYieldVault{value: 1 ether}(100, NATIVE_FLOW, 1 ether);
    vm.startPrank(coa);
    c.startProcessing(depositReq);
    c.completeProcessing(depositReq, true, 100, "Deposited");
    vm.stopPrank();

    // Try to withdraw but COA provides wrong ID
    vm.prank(user);
    uint256 withdrawReq = c.withdrawFromYieldVault(100, 0.5 ether);
    vm.startPrank(coa);
    c.startProcessing(withdrawReq);
    vm.expectRevert(abi.encodeWithSelector(
        FlowYieldVaultsRequests.YieldVaultIdMismatch.selector,
        100,  // expected
        101   // provided (wrong)
    ));
    c.completeProcessing(withdrawReq, true, 101, "Wrong ID");
    vm.stopPrank();
}

2. Potential Edge Case - CLOSE with Wrong User

The test test_CompleteProcessing_RevertInvalidYieldVaultId() validates that closeYieldVault() reverts when called by a non-owner, which is correct. However, it doesn't test the scenario where the COA tries to complete a valid user's CLOSE request but provides a yieldVaultId owned by a different user.

Consider adding:

function test_CompleteProcessing_RevertCloseWithWrongUserVault() public {
    // User1 creates vault 100
    vm.prank(user);
    uint256 req1 = c.createYieldVault{value: 1 ether}(NATIVE_FLOW, 1 ether, VAULT_ID, STRATEGY_ID);
    vm.startPrank(coa);
    c.startProcessing(req1);
    c.completeProcessing(req1, true, 100, "Created");
    vm.stopPrank();

    // User2 creates vault 101
    vm.prank(user2);
    uint256 req2 = c.createYieldVault{value: 1 ether}(NATIVE_FLOW, 1 ether, VAULT_ID, STRATEGY_ID);
    vm.startPrank(coa);
    c.startProcessing(req2);
    c.completeProcessing(req2, true, 101, "Created");
    vm.stopPrank();

    // User1 closes their vault but COA mistakenly tries to close user2's vault
    vm.prank(user);
    uint256 closeReq = c.closeYieldVault(100);
    vm.startPrank(coa);
    c.startProcessing(closeReq);
    vm.expectRevert(abi.encodeWithSelector(
        FlowYieldVaultsRequests.InvalidYieldVaultId.selector,
        101,  // yieldVaultId provided
        user  // request.user (who doesn't own 101)
    ));
    c.completeProcessing(closeReq, true, 101, "Wrong vault");
    vm.stopPrank();
}

This would catch the exact scenario described in the issue where Alice's CLOSE request could accidentally unregister Bob's vault.

3. Documentation Enhancement

Consider adding inline comments explaining the two-path logic in completeProcessing():

// Enforce strong ID binding for DEPOSIT/WITHDRAW/CLOSE by requiring the
// supplied yieldVaultId matches the request's stored yieldVaultId
if (request.requestType == RequestType.CREATE_YIELDVAULT) {
    // CREATE: assign the Cadence-generated ID (first time we learn it)
    request.yieldVaultId = yieldVaultId;
} else if (request.yieldVaultId != yieldVaultId) {
    // DEPOSIT/WITHDRAW/CLOSE: enforce ID must match what user specified
    revert YieldVaultIdMismatch(request.yieldVaultId, yieldVaultId);
}

🛡️ Security Analysis

Attack Vector Prevention ✅

Vector 1: Duplicate Registration

  • Before: COA could call completeProcessing with an already-registered ID, overwriting ownership
  • After: _registerYieldVault reverts if validYieldVaultIds[yieldVaultId] is already true
  • Status: ✅ FIXED

Vector 2: ID Misbinding on DEPOSIT/WITHDRAW/CLOSE

  • Before: COA could provide wrong yieldVaultId parameter, causing operations on wrong vault
  • After: completeProcessing enforces request.yieldVaultId == yieldVaultId for non-CREATE requests
  • Status: ✅ FIXED

Vector 3: Cross-User Unregistration

  • Before: COA could unregister user B's vault when processing user A's CLOSE request
  • After: _unregisterYieldVault validates yieldVaultOwners[yieldVaultId] == user before any state changes
  • Status: ✅ FIXED

Gas Considerations

  • All new checks are single SLOAD operations (very cheap)
  • No loops or complex computations added
  • Negligible gas impact (estimated <1000 gas per affected operation)

Reentrancy

  • No new external calls introduced
  • All checks happen before state changes (checks-effects pattern maintained)
  • Existing nonReentrant modifier on completeProcessing provides protection

📋 Code Quality

Best Practices ✅

  • Error handling follows Solidity 0.8+ custom error pattern
  • Clear separation of concerns (validation, state updates, cleanup)
  • Consistent naming conventions

Potential Improvements

  1. The test test_CompleteProcessing_RevertInvalidYieldVaultId has a misleading name - it actually tests closeYieldVault input validation, not completeProcessing with invalid ID. Consider renaming to test_CloseYieldVault_RevertWhenNotOwner for clarity.

  2. Consider adding NatSpec comments to the new error definitions:

/// @notice Thrown when attempting to register a YieldVault ID that already exists
/// @param yieldVaultId The duplicate YieldVault ID
error YieldVaultIdAlreadyRegistered(uint64 yieldVaultId);

/// @notice Thrown when COA provides mismatched YieldVault ID during request completion
/// @param expectedId The YieldVault ID stored in the request
/// @param providedId The YieldVault ID provided by COA
error YieldVaultIdMismatch(uint64 expectedId, uint64 providedId);

✅ Conclusion

This PR successfully addresses a critical security vulnerability with minimal code changes and excellent test coverage. The implementation is sound, follows Solidity best practices, and maintains backward compatibility.

Recommendation: APPROVE with minor suggestions

The suggested additional tests would strengthen coverage but are not blockers for merging, as the core vulnerability is properly fixed. The current implementation correctly prevents all three attack vectors identified in issue #24.

Risk Assessment: Low - Changes are defensive (add validation only), well-tested, and don't modify core business logic.


Great work @m-Peter! 🎉

@liobrasil liobrasil merged commit c59ef47 into main Jan 29, 2026
4 checks passed
@liobrasil
Copy link
Collaborator

Forgot to Approve but LGTM ! Approved here...

@liobrasil liobrasil deleted the mpeter/FLOW-9-guard-against-local-registry-corruption branch January 29, 2026 02:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Low Low severity security finding ⎈ QuantStamp QuantStamp audit finding

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FLOW-9: Yieldvault Id Misbinding Can Corrupt Local Registry

2 participants