MARBLEX security record

BNB Chainmarblex.io

As of 3 Nov 2026, MARBLEX has 4 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 11 Apr 2024. Those reports list no open critical findings. It also holds 1 incident (latest 11 Oct 2023).

Critical findings
No open critical findings
Across the 4 reports, 1 critical finding was reported. It is marked fixed by the firm that reported it. See the findings
Incident
Incident 11 Oct 2023
Source: De.Fi incident database. 2 audits have been published since. See the incident
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.

Audits4

Newest first

AuditSecurity firmDateScopeCritical findingsReport
Marblex - Audit 2Security firm: CertiKDate: Scope: Not recorded (see the report)No open critical findingsOpen · skynet.certik.com
Warp - AuditSecurity firm: CertiKDate: Scope: Not recorded (see the report)No open critical findingsOpen · skynet.certik.com
MarblexSecurity firm: CertiKDate: Scope: Files named in the report1 critical finding, fixedOpen · skynet.certik.com
MBX token-AuditSecurity firm: CertiKDate: Scope: Not recorded (see the report)No open critical findingsOpen · skynet.certik.com

Findings30

Statuses are the security firm’s own, from each report.

SeverityOpenAcknowledgedFixed
Critical001
High220
Medium001
Low4020
CriticalLack of Access ControlMarblex · CertiK · 10 Aug 2023Fixed

The changes made in commit [d4302c2d89369e99b154b4c3cee7f7cb727878f0](https://github.com/MarblexAudit/MBXToken-ERC20/commit/d4302c2d89369e99b154b4c3cee7f7cb727878f0) introduce a lack of access control on a critically privileged function: ```solidity function grantRole(bytes32 role, address account) public override(AccessControl, IAccessControl) { _beforeSetRole(role, account, true); super._grantRole(role, account); } ``` This introduction allows anyone to call the function `grantRole()` because the override calls `super._grantRole()` instead of `super.grantRole()`. Since the external `super.grantRole()` is where the access protection is located, this function can now be called by anyone. In turn, anyone can take on the `MINTER_ROLE` and `PAUSER_ROLE`.

Open the report
MediumPotential Bypass of the `lowerHandleNonce` CheckWarp - Audit · CertiK · 13 Dec 2023Fixed

The `update_handled_nonce()` function updates nonce values in the bridge contract. The calculates a `limit` as the sum of `lowerHandleNonce` and `NONCE_LOWER_SEARCH_LIMIT`, ensuring it doesn't exceed `upperHandleNonce`. It iterates from 0 to `limit` and checks if each nonce exists in `handleNoncesToBlockNumbers`. If a nonce is missing in `handleNoncesToBlockNumbers`, the iteration breaks, and `lowerHandleNonce` is updated to this missing nonce value. ```rust=408 inline fun update_handled_nonce(requestedNonce_: u256) acquires BridgeNonceInfo, HandledInfo { let bridgeNonceInfo = borrow_global_mut<BridgeNonceInfo>(@warp); if(requestedNonce_ > bridgeNonceInfo.upperHandleNonce) { bridgeNonceInfo.upperHandleNonce = requestedNonce_; }; let limit: u256 = bridgeNonceInfo.lowerHandleNonce + NONCE_LOWER_SEARCH_LIMIT; if(limit > bridgeNonceInfo.upperHandleNonce) { limit = bridgeNonceInfo.upperHandleNonce; }; let handledInfo = borrow_global_mut<HandledInfo>(@warp); let i:u256 = 0; while(i <= limit) { if(simple_map::contains_key(&handledInfo.handleNoncesToBlockNumbers, &i) == false) break; i = i + 1; }; bridgeNonceInfo.lowerHandleNonce = i; } ``` There is a potential issue with the logic that increments `lowerHandleNonce`. If a nonce within the range from `lowerHandleNonce` to `limit` is not found in `handleNoncesToBlockNumbers`, it indicates that a particular nonce was not processed. In such a case, `lowerHandleNonce` is set to this unprocessed nonce, and the function exits. This implementation could potentially allow bypassing certain checks if specific nonces are never processed (i.e., they don't exist in `handleNoncesToBlockNumbers`). For example, if a user's request for calling `handle_bridge_swap()` was never to be triggered, the `lowerHandleNonce` will never be increased, and therefore, the following checks in the `handle_bridge_swap()` will always succeed. ```rust=341 let bridgeNonceInfo = borrow_global_mut<BridgeNonceInfo>(@warp); assert!(bridgeNonceInfo.lowerHandleNonce <= requestedNonce_, ERR_HANDLE_BRIDGE_SWAP_REQUEST_NONCE_LOWER_THAN_LOWERHANDLE); ``` According to the chain implementation, the `requestedNonce_` started from `1` and therefore, the `lowerHandleNonce` will always return 0 and never increase.

