Description
The asset.rs module handles token types (Stellar Asset Contract tokens) but does not implement a proper token whitelist, blacklist, or sanitization for the tokens accepted by the protocol. Any token address can be used in register_meter, lock_guarantor_deposit, initialize_gas_buffer, and other functions. This exposes the protocol to potential attacks using malicious or non-standard tokens (rebasing tokens, fee-on-transfer tokens, ERC-777-style hooks, pausable tokens, tokens that revert on zero-value transfers).
Token sanitization gaps:
- No token whitelist enforced for critical operations (gas buffer, insurance pool, streaming)
- No balance checks before/after transfers to detect fee-on-transfer tokens
- No maximum approval limits for token interactions
- No detection of non-standard token behavior (rebasing, pausing)
- No handling for tokens with >18 or <6 decimals
- No token metadata validation (name, symbol, decimals)
Technical Context & Impact
- Affected Components/Files:
contracts/utility_contracts/src/asset.rs, contracts/utility_contracts/src/lib.rs (all token transfer calls), contracts/utility_contracts/src/stream.rs
- Impact: Security, Protocol Solvency, User Protection
Step-by-Step Implementation Guide
- Implement token whitelist:
DataKey::ApprovedTokens as a Vec<Address>; require_approved_token(&env, &token_address) guard on all token deposit/transfer functions
- Add balance change verification: After every
token_client.transfer, verify balance_after == balance_before + expected_amount (vs balance_after >= balance_before + expected_amount for fee-on-transfer)
- Add admin token management:
approve_token(token_address), revoke_token(token_address), get_approved_tokens(page, page_size)
- Detect non-standard tokens:
validate_token(token_address) -> TokenStandard { Standard, Rebasing, FeeOnTransfer, Blacklisted } using on-chain heuristics
- Handle decimals: Store
DataKey::TokenDecimals(token) and normalize all amounts to 7-decimal fixed point internally
- Add max transfer guard:
transfer(amount) should cap at min(amount, user_balance) to prevent partial fill issues
- Write token compatibility tests: Test with mock rebasing token, fee-on-transfer token, and reverting token contracts
Verification & Testing Steps
- Test with standard token: deposit -> verify -> withdraw -> verify amounts match
- Test with fee-on-transfer mock token (charges 1% on transfer): deposit -> verify balance reflects fee -> withdrawal should use actual balance
- Test with unapproved token: attempt deposit -> should be rejected
- Test decimals: register token with 6 decimals, 8 decimals, 18 decimals -> verify internal normalization works
- Run full test suite:
cargo test --package utility_contracts
- Deploy to testnet and test with real Stellar tokens (USDC, XLM)
Description
The
asset.rsmodule handles token types (Stellar Asset Contract tokens) but does not implement a proper token whitelist, blacklist, or sanitization for the tokens accepted by the protocol. Any token address can be used inregister_meter,lock_guarantor_deposit,initialize_gas_buffer, and other functions. This exposes the protocol to potential attacks using malicious or non-standard tokens (rebasing tokens, fee-on-transfer tokens, ERC-777-style hooks, pausable tokens, tokens that revert on zero-value transfers).Token sanitization gaps:
Technical Context & Impact
contracts/utility_contracts/src/asset.rs,contracts/utility_contracts/src/lib.rs(all token transfer calls),contracts/utility_contracts/src/stream.rsStep-by-Step Implementation Guide
DataKey::ApprovedTokensas aVec<Address>;require_approved_token(&env, &token_address)guard on all token deposit/transfer functionstoken_client.transfer, verifybalance_after == balance_before + expected_amount(vsbalance_after >= balance_before + expected_amountfor fee-on-transfer)approve_token(token_address),revoke_token(token_address),get_approved_tokens(page, page_size)validate_token(token_address) -> TokenStandard { Standard, Rebasing, FeeOnTransfer, Blacklisted }using on-chain heuristicsDataKey::TokenDecimals(token)and normalize all amounts to 7-decimal fixed point internallytransfer(amount)should cap atmin(amount, user_balance)to prevent partial fill issuesVerification & Testing Steps
cargo test --package utility_contracts