Back to Blog

移动开发中的安全实践(二):Aptos Coin

Code Auditing
November 21, 2022
18 min read

在上一篇文章中,我们简要介绍了如何在 Aptos 网络上开发 Hello World 程序。从现在开始,我们将更深入地探讨 DeFi 应用程序的开发及其安全问题。一如既往,我们想从一些基本但重要的概念开始。在本文中,我们将重点关注 Aptos 币(即 Aptos 中的同质化代币 [1]),包括其开发、管理和交互。

摘要

本文将告诉您:

  • 什么是 Aptos 币?
  • 如何创建和管理您的币?
  • 如何与您的币进行交互?

0x1. 关于 Aptos 币

作为 DeFi 的基石,代币(或币)已被广泛应用于区块链生态系统中。它们可以用来代表各种事物,包括电子货币、质押份额以及组织管理的投票权。在某种程度上,DeFi 的日常活动可以简单地看作是区块链系统之间大量的代币流动。

以太坊为代币制定了一套标准。其中最著名的是 ERC20,它规定了标准 ERC20 代币需要遵守的接口。ERC20 是一个同质化代币标准,同时也有非同质化代币标准,如 ERC721

与其他区块链系统类似,Aptos 也有其代币标准 [2],它定义了数字资产在其各自区块链上的创建和使用方式。具体来说,在 Aptos 中,同质化代币称为 币 (coin),而非同质化代币(即 NFT)则称为 代币 (token)。下面,我们将讨论创建、管理和交互 Aptos 币的方法。

0x2. 创建和管理您的第一个币

Aptos 提供了一个官方标准模块(类似于 ERC20):coin.move。通过调用此模块的 API,任何用户都可以轻松创建自己的币。此外,coin.move 还提供了权限机制来管理币,这对于构建复杂的 DeFi 应用程序非常重要且有用。下面,我们将演示如何基于此模块创建币。

如前一篇文章所述,您可以通过键入以下命令创建一个项目:

aptos move init --name my_coin

然后,您需要在 sources 文件夹下创建一个新的 Move 文件。现在,让我们用以下示例代码填充它,该代码定义了一个名为 bsc 的模块来创建和管理一个名为 BSC 的标准币。

module BlockSec::bsc{
    use aptos_framework::coin;
    use aptos_framework::event;
    use aptos_framework::account;
    use aptos_std::type_info;
    use std::string::{utf8, String};
    use std::signer;


    struct BSC{}
    
    struct CapStore has key{
        mint_cap: coin::MintCapability<BSC>,
        freeze_cap: coin::FreezeCapability<BSC>,
        burn_cap: coin::BurnCapability<BSC>
    }

    struct BSCEventStore has key{
        event_handle: event::EventHandle<String>,
    }

    fun init_module(account: &signer){
        let (burn_cap, freeze_cap, mint_cap) = coin::initialize<BSC>(account, utf8(b"BSC"), utf8(b"BSC"), 6, true);
        move_to(account, CapStore{mint_cap: mint_cap, freeze_cap: freeze_cap, burn_cap: burn_cap});
    }

    public entry fun register(account: &signer){
        let address_ = signer::address_of(account);
        if(!coin::is_account_registered<BSC>(address_)){
            coin::register<BSC>(account);
        };
        if(!exists<BSCEventStore>(address_)){
            move_to(account, BSCEventStore{event_handle: account::new_event_handle(account)});
        };
    }

    fun emit_event(account: address, msg: String) acquires BSCEventStore{
        event::emit_event<String>(&mut borrow_global_mut<BSCEventStore>(account).event_handle, msg);
    }

    public entry fun mint_coin(cap_owner: &signer, to_address: address, amount: u64) acquires CapStore, BSCEventStore{
        let mint_cap = &borrow_global<CapStore>(signer::address_of(cap_owner)).mint_cap;
        let mint_coin = coin::mint<BSC>(amount, mint_cap);
        coin::deposit<BSC>(to_address, mint_coin);
        emit_event(to_address, utf8(b"minted BSC"));
    }