Open the report
LowInconsistent permission in commentMarblex - Audit 2 · CertiK · 11 Apr 2024Fixed

The linked comments do not correctly reflecting the access control management ```rust=158 /// Permission management is managed by sha3_256 (b"MINTER_ROLE") in access_control. /// - sender : A signer object for permission checking. /// - coin : The withdrawn coin object. public fun burn(sender: &signer, coin: coin::Coin<MBX>) acquires CoinRoleStore { whenNotPaused(); // modifier let amount = coin::value<MBX>(&coin); coin::burn<MBX>(coin, get_burn_cap(sender)); event::emit(BurnEvent { sender: address_of(sender), amount }); } /// Set the frozen state of 0x1::coin to true.\ /// Permission management is managed by sha3_256 (b"BURNER_ROLE") in access_control. /// - sender : A signer object for permission checking. /// - account : The address of the target account to be frozen. public entry fun set_freeze(sender: &signer, account: address) acquires CoinRoleStore { coin::freeze_coin_store(account, get_freeze_cap(sender)); event::emit(FreezeEvent { account }); } ``` - L158: MINTER_ROLE should be BURNER_ROLE - L169: MINTER_ROLE should be FREEZER_ROLE

Open the report
LowConcerns on default value of `ChainID`Warp - Audit · CertiK · 13 Dec 2023Fixed

The default value for `bridgeConfig.allowedToChainId` is 0, and the account `@warp` may update this value via `set_allowed_chain_id`. However, there is no limitation on the given `allowedToChainId_` value usage or update, which means 0 can still be valid.

Open the report
LowConcerns with Power and Threshold for SignatureWarp - Audit · CertiK · 13 Dec 2023Fixed

When validate the signatures for the `handle_bridge_swap` operation, the function will go through a list of signatures and calculate the overall power, there are several concern about the current implementation: 1. The initial value for the threshold is `DEFAULT_THRESHOLD_VALUE` which is 0, if the admin not setup the value, there is no signature needed for the cross-chain operations. 2. There is no limitation on the single validator's power, for example, if one validator is too powerful that its power is already above the threshold, which will raise the chance for the single point failure.

Open the report
LowInvalid Event ContentWarp - Audit · CertiK · 13 Dec 2023Fixed

When adding/removing admin, the module `warp::admin` will emit events about the admin update information. However, when adding admin, the module will emit a message with `MEMBER_REMOVED` instead of `MEMBER_ADDED`, which will raise an inaccurate message.

Open the report
LowMissing Upper Bound For FeeWarp - Audit · CertiK · 13 Dec 2023Fixed

In the current implementation, the fee bridge can be set as any value. There should be a bound that limits how high the fees can be set in the linked functions.

Open the report
LowMissing Validation on Zero AddressWarp - Audit · CertiK · 13 Dec 2023Fixed

The fee address will collect fee while `request_bridge_swap`. However, the initial value is `@0x00`, and there is no validation in the `set_fee_address`, for example, `@0x0`, `@aptos_framework` or `aptos_token`. The invalid account may cause: 1. fee loss 2. DoS on `request_bridge_swap`, for example, if the fee address set to `@0x0`, the transaction will be reverted with the following issue: ``` │ 190 │ ╭ assert!( │ 191 │ │ new_address != @vm_reserved && new_address != @aptos_framework && new_address != @aptos_token, │ 192 │ │ error::invalid_argument(ECANNOT_RESERVED_ADDRESS) │ 193 │ │ ); │ │ ╰─────────^ Test was not expected to error, but it aborted with code 65541 originating in the module 0000000000000000000000000000000000000000000000000000000000000001::account rooted here ```

Open the report
LowTypoWarp - Audit · CertiK · 13 Dec 2023Fixed

The following potential typo inside the contract can be fixed: - L208: `swarp` should be `swap`

Open the report
LowUnnecessary Usage of `borrow_global_mut<>` operatWarp - Audit · CertiK · 13 Dec 2023Fixed

