Skip to content

Conversation

@Azorlogh
Copy link
Contributor

@Azorlogh Azorlogh commented Dec 2, 2025

Summary by cubic

Fixes save + overdraft interaction and enforces proper @world handling. Save no longer hides negative balances or touches missing balances, so overdraft limits work as expected.

  • Bug Fixes
    • OP_SAVE only updates known balances:
      • Asset: zeroed only if current balance > 0.
      • Monetary: subtracts only when the asset balance exists.
    • @world as a variable now errors when used as a source (allowed as destination) with a clearer message.
    • Added tests for “save all” with overdraft and @world variable behavior; tweaked error assertion formatting.

Written for commit c0f3f4b. Summary will update automatically on new commits.

@Azorlogh Azorlogh marked this pull request as ready for review December 2, 2025 21:43
@Azorlogh Azorlogh requested a review from a team as a code owner December 2, 2025 21:43
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 2, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

OP_SAVE now guards balance writes by checking for existing account/balance entries before updating or zeroing balances; ResolveBalances initializes m.Balances earlier and rejects the special "world" account in certain contexts. Tests added for world-variable usage and a "save all and overdraft" scenario.

Changes

Cohort / File(s) Summary
Balance operation & ResolveBalances
internal/machine/vm/machine.go
OP_SAVE: guarded Asset and Monetary saves — only update when account/balances entries exist and only zero positive balances; removed unconditional writes. ResolveBalances: m.Balances initialized earlier; explicit initialization of the world asset balance removed; added validation returning an error when accountAddress == "world".
Tests: world-variable handling & save/overdraft behavior
internal/machine/vm/machine_test.go
Error assertion formatting changed to include the concrete error type in output ("got wrong error, want: %[1]v (%[1]T), got: %v"). Added tests TestWorldSourceVariable (expects invalid world-as-source error) and TestWorldNonSourceVariable (world used as non-source destination permitted). Added subtest "save all and overdraft" under TestSaveFromAccount expecting ErrInsufficientFund with message containing "insufficient funds".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify OP_SAVE guard logic for both Asset and Monetary paths and ensure no regressions in balance semantics
  • Confirm earlier initialization of m.Balances doesn't change resolution order or cause nil/map panics elsewhere
  • Check the new "world" account validation placement and the exact error returned for consistency
  • Review new tests (TestWorldSourceVariable, TestWorldNonSourceVariable, overdraft subtest) for correctness and coverage

Poem