    public entry fun burn_coin(account: &signer, amount: u64) acquires CapStore, BSCEventStore{
        let owner_address = type_info::account_address(&type_info::type_of<BSC>());
        let burn_cap = &borrow_global<CapStore>(owner_address).burn_cap;
        let burn_coin = coin::withdraw<BSC>(account, amount);
        coin::burn<BSC>(burn_coin, burn_cap);
        emit_event(signer::address_of(account), utf8(b"burned BSC"));
    }

    public entry fun freeze_self(account: &signer) acquires CapStore, BSCEventStore{
        let owner_address = type_info::account_address(&type_info::type_of<BSC>());
        let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
        let freeze_address = signer::address_of(account);
        coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
        emit_event(freeze_address, utf8(b"freezed self"));
    }

    public entry fun emergency_freeze(cap_owner: &signer, freeze_address: address) acquires CapStore, BSCEventStore{
        let owner_address = signer::address_of(cap_owner);
        let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
        coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
        emit_event(freeze_address, utf8(b"emergency freezed"));
    }

    public entry fun unfreeze(cap_owner: &signer, unfreeze_address: address) acquires CapStore, BSCEventStore{
        let owner_address = signer::address_of(cap_owner);
        let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
        coin::unfreeze_coin_store<BSC>(unfreeze_address, freeze_cap);
        emit_event(unfreeze_address, utf8(b"unfreezed"));
    }
    
}

0x2.1 基本设计

首先,我们来看结构部分。总共定义了三个结构。

  • BSC 结构,用作币的唯一标识符。因此,可以通过 BlockSec::bsc::BSC 路径唯一确定此币。
  • CapStore 结构,用于存储从 aptos_framework::coin 模块获取的一些能力。这些能力对应于某些特殊操作的权限,稍后将进行解释。
  • BSCEventStore 结构,用于记录用户事件。

在这些结构之后,有一个 init_module 函数,用于初始化模块,并且仅在模块发布到链上时调用一次。在此函数中,模块调用 coin::initialize<BSC> 来注册 BlockSec::bsc::BSC 作为新币的唯一标识符。

public fun initialize<CoinType>(
    account: &signer,
    name: string::String,
    symbol: string::String,
    decimals: u8,
    monitor_supply: bool,
): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
    initialize_internal(account, name, symbol, decimals, monitor_supply, false)
}

/// Capability required to mint coins.
struct MintCapability<phantom CoinType> has copy, store {}

/// Capability required to freeze a coin store.
struct FreezeCapability<phantom CoinType> has copy, store {}

/// Capability required to burn coins.
struct BurnCapability<phantom CoinType> has copy, store {}

注册完成后,aptos_framework::coin 模块中所有接受 BlockSec::bsc::BSC 类型的泛型函数都将操作此币。此注册过程将返回三种能力,即 MintCapabilityFreezeCapabilityBurnCapability。这些能力分别用于铸造币、冻结用户账户和销毁币。在某种程度上,这种能力结构的函数功能类似于密钥,用于打开特定权限的锁。如果有人拥有密钥,他就可以获得相应的权限。在这里,我们将这些能力存储在 CapStore 结构(由该模块的管理员/发布者拥有)中以备将来使用。

同时,在注册过程中,会在管理员地址下存储一个 CoinInfo 结构来记录相关信息:

/// Information about a specific coin type. Stored on the creator of the coin's account.
struct CoinInfo<phantom CoinType> has key {
    name: string::String,
    /// Symbol of the coin, usually a shorter version of the name.
    /// For example, Singapore Dollar is SGD.
    symbol: string::String,
    /// Number of decimals used to get its user representation.
    /// For example, if `decimals` equals `2`, a balance of `505` coins should
    /// be displayed to a user as `5.05` (`505 / 10 ** 2`).
    decimals: u8,
    /// Amount of this coin type in existence.
    supply: Option<OptionalAggregator>,
}

