GithubHelp home page GithubHelp logo

beefyfinance / beefy-contracts Goto Github PK

View Code? Open in Web Editor NEW
174.0 15.0 163.0 4.93 MB

Public repo for the community devs to advance the Beefy protocol.

Home Page: https://app.beefy.finance

Shell 0.39% Solidity 94.73% JavaScript 3.25% TypeScript 1.62% Makefile 0.01%

beefy-contracts's Introduction

Beefy Contracts

Official repo for strategies and vaults from Beefy. Community strategists can contribute here to grow the ecosystem.

Vault Deployment Process

1. Select a farm

The first step to have a vault deployed on Beefy is to select a farm to deploy a vault around. At the moment the rewards for a strategist are:

  • 0.5% of all rewards earned by a vault they deployed.

This means that you want to select a farm with:

  1. High APR
  2. High expected TVL
  3. Long farm life

First time strategists must deploy contracts for farms on existing platforms on Beefy first. New platforms must undergo an audit by Beefy dev team before development can begin.

2. Prepare the smart contracts

If you decided to do a simple LP vault, or a single asset vault, the most likely thing is that there is a working template that you can use. Most farms work under a version of the Masterchef contract (like Goose Finance), or Reward Pool contract (like Beefy Reward Pool).

3. Test the contracts

If you're doing something completely custom you should add automated tests to facilitate review and diminish risks. If it's a copy/paste from another strategy you can get by with manual testing for now as everything has been battle tested tested quite a bit.

For extra help in debugging a deployed vault during development, you can use the ProdVaultTest.t.sol, which is written using the forge framework. Run yarn installForge to install if you don't have forge installed.

To prep to run the test suite, input the correct vault address, vaultOwner and stratOwner for the chain your testing in ProdVaultTest.t.sol, and modify the yarn forgeTest:vault script in package.json to pass in the correct RPC url of the chain your vault is on. Then run yarn forgeTest:vault to execute the test run. You can use console.log within the tests in ProdVaultTest.t.sol to output to the console.

4. Deploy the smart contracts

Once you are confident that everything works as expected you can do the official deploy of the vault + strategy contracts. There are some scripts to help make deploying easier.

Make sure the strategy is verified in the scanner. A fool-proof way to verify is to flatten the strategy file using the yarn flat-hardhat command and removing the excess licenses from the flattened file. Verify the strategy contract using the flattened file as the source code, solidity version is typically 0.6.12 and is optimized to 200 runs. Constructor arguments can be found from the end of the input data in the contract creation transaction; they are padded out with a large number of 0s (include the 0s).

5. Update the app

The only file you really need to touch on the app is the respective json file located in the vault folder. This is the config file with all the live pools. Just copy one of the other pools as template, paste it at the top (below the BIFI Maxi and boosted vaults) and fill it out with your data. earnedTokenAddressand earnedContractAddress should both be the address of the vault contract. These addresses must be checksummed. Use the getPoolCreationTimestamp.js script to get creation dates. You will also need to update the addressBook to the current version in package.json in order for Zap to work if the tokens are new to the address book.

6. Test the vault

Run yarn start on the local app terminal and test the vault as if you were a user on the localhost page.

Manual Testing Is Required for All Live Vaults

  1. Give vault approval to spend your want tokens.
  2. Deposit a small amount to test deposit functionality.
  3. Withdraw, to test withdraw functionality.
  4. Deposit a larger amount wait 30 seconds to a minute and harvest. Check harvest transaction to make sure things are going to the right places.
  5. Panic the vault. Funds should be in the strategy.
  6. Withdraw 50%.
  7. Try to deposit, once you recieve the error message pop up in metamask you can stop. No need to send the transaction through.
  8. Unpause.
  9. Deposit the withdrawn amount.
  10. Harvest again.
  11. Switch harvest-on-deposit to true for low-cost chains (Polygon, Fantom, Harmony, Celo, Cronos, Moonriver, Moonbeam, Fuse, Syscoin, Emerald).
  12. Check that callReward is not 0, if needed set pendingRewardsFunctionName to the relevant function name from the masterchef.
  13. Transfer ownership of the vault and strategy contracts to the owner addresses for the respective chains found in the address book.
  14. Leave some funds in the vault until users have deposited after going live, empty vaults will fail validation checks.
  15. Run yarn validate to ensure that the validation checks will succeed when opening a pull request.