The view functions in the above-linked code use `borrow_global_mut` to acquire the information that returns a mutable reference. However, it is unnecessary to return a mutable reference since the corresponding variables will not be changed inside the code. Additionally, Aptos compiler will insert `freeze` instruction to allow the mutable reference used in a context where an immutable reference is expected. Ref: https://aptos.dev/move/book/references#freeze-inference ```rust=430 #[view] public fun get_bridge_nonce_info(): BridgeNonceInfo acquires BridgeNonceInfo { let bridgeNonceInfo = borrow_global_mut<BridgeNonceInfo>(@warp); *bridgeNonceInfo } #[view] public fun get_bridge_handled_info(): HandledInfo acquires HandledInfo { let handledInfo = borrow_global_mut<HandledInfo>(@warp); *handledInfo } #[view] public fun get_reqest_info_all(creator_: address): SimpleMap<vector<u8>, RequestDetail> acquires RequestLog { to_simple_map(&borrow_global<RequestLog>(creator_).requestDetails) } #[view] public fun get_reqest_info_for_uuid(creator_: address, uuid_: vector<u8>): RequestDetail acquires RequestLog { *smart_table::borrow(&borrow_global_mut<RequestLog>(creator_).requestDetails, uuid_) } #[view] public fun get_operator_list() : vector<address> { let operatorList = operator::get_operators(); operatorList } #[view] public fun get_resource_signer_address() : address acquires ModuleData{ let resource_signer = get_resource_signer(); let resourceSignerAddress = signer::address_of(&resource_signer); resourceSignerAddress } #[view] public fun get_bridge_config(): BridgeConfig acquires BridgeConfig { let bridgeConfig = borrow_global_mut<BridgeConfig>(@warp); *bridgeConfig } #[view] public fun get_fee_amount(): u64 acquires BridgeConfig { let bridgeConfig = borrow_global_mut<BridgeConfig>(@warp); bridgeConfig.feeAmount } #[view] public fun get_fee_address(): address acquires BridgeConfig { let bridgeConfig = borrow_global_mut<BridgeConfig>(@warp); bridgeConfig.feeAddress } ```

Open the report
Show 10 more
LowUnused `recoveryBlockNumber`Warp - Audit · CertiK · 13 Dec 2023Fixed

In `BridgeNonceInfo`, there is a field `recoveryBlockNumber` which is not being used in the current implementation.

Open the report
LowConsider Added Checks with `notFrozen` ModifierMarblex · CertiK · 10 Aug 2023Fixed