🐰 I hop through ledgers, tidy and small,
I peek before writing, lest balances fall.
The "world" stays apart, a careful divide,
I save, I test, then watch overdraft subside.
✨ carrots, checks, and a ledgered stride.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: fixes for OP_SAVE and overdraft interaction, plus @world variable error handling.
Description check ✅ Passed The description is directly related to the changeset, detailing the bug fixes for OP_SAVE balance updates, @world variable handling, and new tests.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/machine/save-overdraft

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Dec 2, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.45%. Comparing base (1e0828a) to head (c0f3f4b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1177      +/-   ##
==========================================
+ Coverage   81.43%   81.45%   +0.02%     
==========================================
  Files         190      190              
  Lines        9334     9334              
==========================================
+ Hits         7601     7603       +2     
+ Misses       1228     1227       -1     
+ Partials      505      504       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
internal/machine/vm/machine.go (1)

434-456: OP_SAVE guards look correct; consider tightening semantics and readability slightly

The new guarded writes in OP_SAVE match the intent:

  • Only mutate m.Balances when an account/asset entry already exists, so save doesn’t “materialize” balances for accounts that were never tracked.
  • For save [ASSET *], only zeroing positive balances and leaving zero/negative untouched correctly fixes the overdraft case where a negative balance was previously reset to 0 before overdraft checks.
  • For save of a machine.Monetary, subtracting from the existing tracked balance keeps the existing “save more than balance” behavior (balance can go negative and is then accounted for by later withdrawal logic).

Two minor follow‑ups you might consider:

  • For the Asset branch, using the numeric helpers would be clearer and avoids the ToBigInt() detour, e.g.:

    if balance.Gt(machine.Zero) {
        balances[v] = machine.Zero
    }
  • If Program.NeededBalances were ever incomplete, these silent no‑ops when balances[a] or balances[a][asset] is missing could mask miscompilations. If that’s a concern, you could optionally add an internal assertion/log when DEBUG is on rather than failing hard in production.

Given the current design, the behavior looks correct and fixes the save/overdraft interaction covered by the new test.

Please double‑check (via compiler tests) that any account/asset pair reachable in OP_SAVE is always present in m.Balances when it is meant to affect funding, so the new guards don’t accidentally turn a valid save into a no‑op in edge cases.

internal/machine/vm/machine_test.go (1)

135-141: Error assertion remains correct; %w here is only cosmetic

Using errors.Is(err, expected.Error) is the right way to compare the error type (including wrapped errors). Changing the format string to use %w in the failure message is harmless but slightly unconventional, since wrapping semantics only matter in fmt.Errorf. It prints like %v here.

If you want to avoid potential confusion for readers skimming tests, you could keep %v:

require.True(t, errors.Is(err, expected.Error),
    "got wrong error, want: %v, got: %v", expected.Error, err)

Functionally, the current code is fine.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1e0828a and c66375e.

📒 Files selected for processing (2)
  • internal/machine/vm/machine.go (2 hunks)
  • internal/machine/vm/machine_test.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/machine/vm/machine.go (3)
internal/machine/monetary.go (5)
  • Zero (26-26)
  • Monetary (9-12)
  • Monetary (14-14)
  • MonetaryInt (41-41)
  • MonetaryInt (43-43)
internal/machine/vm/program/program.go (1)
  • Program (10-14)
internal/machine/errors.go (1)
  • NewErrInvalidVars (108-112)
internal/machine/vm/machine_test.go (2)
internal/machine/vm/run.go (1)
  • Run (57-87)
internal/machine/errors.go (1)
  • ErrInsufficientFund (34-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Tests
  • GitHub Check: Dirty
🔇 Additional comments (2)
internal/machine/vm/machine.go (1)

509-520: Unconditional Balances init and world-account guard are sensible; ensure coverage of world use-cases

Initializing m.Balances up front in ResolveBalances is a solid improvement:

  • Ensures Execute’s ErrBalancesNotInitialized check behaves purely as “ResolveBalances was never called”, not “no balances were requested”.
  • Avoids accidental writes to a nil map if future code starts assigning outside the current guarded block.

The new @world guard in the NeededBalances loop also makes sense conceptually: it prevents the compiler/runtime from treating world as a “locked/bounded” account when it shows up via variables, while direct @world uses and balance(@world, …) via UnresolvedResourceBalances remain allowed.

One thing to confirm:

  • For scripts like monetary $amount = balance(@world, USD/2) or other places where @world participates only in balance() or as an unbounded source, Program.NeededBalances should not contain the world resource address. Otherwise this new guard would reject previously valid programs and conflict with tests that rely on balance(@world, …) semantics.

If that invariant holds, this change is well-scoped and aligns with the “special unbounded account” semantics of world.

Consider adding (or pointing to) a small focused test that exercises a variable-resolved account equal to "world" in a context that should now be rejected (e.g., bounded overdraft via a var), to make the new error path explicit.

internal/machine/vm/machine_test.go (1)

2271-2288: New “save all and overdraft” test accurately captures the previous bug

This subtest nicely encodes the regression you’re fixing:

  • Initial balance @alice = -10 USD with an overdraft limit of [USD 10] means there should be no remaining capacity to send funds.
  • save [USD *] from @alice must not reset the tracked balance to zero when it’s already negative.
  • The subsequent send [USD 10] with allowing overdraft up to [USD 10] should therefore fail with ErrInsufficientFund and an “insufficient funds” message, as asserted.

This directly guards the interaction between OP_SAVE (asset branch) and withdrawAll overdraft logic and will prevent regressions in the scenario the PR targets.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files

Prompt for AI agents (all 1 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="internal/machine/vm/machine.go">

<violation number="1" location="internal/machine/vm/machine.go:518">
P1: ResolveBalances now rejects `@world` entirely, but other VM code and tests still compile programs that read `balance(@world, …)`, so those scripts will now fail before execution with ErrInvalidVars instead of the expected behavior.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/machine/vm/machine.go (1)

435-456: OP_SAVE now respects tracked balances and preserves existing overdraft usage

The new OP_SAVE logic only mutates m.Balances when an account/asset pair is already tracked and:

  • For machine.Asset saves, it zeroes the balance only when the current tracked balance is strictly positive.
  • For machine.Monetary saves, it subtracts the saved amount only when a tracked balance for that asset exists.

This aligns with the goal of:

  • Avoiding creation of synthetic balances for accounts/assets that are never used as sources.
  • Not “resetting” negative balances, which would effectively erase already-used overdraft (the scenario covered by the new “save all and overdraft” test).

Given how ResolveBalances populates m.Balances only for Program.NeededBalances, these guards look correct and avoid side effects on untracked or world-like accounts while preserving existing tests that rely on saving from positive balances.

If you want to tighten this further, you could consider centralizing the “only touch if tracked” pattern (asset + monetary) into a small helper to keep OP_SAVE branches symmetrical and easier to audit in the future, but functionally this is fine as-is.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6695000 and c0f3f4b.

📒 Files selected for processing (2)
  • internal/machine/vm/machine.go (2 hunks)
  • internal/machine/vm/machine_test.go (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/machine/vm/machine.go (3)
internal/machine/monetary.go (5)
  • Zero (26-26)
  • Monetary (9-12)
  • Monetary (14-14)
  • MonetaryInt (41-41)
  • MonetaryInt (43-43)
internal/machine/vm/program/program.go (1)
  • Program (10-14)
internal/machine/errors.go (1)
  • NewErrInvalidVars (108-112)
internal/machine/vm/machine_test.go (4)
internal/machine/vm/machine.go (1)
  • Posting (45-50)
internal/controller/ledger/errors.go (1)
  • ErrInvalidVars (148-148)
internal/machine/errors.go (2)
  • ErrInvalidVars (95-97)
  • ErrInsufficientFund (34-36)
internal/machine/vm/run.go (1)
  • Run (57-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Tests
  • GitHub Check: Dirty
🔇 Additional comments (5)
internal/machine/vm/machine_test.go (4)

135-141: Improved error diagnostics for expected vs actual error type/value look correct

Using errors.Is(err, expected.Error) is consistent with other tests, and the new format string (%[1]v (%[1]T)) avoids the previous misuse of %w while giving clear visibility into both value and dynamic type of the expected error.


1702-1721: Good coverage for invalid @world usage via account variable as source

This test precisely exercises the new constraint in ResolveBalances where @world coming from an account variable and used as a source yields ErrInvalidVars with the specific explanatory message. This should guard against accidentally treating @world as a bounded account in the standard interpreter.


1723-1748: Nice positive test ensuring @world is allowed as a non‑source via variables

This test complements TestWorldSourceVariable by validating that @world used only as a destination via an account variable remains valid and produces the expected posting. This matches the intended “no balances / no restriction when not a source” behavior.


2319-2336: New “save all and overdraft” regression test correctly captures the overdraft bug

Starting Alice at -10 and then doing save [USD *] from @alice followed by a bounded overdraft send is a good reproducer: previously, zeroing the balance would effectively reset the overdraft usage. Asserting ErrInsufficientFund with "insufficient funds" ensures the new OP_SAVE semantics preserve existing negative balances and prevent double‑spending across saves + overdraft.

internal/machine/vm/machine.go (1)

509-520: ResolveBalances initialization and @world guard match the intended invariants

Two behaviors here look appropriate:

  • m.Balances = make(...) before processing needed balances ensures that even scripts that don’t require any balances will have m.Balances non‑nil, so Execute no longer fails with ErrBalancesNotInitialized for “no-source” programs.
  • Rejecting any account whose resolved address is "world" in Program.NeededBalances with NewErrInvalidVars enforces that @world cannot appear as a bounded source in the standard interpreter. Literal @world sources (which are excluded from NeededBalances) and balance(@world, …) look to continue flowing through the UnresolvedResourceBalances path, preserving existing behaviors and the negative‑balance check used by TestVariableBalance.

Overall this is consistent with the new tests guarding world-as-variable usage and should close the ambiguity around treating @world as a normal account in the standard VM.

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