Skip to content

Fix/escrow support transfer fee [EXO-8]#97

Merged
dev-jodee merged 4 commits intomainfrom
fix/escrow-support-transfer-fee
Apr 14, 2026
Merged

Fix/escrow support transfer fee [EXO-8]#97
dev-jodee merged 4 commits intomainfrom
fix/escrow-support-transfer-fee

Conversation

@sebatustra
Copy link
Copy Markdown
Collaborator

@sebatustra sebatustra commented Apr 13, 2026

fix(escrow): support Token2022 TransferFeeConfig extension

Summary

Adds proper support for SPL Token 2022 mints with the TransferFeeConfig extension in both the deposit and release_funds instructions.

Previously, both processors used Transfer (the basic Token2022 CPI), which is rejected by the SPL Token 2022 program with error Custom(31) ("Mint required for this account to transfer tokens, use transfer_checked") whenever the mint has any extensions configured. Additionally, the processors assumed a 1:1 relationship between the requested amount and the tokens actually transferred — an assumption that breaks with transfer fee mints, where fees are withheld at the destination ATA.

Changes

Program (deposit.rs, release_funds.rs)

  • TransferTransferChecked: Both processors now use TransferChecked, which requires passing the mint account and decimals. This is required by SPL Token 2022 for any mint with extensions.

  • Balance-delta pattern: Instead of asserting balance_after == balance_before ± args.amount (which fails when fees are withheld), both processors now compute the actual transferred amount from the observed balance change:

    • Deposit: received = escrow_balance_after - escrow_balance_before
    • Release: released = escrow_balance_before - escrow_balance_after

    The delta is used in the emitted event and replaces the strict equality check that was previously returning InvalidEscrowBalance.

Tests (test_deposit/mod.rs, test_release_funds/mod.rs, utils.rs)

  • Added create_mint_2022_with_transfer_fee helper in utils.rs that creates a Token2022 mint with TransferFeeConfig using real on-chain instructions (create_accountinitialize_transfer_fee_configinitialize_mint). The order matters — the extension must be initialized before the base mint.

  • Fixed get_token_balance in utils.rs to use StateWithExtensions::<Token2022Account>::unpack instead of Token2022Account::unpack. The plain unpack fails on accounts that have the TransferFeeAmount extension attached (which is added automatically to every ATA for a fee-bearing mint).

  • Added test_deposit_token_2022_transfer_fee_success and test_release_funds_token_2022_transfer_fee_success.

Note on test assertions: These two tests do not use the shared assert_get_or_deposit / assert_deposit_balances helpers used by the rest of the suite. Those helpers assert that the escrow balance increases by exactly deposit_amount, which is incorrect for fee-bearing mints (escrow only receives amount - fee). The transfer fee tests use inline balance assertions with explicit fee calculations instead:

let expected_fee = (DEPOSIT_AMOUNT as u128 * TRANSFER_FEE_BASIS_POINTS as u128 / 10_000) as u64;
let expected_received = DEPOSIT_AMOUNT - expected_fee;
assert_eq!(instance_balance_after, instance_balance_before + expected_received);

The release funds test also raises the compute unit limit to 1_200_000 (vs the default 200_000) because SMT proof verification is expensive enough to exceed the default budget.

Coverage Report

Component Lines Hit Lines Total Coverage Artifact
Core - - - -
Indexer - - - -
Gateway - - - -
Auth - - - -
Withdraw Program 118 230 51.3% unit-coverage-reports
Escrow Program 1,170 1,951 60.0% unit-coverage-reports
E2E Integration 7,895 11,473 68.8% e2e-coverage-reports
Total 9,183 13,654 67.3%

Last updated: 2026-04-13 23:13:31 UTC by E2E Integration

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 13, 2026

Greptile Summary