调用 init_module 函数后,您的币已在链上注册。但是,由于目前还没有流通,没有人可以使用此币。要使币可用,需要支持一些操作,包括发行、分配和销毁。这些操作需要我们在注册币时获得的能力。

0x2.2 币管理

此币设计遵循以下规则:

  • 只有管理员(admin)可以铸造币。
  • 用户可以随时销毁自己的币。
  • 用户可以随时冻结/解冻自己的账户。

因此,我们定义了五个管理函数,即 mint_coinburn_coinfreeze_selfemergency_freezeunfreeze。前两个函数分别负责铸造币和销毁币;而后三个函数则用于冻结和解冻账户。

铸造币

在我们的模块中,mint_coin 函数用于铸造币。因为只有管理员可以铸造币,所以我们必须在此函数中验证相应的能力。

public entry fun mint_coin(cap_owner: &signer, to_address: address, amount: u64) acquires CapStore, BSCEventStore{
    let mint_cap = &borrow_global<CapStore>(signer::address_of(cap_owner)).mint_cap;
    let mint_coin = coin::mint<BSC>(amount, mint_cap);
    coin::deposit<BSC>(to_address, mint_coin);
    emit_event(to_address, utf8(b"minted BSC"));
}

此函数需要三个参数:

  • cap_owner 的类型是 &signer,即交易的发起者。
  • to_address 指明铸造的币将被存入的地址。
  • amount 指明铸造的币的数量。

它包含三个步骤:获取铸造币的能力,铸造币,以及存入币。

首先,在 mint_coin 函数的开始处,可以通过 signer::address_of(cap_owner) 获取交易发起者的账户地址。然后,使用 borrow_global<CapStore> 确认该账户是否拥有 CapStore,以验证它是否是该模块的管理员。通过这样做,我们可以确保只有管理员可以铸造币,而其他用户将在这一步失败。

其次,mint_coin 函数将调用 aptos_framework::coin 模块的 mint 函数来铸造币。

public fun mint<CoinType>(
        amount: u64,
        _cap: &MintCapability<CoinType>,
    ): Coin<CoinType> acquires CoinInfo {
    if (amount == 0) {
        return zero<CoinType>()
    };

    let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
    if (option::is_some(maybe_supply)) {
        let supply = option::borrow_mut(maybe_supply);
        optional_aggregator::add(supply, (amount as u128));
    };

    Coin<CoinType> { value: amount }
}

这里需要 MintCapability。具体来说,需要将一个名为 _cap 的参数作为 MintCapability 的引用传递。然后,与 MintCapability 相关联的权限也会相应地转移。虽然没有明确的访问控制,但验证由 Move 语言强制执行。

第三,mint_coin 函数将调用 deposit 函数将铸造的币存入指定的 to_address

技巧#1:可以通过类似的方法验证特权账户的访问控制。

销毁币

销毁币的过程与铸造币不同。具体来说,只有管理员可以调用 mint_coin 函数,而任何用户都可以调用 burn_coin 函数。为此,burn_coin 函数必须暂时提升权限,即为这些用户获取 BurnCapability 能力。

public entry fun burn_coin(account: &signer, amount: u64) acquires CapStore, BSCEventStore{
    let owner_address = type_info::account_address(&type_info::type_of<BSC>());
    let burn_cap = &borrow_global<CapStore>(owner_address).burn_cap;
    let burn_coin = coin::withdraw<BSC>(account, amount);
    coin::burn<BSC>(burn_coin, burn_cap);
    emit_event(signer::address_of(account), utf8(b"burned BSC"));
}

此函数需要两个参数:

  • account 的类型是 &signer,即交易的发起者。
  • amount 指明要销毁的币的数量。

它也包含三个步骤:获取销毁币的能力,提取币,以及销毁币。

显然,aptos_framework::coin 模块的 burn 函数要求调用者传入 BurnCapability 的引用,但此能力存储在管理员的 CapStore 中。因此,我们必须允许普通用户获取此能力来销毁他们持有的币。

