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
Findings 16
Main Review
13 findings · February 23 to 25, 2026-
M-01 Medium Rent Payer Can Be Tricked For Escrow Payment Validation Resolved
Description
In this protocol, account rent for escrows is subsidized by the protocol via a designated
rent_payersigner (e.g., a backend or frontend-controlled bot). Theinitialize_gift_escrowinstruction requires bothinitializerandrent_payerto 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 pubkeyrent_payer= subsidy bot pubkeyinitializer_token_account= a token account controlled by the botclaim_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_payerandinitializer, 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_payeritself.Alternatively, if an on-chain solution is desired, consider enforcing
rent_payer != initializerduring 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. -
L-01 Low Escrow Created Before Config Initialization Validation Acknowledged
Description
The
initialize_gift_escrowinstruction does not require the Config PDA to exist as an input account. This means escrows can be created and tokens deposited into vaults beforeinitialize_confighas been called.However, all resolution paths:
claim,cancel, andrefund_expired; require a validConfigaccount withseeds = [b"config"], bump = config.bumpand a treasury that matchesconfig.treasury.Recommendation
Consider requiring the
Configaccount inInitializeGiftEscrowto guarantee that no escrow can be created until protocol configuration is finalized. -
L-02 Low Escrow Amount Not Enforced On Payout Logical Error Acknowledged
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.
-
L-03 Low No Minimum Amount Check Can Cause Griefing Validation Resolved
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.
-
L-04 Low PermanentDelegate token Can Drain Escrow Vault Trust Assumptions Resolved
Description
Token-2022 mints with the
PermanentDelegateextension 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 enforcevault_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
PermanentDelegateconfigured. -
I-01 Informational Rent Is Routed To Treasury In Cancel/Refund Logical Error Acknowledged
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.
-
I-02 Informational Transfer Fee Mints Break Escrow Resolution Validation Acknowledged
Description
Token-2022 mints with the
TransferFeeConfigextension can be used to create escrows. When aTransferFeemint is used, the vault receives less than the requested amount due to the on-transfer fee deduction.The stored
escrow.amountreflects the full pre-fee value, causing thevault_balance >= escrow.amountguard to fail on all resolution paths (claim, cancel, refund_expired).Recommendation
Consider rejecting mints with the
TransferFeeConfigextension duringinitialize_gift_escrowbefore the token transfer occurs. -
I-03 Informational Refund Blocked If Initializer ATA Closed Logical Error Acknowledged
Description
The
refund_expiredinstruction 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 validinitializer_token_accountthat 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_accountis declared as a plainInterfaceAccount<'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.
-
I-04 Informational No Duplicate Check On Treasury and Admin Update Informational Acknowledged
Description
The
admincan 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.
-
I-05 Informational Incorrect Error Codes in Migrate Escrow Informational Acknowledged
Description
In
migrate_escrow:EscrowError::InvalidEscrowStatusis 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.EscrowError::InvalidTokenAccountis 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.
-
I-06 Informational No Event Emitted for Escrow Migration Events Acknowledged
Description
The
migrate_escrowinstruction 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_escrowemitsEscrowInitialized, claim emitsEscrowClaimed, cancel emitsEscrowCanceled,refund_expiredemitsEscrowExpiredRefund,initialize_configemitsConfigInitialized,update_treasuryemitsTreasuryUpdated, andupdate_adminemitsAdminUpdated.Recommendation
Consider emitting a
EscrowMigratedevent. -
I-07 Informational Refunds Can Be Redirected To Any New Address Informational Acknowledged
Description
refund_expiredis 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.
-
I-08 Informational Gift Key Can Be Unusable Validation Acknowledged
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-
I-01 Informational Warning About Fix on M-01 Warning Acknowledged
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_payerThe onchain restriction can be satisfied by setting:
initializer= subsidy bot pubkeyrent_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.
-
I-02 Informational High Decimal Mint DoS Informational Acknowledged
Description
initialize_gift_escrownow enforces a dust floormin_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 withAmountBelowMinimumWhile 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.
-
I-03 Informational Unfixed I-02 Informational Acknowledged
Description
The
SECURITY_HARDENING.mdfile states that Token-2022 withTransferFeeis 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.
No findings match.
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.
