Skip to content

Conversation

@dkuthoore
Copy link
Collaborator

@dkuthoore dkuthoore commented Oct 31, 2025

Summary

This PR replaces transfer-based vault seeding with a single-step AssignDeposit approach. New vaults are seeded directly from the proposer’s on-chain deposits, and seeding logic robustly falls back to combining multiple deposits if a selected deposit becomes exhausted.

Changes

Feature flags and types

  • Added VAULT_SEEDING to env schema and documented in .env section of README and .env.example.
  • Updated EnvironmentConfig to include VAULT_SEEDING.
  • Introduced AssignDepositProof in src/types/core.ts for publish-time proof typing.

Seeding: construction, handling, publish-time effects

  • src/proposer.ts:

    • createAndSubmitSeedingIntention(newVaultId) now builds an AssignDeposit intention only if VAULT_SEEDING=true.
    • Seeding intentions omit inputs.from and inputs.data; they include {asset, amount, chain_id} only.
    • Nonce set to proposerVaultNonce + 1; fee arrays empty.
    • Logs include controller, submitter vault, nonce, and token summary.
    • publishBundle():
      • Primary path: if deposit_id in proof and sufficient remaining, assign and credit balance.
      • Fallback path: if deposit insufficient or deposit_id omitted, combine multiple deposits (findNextDepositWithAnyRemaining) until the full amount is assigned; errors include required vs available.
      • Added INFO/WARN/ERROR logs for seeding, fallbacks, combination counts, and insufficient-liquidity errors.
    • Nonce update: upon publish, update the submitter vault’s nonce to intention.nonce for all non-CreateVault intentions (skip CreateVault and from === 0).
  • src/utils/intentionHandlers/AssignDeposit.ts:

    • Uniform handling: determines execution.from (submitter vault) from inputs.from if provided; otherwise from getVaultsForController.
    • Selects a deposit at intention time via findDepositWithSufficientRemaining; embeds deposit_id in proof.
    • No special casing for seeding; seeding intentions use the same handling path as external submissions.
  • src/utils/intentionHandlers/CreateVault.ts:

    • Best-effort seeding: wrap createAndSubmitSeedingIntention in try/catch; vault creation succeeds even if seeding scheduling fails.
    • Added success/error logs for seeding scheduling.

Deposit utilities

  • src/utils/deposits.ts:
    • Added findNextDepositWithAnyRemaining(depositor, token, chain_id) to find the oldest deposit with any remaining.
    • Added getTotalAvailableDeposits(depositor, token, chain_id) to sum remaining across all deposits.
    • createAssignmentEventTransactional(...) remains the gatekeeper enforcing no over-assignment; sets assigned_at when fully assigned.

Documentation

  • README:
    • Added a dedicated “Vault Seeding” section: how it works, prerequisites, configuration, and resilience properties.
    • Added env vars (VAULT_SEEDING, PROPOSER_VAULT_ID) to the Environment Variables section.

Tests

  • test/integration/deposits.db.test.ts:
    • New tests for findNextDepositWithAnyRemaining and getTotalAvailableDeposits (oldest-first behavior, partial assignment effects, zero when none).
  • test/integration/seeding.assignDeposit.test.ts:
    • Happy path: multi-deposit combination fulfills a target.
    • Race condition: deposit partially consumed → fallback succeeds.
    • Insufficient deposits: error message includes required vs available.
    • Nonce tracking: publish-time update simulated; proposer vault nonce increments.
    • Oldest-first ordering validated.
  • All integration tests pass (14 tests total).