public fun burn<CoinType>(
    coin: Coin<CoinType>,
    _cap: &BurnCapability<CoinType>,
) acquires CoinInfo {
    let Coin { value: amount } = coin;
    assert!(amount > 0, error::invalid_argument(EZERO_COIN_AMOUNT));

    let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
    if (option::is_some(maybe_supply)) {
        let supply = option::borrow_mut(maybe_supply);
        optional_aggregator::sub(supply, (amount as u128));
    }
}

为了实现这个目标,我们可以使用 Move 语言提供的 borrow_global 操作符。它用于从账户的不可变全局存储中读取特定数据类型。使用此操作符,模块可以 借用 管理员拥有的能力给其他用户。也就是说,需要管理员地址来获取我们想要的能力。

然而,burn_coin 函数的交易发起者是用户而不是管理员。因此,无法像 mint_coin 函数那样通过 signer 获取管理员地址。幸运的是,可以通过 aptos_std::type_infoBSC 来获取模块的地址,在该地址定义了这个结构。由于该模块是在管理员地址下发布的,我们可以进一步获取管理员地址,并最终获得 BurnCapability 能力。

技巧#2:_borrow_global_ 操作符可用于临时获取模块的能力。

在获得 BurnCapability 后,模块可以从用户那里提取指定数量的币,并使用该能力销毁币。

冻结和解冻币账户

基于以上讨论,现在我们可以轻松地完成币账户的管理。具体来说,我们提供了 freeze_self 函数供用户冻结自己的币账户。我们还提供了 emergency_freeze 函数用于紧急冻结,该函数只能由管理员使用。此外,由于存在紧急冻结机制,不应允许用户自行解冻。因此,unfreeze 函数也需要管理员来解冻用户账户。

public entry fun freeze_self(account: &signer) acquires CapStore, BSCEventStore{
    let owner_address = type_info::account_address(&type_info::type_of<BSC>());
    let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
    let freeze_address = signer::address_of(account);
    coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
    emit_event(freeze_address, utf8(b"freezed self"));
}

public entry fun emergency_freeze(cap_owner: &signer, freeze_address: address) acquires CapStore, BSCEventStore{
    let owner_address = signer::address_of(cap_owner);
    let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
    coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
    emit_event(freeze_address, utf8(b"emergency freezed"));
}

public entry fun unfreeze(cap_owner: &signer, unfreeze_address: address) acquires CapStore, BSCEventStore{
    let owner_address = signer::address_of(cap_owner);
    let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
    coin::unfreeze_coin_store<BSC>(unfreeze_address, freeze_cap);
    emit_event(unfreeze_address, utf8(b"unfreezed"));
}

技巧#3:返回能力时要谨慎!获取这些能力的恶意用户可能会滥用它们对币持有者造成损害!

0x3. 与币交互

这里我们重点关注与币交互的方式。

0x3.1 注册

您可能已经注意到模块中存在一个 register 函数:

public entry fun register(account: &signer){
    let address_ = signer::address_of(account);
    if(!coin::is_account_registered<BSC>(address_)){
        coin::register<BSC>(account);
    };
    if(!exists<BSCEventStore>(address_)){
        move_to(account, BSCEventStore{event_handle: account::new_event_handle(account)});
    };
}

此函数用于帮助用户注册币的使用权和事件记录器。为了使用某种币,aptos_framework::coin 模块规定用户必须首先通过 aptos_framework::coin::register 函数显式注册使用该币的权利。

public fun register<CoinType>(account: &signer) {
    let account_addr = signer::address_of(account);
    assert!(
        !is_account_registered<CoinType>(account_addr),
        error::already_exists(ECOIN_STORE_ALREADY_PUBLISHED),
    );

    account::register_coin<CoinType>(account_addr);
    let coin_store = CoinStore<CoinType> {
        coin: Coin { value: 0 },
        frozen: false,
        deposit_events: account::new_event_handle<DepositEvent>(account),
        withdraw_events: account::new_event_handle<WithdrawEvent>(account),
    };
    move_to(account, coin_store);
}