This PR adds proper Token2022 TransferFeeConfig support to deposit and release_funds by upgrading TransferTransferChecked (required for any mint with extensions) and replacing the strict post-transfer balance equality check with a balance-delta pattern that correctly handles fee-bearing mints. The on-chain changes are sound: on deposit the escrow delta is emitted as received; on release the escrow is always debited the full args.amount since fees are withheld at the destination, so the delta equals args.amount in practice.

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 documentation issues that do not affect runtime correctness.

The on-chain logic is correct: TransferChecked is the proper CPI for Token2022 mints with extensions, the balance-delta pattern accurately accounts for withheld fees on deposit, and release correctly captures the full escrow debit (fees are always at the destination). Only P2 doc/comment issues found.

No files require special attention; test documentation comments in test_release_funds/mod.rs are slightly inaccurate but do not affect correctness.

Important Files Changed

Filename Overview
contra-escrow-program/program/src/processor/deposit.rs Upgraded TransferTransferChecked (adds mint + decimals); replaced strict post-transfer equality check with a balance-delta pattern that correctly handles fee-bearing mints.
contra-escrow-program/program/src/processor/release_funds.rs Same TransferTransferChecked migration as deposit; balance-delta computes released (always equals args.amount for fee-bearing mints since fees are withheld at destination, not source).
contra-escrow-program/tests/integration-tests/src/utils.rs Added create_mint_2022_with_transfer_fee (correct init order: create_account → init_fee → init_mint); fixed get_token_balance to use StateWithExtensions::unpack for Token2022 accounts with the TransferFeeAmount extension.
contra-escrow-program/tests/integration-tests/src/test_deposit/mod.rs New test_deposit_token_2022_transfer_fee_success test correctly uses real on-chain instructions and inline balance assertions; fee calculation uses floor division where SPL Token 2022 uses ceiling (values are evenly divisible so result is the same, but comment is misleading).
contra-escrow-program/tests/integration-tests/src/test_release_funds/mod.rs New test_release_funds_token_2022_transfer_fee_success test correctly validates escrow debit and user receipt with fee; comment incorrectly states escrow starts with 990,000 when mint_to actually seeds it with 1,000,000.

Sequence Diagram

sequenceDiagram
    participant U as User/Operator
    participant EP as EscrowProgram
    participant T22 as SPL Token 2022

    Note over EP,T22: Deposit (fee-bearing mint)
    U->>EP: deposit(amount)
    EP->>EP: read escrow_balance_before
    EP->>T22: TransferChecked(from=user_ata, to=escrow_ata, amount, mint, decimals)
    T22-->>T22: withhold fee at escrow_ata (destination)
    T22-->>EP: ok
    EP->>EP: read escrow_balance_after
    EP->>EP: received = after - before
    EP->>EP: emit DepositEvent(amount=received)

    Note over EP,T22: Release (fee-bearing mint)
    U->>EP: release_funds(amount)
    EP->>EP: read escrow_balance_before
    EP->>T22: TransferChecked(from=escrow_ata, to=user_ata, amount, mint, decimals)
    T22-->>T22: withhold fee at user_ata (destination)
    T22-->>EP: ok
    EP->>EP: read escrow_balance_after
    EP->>EP: released = before - after
    EP->>EP: update SMT root
    EP->>EP: emit ReleaseFundsEvent(amount=released)
Loading

Reviews (1): Last reviewed commit: "fix: include program processor changes f..." | Re-trigger Greptile

@sebatustra sebatustra changed the title Fix/escrow support transfer fee Fix/escrow support transfer fee [EXO-8] Apr 13, 2026
@linear
Copy link
Copy Markdown

linear bot commented Apr 13, 2026

@dev-jodee dev-jodee requested review from amilz and dev-jodee April 14, 2026 13:22
Copy link
Copy Markdown
Collaborator

@dev-jodee dev-jodee left a comment

Choose a reason for hiding this comment

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

lgtm

@dev-jodee dev-jodee merged commit 23f1696 into main Apr 14, 2026
10 checks passed
@dev-jodee dev-jodee deleted the fix/escrow-support-transfer-fee branch April 14, 2026 13:32
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