As of 3 Nov 2026, MELD has 2 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 9 Jun 2023. Those reports list no open critical findings.
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 1 | 0 |
| Medium | 0 | 2 | 3 |
| Low | 4 | 0 | 10 |
The function `redeem()` in the contract `MeldDaoERC721` only ensures that it has voted in an ongoing proposal, but does not check whether it is delegated to another NFT. As a result, the delegator cannot call the function `submitVote()` to submit the vote once the NFT which is delegated to it has been redeemed, because the transaction will be reverted by the owner check in the function `updateRedeemableTimestamp()`. To solve the issue, the delegator has to identify the redeemed NFT and call the function `removeDelegatedNft()` to remove the delegation.
Open the reportThe function `rescueERC721()` allows the role `DEFAULT_ADMIN_ROLE` to transfer all types of the `ERC721` tokens including the current `MeldDaoERC721` tokens to an arbitrary address. The function does not check whether the `_token` address equals `address(this)`.
Open the reportThe contract `BaseToken` adopts the eip-2771 standard which overrides the function `_msgSender()` and uses the last 20 bytes of the call data instead of `msg.sender` if the `msg.sender` is the forwarder. Meanwhile, the function `_checkRole()` in the OpenZeppelin contract `AccessControl` is using the function `_msgSender()` for the role checks. Thus, the forwarder can forge the value of _msgSender() and effectively send transactions from privileged addresses.
Open the reportThe function `removeAllDelegatedNfts()` does not clear the `delegatedIndexes` as the function `removeDelegatedNft()`. The behaviors should be consistent for both functions, the only difference should be execution to all delegations. ```solidity nft.delegatedIndexes[delegatedNftId] = 0; ``` As a result, the mapping `nft.delegatedIndexes` will contain obsolete data which contradicts the mapping `nft.delegatedNfts`.
Open the reportThe mapping `delegatedIndexes` is the index of a given NFT in the `delegatedNfts` array. We can see that the index starts from 0 in the below code snippet in the function `delegateMyNft()`. However, this mechanism cannot distinguish the actual zero index from the null value, because the default value of the type `uint256` is also 0. If the `delegatedNfts` only has one element whose token id is 1, the value of `nft.delegatedIndexes[userNftId]` is still 0 which equals the default value. ```solidity nft.delegatedNfts.push(userNftId); nft.delegatedIndexes[userNftId] = nft.delegatedNfts.length - 1; ```
Open the reportA reentrancy attack can occur when the contract creates a function that makes an external call to another untrusted contract before resolving any effects. If the attacker can control the untrusted contract, they can make a recursive call back to the original function, repeating interactions that would have otherwise not run after the external call resolved the effects. *This finding is considered informational because the meldToken is a known contract. Also, the effect of out-of-order increments may be unobservable after the transaction and the `require` statement may prevent the attack. However, the reentrancy vulnerability may still cause other issues in the middle of the transaction.* --- In the function `mint()`: ### External call(s) ```solidity=73 meldToken.safeTransferFrom(msg.sender, address(this), nftPrice); ``` ### State variables written after the call(s) ```solidity=78 lockedMeldTokens += nftPrice; ``` ```solidity=77 pricePaidPerNft[tokenId] = nftPrice; ``` ```solidity=76 _mint(msg.sender, tokenId); ``` * This function call executes the following assignment(s). * In `ERC721._mint`, * `_owners[tokenId] = to` *Note: Only a sample of 3 assignments (out of 5) are shown above.* --- In the function `redeem()`: ### External call(s) ```solidity=93 meldToken.safeTransfer(msg.sender, pricePaid - fee); ``` ```solidity=94 meldToken.safeTransfer(feeAddress, fee); ``` ### State variables written after the call(s) ```solidity=95 redeemedNfts++; ```
Open the reportThe variable names `index` and `nftId` in the mapping `delegatedIndexes` are reversed. The key should be `nftId` and the value should be `index`. ```solidity=24 struct Nft { bool delegateable; uint256[] delegatedNfts; mapping(uint256 index => uint256 nftId) delegatedIndexes; } ```
Open the reportFunctions that update state variables should emit relevant events as notifications. For example: `MeldVotingContract.startNewVotingForProposal()`.
Open the reportIf the wrong proposals are made, there is no cancellation function in the contracts `MeldVotingContract` and `DaoRegistry`, although the `DaoRegistry` contract mentioned the `flags` can be `canceled`(code comment L90).
Open the reportThe function `startNewVotingForProposal()` in the contract `MeldVotingContract` receives a parameter in `bytes` type with no name and does not use it. It might be used for recording the proposal content.
Open the reportAccording to the following code snippet in the function `mint()`, each new minter will start a new minting period in the first mint, and any further mint operation from this minter within the period cannot exceed the threshold limit. The first mint operation after the period will clear the minted amount(`currentMintingAmount[minter]`) and start a new period, so the minter can mint more tokens below the threshold. If the `mintingPeriodLength[minter]` is set to 0, this logic is like a maximum mint limit for a single mint transaction. ```solidity=113 if (block.timestamp > lastMintingPeriod[minter] + mintingPeriodLength[minter]) { currentMintingAmount[minter] = 0; lastMintingPeriod[minter] = block.timestamp; } require( currentMintingAmount[minter] + _amount <= mintingAmountThreshold[minter], "BaseToken: Minting amount exceeds threshold" ); currentMintingAmount[minter] += _amount; ```
Open the reportThe cited addresses input are missing a check that it is not `address(0)`. For example: - `_token` in the `RescueTokens._rescueERC20()`, `RescueTokens._rescueERC721()`, `RescueTokens._rescueERC1155()`. - `_defaultAdmin` in the `BaseToken.constructor()`. - `_account` in the `BaseToken.setMintingAccount()`. - `_forwarder` in the `BaseToken.setTrustedForwarder()`.
Open the reportThe linked variable `__decimals` 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 reportThe reports name a repository or files. Open a report to see its scope.
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.
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.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="meld"></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