This is required so that maintainers can review everything before the vault is actually live on the app and manage it after its live.

7. Update the API

Existing platform

If you're deploying a vault for a platform where we already have live vaults, you will probably only need to add some data to the respective config file in the data folder. For example if you're doing a new Pancakeswap LP vault, you only need to add the relevant data at cakeLpPools.json

Simpler than that is to use the scripts available to add existing protocol farms.

  • yarn bsc:pancake:add --pool <pid> will add the new pancake farm.
  • yarn polygon:quick:add --pool <reward pool address> will add the new quickswap reward pool.

New platform

If it's a new platform you're going to have to add code to a few files.

  1. Create a data file for the platform in the relevant chain's folder in data and fill out the farm data.
  2. Create a folder under /api/stats in the relevant chain and add code to get the APYs. You will probably be able to use the template for MasterChefs i.e. in getJetswapApys.js.
  3. Import the new getApys file to the chain folder's index i.e. index.js.
  4. Lastly, add a route handler to getAmmPrices.ts so that API and the app can access token and LP prices.

Token not in address book

If any of the relevant tokens do not exist in token.ts in the address book for the network the vault will be deployed on, you will need to add them. Example below.

 SUSHI: {
    name: 'Sushi',
    address: '0xd4d42F0b6DEF4CE0383636770eF773390d85c61A',
    symbol: 'SUSHI',
    decimals: 18,
    chainId: 42161,
    website: 'https://sushi.com/',
    description:
      'SushiSwap is an automated market-making (AMM) decentralized exchange (DEX) currently on the Ethereum blockchain.',
    logoURI: 'https://ftmscan.com/token/images/sushiswap_32.png',
 },

Done!

Another Beefy dev will review everything, merge the PRs and ship it to production.

Environment variables

bsc-rpc: "https://bsc-dataseed2.defibit.io/",

heco-rpc:"https://http-mainnet-node.huobichain.com",

avax-rpc: "https://api.avax.network/ext/bc/C/rpc",

polygon-rpc: "https://polygon-rpc.com/",

fantom-rpc: "https://rpc.ftm.tools",

one-rpc: "https://api.s0.t.hmny.io/",

arbitrum-rpc: "https://arb1.arbitrum.io/rpc",

Troubleshooting

  • If you get the following error when testing or deploying on a forked chain: Error: VM Exception while processing transaction: reverted with reason string 'Address: low-level delegate call failed', you are probably using hardhat network rather than localhost. Make sure you are using --network localhost flag for your test or deploy yarn commands.
  • If you get the following error when running the fork command i.e. yarn net bsc: FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory. Run this command to increase heap memory limit: export NODE_OPTIONS=--max_old_space_size=4096
  • If you are getting hanging deployments on polygon when you run yarn deploy-strat:polygon, try manually adding {gasPrice: 8000000000 * 5} as the last arg in the deploy commands, i.e. const vault = await Vault.deploy(predictedAddresses.strategy, vaultParams.mooName, vaultParams.mooSymbol, vaultParams.delay, {gasPrice: 8000000000 * 5});

beefy-contracts's People

Contributors

0xcmdrkeen avatar aabeely avatar aleem-ahmed avatar andreb0x avatar armand-go avatar bitwonka avatar brknrobot avatar cartman-dev avatar defidebauchery avatar frondoto avatar iamjackgale avatar kexleybeefy avatar kidal5 avatar kobe-eth avatar lodelux avatar marth007 avatar mirthfutures avatar mo0o0d avatar moonster-bsc avatar prevostc avatar pumpingghost avatar qkyrie avatar reflectivechimp avatar roman-monk avatar schnarkus avatar shadoweddev avatar sirbeefalot avatar tburm avatar theothug avatar wivern-co-uk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

