by August Wang & Nick Rossi
May 26, 2023
Security and good governance are everything in web3 these days.
To ensure your NFT smart contract is secure, it’s essential to get ahead of actors who seek to wreak havoc with your code. The process starts with a thorough audit for bugs and/or vulnerabilities. This due diligence will also provide assurance to investors, legal teams, project participants, and other stakeholders that contracts are secure and will function as expected on the mainnet. Here are 9 vulnerabilities to keep top of mind.
Reentrancy vulnerabilities allow malicious actors to repeatedly call the contract’s functions and potentially drain its balance.
In a contract that allows users to withdraw funds, an attacker can call the withdrawal function repeatedly before the balance is updated, allowing them to drain the contract’s balance.
A common example of reentrancy is a wrapping attack on mints or drops.
Let’s say that you’re minting an NFT that is intended to be random. However, you have knowledge ahead of time about which NFTs in the collection are most valuable — or you can evaluate it with solidity code. You could theoretically deploy a contract that mints a token, checks if it is valuable, and then reverts if it is not.
You would still pay the gas for the mint, but your purchase price would be refunded and you can call this again and again guaranteeing you only get the best NFTs.
Some contracts mitigate this risk by not allowing other contracts to mint and blocking multisig wallets. Other contracts source their randomness using a solution like chainlink, which not only improves the randomness but injects a delay between purchase and minting so that a contract cannot check and revert on a poor quality mint.
Another example of a reentrancy attack is refund blocking, which impacts unprotected auction mechanics.
Let’s say a contract is implemented such that when the top bidder is out-bid, they are refunded for the bid. At face value, this decision criteria seems reasonable. However, if the top bidder is a contract, it can be set up to reject the refund. If the auction contract doesn’t handle the failed refund, it will bubble up and revert the transaction attempting to outbid the malicious bidder. As a result, the malicious bidder’s bid survives as the top bid, which nobody can outbid.
Typically, the way to address this situation is to handle failed ETH gracefully, for instance escrowing a withdrawable payment on a failure or by completely adopting a withdraw-only refund mechanism.
ReentrancyGuard is an effective way to ensure the contract balance is updated prior to any withdrawals.
“Frontrunning is a form of market manipulation where traders take advantage of pre-market knowledge of an order to buy or sell a cryptocurrency before the order is executed,” writes OMNIA, a company that specializes in consumer protection and privacy.
It can happen when an attacker observes pending transactions and manages to sneak their own order ahead in the pipeline, gaining an advantage over others in the market.
A contract that allows users to make bids on an auction can be vulnerable to frontrunning if an attacker can observe the pending transactions and submit their own bid with a higher gas price. To prevent this, the contract can use a random delay or a commitment scheme to make it more difficult for an attacker to predict when a bid will be processed.
To make sure the smart contract prevents frontrunning, implement a random delay or commitment scheme which increases unpredictability to stop frontrunners from profiting off others’ bids. With these measures in place, users can bid with confidence and know their chances in competing against frontrunners are secure.
A contract that manages a collection of NFTs can be vulnerable if an attacker can call the functions that transfer ownership of the NFTs. To prevent this, the contract can check that the caller is the owner of the NFT before allowing the transfer.
Protect your NFT collection by ensuring that the smart contract restricts access to only authorized users through mapping, and verifies ownership before permission is granted for any transfers.
Make sure that the contract’s functions are only accessible to the intended users, and that ownership and transfer of the NFTs is properly restricted and auditable.
Consider a contract that emits an event when an NFT is sold. An attacker can use a malicious contract to listen for this event and perform actions when it is emitted. To prevent this, the contract can include a guard condition for the event, so that it can only be emitted when certain conditions are met.
Ensuring events are emitted in the expected way and under specified conditions is crucial for any contract deployment. This type of verification will ensure events are sent accurately and securely, making sure that an attacker cannot use a malicious contract to eavesdrop on these events or perform unauthorized actions.
The key is to validate that the contract emits events in the expected way, and that the events can be used for proper auditing and logging.
While address length may seem like a minor precaution for security, it’s worth noting that it can make all the difference when it comes to keeping your contract secure.
Checking the length of an address can also help to improve the security of the contract, as it can help to prevent attacks that involve using maliciously crafted addresses. As an example, if an attacker sends a very short address, it could be interpreted as a zero address, which could be used to steal the contract’s balance.
By confirming that address lengths are within the expected range, you can help prevent malicious attackers from using specially crafted address inputs as a way to exploit your contract and siphon off funds.
It’s important to thoroughly test the capabilities of your functions by running them with a range of input values. This includes using the highest and lowest possible values, as well as a random value in the middle, to ensure that your functions perform correctly and handle unexpected situations appropriately.
If a function is supposed to return values greater than 0, test it with a return of 0 to see if it handles that case correctly.
Ensure that the output of your functions falls within the expected range.
Planning the number of iterations is key when using loops in a smart contract; otherwise, you could end up hitting your block’s gas limit. To make sure that doesn’t happen, establish an upper limit on how many times you loop. If more iterations are needed, break the process down into multiple transactions.
It’s important to note that the timestamp of a block can be altered by a miner, therefore all uses of timestamps, both direct and indirect, should be thoroughly examined and verified.
If the time-sensitive event (such as end of auctions or registration periods) can vary a 30-seconds deviation and still maintain its integrity, timestamp can be used safely. It’s important not to use the block number property as a timestamp.
Ensure that the output values are correctly formatted. Verify that functions which are intended to return an array of numbers do not return an empty array, as issues could arise.
For example, say your parameter is as follows:
function processData(string randomText, uint256 randomNumber) public {}
Ensure that the parameters “string” and “uint256” are properly validated. Develop tests to cover scenarios where the “string” parameter is an empty string, a very long string, and the “uint256” parameter is zero, negative, and a very large number. This will help ensure that your contract can handle all possible input values and minimize security risks.
Testing in a private environment is crucial for the development and deployment of NFT smart contracts or any decentralized applications.
Private testing allows developers to simulate real-world scenarios and ensure that the contract functions as intended without risking any actual assets.
Testing on a private testnet can detect bugs, security vulnerabilities, and other issues that may not have been identified during initial development. By thoroughly testing in a private environment, developers can increase the likelihood of a successful deployment and minimize potential problems.
Contributors Statement
This work was a collaboration between the nameless editorial team. Ritika Puri contributed to this story.
Disclosure statement
Copyright © 2023 nft42, Inc. All Rights Reserved. This material is for informational purposes only, and is not offered or intended to be used or relied upon as investment, accounting, financial, legal, or tax advice, or advice of any kind. The material is not an endorsement of any particular company, project or token. The material herein represents the opinions of the author(s) at the time of writing, and does not necessarily reflect the views of the publisher or editor. nft42, Inc. makes no warranties, express or implied, as to the accuracy, completeness, or timeliness of the information contained in the material. By using this website, you agree to the Terms of Use and Privacy Policy. If you have any questions, please contact us using the information provided in those documents.