# Buttonwood Security Report

By [Shanmuga Bharathi](https://paragraph.com/@shanmuga-bharathi) · 2024-04-09

---

Protocol Overview:
------------------

Buttonwood is a collection of DeFi primitives for building powerful decentralized financial instruments. One of Buttonwood's insights is that everything in finance is some form of a tranche. The ButtonTranche contracts serve as a foundation for marketplaces such as liquidation-free debt, convertible bonds, and options, and assets like fiat-free stablecoins.

This write-up covers some bug bounty reports hosted by Buttonwood on Immunefi.

#1 Accounting bug causes the free minting of dust tokens. \[Bounty: $10,000 💰\]
--------------------------------------------------------------------------------

### Scope:

[https://github.com/buttonwood-protocol/button-wrappers/blob/main/contracts/UnbuttonToken.sol](https://github.com/buttonwood-protocol/button-wrappers/blob/main/contracts/UnbuttonToken.sol)

### Description:

The `mint()` and `mintFor()` function on the ButtonToken contract allow any user to mint a free  amount of `bits`. Relatively, the `mint` function takes a number of bits to be minted for the user by calculating through `_amountTobits`. When certain bits are calculated, the amount of the underlying token(Uamount) to be deposited is calculated. Which results in `0` amount of underlying tokens being deposited by the user. The button token generally accepts Uamount in the `_deposit()` function.

In this bug report, when a user gives a low amount for minting, which can be measured in 5 decimals of uint Eg: 55555. The contract mints a free `X` amount of `bits` for the user.

The root cause of the issue is actually due to the floor division rounding when converting the share amount into the underlying amount. This means that the absolute maximum that can be extracted is the equivalent of 1 underlying "wei" worth of share tokens for free.

This vulnerability can be exploited through the repeated call of the mint() function through multicall contracts to save some gas.

**_Proof of Concept:_**

As said in the bug report, the mint function can be exploited through the dust amount input for tokens.

This POC can be demonstrated through the Tenderly mainnet fork.

1.  Add ButtonToken(bWbtc) contract to the Tenderly Dashboard.
    
2.  Fork the Ethereum mainnet using the Tenderly Fork tab.
    
3.  Create a new simulation and choose the button token that has been added before.
    
4.  Select the `mint()` function and give input for the number of tokens in 5 decimals, eg: 55555
    
5.  The default caller address will be `address(0)` changed to any random address.
    
6.  Now click simulate, The portion of bits will be transferred to that random caller without receiving any amount of underlying tokens from the caller.
    

From the attacker's side, to make a profit from the `mint()` function, the attacker can use multicall code to repeatedly call the `mint()` function with low gas fees.

### Project’s Response:

While this is certainly a valid concern, the exploitable value is extremely limited, even with an automated loop calling the function iteratively. On a chain where gas has any value at all, we don't see this being a profitable exploit under any reasonable circumstances.

We truly appreciate the report and are implementing a fix for the numerical instability in future versions. Due to the extremely limited scope of the exploit, we propose to change the severity to \``` Medium` `` and **_offer a $10,000 payout._**

#2 The owner can make the bond mature before the maturity date.
---------------------------------------------------------------

### Scope:

[https://github.com/buttonwood-protocol/tranche/blob/main/contracts/BondController.sol](https://github.com/buttonwood-protocol/tranche/blob/main/contracts/BondController.sol)

### Description:

The bond should mature on a pre-defined maturity date. But the `mature()` function can be called before the maturity date due to the wrong operator used in the require statement.

    function mature() external {
        require(owner() == _msgSender() || maturityDate < block.timestamp,         "BondController: Invalid call to mature");
    }
    

The `||` (OR) operator is either or, operator means either one statement can be true to pass the require statement. So, in that case, **if the owner calls mature(), the statement is passed and able to mature the bond before the maturity date.**

This will cause certain functionalities to be unable to call and malicious owners can transfer the collateral token before the maturity date.

**_Proof of Concept:_**

The attacker creates a bond.

Then the bond contract is rug pulled by the malicious owner(attacker) by calling the mature function before the maturity date.

    pragma solidity 0.8.0;
    
    interface IBondFactory {
        function createBondWithDepositLimit(
            address _collateralToken,
            uint256[] memory trancheRatios,
            uint256 maturityDate,
            uint256 depositLimit
        ) external;
    }
    
    interface IBondController {
        function mature() external;
    }
    
    contract exploit {
        IBondFactory bf = IBondFactory(0x000001);
        IBondController bc = IBondController(0x000002);
    
        //Creating Malicious bond
        function createbond(address _collateralToken, uint256[] calldata trancheRatios, uint256 maturityDate, uint256 depositLimit) external {
            bf.createBondWithDepositLimit(_collateralToken, trancheRatios, maturityDate, depositLimit);
        }
    
        //Malicious owner can call mature at any time/ before the maturity date
        function attack() external {
            bc.mature();
        }
    }
    

### Recommendation(s):

It is recommended to use the `&&` operator to make it callable by the owner on the maturity date.

    require(owner() == _msgSender() && maturityDate < block.timestamp, "BondController: Invalid call to mature");

---

*Originally published on [Shanmuga Bharathi](https://paragraph.com/@shanmuga-bharathi/buttonwood-security-report)*