beefy-contracts's Issues

Create BatchFees contract

BSC is getting expensive and there's more need to optimize.

We're currently doing a bunch of swaps and ERC20 transactions with each harvest() call. We could just send it all as one to a BatchFees contract and have a public distribute() call there which would divide everything once per day for example.

This would also remove logic from the strat

Gas price throttler design

To protect from front-running on harvest we gonna check transaction's gas price tx.gasprice to be less than current BSC gasprice value.
We also need to make it upgradable in case of Binance will change it as it was before from 20 to 15, and then to 10.
And to not update every single contract we can have 1 GasPrice contract deployed where other contracts will read price from.

GasPrice contract might look like this, owned by cowllector:

contract GasPrice is Ownable {

    uint public maxGasPrice = 10 gwei;   

    event NewMaxGasPrice(uint oldPrice, uint newPrice);
    
    function setMaxGasPrice(uint _maxGasPrice) external onlyOwner {
        emit NewMaxGasPrice(maxGasPrice, _maxGasPrice);
        maxGasPrice = _maxGasPrice;
    } 
}

Then we have GasThrottler contract that has gasThrottle modifier checking that tx.gasprice <= max price.
Contract which needs to limit gas will extend from it and add modifier on needed functions.

interface IGasPrice {
    function maxGasPrice() external returns (uint);
}

contract GasThrottler {
    
    address gasprice = address(0x230562106833b3618A053e6fe0bdC8fDBb59eb12);
    
    modifier gasThrottle() {
        require(tx.gasprice <= IGasPrice(gasprice).maxGasPrice(), "gas is too high!");
        _;
    }
}

I guess we can hardcode GasPrice address here and i don't see use cases when it's needed to be configurable.
We might need HecoGasThrottler, AvaxGasThrottler and so on in the future though.

And finally new Strategies will extend GasThrottler and add gasThrottle modifier on harvest().

contract StrategyLP is GasThrottler {
    
    function harvest() external gasThrottle {
        ...
    }   
}

I've deployed these 2 contracts if someone wants to test.
GasPrice without owner
https://bscscan.com/address/0x230562106833b3618A053e6fe0bdC8fDBb59eb12#code

TestStrategy with empty harvest
https://bscscan.com/address/0x5675d63494EDDa594727033864F1bca375736191#code

beets rounding error causes vault to throw at >0 if-statement

encountered an issue pertaining to this line with the current beets-cre8r vault:

I setup the vault to accept beets outputs in case they were eventually bribed for by cre8r. however, while current beets emissions are nominally zero, because of "rounding error" (according to beets admin) 1gwei beets is emitted every day or so. because the strat contract tries to swap beets whenever beets > 0, when it detects 1gwei beets it tries to trade, and as a consequence throws.

its unclear what a solution might be. any lower bound on trade would be arbitrary since its difficult to predict the price range at which the want token will swap against beets (it could be that a really really cheap token does in fact swap for 1 gwei beets in the future).

Upgrade BeefyVaults to allow EIP-2612 signed approvals