用户只有通过此函数注册了该类型的币,才能正常持有该币。也就是说,如果您不希望持有某种币,其他人就无法在未经您同意的情况下将该币放入您的账户。注册实际上是将一个 CoinStore 结构(目标币类型)放入您的账户。这个 CoinStore 结构包含一个 Coin 结构来记录您的余额。

技巧#4:与以太坊代币不同,Aptos 币不能在用户未显式注册的情况下持有和操作。

0x3.2 转账

假设您现在拥有一些 BSC 币,那么您可以通过调用 aptos_framework::coin 模块的 transfer 函数来转账这些币。

public entry fun transfer<CoinType>(
    from: &signer,
    to: address,
    amount: u64,
) acquires CoinStore {
    let coin = withdraw<CoinType>(from, amount);
    deposit(to, coin);
}

请注意,这是 coin 模块提供的入口函数。其逻辑包括调用两个公共函数,即 withdrawdepositwithdraw 函数需要 &signer 权限,用于从您的账户中提取一定数量的资产到币中。deposit 函数可以将币存入任何已注册的账户。此函数不需要额外的权限,并将指定的币存入账户地址。最后,转账的币将自动与存储在目标地址的 CoinStore 结构中的币合并。

技巧#5:提取后,币中的资产由当前的 _transfer_ 函数控制。该函数可以在不获取额外权限的情况下将这些资产交付给 _deposit_ 函数。

0x3.3 分割和合并

