On March 27, 2022, the SlowMist intelligence zone received an alert that the Revest contract by Revest Finance was exploited. The hackers stole nearly 7.7 million ECO, 579 LYXe, nearly 715 million BLOCKS, and over 350,000 RENA. The SlowMist security team conducted an analysis of the situation, and these are our findings.
Revest Finance proposed a new protocol for packaging, transferring, and storing fungible ERC-20 tokens as non-fungible tokens. This allows them to leverage the ERC-1155 non-fungible token ( NFT ) standard to simplify access and commercial versatility. Using this product, the ownership of assets can be traded in a way that does not affect the value of the assets, resulting in a new business model. The mechanism, governance, and monetization of the agreement are identified through targeted use cases.
Addresses involved in this incident:
Cause of Incident
In the targeted Revest contract, when a user calls the mintAddressLock function to deposit a certain amount of ERC-20 tokens into the Revest Smart Vault, an FNFT will be created. This NFT represents the number of token assets owned by the user, and the withdrawFNFT function can be called to redeem the tokens in the future.
The root cause of the attack is that when the attacker uses the ER1155 standard to cast an NFT, the onERC1155Received function of the recipient’s address will be called. The attacker uses this point to call back the depositAdditionalToFNFT function in the Revest contract. This function will mint a new NFT, and then it will call the tokenVault contract’s handleMultipleDeposits function to record the new NFT information. However, the handleMultipleDeposits function has no way to verify whether the newly minted NFT exists. Therefore, the attacker used a reentrancy attack to modify the information of the NFT that had been minted. Since the process of creating NFT tokens for ERC20 assets was prior to the attack, the user has now successfully created NFTs representing their 360001 ERC20 tokens without having to deposit ERC20 tokens.
We’ll focus on one transaction since the others are done with the same method.
1.The attacker first borrows 2 RENA tokens from the uniswap pool via a flashloan.
2.Next, they called the mintAddressLock function in the Revest contract and passed in the quantities as 2. After the function performs the locking operation, it will call the doMint function to mint NFT.
In the doMint function, the createFNFT function of the tokenVault contract is called to record the minted NFT function information. Then the user transfers the corresponding ERC20 tokens to the tokenVault contract, and finally calls the mint function in the FNFTHandler contract to issue the NFT.
The fnftId of the minted NFT is 1027, and the recorded information about the NFT is as follows:
Because the depositAmount is 0, the NFT represents that the ERC20 token assets owned by the user are 0, so there is no need to transfer the relevant asset tokens to the contract.
3.Call the mintAddressLock function in the Revest contract again, pass in the quantities of 360000, and called doMint to mint NFT in the same steps as above. The mint’s fnftId is 1028, and the recorded NFT information is as follows:
The depositAmount is still 0, so there is no need to transfer the token assets to tokenVault, but it’s different than before. This operation of minting NFT is different because the _doSafeTransferAcceptanceCheck function is called when the mint function of the FNFTHandler contract is called.
This function will call the onERC1155Received function of the attack contract, so the attacker uses the rewritten onERC1155Received function in the attack contract to call back the depositAdditionalToFNFT function of the Revest contract.
In the depositAdditionalToFNFT function, you need to pass in the specified fnftId (here is 1027), the quantity of NFTs (here is 1), and the number of assets that need to be deposited in a single NFT (here is 1). This function will burn the specified amount of NFTs of the incoming fnftId, and then the user transfers the specified amount of ER20 token assets and mints the new NFTs. The quantity that needs to be transferred is quantity * amount is 1 and the tokenVault contract is called. handleMultipleDeposits records that the deposit amount of the new NFT is the depositAmount value of the NFT passed in with the specified fnftId above + the value of the amount passed in.
However, when the handleMultipleDeposits function mints a new NFT, it does not determine whether the information of the NFT exists in the tokenVault contract. Therefore, the attacker used this exploit to directly modify the data of the NFT №1028. Although the depositAmount recorded for the first time during the doMint operation was 0, it was modified to 1 after the reentrancy attack.
4.Finally, calling the withdrawFNFT function to extract the ERC20 token assets represented in the NFT.
After the function burns the specified NFT, it will call the withdrawToken function in the tokenVault contract to withdraw.
Since the depositAmount was modified to 1 after the callback, the hacker was able to withdraw close to 360,000 RENA.
5.The attacker kept the remaining funds after paying back the flashloan.
This incident is due to the failure to verify whether the newly minted NFT exists in the handleMultipleDeposits function of the tokenVault contract. The attacker used this exploit to directly modify the data of the minted NFT, and the key functions in the Revest contract did not have any reentrancy restrictions, resulting in the use of callbacks.
The SlowMist security team recommends that when performing sensitive operations such as minting NFTs, it is necessary to verify the NFTs already exist, and the restriction of reentrant locks must be added to the key functions contracts to avoid incidents like this in the future.