Guardian's review of suiUSDe for Mysten Labs, published December 2025. The report records 33 findings, including 4 low and 29 informational.
- Published
- Review window
- December 10 to 19, 2025
- Language
- Move
- Chains
- Sui
- Sector
- Infrastructure
- 0 Critical
- 0 High
- 0 Medium
- 4 Low
- 29 Informational
Findings 33
-
L-01 Low Replenish Does Not Validate Collateral Status Validation Acknowledged
Description
The
replenishfunction does not validate the active status of a collateral that is being replenished. Because of this it might be possible for any caller to accidentally replenish a disabled collateral.Recommendation
Consider validating that the collateral is enabled in the
replenishfunction. -
L-02 Low Malicious Nonce Strings Warning Acknowledged
Description
Nonces in the Move implementation are arbitrary strings with no distinct limit on their length.
This means that they could be used to inject malicious code into whatever systems are consuming the mint/redeem events that emit this nonce value.
Recommendation
Consider adding a reasonable length validation on the nonce string to decrease the possibility of them being able to be used as malicious injection.
-
L-03 Low Missing Global Pause Capability Unexpected Behavior Resolved
Description
When creating the suiUSDe token in the
init_treasuryfunction themake_regulatedfunction receives anallow_global_pausevalue oftruewhich indicates that the protocol would like to have the ability to pause usage of the suiUSDe coin across the entire network with thedeny_list_v2_enable_global_pausefunction.However in the
denylist_managermodule there are only functions exposed to add addresses and remove addresses from the denylist. None that expose the ability to pause and unpause the global pause functionality which has been whitelisted for the suiUSDe token.Recommendation
Consider exposing the global pause and unpause functionality through the
deny_list_v2_enable_global_pausefunction if it is desired to have this capability. -
L-04 Low Missing Version Check Access Control Resolved
Description
The
new_oracle_proofandcommit_pyth_pricefunctions do not implement a config version check from the Treasury, and thus are callable even after a package upgrade has occurred and the version has been bumped.If important updates were made to the Oracle in the upgrade, as long as no new logic has been added in the upgrade that prevents the old Oracle logic from being used, then the old oracle logic can be used to create unexpected OracleProof objects which could contain prices that do not meet the standards of the new version of the contracts.
Recommendation
Enforce the config version check in the
new_oracle_proofandcommit_pyth_pricefunctions. -
I-01 Informational Aggregated Oracle Allows Arbitrage Validation Acknowledged
Description
In the commit_pyth_oracle_price flow it is possible for a benefactor user to control the end outcome of the median result and potentially arbitrage the system when multiple oracle feeds are supported because of a few factors.
The commit_pyth_oracle_price function will commit “invalid” price types to the proof to indicate that the price was provided for the relevant feed but was found to be invalid. The validate_and_get_valid_prices function which retrieves the valid prices on usage tosses out any entries that were deemed to be invalid upon commitment.
The user can control whether a price is considered valid on commitment by choosing to pass a stale Pyth update versus passing an up to date one.
This means a benefactor could provide a commitment such as the following:
- Assume minimum_sources is assigned as 2 and there are 3 total feeds configured
- For feed 0 (A pyth feed) the value is $1.01, the user provides a price that is 5 minutes old, it is considered invalid
- For feed 1 the value is $1.00, and the price is valid
- For feed 2 the value is $0.99, and the price is valid
- The computed median is the average of feed 1 and feed 2 which is $0.995
The benefactor could mint using this price, and then redeem using a different price they commit:
- For feed 0 (A pyth feed) the value is $1.01, the user now provides a price that is only 10 seconds old, it is considered valid
- For feed 1 the value is $1.00, and the price is valid
- For feed 2 the value is $0.99, and the price is valid
- The computed median is now feed 1, which is $1.00
- If the spread between these two median results is large enough, the user could gain an immediate arbitrage on the system
Recommendation
When multiple feeds are supported by the aggregate oracle, consider requiring that all pull based feeds where the user provides their own price update have valid staleness.
-
I-02 Informational Oracle Staleness Validated Only On Commitment Validation Acknowledged
Description
At the time of price usage, in the latest_price function, the earliest price from all oracle sources is compared against the collateral_limits maximum age configuration.
However this is a different configuration than the
oracle.limits.max_age_ms. Currently these are to be configured to the same value, but in the event that they are not configured to the same value this may allow prices to be committed in a fresh state and then become stale by the aggregate oracle’s standards by the time the proof is used.More notably, when multiple oracle feeds are supported the collateral_limits max staleness will not be able to apply to each feed individually and so therefore must take the maximum staleness value of all of the feeds. Therefore if there is an onchain aggregator in the list of supported feeds, which has a heartbeat of 12 hours, then the collateral_limits max staleness must be configured to at least 12 hours.
This is far too long for a pyth push based feed, and would allow a user to knowingly or unknowingly commit a pyth push price and wait up to 12 hours before using it, at which point it can be notably stale and out of line with the current market values.
Recommendation
In the future when multiple feeds are supported by the Aggregate Oracle, also perform per-feed level validations on staleness at usage time in the latest_price function.
-
I-03 Informational AggregateOracle Multiple Sources Incompatibility Validation Acknowledged
Description
The Aggregated Oracle is intended to function with multiple feed sources and compute a median result of those provided. However the validation on staleness is only applied at the aggregate oracle level.
When multiple feeds are supported they may have different staleness tolerance requirements and thus should have a feed level staleness validation.
Recommendation
While the existing setup only uses a single pyth feed, in the future when an upgrade is made to support multiple feeds, an individual feed level staleness validation should be introduced as a broader aggregate feed validation may not be able to be strict enough for some feeds in the aggregate oracle.
-
I-04 Informational Unnecessary assert_is_enabled Check Superfluous Code Acknowledged
Description
In the
validate_statesfunction thebenefactor_config.assert_is_enabledmethod is invoked to validate that the benefactor is enabled, however the followingbenefactor_config.add_noncecall already includes such validation in its function body. Therefore the previous bespoke invocation is unnecessary.Recommendation
Consider removing the
benefactor_config.assert_is_enabledinside thevalidate_statesfunction as this is already performed within theadd_nonceinvocation. -
I-05 Informational Typo Typo Resolved
Description
Throughout the codebase Committed is misspelled as
Commited.Recommendation
Correct these instances to Committed.
-
I-06 Informational Treasury Created With Incorrect Version Config Validation Acknowledged
Description
In the treasury.move file the create function for the Treasury object does not validate that the config provided uses an up to date version.
As a result, a treasury can be errantly created that is immediately invalid and outdated.
Recommendation
Consider adding validation that the config provided to the create function is using an up to date config version.
-
I-07 Informational Redundant oracle_limits validation Superfluous Code Acknowledged
Description
The
validate()call inset_minimum_sourcesrevalidatesoracle.limits, even though limits aren’t changed there. Limits are already validated at creation and when setters are used, so this check is redundant in this path.Recommendation
Remove this oracle limits validation in
set_minimum_sourcespath -
I-08 Informational Validation Allows Same Maximum And Minimum Validation Resolved
Description
In the oracle_limits module within the suiusde package, the validate function which is used to check the limit configurations allows the max_price to be equal to the min_price.
This however is unlikely to be a realistic valid configuration and does not align with the validations made on the Solidity implementation.
Recommendation
Consider enforcing more strict validations that require the max_price to be strictly greater than the min_price.
-
I-09 Informational fee_is_charged Inaccuracy Unexpected Behavior Resolved
Description
In the mint and redeem functions for the benefactor, the
fee_is_chargedvalue is intended to indicate whether the collateralization of the action was better than a direct oracle exchange for the protocol, e.g. that the fee was charged.However the
fee_is_chargedvalue is also assigned to true when theone_to_one_amount_outis equivalent to theoracle_amount_out. When this is the case no fee was not charged relative to the oracle price, so thefee_is_chargedvalue should instead be false to indicate this.Recommendation
Consider updating the fee_is_charged definition to be a strict less than comparison, so that it is only true when some nonzero fee is charged between the
one_to_one_amount_outandoracle_amount_out. -
I-10 Informational Lacking minAmountOut Validation Validation Resolved
Description
In the Solidity implementation of the stablecoin system, there is validation which prevents the user from providing a minAmountOut of 0 accidentally. However the Move implementation does not implement such a validation.
Recommendation
Consider if the minimum amount out should be required to be nonzero to protect against potential misconfigurations.
-
I-11 Informational Lack of version validation Validation Acknowledged
Description
The
set_versionfunction in the admin module allows setting any values as version, including older or current versions. This can lead to unintended downgrades or redundant updates, potentially causing compatibility or versioning issues.Recommendation
In
set_versionfunction only allow strictly increasing values to version to prevent accidental or malicious downgrades and redundant updates -
I-12 Informational Blacklist DoS During Upgrade DoS Acknowledged
Description
In the
add_to_denylistfunction the treasury'sdeny_cap_mutfunction and other similar functions which validate the config version against the current breaking version is used.This means that when in the middle of an upgrade process, or generally when the contracts for the minting system have been temporarily disabled, the denylist is not able to be updated.
This may create a risk of a lack of ability to respond to an incident if it occurs during this temporary period, or may create operational issues with the ability to adhere to compliance of KYC etc... during this period.
Furthermore, the
add_to_denylistrequires a &Auth reference value which can only be obtained by thenew_authfunction which also performs theassert_valid_versionvalidation.Recommendation
Consider implementing and using versions of the
has_benefactor,benefactor_mut, anddeny_cap_mutfunctions that do not validate the config version in order to support blacklisting addresses in emergency scenarios even when the minting system is temporarily disabled with theadd_to_denylistfunction.Furthermore, consider using the following authentication check in the
add_to_denylistfunction instead of a typed check:treasury.roles_for_version_update().assert_is_authorized<DenylistManagerRole>(ctx.sender()); -
I-13 Informational Oracle Can Be Accidentally Bricked DoS Acknowledged
Description
In the EVM implementation of the stablecoin system the oracle admin cannot accidentally brick the oracle by assigning the minimum oracle sources to be above the available number of oracles in the aggregate oracle.
However in the Move implementation there is no such validation that prevents the oracle manager from assigning a minimum_sources value that is above the number of supported feeds.
Furthermore, in the remove_feed function there is no validation that requires that the number of remaining oracle feeds exceeds the minimum. This also deviates from the EVM implementation.
Recommendation
Consider if these validations should be implemented to be in line with the EVM implementation.
-
I-14 Informational Benefactors Cannot Be Configured Before Live Configuration Acknowledged
Description
In the EVM implementation of the minting system Benefactor configurations such as mint and redeem fees, benefactor limits, and fee exemption statuses can be configured by the benefactor manager before a benefactor address has been whitelisted as a benefactor.
This is however not the case in the Move implementation as the benefactor_mut function requires that the address is contained in the
treasury.benefactorsBag.Recommendation
This is a fine behavior, however deviates from the EVM implementation. For any integrators who are supporting and working with the EVM implementation this may be unexpected.
Consider documenting this behavior, or at least be aware of this discrepancy compared to the EVM version.
-
I-15 Informational Idempotent Configurations Emit Events Events Acknowledged
Description
In the EVM implementation the benefactor manager configuration functions are careful to only emit events that indicate a successful configuration when the newly configured value is different from the one that existed before it.
The Move implementation however does not do this and always emits the relevant event even if the configuration did not change anything.
Recommendation
Be aware of this discrepancy between the EVM and Move implementations.
-
I-16 Informational Overrestrictive Oracle Buffer Validation Validation Acknowledged
Description
In the
calculate_usd_price_internalfunction the oracle buffer validation requires that:constants::oracle_buffer() + target_decimals > oracle_decimalswith strict inequality.However the equality case is a valid scenario as well which does not cause an underflow when calculating the exponent_with_buffer result, though perhaps unlikely in real world configurations.
Recommendation
Consider using a greater than or equal to comparison for the validation to allow the full range of technically valid configurations.
-
I-17 Informational Lacking Upper Oracle Timestamp Bound Validation Acknowledged
Description
Since Pyth prices originate from offchain it is entirely possible that the oracle timestamp is in the future relative to the trusted Sui clock.
The Solidity version accounts for this by validating that prices are no more than 15 seconds into the future, as anything further would be unexpected and likely point to some mis-configuration.
Recommendation
Consider adding a future timestamp threshold, whereby if a Pyth price result is from too far in the future than would be normally realistic for time mismatch between Pyth’s offchain system and Sui’s trusted clock the the transaction is aborted.
-
I-18 Informational Unnecessary Treasury Parameter Mutability Best Practices Acknowledged
Description
In the
new_authfunction the treasury parameter is passed as a mutable reference, however it is not used in a way that requires mutability.Recommendation
Mark the treasury variable as an immutable parameter.
-
I-19 Informational Oracle Reduces Precision Rounding Acknowledged
Description
In the EVM implementation the oracle feed contracts used in the aggregate oracle contract scale the price result up to 18 decimals for an internal price representation that has 18 decimals of precision.
However the oracle implementation in the Move implementation scales price results (often times down) to 6 decimals. For example, the pyth feed for USDC has 8 decimals of price precision, which would be scaled down to 6, thus destroying some precision.
Recommendation
Consider using a higher magnitude scale such as 8 decimals instead of down to 6 for the internal price representation to preserve as much price granularity as possible.
-
I-20 Informational Lacking Overflow Check Validation Resolved
Description
In the
collateral_to_suiusde_amountfunction there is no validation that prevents the final suiusde_amount result from overflowing the u64 cast that is made.This scenario is certainly invalid as the Coin object has a value field of u64 and thus the totalSupply cannot exceed this.
However it may technically be possible to reach this state if a certain collateral with lower decimals is used in tandem with the peg price being set extremely low.
Consider the following case:
- suiusde_decimals = 6
- collateral_usd_price = 1e6
- collateral_amount = 20_000_000e6
- collateral decimals = 6
- suiusde_peg_price = 1
- numerator = 20_000_000e6 * 1e6 * 10**6 = 20e24
- denominator = 1e6 * 1 = 1e6
- suiusde_amount = 20e24 / 1e6 = 20e18 > type(u64) maximum
Assigning the suiusde_peg_price to 1 is unlikely to ever happen and would be considered an mis-configuration in almost all scenarios. However it does produce this overflow case. Furthermore, unusual tokens with less than 6 decimals would increase the possible peg price assignments which can lead to an overflow.
Recommendation
Consider validating that the numerator / denominator result is not larger than the maximum u64 as a safety check in the
collateral_to_suiusde_amountfunction. Furthermore, consider validating that the peg price is above some reasonable minimum which would also prevent this possibility. -
I-21 Informational Max Peg Price Allows For Overflow Validation Resolved
Description
The maximum peg price validation allows a peg price of up to 1,000 with six decimal places. However under certain circumstances, for collateral tokens that have higher decimals such as 9 decimals, matching that of the Sui token, an overflow case can still occur in the
suiusde_to_collateral_amountfunction.Consider the following scenario:
- suiusde_peg_price = 1_000e6
- suiusde_amount = 20_000_000e6
- collateral_decimals = 1e9
- collateral_usd_price = 1e6
- suiusde_decimals = 1e6
- numerator = 1_000e6 * 20_000_000e6 * 1e9 = 20e30
- denominator = 1e6 * 1e6 = 1e12
- collateral_amount = 20e30 / 1e12 = 20e18 > type(u64) maximum
This scenario is unlikely to ever occur, but still shows an overflow with the maximum allowable peg price in place.
Recommendation
Consider lowering the maximum allowable peg price to a more restrictive value. And furthermore consider adding an overflow check in the
suiusde_to_collateral_amountjust as a safety precaution to prevent any unexpected scenarios. -
I-22 Informational fee_is_charged Can Be Misleading Warning Acknowledged
Description
In the mint and redeem functions the fee_is_charged value is a boolean which is set to true whenever the one_to_one_amount, which includes a fee, is less than the oracle_amount_out.
The
fee_is_chargedvalue is included in theemit_order_executedevent emission directly after thefee_bpsvalue, which may lead consumers to believe that the entire bps of the order amount were taken as a fee.However, in the event that
fee_is_chargedis true it just means that some fee was charged relative to the oracle priced valuation, not that the entire bps was charged relative to the oracle valuation.For example:
- Collateral of order = 10 USDC
- Fee = 3%
- Oracle valuation of order collateral = $9.90
- One to one valuation of order collateral with fee = $9.70
- The technical fee levied was only 200 BPS relative to the oracle valuation
Recommendation
Be aware of this inconsistency and consider emitting the actual delta from the oracle valuation as the fee in the event. Otherwise document this for consumers.
-
I-23 Informational Mutable Oracle Reference May Congest Actions Best Practices Acknowledged
Description
In the
new_prooffunction the oracle parameter is passed by mutable reference, however the contents of the function do not use the value mutably and it can instead be an immutable reference parameter.Since the parameter is currently marked as mutable, it must be ordered with other mint and redeem actions which necessarily also require a mutable reference to the oracle through the proof creation process as well as any malicious transactions which simply touch the permissionless
new_oracle_prooffunction. In some cases through high usage or malicious flow, this may cause unnecessary congestion for minting and redeeming actions.Recommendation
Make the oracle parameter a immutable reference instead of a mutable one.
-
I-24 Informational Pyth Arbitrage Window Warning Acknowledged
Description
Given that the Pyth feed is a push based one where the exact price that is used is controlled by the publisher, within the staleness threshold enforced, the protocol should carefully consider the staleness threshold with respect to the volatility of the collateral being configured.
For example, if a staleness period of 300 seconds or 5 minutes is used, then some assets may realistically move 1% in that timeframe on a semi-regular instance. If the price of a collateral token at t = 0 was $1.01 while it is now at t = 300 at $1.00 this may allow for an arbitrage opportunity depending on the fee configurations of the protocol.
For example:
- Benefactor A uses the price update from t = 0 to mint with their collateral token being valued at $1.01, it is within the staleness tolerance of 300 seconds
- Benefactor A then uses the latest price from the current t = 300 to redeem with the same collateral token they are redeeming now being valued at only $1.00
- As long as the fees do not overcome the 1% delta then the Benefactor A stands to make a risk free profit
Recommendation
Consider the staleness threshold for each asset carefully in accordance with the collateral volatility and the corresponding fees being used, also keeping in mind that some benefactors may have little fees with a custom fee or no fees with an exemption.
-
I-25 Informational Epoch And Period Counter Reset Permanently Warning Acknowledged
Description
There is a subtle difference between the EVM implementation and the Move implementation with regards to the changing of duration of epochs and periods and the maintaining of the previous duration’s counter values.
In the EVM implementation the epoch and period states are not overwritten, as they are keyed based on the duration length. Instead when a new duration is assigned the epoch/period state is pointing to an entirely new storage slot while the old duration’s storage slot remains intact.
In the Move implementation there is only one object that stores the current state of the counter for the epoch or period. This means that when the duration is changed the counter values for the epoch and period are overwritten when the relevant mint/redeem action touches this limit.
This subtle difference only manifests itself when a period/epoch duration is changed from A to B and back to A again while still within the same sequence number. In the EVM implementation the previous counter would still hold the original values, in the Move implementation they have been reset to zero.
This behavior also presents itself where some epoch/period values may have been reset to zero while others have not been. This is because counters are only rolled over/reset when they are interacted with. So the global counter is likely to be rolled over in this case, but it may be the case that a benefactor counter or particular collateral counter are not reset since a mint/redeem action may not have happened with them while the package was using the updated B duration.
Recommendation
Such a configuration scenario is unlikely to arise, simply be aware of this difference between the Move and EVM implementations.
-
I-26 Informational Misleading Documentation Documentation Resolved
Description
The comment above the
validate_statesfunction suggests that the order timestamp validation is performed as a part of thevalidate_statesexecution. However this validation is not performed until the end of the mint/redeem action whenorder.finalize()is invoked.Recommendation
Correct the comment above the
validate_statesfunction to indicate that it does not perform the expiry validation on the order. -
I-27 Informational Mismatching MAX_ORACLE_AGE Warning Acknowledged
Description
In the EVM implementation the
MAX_ORACLE_AGEis defined as 1441 minutes, or one day plus one minute. In the Move implementation however this is just 1 day represented in milliseconds.Recommendation
Consider if there should be parity between the Move and EVM implementations on the
MAX_ORACLE_AGEvalue. -
I-28 Informational Lacking Sanity Overflow Check Validation Resolved
Description
In the
calculate_usd_price_internalfunction there is no sanity validation that ensures that the resultingtarget_usd_pricefits within a u64 value before being cast as such.It is incredibly unlikely that the value would exceed u64, however in the event of a misconfiguration or oracle malfunction it would be best to include a validation that aborts if the result is unexpectedly found to be larger than the u64 type.
Recommendation
Consider adding a sanity validation that prevents logic from continuing if the
target_usd_priceresult would overflow the u64 cast. -
I-29 Informational Custody Transfer Ignores Collateral Enabled Flag Informational Acknowledged
Description
In collateral_manager,
transfer_to_custodytransfers the redeem balance to the configuredcustodian_address. However, it does not check whether the collateral is enabled, whereas the Solana equivalent withdraw flow gates withdrawals by checking whether the vault is currently enabled.Recommendation
Document this as intended behavior, or enforce
assert_is_enabledintransfer_to_custody.
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.
