Sitemap

Is the Move Language Secure? The Typus Permission-Validation Vulnerability

8 min readOct 21, 2025

Author: Johan & Lisa
Editor: 77

Press enter or click to view image in full size

On October 16, the DeFi project Typus Finance on the Sui blockchain suffered a hacker attack. The official team has released an incident report, expressing gratitude to the SlowMist Security Team for their assistance in the investigation and tracing efforts.

Press enter or click to view image in full size
(https://medium.com/@TypusFinance/typus-finance-tlp-oracle-exploit-post-mortem-report-response-plan-ce2d0800808b)

This article will provide an in-depth analysis of the causes behind the attack and explore the characteristics of permission control in Sui Move smart contracts.

Detailed of the attack steps

We analyze the first attack transaction: https://suivision.xyz/txblock/6KJvWtmrZDi5MxUPkJfDNZTLf2DFGKhQA2WuVAdSRUgH

The attack steps are as follows:

1. Price manipulation

Related code: typus_oracle/sources/oracle.move

public fun update_v2(
oracle: &mut Oracle,
update_authority: & UpdateAuthority,
price: u64,
twap_price: u64,
clock: &Clock,
ctx: &mut TxContext
) {
// check authority
vector::contains(&update_authority.authority, &tx_context::sender(ctx));
version_check(oracle);

update_(oracle, price, twap_price, clock, ctx);
}
//…
fun update_(
oracle: &mut Oracle,
price: u64,
twap_price: u64,
clock: &Clock,
ctx: & TxContext
) {
assert!(price > 0, E_INVALID_PRICE);
assert!(twap_price > 0, E_INVALID_PRICE);

let ts_ms = clock::timestamp_ms(clock);

oracle.price = price;
oracle.twap_price = twap_price;
oracle.ts_ms = ts_ms;
oracle.epoch = tx_context::epoch(ctx);

emit(PriceEvent {id: object::id(oracle), price, ts_ms});
}

Let’s take a look at the permission verification method in update_v2:

It takes an UpdateAuthority capability object as input, but this capability object is a shared object, which means it can be accessed by anyone.

public struct UpdateAuthority has key {
id: UID,
authority: vector<address>,
}

entry fun new_update_authority(
_manager_cap: &ManagerCap,
ctx: &mut TxContext
) {
let update_authority = UpdateAuthority {id: object::new(ctx), authority: vector[ tx_context::sender(ctx) ]};
transfer::share_object(update_authority);
}

The developer’s original intention was to use the authority list to manage a whitelist of users who are allowed access. However, the code did not perform a check on the returned result of vector::contains(&update_authority.authority, &tx_context::sender(ctx)).

As a result, the attacker was able to call update_v2 to update oracle.price.

Transaction 1–2: Updating Oracle Prices

  • Oracle 0x0a31…c0d0: Price set to 651,548,270
  • Oracle 0x6e7c…bc21: Price set to 1

2. First Arbitrage: SUI → XBTC

Related code: typus_perp/sources/tlp/lp_pool.move

public fun swap<F_TOKEN, T_TOKEN>(
version: &mut Version,
registry: &mut Registry,
index: u64,
oracle_from_token: &Oracle,
oracle_to_token: &Oracle,
from_coin: Coin<F_TOKEN>,
min_to_amount: u64,
clock: &Clock,
ctx: &mut TxContext,
): Coin<T_TOKEN> {
// …
let (price_f_token_to_usd, price_f_decimal) = oracle_from_token.get_price_with_interval_ms(clock, 0);
let (price_t_token_to_usd, price_t_decimal) = oracle_to_token.get_price_with_interval_ms(clock, 0);

//…

// calculate to_amount_value by oracle price
let from_amount_usd = math::amount_to_usd(
from_amount,
f_token_config.liquidity_token_decimal,
price_f_token_to_usd,
price_f_decimal
);
let to_amount_value = math::usd_to_amount(
from_amount_usd,
t_token_config.liquidity_token_decimal,
price_t_token_to_usd,
price_t_decimal
);

//…

let to_amount_after_fee = math::usd_to_amount(
from_amount_usd — fee_amount_usd,
t_token_config.liquidity_token_decimal,
price_t_token_to_usd,
price_t_decimal
);

assert!(to_amount_after_fee >= min_to_amount, error::reach_slippage_threshold());

// deposit
{
let swap_fee_protocol_share_bp = {
let token_pool = get_mut_token_pool(liquidity_pool, &f_token_type);
token_pool.config.spot_config.swap_fee_protocol_share_bp
};
let mut from_balance = from_coin.into_balance();
let protocol_fee_balance = from_balance.split(((fee_amount as u128)
* (swap_fee_protocol_share_bp as u128)
/ 10000 as u64));
let from_balance_value_after_fee = from_balance.value();
admin::charge_fee(version, protocol_fee_balance);
balance::join(dynamic_field::borrow_mut(&mut liquidity_pool.id, f_token_type), from_balance);
let token_pool = get_mut_token_pool(liquidity_pool, &f_token_type);
assert!(token_pool.state.liquidity_amount + from_balance_value_after_fee <= token_pool.config.spot_config.max_capacity, error::reach_max_capacity());
token_pool.state.liquidity_amount = token_pool.state.liquidity_amount + from_balance_value_after_fee;
update_tvl(version, liquidity_pool, f_token_type, oracle_from_token, clock);
};

// withdraw
let to_balance = {
let to_balance = balance::split(
dynamic_field::borrow_mut<TypeName, Balance<T_TOKEN>>(&mut liquidity_pool.id, t_token_type),
to_amount_after_fee
);
let withdraw_amount = balance::value(&to_balance);
let token_pool = get_mut_token_pool(liquidity_pool, &t_token_type);
token_pool.state.liquidity_amount = token_pool.state.liquidity_amount — withdraw_amount;
assert!(token_pool.state.liquidity_amount >= token_pool.state.reserved_amount, error::liquidity_not_enough());
update_tvl(version, liquidity_pool, t_token_type, oracle_to_token, clock);
to_balance
};

emit(SwapEvent {
sender: tx_context::sender(ctx),
index,
from_token_type: f_token_type,
from_amount,
to_token_type: t_token_type,
min_to_amount,
actual_to_amount: to_amount_after_fee,
fee_amount,
fee_amount_usd,
oracle_price_from_token: price_f_token_to_usd,
oracle_price_to_token: price_t_token_to_usd,
u64_padding: vector::empty()
});

coin::from_balance(to_balance, ctx)
}

The attacker called the swap method to exchange tokens. Because the pool’s swap algorithm does not use the classic constant-product formula to calculate the token output, it instead calls the .get_price_with_interval_ms() method to fetch the oracle price — and that price had already been tampered with by the attacker.

Transaction 3: LP pool swap

  • Input: 1 SUI (precision: 9)
  • Output: 60,000,000 XBTC (precision: 8, value ≈ 0.6 BTC)

In this way the attacker used a small amount of tokens to acquire high-value tokens, then simply repeated the same trick to drain other pools.

3. Continuous Arbitrage Attacks

From the attacker’s account activity records, we can see that the attacker launched a total of 10 separate update_v2 attacks, and immediately transferred the stolen assets through a cross-chain bridge.

https://suivision.xyz/account/0xc99ac031ff19e9bff0b5b3f5b870c82402db99c30dfec2d406eb2088be6c2194?tab=Activity

4. MistTrack Analysis

According to the blockchain anti–money laundering and tracing tool MistTrack, the total amount of stolen assets is approximately USD 3.44 million, including 588,357.9 SUI, 1,604,034.7 USDC, 0.6 xBTC, and 32.227 suiETH.

Press enter or click to view image in full size

The hacker address on the SUI chain, 0xc99ac031ff19e9bff0b5b3f5b870c82402db99c30dfec2d406eb2088be6c2194, first swapped suiETH, xBTC, and most of the SUI tokens for USDC via platforms such as Cetus, Haedal Protocol, and Turbos.

Press enter or click to view image in full size

Subsequently, approximately 3,430,717 USDC was bridged to the Ethereum hacker address 0xeb8a15d28dd54231e7e950f5720bc3d7af77b443 via Circle CCTP in a total of 14 transactions.

Press enter or click to view image in full size

On Ethereum, the funds were swapped for 3,430,241.91 DAI and then transferred to a new address, 0x4502cf2bc9c8743e63cbb9bbde0011989eed03c1 via Curve. As of now, the DAI in this address has not been moved.

Press enter or click to view image in full size

Tracking reveals that the initial funds of the Ethereum hacker address 0xeb8a15d28dd54231e7e950f5720bc3d7af77b443 originated from address 0x7efcc6f928fb29fddc8dd5f769ff003da590e9eb — which had previously withdrawn 1 BNB from Tornado Cash on the BSC network. Of that amount, 0.146 BNB was swapped for 0.041 ETH and then bridged to Ethereum.

Press enter or click to view image in full size

In addition, the Ethereum hacker address also received a small amount of funds from the SUI hacker address: a total of 120.7242 SUI was bridged via Mayan Finance, converted into approximately 0.079 ETH, and deposited into the account. Subsequently, the Ethereum hacker address 0xeb8a transferred 0.11 ETH to the address 0x4502cf2bc9c8743e63cbb9bbde0011989eed03c1.

Further analysis shows that the address 0x4502 also received 0.1379 ETH from address 0x04586599bbe44cb9f0d8faa96567670f93d873e3. This 0.1379 ETH was transferred through Mayan Finance, and tracing it further upstream reveals that the funds originated from another SUI address: 0x840ed9288595e684e42606073d7a7d5aae35ecf1571943d8bf2d67e283db8466.

Press enter or click to view image in full size

The SUI address 0x840ed9288595e684e42606073d7a7d5aae35ecf1571943d8bf2d67e283db8466 received a total of 207.6 SUI (of which 41.68 SUI came from Bybit). All the SUI was swapped for USDC and then bridged to another chain.

Press enter or click to view image in full size

The related addresses have been added to SlowMist AML’s Malicious Address Library, and we will continue to monitor any further movements of the associated funds.

Security Recommendations

This is a typical oracle price-manipulation attack: the hacker exploited the fact that a shared object can be accessed by anyone, combined with a whitelist-validation vulnerability, to arbitrarily modify the oracle price and then extract large sums through arbitrage.

The fix is simple — just add an assert! check:

Press enter or click to view image in full size

Characteristics of Permission Control in Sui Move Smart Contracts

Compared with Solidity, the permission control in Sui Move is more “built-in” and enforced by design. While Solidity relies on developers to manually implement access control (such as the Ownable contract), Sui Move enforces it through language-level mechanisms, thereby reducing human error.

In Sui Move, all data exists in the form of objects, each with a unique Object ID and bound to an owner. Objects cannot be copied; they can only be moved (Move) or destroyed, a rule that is strictly enforced by Move’s linear type system.

Ownership Types:

  • Address-Owned: The object belongs to a specific address, and only the owner of that address can modify or transfer it. This is the most common form of permission control, similar to private property, preventing unauthorized access by others.
  • Shared: The object can be accessed by anyone, but modifications must go through the consensus mechanism. This is suitable for shared assets in DeFi or gaming applications, though it introduces a slight performance overhead.
  • Immutable: Once an object is frozen, it can no longer be modified — similar to read-only permissions. This type is often used for NFTs or stable configurations.
  • Package-Owned: The object belongs to a specific module package and is typically used for maintaining internal module states.

This model inherently prevents double spending and unauthorized modifications, as resource ownership is exclusive. When transferring ownership, the transfer function is used to explicitly move permissions.

To illustrate this more intuitively, let’s refer to a secure code snippet from the Typus project analyzed in this case, which demonstrates the correct way to use capability control to manage administrator permissions:

  • typus_oracle/sources/oracle.move

fun init(ctx: &mut TxContext) {

transfer::transfer(

ManagerCap {id: object::new(ctx)},

tx_context::sender(ctx)

);

}

//…

public fun update(

oracle: &mut Oracle,

_manager_cap: &ManagerCap,

price: u64,

twap_price: u64,

clock: &Clock,

ctx: &mut TxContext

) {

version_check(oracle);

update_(oracle, price, twap_price, clock, ctx);

}

In this example, during the init initialization process, the ManagerCap is transferred to the sender, who serves as the protocol administrator. The ManagerCap acts as a capability object that must be passed in during updates, ensuring that only the administrator can perform sensitive operations.

Conclusion

Although Sui Move provides a strong foundation for permission control through its object ownership model and capability mechanism, developers still need to implement proper permission checks in critical functions. This case serves as a reminder that no matter how advanced language features are, they cannot fully replace rigorous coding practices and comprehensive security audits.

About SlowMist

SlowMist is a threat intelligence firm focused on blockchain security, established in January 2018. The firm was started by a team with over ten years of network security experience to become a global force. Our goal is to make the blockchain ecosystem as secure as possible for everyone. We are now a renowned international blockchain security firm that has worked on various well-known projects such as HashKey Exchange, OSL, MEEX, BGE, BTCBOX, Bitget, BHEX.SG, OKX, Binance, HTX, Amber Group, Crypto.com, etc.

SlowMist offers a variety of services that include but are not limited to security audits, threat information, defense deployment, security consultants, and other security-related services. We also offer AML (Anti-money laundering) software, MistEye (Security Monitoring), SlowMist Hacked (Crypto hack archives), FireWall.x (Smart contract firewall) and other SaaS products. We have partnerships with domestic and international firms such as Akamai, BitDefender, RC², TianJi Partners, IPIP, etc. Our extensive work in cryptocurrency crime investigations has been cited by international organizations and government bodies, including the United Nations Security Council and the United Nations Office on Drugs and Crime.

By delivering a comprehensive security solution customized to individual projects, we can identify risks and prevent them from occurring. Our team was able to find and publish several high-risk blockchain security flaws. By doing so, we could spread awareness and raise the security standards in the blockchain ecosystem.

--

--

SlowMist
SlowMist

Written by SlowMist

SlowMist is a Blockchain security firm established in 2018, providing services such as security audits, security consultants, red teaming, and more.

No responses yet