Vault Seeding Flow

  1. Precondition: The PROPOSER_ADDRESS has on-chain deposits for all tokens in SEED_CONFIG on the configured chain (default: Sepolia).
  2. On CreateVault, if VAULT_SEEDING=true, the node builds an AssignDeposit intention with nonce = proposerVaultNonce + 1, no inputs.from/data, and outputs to the new vault; fee arrays are empty.
  3. The intention is verified and handled uniformly by the AssignDeposit handler; if inputs.from is missing, the submitter vault is inferred from the controller.
  4. At publish time, if the proof’s deposit_id is still sufficient, the assignment is recorded and the destination vault balance credited; otherwise the node falls back to combining multiple deposits until the amount is met or it errors with required vs available.
  5. After publish, the proposer’s vault nonce is updated to intention.nonce for all non-CreateVault intentions (skipping from === 0), ensuring correct ordering and replay protection.
  6. Logs capture seeding requests, fallback activation, deposits-combined counts, success events, and clear errors for insufficient deposits.
  7. If seeding scheduling fails, vault creation still succeeds; seeding can be retried later when liquidity is available.

@dkuthoore dkuthoore marked this pull request as ready for review October 31, 2025 22:24
@dkuthoore dkuthoore requested a review from pemulis as a code owner October 31, 2025 22:24
.env.example Outdated
PROPOSER_VAULT_ID=1

# Feature flag for automatically seeding new vaults with OyaTest Tokens
#VAULT_SEEDING=true
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is VAULT_SEEDING commented out?

token: string
to: number
amount: string
deposit_id?: number // Optional: present when deposit was selected at intention time
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think that deposit_id can be used at the protocol level—this feels like something individual nodes create as they recognize deposits, but don't mention the deposits in bundles until they're assigned, and different nodes may have different values for this field.

We could make this an optional data field that happens to contain a deposit_id if it would be useful to a particular node and that node knows how to handle it!

But also: This is a proof interface, not the AssignDeposit action itself, so it feels like the deposit_id should be in the data field for the AssignDeposit intention if anywhere.

* - Selects deposits with sufficient remaining balance
* - Supports partial deposit assignments (can combine multiple deposits)
* - Determines submitter vault for nonce tracking:
* - If inputs have `from` field: uses that vault ID
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we will ever have a from field for AssignDeposit since the deposits haven't been added to a vault yet? Basically, nonces are not relevant for AssignDeposit intentions. If we have conflicting assignments, we use the one that was included in a bundle first, and ignore the others.

Copy link
Collaborator

Choose a reason for hiding this comment

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

More notes on this below: I think from can always be set to 0, the "protocol vault," since this is a special protocol-level action rather than an action in a user's vault.


// Determine submitter vault for nonce tracking
// 1. If inputs have `from` field, use that (all inputs must have the same `from` value per validator)
// 2. If no `from` field, determine from controller by querying vaults
Copy link
Collaborator

Choose a reason for hiding this comment

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

See note above about how we will never have a from vault for assigning deposits, since unassigned deposits by definition are not yet in a nonce, and will also therefore never have a vault nonce.

context.logger.info(
`Controller ${validatedController} does not control any vaults, using from=0 (no nonce update)`
)
submitterVaultId = 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can always use 0, the "protocol vault," as the from field, since from is required by the type. This is also a good pattern for the future—anything intentions that involve the higher-level protocol rather than assets in a specific vault could use the 0 as the from field.

@dkuthoore
Copy link
Collaborator Author

Changes since feedback

  • AssignDeposit protocol-level semantics:
    • Always from=0 and nonce=0; skip nonce updates when execution.from === 0.
    • Removed submitter-vault logic in AssignDeposit handling.
  • Remove deposit_id from protocol:
    • Dropped deposit_id from AssignDepositProof.
    • Publisher no longer branches on deposit_id; always uses publish-time multi-deposit combination.
  • Seeding flow:
    • createAndSubmitSeedingIntention creates a protocol-level AssignDeposit (no from).
  • Feature flag clarity:
    • .env.example: uncommented VAULT_SEEDING=false with a clear comment.
  • Cleanup:
    • Removed legacy transfer-based seeding function (commented-out).
    • Fixed lint: removed unused imports/vars affected by the above changes.
  • Tests:
    • Updated to assert AssignDeposit does not update vault nonces and to rely solely on publish-time combination.

@dkuthoore dkuthoore requested a review from pemulis November 3, 2025 20:23
@pemulis pemulis merged commit 6ba5428 into main Nov 3, 2025
2 checks passed
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