与以太坊代币不同,币的流通不能通过修改用户余额来更新。相反,可以通过提取 coin 模块中的 Coin 结构来实现。通过这样做,用户通过将该结构传递给其他模块来实现资产流通。由于一个结构只能由定义它的模块操作,因此 coin 模块提供了一些接口来操作 Coin 结构,包括将币分割成更小的单位和合并多个币以满足不同场景的需求。

  1. extract 函数用于分割币。它接收一个 Coin 结构,从中提取一部分资产生成一个新的 Coin 结构,并返回新结构。

    public fun extract<CoinType>(coin: &mut Coin<CoinType>, amount: u64): Coin<CoinType> {
        assert!(coin.value >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
        coin.value = coin.value - amount;
        Coin { value: amount }
    }
  2. extract_all 函数用于提取原始 Coin 结构的所有值并将其存入一个新的 Coin 结构。结果是原始 Coin 结构的值将变为零(即 zero_coin)。可以通过调用 destroy_zero 函数销毁 zero_coin 结构。

    public fun extract_all<CoinType>(coin: &mut Coin<CoinType>): Coin<CoinType> {
        let total_value = coin.value;
        coin.value = 0;
        Coin { value: total_value }
    }
    
    public fun destroy_zero<CoinType>(zero_coin: Coin<CoinType>) {
        let Coin { value } = zero_coin;
        assert!(value == 0, error::invalid_argument(EDESTRUCTION_OF_NONZERO_TOKEN))
    }
  3. merge 函数用于合并币。您可以将两个 Coin 结构的价值,即 source_coindst_coin,合并到 dst_coin 结构中,并销毁 source_coin 结构。

    public fun merge<CoinType>(dst_coin: &mut Coin<CoinType>, source_coin: Coin<CoinType>) {
        spec {
            assume dst_coin.value + source_coin.value <= MAX_U64;
        };
        dst_coin.value = dst_coin.value + source_coin.value;
        let Coin { value: _ } = source_coin;
    }
  4. zero 函数用于生成一个 zero_coin 结构。

    public fun zero<CoinType>(): Coin<CoinType> {
        Coin<CoinType> {
            value: 0
        }
    }

0x4. 测试币

要快速测试此币,您可以先使用以下命令进行部署(不要忘记在 Move.toml 中设置您的发布者账户地址!)

$ aptos move publish --package-dir ./
Compiling, may take a little while to download git dependencies...
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my_coin
package size 2751 bytes
Do you want to submit a transaction for a range of [868300 - 1302400] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
  "Result": {
    "transaction_hash": "xxx",
    ...

这样,币就成功发布到链上了,但它没有任何流通。您需要注册您的账户才能接收铸造的币。

$ aptos move run --function-id default::bsc::register
Do you want to submit a transaction for a range of [153100 - 229600] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
  "Result": {
    "transaction_hash": "xxx",
    ...

请注意,第二个命令中的 Your-address 需要替换为您自己的地址(参见 .aptos/config.yaml 中的 account)。在 浏览器 中输入您的地址,然后单击 Resources 选项卡,您可以看到该账户现在拥有 100 个 BSC 币。

如果您想将币转账给另一个账户,请不要忘记让该账户注册使用该币。因为 transfer 函数是一个泛型函数,您需要指定泛型参数为 BSCto_address,如下所示:

$ aptos move run — function-id 0x1::coin::transfer — type-args BSC-module-address::bsc::BSC — args address:To_address u64:1

此命令将调用 coin 模块的 transfer 函数,将 1 个 BSC 币转账给 To_address。这里 0x1::coin::transfertransfer 函数的函数 ID。请记住,BlockSec::bsc::BSC 是您的币的标识符,必须指定泛型参数。此外,BSC-module-address 应替换为模块发布者账户地址,该地址在 Move.toml 中被指定为 BlockSec

0x5. 下一步

在了解了如何创建、管理和交互自己的币之后,我们将演示如何构建第一个 DeFi 基石项目:自动做市商(AMM)。还将涵盖更多关于 Move 开发和安全实践的有趣主题。敬请关注!

参考

[1] https://aptos.dev/concepts/coin-and-token/aptos-coin/
[2] https://aptos.dev/concepts/coin-and-token/index
[3] https://aptos.dev/concepts/coin-and-token/aptos-token

Sign up for the latest updates
The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis
Security Insights

The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis

This BlockSec deep-dive analyzes the KelpDAO $290M rsETH cross-chain bridge exploit (April 18, 2026), attributed to the Lazarus Group, tracing a causal chain across three layers: how a single-point DVN dependency enabled the attack, how DeFi composability cascaded the damage through Aave V3 lending markets to freeze WETH liquidity exceeding $6.7B across Ethereum, Arbitrum, Base, Mantle, and Linea, and how the crisis forced decentralized governance to exercise centralized emergency powers. The article examines three parameters that shaped the cascade's severity (LTV, pool depth, and cross-chain deployment count) and provides an exclusive technical breakdown of Arbitrum Security Council's forced state transition, an atomic contract upgrade that moved 30,766 ETH without the holder's signature.

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026

This BlockSec weekly security report covers four attack incidents detected between April 13 and April 19, 2026, across multiple chains such as Ethereum, Unichain, Arbitrum, and NEAR, with total estimated losses of approximately $310M. The highlighted incident is the $290M KelpDAO rsETH bridge exploit, where an attacker poisoned the RPC infrastructure of the sole LayerZero DVN to fabricate a cross-chain message, triggering a cascading WETH freeze across five chains and an Arbitrum Security Council forced state transition that raises questions about the actual trust boundaries of decentralized systems. Other incidents include a $242K MMR proof forgery on Hyperbridge, a $1.5M signed integer abuse on Dango, and an $18.4M circular swap path exploit on Rhea Finance's Burrowland protocol.

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026

This BlockSec weekly security report covers four DeFi attack incidents detected between April 6 and April 12, 2026, across Linea, BNB Chain, Arbitrum, Optimism, Avalanche, and Base, with total estimated losses of approximately $928.6K. Notable incidents include a $517K approval-related exploit where a user mistakenly approved a permissionless SquidMulticall contract enabling arbitrary external calls, a $193K business logic flaw in the HB token's reward-settlement logic that allowed direct AMM reserve manipulation, a $165.6K exploit in Denaria's perpetual DEX caused by a rounding asymmetry compounded with an unsafe cast, and a $53K access control issue in XBITVault caused by an initialization-dependent check that failed open. The report provides detailed vulnerability analysis and attack transaction breakdowns for each incident.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit