Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Security review · April 2026

Escrow

for Stampt

Guardian's review of Escrow for Stampt, published April 2026. The report records 16 findings across 2 review rounds, including 1 medium and 4 low.

Published
Review window
February 23 to March 25, 2026
Rounds
Main Review, Remediation Review
Language
Rust
Chains
Solana
Sector
Payments
  • 0 Critical
  • 0 High
  • 1 Medium
  • 4 Low
  • 11 Informational

3 resolved · 13 acknowledged

Findings 16

Main Review

13 findings · February 23 to 25, 2026
  1. M-01 Medium Rent Payer Can Be Tricked For Escrow Payment Validation Resolved
    Location
    programs/gift-escrow/src/lib.rs:622
    Round
    Main Review

    Description

    In this protocol, account rent for escrows is subsidized by the protocol via a designated rent_payer signer (e.g., a backend or frontend-controlled bot). The initialize_gift_escrow instruction requires both initializer and rent_payer to be signers of the same transaction.

    However, if the same keypair is used for both roles, a single signature satisfies both signer constraints. In this case, an attacker can construct a transaction that sets:

    • initializer = subsidy bot pubkey
    • rent_payer = subsidy bot pubkey
    • initializer_token_account = a token account controlled by the bot
    • claim_authority = attacker-controlled key

    If the subsidy bot signs this transaction to cover the rent, it implicitly authorizes a token transfer from its own token account into the escrow vault. The attacker can later claim the escrowed tokens using the chosen claim_authority.

    When a subsidy bot key is reused as both rent_payer and initializer, and it holds token balances, an attacker can cause token transfers from the bot’s accounts, resulting in direct loss of funds.

    Recommendation

    One option is to handle this off-chain by ensuring the rent_payer account used for rent subsidization holds only SOL and never holds any SPL tokens or associated token accounts. Additionally, the signing bot/account should enforce that the initializer is never the rent_payer itself.

    Alternatively, if an on-chain solution is desired, consider enforcing rent_payer != initializer during escrow creation. However, this would prevent users from creating escrows directly when they also pay the rent themselves, which may be a legitimate use case that should not be blocked. This approach should only be adopted if the expected usage model is that the protocol always subsidizes rent and users never pay rent themselves.

  2. L-01 Low Escrow Created Before Config Initialization Validation Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:588-627
    Round
    Main Review

    Description

    The initialize_gift_escrow instruction does not require the Config PDA to exist as an input account. This means escrows can be created and tokens deposited into vaults before initialize_config has been called.

    However, all resolution paths: claim, cancel, and refund_expired; require a valid Config account with seeds = [b"config"], bump = config.bump and a treasury that matches config.treasury.

    Recommendation

    Consider requiring the Config account in InitializeGiftEscrow to guarantee that no escrow can be created until protocol configuration is finalized.

  3. L-02 Low Escrow Amount Not Enforced On Payout Logical Error Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:277-278
    Round
    Main Review

    Description

    Escrow payments transfer the entire vault balance rather than a predetermined escrow amount. If the recipient expects an exact amount and enforces strict equality checks, the claim may fail.

    Recommendation

    Consider transferring only the intended escrow amount to the recipient, and add handling for any excess balance in cases where the vault receives donations or mistakenly sent funds.

  4. L-03 Low No Minimum Amount Check Can Cause Griefing Validation Resolved
    Location
    programs/gift-escrow/src/lib.rs:40
    Round
    Main Review

    Description

    Escrow creation is permissionless and there are no minimum or maximum escrow amount limits. Since the protocol covers the rent payments for these escrows, a malicious user could create a large number of dust escrows, causing the protocol to incur substantial rent costs for escrow contracts and vaults holding tiny amounts. The impact could be amplified by creating non-expiring escrows with negligible amounts, preventing the treasury from ever recovering the rent paid for these escrows.

    Recommendation

    Introduce a minimum escrow amount to mitigate griefing. Even a small threshold (e.g., $1) could be sufficient to prevent these cases. Additionally, consider enforcing a maximum escrow amount.

  5. L-04 Low PermanentDelegate token Can Drain Escrow Vault Trust Assumptions Resolved
    Location
    programs/gift-escrow/src/lib.rs
    Round
    Main Review

    Description

    Token-2022 mints with the PermanentDelegate extension allow a designated address to transfer tokens from any token account of that mint without requiring the account authority's signature. If an escrow is created using such a mint, the permanent delegate can drain the vault directly through the token program, bypassing the escrow PDA authority entirely. This would cause all resolution paths (claim, cancel, refund_expired) to fail since they all enforce vault_balance >= escrow.amount. The escrow becomes permanently stuck, and the rent paid for the escrow and vault PDAs is locked with no recovery path.

    Recommendation

    Reject mints that have a PermanentDelegate configured.

  6. I-01 Informational Rent Is Routed To Treasury In Cancel/Refund Logical Error Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:731-732
    Round
    Main Review

    Description

    When an escrow is claimed, canceled, or refunded after expiry, both the Escrow PDA and the vault token account are closed. In all cases, the lamports held as rent in these accounts are transferred to the protocol treasury, rather than being refunded to the initializer or claimer.

    Ideally, rent should be refunded to the initializer when the escrow gets cancelled or refunded. The protocol team acknowledged that rent is subsidized for users and paid by the protocol itself. However, if users create escrows and pay the rent themselves, those rent payments are transferred to the treasury and are lost during refunds.

    Recommendation

    Document this behavior and inform users of this scenario.

  7. I-02 Informational Transfer Fee Mints Break Escrow Resolution Validation Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:27
    Round
    Main Review

    Description

    Token-2022 mints with the TransferFeeConfig extension can be used to create escrows. When a TransferFee mint is used, the vault receives less than the requested amount due to the on-transfer fee deduction.

    The stored escrow.amount reflects the full pre-fee value, causing the vault_balance >= escrow.amount guard to fail on all resolution paths (claim, cancel, refund_expired).

    Recommendation

    Consider rejecting mints with the TransferFeeConfig extension during initialize_gift_escrow before the token transfer occurs.

  8. I-03 Informational Refund Blocked If Initializer ATA Closed Logical Error Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:789-791
    Round
    Main Review

    Description

    The refund_expired instruction is designed to be permissionless, anyone can call it after expiry to return funds to the initializer. However, it requires the caller to pass a valid initializer_token_account that satisfies two manual checks in the instruction body:

    require!(
        ctx.accounts.initializer_token_account.mint == escrow.mint,
        EscrowError::InvalidTokenAccount
    );
    require!(
        ctx.accounts.initializer_token_account.owner == escrow.initializer,
        EscrowError::InvalidTokenAccount
    );
    

    The initializer_token_account is declared as a plain InterfaceAccount<'info, TokenAccount> with only #[account(mut)]. it must already exist on-chain. If the initializer closes their Associated Token Account after creating the escrow (e.g., to reclaim rent, or via wallet cleanup), there is no valid account to pass.

    Recommendation

    Consider documenting this behavior so that users are aware of it.

  9. I-04 Informational No Duplicate Check On Treasury and Admin Update Informational Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:152-165
    Round
    Main Review

    Description

    The admin can update the treasury account and admin account. However, there is no check to ensure the new treasury is different than the old treasury, or the new admin is different than the old admin. These functions can be called with the same addresses.

    Recommendation

    Consider having a duplicate check in these functions.

  10. I-05 Informational Incorrect Error Codes in Migrate Escrow Informational Acknowledged
    Location
    https://github.com/GuardianOrg/contracts-team1-1771428637598/blob/main/programs/gift-escrow/src/lib.rs#L537-L555, https://github.com/GuardianOrg/contracts-team1-1771428637598/blob/main/programs/gift-escrow/src/lib.rs#L528-L532
    Round
    Main Review

    Description

    In migrate_escrow:

    1. EscrowError::InvalidEscrowStatus is used for discriminator validation and account size validation. These checks verify that the account has the correct Escrow discriminator and matches the expected old-format size; they are format/version checks, not escrow status checks.
    2. EscrowError::InvalidTokenAccount is used for a program ownership check (escrow_info.owner == ctx.program_id). This check validates that the escrow account is owned by the gift-escrow program, not that a token account is valid

    Recommendation

    Consider using distinct error variants to clearly communicate the actual failure.

  11. I-06 Informational No Event Emitted for Escrow Migration Events Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:525
    Round
    Main Review

    Description

    The migrate_escrow instruction does not emit any event upon successful migration of an old-format escrow to the new format. Every other state-changing instruction in the program emits an event: initialize_gift_escrow emits EscrowInitialized , claim emits EscrowClaimed, cancel emits EscrowCanceled, refund_expired emits EscrowExpiredRefund, initialize_config emits ConfigInitialized, update_treasury emits TreasuryUpdated, and update_admin emits AdminUpdated.

    Recommendation

    Consider emitting a EscrowMigrated event.

  12. I-07 Informational Refunds Can Be Redirected To Any New Address Informational Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:461
    Round
    Main Review

    Description

    refund_expired is permissionless, and anyone can refund an expired escrow to the initializer. During this action, the only check performed is that the provided token account is owned by the initializer; therefore, no other party can receive the funds.

    However, anyone can create token accounts on behalf of any user. As a result, an attacker can create a new token account for the initializer and refund the tokens to that account. While there is no direct incentive to do so, the initializer may not be aware that a new token account has been created on their behalf and may not realize that the refunded funds were sent to a different account.

    Recommendation

    Be aware of this behavior and document it. Alternatively, only allow the initializer to refund the escrow themselves.

  13. I-08 Informational Gift Key Can Be Unusable Validation Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:617
    Round
    Main Review

    Description

    During escrow creation, the claim_authority (gift key) is not validated against obvious invalid or unusable values unlike the checks in admin and treasury updates. In particular, it may be set to default pubkey or system account etc.

    Since claiming the escrow requires the claim_authority to sign the transaction, using such a key makes the escrow unclaimable, and must be cancelled/refunded later

    Recommendation

    Consider validating gift key against obvious invalid ones similar to other instructions.

Remediation Review

3 findings · March 24 to 25, 2026
  1. I-01 Informational Warning About Fix on M-01 Warning Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:105
    Round
    Remediation Review

    Description

    The fix for M-01 ensures on-chain that the initializer is not the rent payer, preventing the bot from being tricked into paying tokens in addition to rent. However, it is important to note that, on the backend, the bot must still ensure it only signs transactions where bot == rent_payer

    The onchain restriction can be satisfied by setting:

    • initializer = subsidy bot pubkey
    • rent_payer = attacker pubkey

    An attacker may attempt to trick the bot into paying the tokens while covering the rent themselves. Off-chain checks should ensure this never occurs.

    Recommendation

    Ensure that the rent subsidy bot never signs a transaction where rent_payer differs from the bot itself, for the previous fix to be fully effective.

  2. I-02 Informational High Decimal Mint DoS Informational Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:62
    Round
    Remediation Review

    Description

    initialize_gift_escrow now enforces a dust floor min_amount = 10_u64.checked_pow(decimals.saturating_sub(1) as u32). For any mint with >20 decimals the exponentiation overflows u64, and the escrow creation reverts with AmountBelowMinimum

    While tokens with such high decimal precision are extremely rare on Solana, they may still exist and cannot be supported by the gift escrow.

    Recommendation

    Document this behavior.

  3. I-03 Informational Unfixed I-02 Informational Acknowledged
    Location
    programs/gift-escrow/src/lib.rs:256
    Round
    Remediation Review

    Description

    The SECURITY_HARDENING.md file states that Token-2022 with TransferFee is supported. However, as noted in the previous I-02 issue, the program cannot support these tokens.

    Recommendation

    Update the documentation if these tokens are not supported and inform users accordingly. Otherwise, update the program to support these tokens.

Put your code through the same review.

This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.

Get a quote