I noticed a few DApps now make use of a new spending permission system outlined in EIP-2612, which involves signing a message in your wallet instead of a separate approval transaction (more details here: https://soliditydeveloper.com/erc20-permit).

Obviously, the token being spent must support this approval style, namely, it must implement the following three methods:

function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external
function nonces(address owner) external view returns (uint)
function DOMAIN_SEPARATOR() external view returns (bytes32)

It does appear that at least SpiritSwap LP tokens on Fantom DO implement this approval style (for example, the USDC-fUSDT pair. Not sure if this only goes for newer pairs and what other exchanges have implemented this, but I will do some research and add a list here.

Of course, the app would need some updates as well in order to support this approval style (and autodetect when it is available), but more importantly, the vaults would need to support this first. I found a possible implementation on a YieldYak vault (license is MIT):

 /**
     * @notice Deposit using Permit
     * @param amount Amount of tokens to deposit
     * @param deadline The time at which to expire the signature
     * @param v The recovery byte of the signature
     * @param r Half of the ECDSA signature pair
     * @param s Half of the ECDSA signature pair
     */
    function depositWithPermit(
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external override {
        depositToken.permit(msg.sender, address(this), amount, deadline, v, r, s);
        _deposit(msg.sender, amount);
    }

A potential depositAllWithPermit() function would look similar.

In the app, the following steps would be required:

  1. Detect if deposit token (i.e. want) supports EIP-2612 approvals.
  2. Produce permission message and ask user to sign it
  3. Instead of calling deposit(amount) or depositAll(), call the _WithPermit version, using the appropriate parameters.

While I don't think this saves any gas or transaction fees (after all, the permit() function still calls approve(), and must additionally decode the signed message), it is a nice quality-of-life improvement and does improve security, since no permanent approvals are required.

Would it be possible to include this in the next iteration of the vault contract?

New vault: Improved auto CAKE using project pools

Hello, I want to offer myself to build a vault for cake built on top of project pools.
They have higher APRs than CAKE->CAKE so it could be a nice addition.
Do you accept strategies from new contributors?
If so I will get started but I want to make sure and not waste time

Let the strat owner increase/decrease the callFee within a certain range.

We can't accurately predict which vaults are going to be the most popular. We also can't predict which vaults will be able to sustain their APYs over the long run. This creates suboptimal fees, because the callFee sometimes ends up being way too high. Beefy is paying to third parties to harvest its vaults, and sometimes it's paying way too much. This makes it so that some vaults are harvested every 2-3 minutes. That harvesting frequency doesn't provide much value to the user and it would better go somewhere else.

If we had a range of 0.05%-0.5% and we could move the callFee within that range. We could decrease how much we pay on profitable vaults, and increase it in some other vaults if it's required. It would be great if the extra % goes to the treasury for example, to help pay for more development/marketing/etc.

Move Treasury to use a multisig implementation

We are growing and so is our need to distribute responsibility over fund management and upgrades.

Initially the team went with a simple account owning everything for simplicity, because there was no ecosystem on BSC and little live implementations of multisig.

At this point all of those things have changed, so moving our operations to use a multisig with 4-6 community members seems like a good next step.

We can move vault upgrades and other functionality to use the same multisig. But starting with the Treasury seems good.

Stop accepting 100% slippage on strategy swaps

Context

Our strategies always send a 0 as amountOutMin when swapping. This means that we're accepting any slippage that the exchange offers.

Problems

This means that our vaults are:

  1. Easier to be exploited by frontrunning bots.
  2. Can burn out a ton of rewards depending on the route.

Solution

  1. Having a healthy default cap on accepted slippage.
  2. Being able to move that cap within a certain range.

Fortube harvests are failing

It seems that since yesterday all the Fortube vaults that attempted a harvest have failed. The error that the transactions get is 'Fail with error 'DEMAX PLATFORM : CHECK DGAS/TOKEN VALUE FROM FAIL'

There is almost no liquidity on any exchange for FOR, so that might be the issue

Make predictAddresses() work within loops

Right now we have to deploy vault/strategy pairs one at a time. This is because predictAddresses fails to predict correctly if you try to do multiple deploys in a row.

I don't exactly know why it is that it fails to predict the correct future addresses, it might be that the nonce remains cached with a call to getSigners().

If we could chain deployments, it would make it easier to launch 5-10 vaults at the same time.

Create "Training Wheels Cap" contract for strategies/vaults to inherit from.

There are sometimes where we're pushing an experimental vault. Or for a few different reasons, we might want to have a temporary cap on deposits.

The goal would be to have a reusable contract that lets strategies have a cap for the first x blocks of their existence.

This would prevent people from apeing $5M into something experimental.

Develop generic integration tests for vaults ($1500)

Context

The initial LP strategies had unit tests for all functions and integration tests for the normal flows. As we've deployed more variations for different platforms, given the number of contracts we deploy, and how similar everything is, we've come to rely more and more on manual testing of flows.

Problem

There are some small mistakes that might go unnoticed. Some small discrepancies in pricePerFullShare for example. New entrants diminishing the share of previous users, etc that we might not catch with manual tests.

Solution

Even though the number of strats deployed keeps growing, we need to not only maintain, but increase the safety of our vaults. A great thing is that all of our vaults share the same interface, so we could have a single suite of tests that check all the usual cases. As time goes on we can build upon the starting suite.

Once we have this tests, strategists can run it on their vaults before submitting for review. Reviewers can also run it on all vaults before being displayed on the app.

Specs

Some tests that could be included:

  • People can deposit and get the correct shares.
  • People can withdraw and get the same value minus the withdrawal fee.
  • Harvest works and all the relevant parties get rewards.
  • New entrants to the vault don't negatively affect previous participants.

Some unit tests on vault's basic features would be good as well. Stress testing the shares math for example.

Stop charging fees when a strat has been paused.

What do we think about adding a check to see if a strat is paused and from that state, don't charge a withdrawal fee.

There are three times where the strats are paused and I think this handles all of them:

  1. Vault reached EOL for some reason. We don't want to punish users for withdrawing.
  2. Vault is in a temporary panic() state without working. Users should be able to move to a different vault freely.
  3. Vault is paused() but funds are still deposited at the farm. There's no risk of arbitrage here as noone can deposit from a paused state. So there's no harm in 0% withdrawal fees.
    function withdraw(uint256 _amount) external {
       ...

        if (tx.origin == owner() || paused()) {
            IERC20(lpPair).safeTransfer(vault, pairBal);
        } else {
            uint256 withdrawalFee = pairBal.mul(WITHDRAWAL_FEE).div(WITHDRAWAL_MAX);
            IERC20(lpPair).safeTransfer(vault, pairBal.sub(withdrawalFee));
        }
    }

If we like it, I can add it to all strats.

Create automated test to verify vault upgrades ($1000)

Context

We have close to 150 vaults and growing. There are times when we have to upgrade multiple vaults at a time, and there might come a time when we have to upgrade +50 in a short period of time. An example would be if we have to change the BIFI rewards pool address, or the treasury address, or add an improvement to all strats.

Right now the process to upgrade a strategy is as follows: When the approval time has passed and the candidate is ready
to replace the old strat, we first do a dry run of the upgrade using a ganache localnet.

We check that:
1- Balances are transfered correctly.
2- We can deposit/withdraw.
3- Harvests can happen and generate earnings for stakers.

If everything looks good, we run the upgrade on mainnet.

Problems

This is quite slow to do. And if we have to do it for several vaults in a row, we could miss a detail somewhere.

Solution

Having an automated test that does this checks would increase our comfort when upgrading vaults. Some of these vaults have $10M-$20M and those numbers are only going to get larger.
It would still be a good idea to run the manual runs at first, but the automated tests would still be very beneficial. The good thing is that these are integration tests which could be re-used for 100% of our vaults.

Specs

The test at VaultUpgrade.test.js would have to receive a vault address and:

  1. Simulate an upgrade using our ganache fork yarn net.
  2. Check that balances transfered correctly.
  3. Check that it's possible to deposit.
  4. Check that a harvest gives funds to the correct parties.
  5. Check that a withdraw gets back the expected funds.

Propose valid candidate for Cake community pools

Last week I proposed a candidate upgrade for the STAX-CAKE/NAR-CAKE/NYA-CAKE vaults to decrease their slippage. For some reason the upgrade didn't work and with the app + api issues that followed, I wasn't able to focus on them.

Even though they're pretty small vaults, it would be good to make sure we deliver the APY that we display. Debugging what went wrong and/or proposing a candidate with the correct custom routes would make this happen.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.