Guardian's review of Stellar Contracts for USDT0, published August 2026. The report records 4 findings, including 4 informational.
- Published
- Review window
- August 7 to 11, 2026
- Language
- Rust, Soroban
- Chains
- Stellar
- Sector
- Stablecoins
- 0 Critical
- 0 High
- 0 Medium
- 0 Low
- 4 Informational
Scope
Findings 4
-
I-01 Informational Stellar Trustline Gaps Block OFT Delivery Documentation Acknowledged
Description
The EVM OFT accepts a 32-byte destination but cannot determine whether the corresponding Stellar recipient can hold the Stellar asset. After tokens are burned on EVM, the Stellar OFT resolves the payload as a deployed C-address or, if no corresponding contract exists, as a G-address and attempts to mint through the Stellar Asset Contract. A classic Stellar account identified by a G-address can receive and hold an issued asset only through a trustline for that asset. Therefore, the recipient must have an authorized trustline whose remaining limit can accommodate the transferred amount. The Stellar OFT only invokes the mint operation and does not create a missing trustline, as trustline creation requires authorization from the recipient and sufficient XLM reserve. If these requirements are not satisfied, destination execution reverts after the source tokens have already been burned. The message remains retryable, but the tokens remain unavailable until the trustline is established or the intended contract is deployed and the message is retried.
Recommendation
Document that G-address recipients must exist and establish an authorized trustline for the Stellar asset before receiving an inbound transfer.
-
I-02 Informational Stellar Pause Leaves Inbound Transfers Pending Documentation Acknowledged
Description
The Stellar pause mechanism blocks both outgoing debits and incoming credits. Outgoing transfers are pause protected indirectly, as both
debitimplementations invoke__debit_viewbefore locking or burning tokens, and that function checks the pause state. Incoming transfers are rejected directly by__credit. This creates a cross-chain asymmetry because the EVM OFT has no corresponding pause mechanism and cannot prevent transfers targeting a paused Stellar deployment. An EVM-to-Stellar transfer can burn tokens successfully on EVM, after which destination execution reverts when Stellar attempts to credit the recipient. Conversely, a Stellar-to-EVM transfer is rejected before any Stellar tokens are locked or burned. The failed inbound message remains retryable, so the tokens are not lost. However, they remain unavailable until Stellar is unpaused and the message is retried.Recommendation
Document that pausing Stellar also suspends settlement of transfers already initiated from other EVM chains. Monitor failed inbound deliveries and establish a process to retry them after unpausing.
-
I-03 Informational Quoted Maximum Ignores Dust Rounding Rounding Acknowledged
Description
The
quote_oftfunction derives the maximum input from the available outbound rate limit capacity. When a fee is configured, the contract converts the available post-fee capacity into an estimated maximum pre-fee input, intending the result to represent the largest input accepted bysend.let mut max_amount_ld = Self::rate_limit_capacity(env, &Direction::Outbound, send_param.dst_eid); if max_amount_ld != i128::MAX { // Back-calculate pre-fee amount so max_amount_ld represents the largest input to send() max_amount_ld = Self::__amount_before_fee(env, send_param.dst_eid, max_amount_ld); } else { // No rate limit configured — use the bounded default max_amount_ld = u64::MAX as i128; } limit.max_amount_ld = max_amount_ld;However, during execution, the contract rounds the amount down according to the decimal conversion rate before applying the rate limit.
let (amount_sent_ld, amount_received_ld) = if !has_fee { // No fee: dust stays with sender (default OFT behavior) let amount_sent_ld = oft_utils::remove_dust(amount_ld, conversion_rate); (amount_sent_ld, amount_sent_ld) } else { // With fee: match EVM OFTFee behavior // - sender pays full amount_ld (no dust removed), dust is absorbed into the charged fee let fee = Self::__fee_view(env, dst_eid, amount_ld); let amount_received_ld = oft_utils::remove_dust(amount_ld - fee, conversion_rate); (amount_ld, amount_received_ld)The rate limiter consequently checks the normalized
amount_received_ld, rather than the original input. As a result, inputs above the reported maximum can still be accepted when fee calculation and dust removal round them down to an amount within the available capacity. Consequently, integrations relying on the quoted maximum may unnecessarily reject valid transfers.Recommendation
Calculate
max_amount_ldusing the same fee and dust rounding as send, so it reflects the largest accepted input. -
I-04 Informational Unsafe Single-Step SAC Admin Transfer Best Practices Acknowledged
Description
The SAC Manager allows an account holding
ADMIN_MANAGER_ROLEto transfer SAC administration immediately to any supplied address. The new administrator is not validated and is not required to accept the transfer. After execution, the Manager loses SAC authority and cannot restore itself because any subsequent administrative transfer must be authorized by the new administrator. Consequently, transferring administration to an incorrect or nonfunctional address can permanently disable minting and other SAC administrative operations.Recommendation
Implement a two-step transfer in which the current administrator proposes a new administrator and the proposed administrator authenticates an acceptance before the Manager calls the SAC’s
set_admin.
No findings match.
More from USDT0
All 35 reportsPut 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.
