BiShares Finance YBF - Audit Report
BiShares Finance is building an innovative platform to easily build, deploy, and maintain Index Funds comprised of various BiShares Vault tokens in a secure and decentralized manner.
We reviewed BiShares Finance's contracts in the following private Github repositories:
- BiShares YBF at commit 6b5746ed77549e03d3961efe5f1fc4b87c41246b
- BiShares Vaults at commit e32fe6615d2849d69b1f76a90257a021a16e5c93
Audit Findings Summary:
Notes on Individual Contracts:
- The project team should exercise caution to avoid adding fee-on-transfer tokens as an asset in any Index Pool which can lead to errors during balance calculations.
- The platform relies on reserve amounts in a single liquidity pool in order to determine prices; This technique is unreliable as a pricing mechanism as it is prone to tampering via price fluctuations caused by flash loans. We recommend using time-weighted averages for all pricing within these contracts.
- The BisharesZapUniswapRouterMinter contract does not allow users to specify a minimum amount of shares to receive in order for the transaction to be successful. This is essential in order to account for high fluctuations in price. We recommend allowing users to specify a minimum amount out value.
- Investing requires placing trust in the project team as they have substantial power in the ecosystem.
- Date: December 15th, 2021.
- This contract contract provides the owner a few permissions across the platform.
- The owner can use this contract to configure and deploy an Index Pool.
- Once an Index Pool is deployed, the PoolInitializer must finish preparing the pool by calculating the weights for each token in the Pool.
- Anyone is able to use the contract to evaluate and update data including denormalized weights for an Index Pool that has reached it's minimum balance requirements at any time.
- Anyone is able to reindex any initialized Index Pool at any time, as long as it is due for a reindex; reindexing recalculates and updates the minimum required balances and the denormalized weights for each token in the Index Pool.
- Anyone is able to reweigh any initialized Index Pool at any time, as long as it is due for a reweigh; reweighing recalculates the denormalized weights for the desired tokens in the Index Pool, as well as the top tokens in the category of interest.
- The owner is able to set the maximum number of different tokens any initialized Index Pool can be comprised of at any time.
PoolFactory and PoolFactoryAccessControl Contract:
- Anyone is able to trigger a category re-ordering which uses in-place insertion sort to optimally sort the tokens by descending order in terms of market cap.
- The owner is able to create a new token category at any time.
- The owner is able to add any token to any category at any time, as long as the amount of tokens in a category is under the maximum of 25.
- The owner is also able to remove any token from any category at any time.
- Approved addresses can use this contract to deploy a new Index Pool using a many-to-one proxy, meaning there can be multiple addresses for the same implementation code; this is used in order to create multiple Index Pools at will.
- The owner of the PoolFactory contract is intended to be the PoolFactoryAccessControl contract.
- The owner is able to use the PoolFactoryAccessControl contract to transfer the ownership of the PoolFactory contract to any address at any time.
- The owner is able to grant or revoke Admin access from any address at any time.
- The owner or any address with the Admin role is able to grant the ability for any address to deploy pools via the Pool Factory contract at any time; only the owner can revoke this permission.
- The project team can use the PoolInitializer contract to initialize an IndexPool via the MarketCapSqrtController contract.
- The Index Pool is not considered to have finished initialization until it has met the minimum contribution amounts for each of the desired tokens; the minimum contribution amounts are based on the weights of each token.
- Users can contribute tokens to the Index Pool in order to meet the balance requirements; the system will assign the users "credits" based on the current ETH value of the tokens being deposited.
- Once the Index Pool has met the target contribution amounts for each token in its composition, the initialization is marked as finished, and the Controller contract calculates the denormalized weights for each token based on the current ETH value of the balance of each token.
- Once the initialization of the Index Pool is completed, users will be able to claim a portion of the Index Pool shares based on the ETH value of their tokens at contribution time.
- In order to join the Pool, a user must deposit an amount of each Vault token the Pool is comprised of proportional to the amount of shares the user wishes to purchase; users will be able to join as long as the Pool has not yet reached its capacity.
- Upon exiting the Pool, shares are liquified and an amount of each Vault token proportional to the amount of shares being burned is delivered to the user.
- The MarketCapSqrtController contract is able to set the maximum pool shares cap to any value at any time.
- The MarketCapSqrtController contract is able to set the minimum balance required for a Vault token in the pool as long as the token is bound and not in Ready status.
- Anyone can use the ZapIn functionality to provide liquidity to any Vault contract's designated LP token using any single token asset.
- The user must provide the router address used to perform the swaps and add liquidity.
- The user is also able to specify the minimum amount of Vault shares to receive from the transaction in order to account for high fluctuations in price.
- The entire amount of the asset provided is swapped for one of the desired tokens in the pair.
- A portion of the received tokens is swapped for the second desired token in such a way as to minimize the potential residual returns of the individual tokens when the tokens are ultimately added as liquidity to the DEX pair.
- The algorithm used by the team takes into account the change in asset ratios and the swap fee charged by the DEX when adding liquidity, but users can expect some residual returns from the liquidity add, albeit minimal.
- The LP tokens are automatically deposited in the Vault to accrue additional rewards.
- Any residual amounts of the asset tokens are transferred to the user.
- Anyone can use the ZapOut functionality to remove liquidity from any Vault contract's designated LP token and receive the output tokens in any single token asset.
- After liquidity is removed from the pair, the contract swaps each of the received tokens to the desired output token.
- The user must provide the router address used to perform the swaps and remove liquidity.
- The user is able to specify the minimum amount of tokens to receive from the transaction in order to account for high fluctuations in price.
- The output tokens are transferred to the user's wallet address.
- Anyone can use the BisharesZapUniswapRouterMinter contract to invest in any Index Pool using ETH.
- The deposited ETH is swapped via the ZapUniswapV2 contract for each of the designated LP tokens underlying each Vault in the Index Pool.
- The LP tokens received are subsequently deposited in the appropriate Vault in exchange for Vault shares; any unused/remaining LP tokens are transferred to the user's address.
- The Vault share tokens are deposited into the Index Pool in exchange for Index Pool shares representing the user's ownership of the Index Pool; any unused/remaining Vault tokens are converted back to ETH and transferred to the user's address.
- Anyone can use the BisharesZapUniswapRouterBurner contract to divest from the Index Pool and receive a single asset token or ETH in return.
- An amount of each Vault share token that comprise the Index Pool proportional to the amount of Index Pool shares the user wishes to burn is swapped via the ZapUniswapV2 contract for the desired output token and delivered to the user.
- The user can specify the minimum amount of the output token to receive in order to account for high fluctuations in price.
- Anyone can use this contract to deposit any amount of the designated LP token into the underlying Strategy contract and receive an amount of shares in return based on the amount of LP tokens the user is depositing in relation the amount of LP tokens that are currently in the system.
- Users can withdraw from the Vault at any time, exchanging their shares for a proportional amount of LP tokens in return; in the event that there are not enough LP tokens to cover the withdrawal, the contract will transfer its balance of LP tokens and consider the shares withdrawn.
- In the case that some amount of the designated LP tokens get stuck in the contract, anyone is able to trigger the earn() function to deposit them into the Strategy contract.
- The owner is able to change the underlying Strategy contract, but must submit a proposal in order to do so and wait until the approval delay time has elapsed before upgrading the Strategy.
- Upon upgrading the Strategy, the LP tokens from the Strategy are withdrawn into the Vault contract and immediately deposited into the new Strategy contract.
- The owner is able to withdraw any tokens, except for the designated LP token, from the contract at any time.
- Anyone can use this contract to deposit the contract's balance of the designated LP tokens into a MasterChef staking contract to earn rewards.
- Anyone can use this contract to harvest the rewards from the underlying MasterChef at any time.
- 3% of the received rewards are swapped for the fee token set by the team; one sixth of the amount is transferred to the dev wallet, and the remaining amount is transferred to a multisig wallet controlled by the team.
- The remaining 97% of the rewards are swapped for the two tokens that comprise the designated LP token, and added as liquidity to the pair.
- The received LP tokens are deposited into the MasterChef staking contract, adding to the pool of funds owned by the shareholders in the Vault.
- The Vault address can withdraw any amount of LP tokens from the Strategy contract and the MasterChef staking contract at any time.
- The owner and the Vault address can trigger an emergency withdraw on the MasterChef staking contract at any time.
- The owner can pause the deposit and harvest functionality at any time.
- The owner can set the Vault address, Uniswap Router address, Fee Router address, Multisig address, and dev wallet address to any value at any time.
General notes across all contracts:
- Anyone can use this contract to harvest rewards on multiple Strategy contracts in a single transaction.
- A for-loop is used to harvest rewards on multiple Strategy contracts. Users must ensure the number of Strategy contracts involved in a single harvest is not too high to prevent this loop from hitting the block gas limit.
- Excellent structuring of logic to prevent reentrancy attacks and to optimize gas usage.
- The Index Pool and BMath libraries used throughout the platform are based on code pioneered by Balancer Finance.
- The BMath libaries also serve to protect transactions from overflows.
- The contracts utilize 112x112 fixed point number representation for arithmetic operations, which promotes gas efficiency in calculations, but sacrifies range and precision that the standard floating point number representation offers.
External Threat Results
|Arbitrary Storage Write||N/A||PASS|
|Delegate Call to Untrusted Contract||N/A||PASS|
|Dependence on Predictable Variables||N/A||PASS|
|State Change External Calls||N/A||Pass|
|User Supplied Assertion||N/A||PASS|
|Critical Solidity Compiler||N/A||PASS|
|Overall Contract Safety||PASS|
Contract Source Summary and Visualizations