Skip to content

Latest commit

 

History

History
 
 

RevestFinance

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

Revest Finance

Step-by-step

Each FNFT could be redeemed by the accounted tokens it backs. The attacker created vaults (repredented by FNFTs) without backing them with RENA and reentered with depositToken which updates the vault's balance before doMint.

  1. Mints first a small amount to determine the currentId and to generate a small NFT position.
  2. Mints a big NFT position and reenters the minting call with revest.depositAdditionalToken() with just 1e18 RENA so each token virtually backs that amount.
  3. After the call finishes, the internal accoutancy interprets that the attacker sent 360,000 * 1e18 RENA instead of what he sent allowing him to redeem that amount of RENA.

Detailed Description

The attacker managed to reenter the minting mechanism of the ERC-1155's with its callback.

   function mintAddressLock(
        address trigger,
        bytes memory arguments,
        address[] memory recipients,
        uint[] memory quantities,
        IRevest.FNFTConfig memory fnftConfig
    ) external payable override returns (uint) {
        uint fnftId = getFNFTHandler().getNextId();

        {
            IRevest.LockParam memory addressLock;
            addressLock.addressLock = trigger;
            addressLock.lockType = IRevest.LockType.AddressLock;
            // Get or create lock based on address which can trigger unlock, assign lock to ID
            uint lockId = getLockManager().createLock(fnftId, addressLock);

            if(trigger.supportsInterface(ADDRESS_LOCK_INTERFACE_ID)) {
                IAddressLock(trigger).createLock(fnftId, lockId, arguments);
            }
        }
        // This is a public call to a third-party contract. Must be done after everything else.
        // Safe for reentry
        doMint(recipients, quantities, fnftId, fnftConfig, msg.value);

        emit FNFTAddressLockMinted(fnftConfig.asset, _msgSender(), fnftId, trigger, quantities, fnftConfig);

        return fnftId;
    }

    // Then, doMint call ends up calling tokenVault.depositToken():

    function depositToken(
        uint fnftId,
        uint transferAmount,
        uint quantity
    ) public override onlyRevestController {
        // Updates in advance, to handle rebasing tokens
        updateBalance(fnftId, quantity * transferAmount); // <----- HERE IS WHERE THE LOCKED BALANCE OF THE NFT IS ACCOUNTED
        IRevest.FNFTConfig storage fnft = fnfts[fnftId];
        fnft.depositMul = tokenTrackers[fnft.asset].lastMul;
    }

    // In revest.depositAdditionalToken(), the balance of an NFT is topped up with more tokens:
    function depositAdditionalToFNFT(
        uint fnftId,
        uint amount,
        uint quantity
    ) external override returns (uint) {
        ...
        ITokenVault(vault).depositToken(fnftId, amount, quantity);
        ...
    }

    // The FNFT Token Handler's mint function does not respect the Checks-Effects-Interactions pattern minting before updating internal variables.
    
    function mint(address account, uint id, uint amount, bytes memory data) external override onlyRevestController {
        supply[id] += amount;
        _mint(account, id, amount, data);
        fnftsCreated += 1;
    }
  1. The deposit flow does not ensure that the token addresses provided match the addresses of the pools that are called (_pid)
  2. The liquidity and internal balances (vars) are updated after adding liquidity inside addLiquidityInternal().
  3. Because of 1. and 2., the deposit flow could be attacked by reentrancy as tokens flow before updating key variables and the pools allow malicious tokens. The deposit flow will update twice the balance of the attacker contract (malicious token) transferring the double of stablecoins.

Possible mitigations

  • Respect the checks-effects-interactions security pattern by minting tokens lastly on the mint call
  • Evaluate if checks are needed before minting in order to guarantee that the system works as intended (e.g. no checks present in the mint function).

Diagrams and graphs

Class

class

Sources and references