The current implementation of the `MBXToken` only ensures that tokens cannot be transferred `from` an account that has been frozen. There are no checks on `msg.sender` (potentially distinct from `_msgSender()`, or the `to` account, all of which may be different addresses. - If tokens are transferred `to` a frozen account, then tokens that were previously in circulation become temporarily unavailable, while the account they were transferred to is left frozen. - In functions `permit()` and `_approve()`, the `spender` address is not checked to ensure the address is not frozen. This may allow a frozen account to send the owner's tokens to maliciously. - The `_msgSender()` executing any function call may be a frozen account. If a user has previously given approval to an account that becomes frozen, then the frozen account can still use the approval to transfer the tokens to any destination address. Additionally, a frozen `msg.sender` account can still use a valid signature in the `permit()` function on behalf of a non-frozen account. - In cases where the `tokenForwarder` contract is used to relay an address for `_msgSender()` that is distinct from the `msg.sender` interacting, then the `_msgSender()` can be a frozen account and still make calls to functions `transferFrom()` and `permit()` as described above.

Open the report
LowDeclaration of Specific Access Control FunctionsMarblex · CertiK · 10 Aug 2023Fixed

The contract `MBXToken` has a code size that exceeds the limit of 24576 bytes. It is noted that there are several functions added which call existing inherited functions from `AccessControlEnumerable` with hardcoded input: - `addMinter()` - `addPauser()` - `renounceMinter()` - `renouncePauser()` - `revokeMinter()` - `revokePauser()` - `isMinter()` - `isPauser()` Functions `renounceMinter()`, `renouncePauser()`, `revokeMinter()`, `revokePauser()`, `isMinter()`, and `isPauser()` appear unneeded. The `MINTER_ROLE` and `PAUSER_ROLE` values are public and include compiler-generated getter functions. These getter functions can be used to return the bytes32 value representing each role, and then these roles can be used as input, along with the desired account address in functions `renounceRole()`, `revokeRole()`, and `hasRole()` respectively. The `bytes32` value of `MINTER_ROLE` is 0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6. The `bytes32` value of `PAUSER_ROLE` is 0x65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a Functions `addMinter()` and `addPauser()` include other checks through call to internal `_beforeGrant()` before calling `_grantRole()`. However, it is noted that `DEFAULT_ADMIN_ROLE` can still directly call function `grantRole()` to bypass these checks. Since the `owner` of the contract is necessarily also a `DEFAULT_ADMIN_ROLE` based on the logic of the contract, this makes the addition of the checks in `addMinter()` and `addPauser()` functions ineffectual.

Open the report
LowDestination of `execute()` Can Be Any AddressMarblex · CertiK · 10 Aug 2023Fixed

The main use case of `TokenForwarder` appears to be its role as the `trustedForwarder` address used in the `ERC2771` inheritance of the `MBXToken` contract. If there are no other use cases of this contract, consider setting the destination address for the low-level `call` in the function `execute()` upon deployment of the contract, instead of letting the user determine the destination. With its current set up, users can sign any message for any `req.to` destination, and the contract will execute the call to that destination.

Open the report
LowInefficient Memory ParameterMarblex · CertiK · 10 Aug 2023Fixed

One or more parameters with `memory` data location are never modified in their functions and those functions are never called internally within the contract. Thus, their data location can be changed to `calldata` to avoid the gas consumption copying from calldata to memory. ```solidity=75 function submitTransaction(address _to, uint256 _value, bytes memory _data) public onlyOwner { ``` `submitTransaction` has memory location parameters: `_data`.

Open the report
LowMissing Zero Address ValidationMarblex · CertiK · 10 Aug 2023Fixed

The `to` address is not validated before assignment or external calls, potentially allowing the use of the zero address and leading to unexpected behavior or vulnerabilities. For example, transferring tokens to a zero address can result in a permanent loss of those tokens.

Open the report
LowModifier `notFrozen` Can Be Refactored for Gas OpMarblex · CertiK · 10 Aug 2023Fixed

The modifier `notFrozen()` can be reconstructed to save gas during deployment by calling an internal view function instead of directly calling a `require` statement. See an example of this implementation in the following OpenZeppelin contract: [https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol#L46](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol#L46). Code explicitly written in modifiers is copied in all other function instances in which the modifier is used within the contract. In turn, the overall size of the contract is increased. This can be prevented by instead using an internal view function for the required check, as is demonstrated in the link above. Note, however, that function calls in which this is used may cost a slight extra amount in gas each time if this revision is made.

Open the report
LowPotential Locked Blockchain Native TokensMarblex · CertiK · 10 Aug 2023Fixed

Function `execute()` of contract `TokenForwarder` is payable, but there is no check that the included `msg.value` matches the input `req.value`. As a result, one of the following scenarios could occur: - `req.value` is 0, but a positive `msg.value` is included, resulting in native tokens left in the contract. - If the contract does retain any native tokens, either through the scenario above, or by any other means, then a user can provide a valid signed message that they create, with `req.value` specified as the amount left in the contract. In this case, the caller of `execute()` does not have to provide a `msg.value`, and whatever is left in the contract will be sent wherever the caller specified with their signed message.

Open the report
LowUnnecessary RequirementsMarblex · CertiK · 10 Aug 2023Fixed

- Function `acceptOwnership()` overridden in `MBXToken` includes a requirement that `pendingOwner()` is not `address(0)`. However, it is not possible for `address(0)` to call this function directly, and the `TokenForwarder` contract that may be used to change the return value of `_msgSender()` cannot send `address(0)` as the source address. This is because the recovered signer of the `ForwardRequest` is checked in the `ECDSA` library to be a nonzero address, and reverts if this is the case. Consequently, the check that the `pendingOwner()` is not `address(0)` is unnecessary and can be removed. - Internal function `_beforeGrant()` requires that the input `role` is not the `DEFAULT_ADMIN_ROLE`, however, this internal function is only called in functions `addMinter()` and `addPauser()` where the `role` is either `MINTER_ROLE` or `PAUSER_ROLE` respectively. Consequently, the check that the `role` is not the `DEFAULT_ADMIN_ROLE` is unnecessary and can be removed.

Open the report
LowUnnecessary Use of `super` KeywordMarblex · CertiK · 10 Aug 2023Fixed

In the locations cited, the function called is inherited by the contract and can be referenced directly, without the use of the keyword `super`.

Open the report
Show 2 more
LowUnused EventMarblex · CertiK · 10 Aug 2023Fixed

Some events are never emitted, which can lead to confusion and code maintainability issues. ```solidity=7 event SetStatus(bool enableERC2612, bool enableERC2771); ``` - `SetStatus` is declared in `IMBXToken` but never emitted.

Open the report
LowVariables That Could Be Declared as ImmutableMarblex · CertiK · 10 Aug 2023Fixed

The linked variables assigned in the constructor can be declared as `immutable`. Immutable state variables can be assigned during contract creation but will remain constant throughout the lifetime of a deployed contract. A big advantage of immutable variables is that reading them is significantly cheaper than reading from regular state variables since they will not be stored in storage.

Open the report

Incidents1

Attached through a shared contract address. Our incident data ends in Oct 2026.

Incident

Incident,

Source: De.Fi incident database.

Audits before it
2.
Audits since
2, the latest dated 11 Apr 2024.

Contracts

No deployed address is recorded for these audits.

The reports name a repository or files. Open a report to see its scope.

The repositories and files the reports name

Show this on your site

Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.

Content

Facts

Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-tb-project-slug="marblex"
></div>
</body>

Paste the code where the label should appear. It uses no API credit and updates by itself.

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us