binSPIRIT
Beefy wrapped SPIRIT.

Introduction

What is inSPIRIT?

When users lock their SPIRIT tokens in SpiritSwap for between 1 week and four years, they receive a proportional amount of inSPIRIT. inSPIRIT is non-transferrable and the amount in the holder's wallet decreases steadily to 0 when the lock is over. inSPIRIT holders can claim protocol revenue fees (variable week by week but have been up to 80% APR) and receive up to a 2.5x boost in farms. The boost depends on the user's balance of inSPIRIT compared to all other holders and the balance they have staked in the farm compared with the farm’s TVL. You also will not be able to liquidate your position until the end of the time lock.

What is binSPIRIT?

binSPIRIT is the Beefy wrapped version of SPIRIT, allowing holders to earn SpiritSwap protocol fees without having to lock their SPIRIT. A user can mint binSPIRIT 1:1 using SPIRIT through the UI on the binSPIRIT vault page. If it is more profitable to buy binSPIRIT then Beefy's Smart Minter will do exactly that, yielding the user more binSPIRIT for their SPIRIT:
beMINT determines the most profitable strategy
Users can also get binSPIRIT by buying on a DEX. binSPIRIT can be staked in the Beefy vault for more binSPIRIT, or directly in the reward pool to receive SPIRIT.
The SPIRIT used to mint binSPIRIT is locked for inSPIRIT. Beefy uses this inSPIRIT to boost all Beefy SpiritSwap vaults and vote for incentive emissions.

How does binSPIRIT keep its peg?

Should a user want to liquidate their position they will be able to trade binSPIRIT on an exchange. Unlike inSPIRIT, users are never locked in and can leave anytime. This also means that binSPIRIT may not always be pegged to SPIRIT, but will maintain a loose peg.
If binSPIRIT goes under peg then the SpiritSwap protocol fees will buy back more binSPIRIT per SPIRIT and the Beefy vault APY will increase. APR for the reward pool would increase as well, allowing users to buy and stake binSPIRIT for a much larger proportion of the SpiritSwap protocol fees than just locking SPIRIT.
If binSPIRIT goes over peg then arbitrageurs will mint binSPIRIT and sell for a profit.

How does the Gauge Staker work?

The Gauge Staker contract has two notable parts: locking SPIRIT to mint binSPIRIT and passing tokens between strategies and gauges. Locking the SPIRIT on the Gauge Staker allows the contract to obtain non-transferrable inSPIRIT. All SpiritSwap strategies for gauges will pass their deposits, withdrawals and harvest rewards through the Gauge Staker. Since all deposits are coming from the Gauge Staker address the highest boost can be obtained across all gauges as the contract also holds the concentrated inSPIRIT.

Deposit SPIRIT to mint binSPIRIT

A user can deposit SPIRIT (want) and the contract will confirm the amount that is received by checking balances before and after the transfer. If the received amount is non-zero then check if an existing lock for SPIRIT exists, which it likely will unless the lock has not been initiated before or has been left to expire. If the lock exists then it will extended out to the full 4 years if the current lock time is less than the full amount, and the received balance of SPIRIT is locked to get a 1:1 amount of inSPIRIT. If no lock currently exists then create a new one and lock the balance of SPIRIT on the contract. Finally mint an equal amount of binSPIRIT as the received balance of SPIRIT from the user.
1
// deposit 'want' and lock
2
function _deposit(address _user, uint256 _amount) internal nonReentrant whenNotPaused {
3
uint256 _pool = balanceOfWant();
4
want.safeTransferFrom(msg.sender, address(this), _amount);
5
uint256 _after = balanceOfWant();
6
_amount = _after.sub(_pool); // Additional check for deflationary tokens
7
if (_amount > 0) {
8
if (balanceOfVe() > 0) {
9
increaseUnlockTime();
10
veWant.increase_amount(_amount);
11
} else {
12
_createLock();
13
}
14
_mint(_user, _amount);
15
emit DepositWant(balanceOfVe());
16
}
17
}
Copied!

Vote on which gauges to boost

The Beefy Keeper can vote on gauge incentives using the inSPIRIT balance on the Gauge Staker as voting power. It will be mainly used to vote for Beefy and strategic partner's gauges, and can be governed by Beefy DAO to vote for various incentives on gauges. The voting function is a simple call to SpiritSwap's Gauge Proxy contract which records the votes and decides the distribution of gauge incentives. The Beefy keeper can split the voting power between multiple gauges in a single call using the parameter arrays.
1
// vote on boosted farms
2
function vote(address[] calldata _tokenVote, uint256[] calldata _weights) external onlyManager {
3
gaugeProxy.vote(_tokenVote, _weights);
4
emit Vote(_tokenVote, _weights);
5
}
Copied!

Pass through tokens between strategies and gauges

Strategies for Beefy's SpiritSwap vaults must pass through their deposits, withdrawals and harvests to and from the Gauge Staker. Only a whitelisted strategy can interact with the Gauge Staker, and each gauge is assigned at most one strategy.
Deposits and withdrawals pass through the exact amount that is requested (_amount) in the token that is assigned to the gauge (_underlying). Harvests (claimGaugeReward()) pass through only the SPIRIT (want) reward that's received by the Gauge Staker when claiming the reward, ignoring the existing balance on the Gauge Staker. None of the funds are kept on the Gauge Staker, they are always passed across in the same transaction.
1
// pass through a deposit to a gauge
2
function deposit(address _gauge, uint256 _amount) external onlyWhitelist(_gauge) {
3
address _underlying = IGauge(_gauge).TOKEN();
4
IERC20Upgradeable(_underlying).safeTransferFrom(msg.sender, address(this), _amount);
5
IGauge(_gauge).deposit(_amount);
6
}
7
8
// pass through a withdrawal from a gauge
9
function withdraw(address _gauge, uint256 _amount) external onlyWhitelist(_gauge) {
10
address _underlying = IGauge(_gauge).TOKEN();
11
IGauge(_gauge).withdraw(_amount);
12
IERC20Upgradeable(_underlying).safeTransfer(msg.sender, _amount);
13
}
14
15
// pass through rewards from a gauge
16
function claimGaugeReward(address _gauge) external onlyWhitelist(_gauge) {
17
uint256 _before = balanceOfWant();
18
IGauge(_gauge).getReward();
19
uint256 _balance = balanceOfWant().sub(_before);
20
want.safeTransfer(msg.sender, _balance);
21
}
Copied!

Claim SpiritSwap protocol fees

Holding inSPIRIT gives the Gauge Staker the right to claim a portion of SpiritSwap's protocol fees, which will be distributed to binSPIRIT stakers in a reward pool. The protocol fees are distributed once a week in the form of SPIRIT and need to be claimed from the Fee Distributor contract. The Reward Pool contract will call the claim function via claimVeWantReward(). Only the SPIRIT (want) reward is immediately passed back to the Reward Pool, if there is anything available to claim.
1
// pass through rewards from the fee distributor
2
function claimVeWantReward() external onlyRewardPool {
3
uint256 _before = balanceOfWant();
4
feeDistributor.claim();
5
uint256 _balance = balanceOfWant().sub(_before);
6
want.safeTransfer(msg.sender, _balance);
7
}
Copied!

Whitelisting strategies

The Beefy Keeper can whitelist a strategy address as long as there isn't an active strategy that has funds deployed in the same gauge as the new strategy. An old strategy must be panicked before a new strategy for the same gauge can be tested, so user funds are always protected. The approval for the token (_want) assigned to the gauge is reset and increased to the max limit for spending by the gauge. The gauge is mapped to the whitelisted strategy and the strategy is allowed access to the Gauge Staker for the specified gauge.
1
// whitelists a strategy address to interact with the Gauge Staker and gives approvals
2
function whitelistStrategy(address _strategy) external onlyManager {
3
IERC20Upgradeable _want = IGaugeStrategy(_strategy).want();
4
address _gauge = IGaugeStrategy(_strategy).gauge();
5
require(IGauge(_gauge).balanceOf(address(this)) == 0, '!inactive');
6
_want.safeApprove(_gauge, 0);
7
_want.safeApprove(_gauge, type(uint256).max);
8
whitelistedStrategy[_gauge] = _strategy;
9
}
Copied!

Upgrading strategies

A new strategy for a gauge with an existing strategy can be proposed once it has been fully tested. proposeStrategy() should be called on the Gauge Staker before upgradeStrat() on the vault so the switch will succeed. The new strategy must have the same gauge as the previous strategy. upgradeStrategy() is only called in retireStrat() on the previous strategy, so is controlled indirectly by the vault owner through upgrading the strategy address on the vault.
1
// prepare a strategy to be retired and replaced with another
2
function proposeStrategy(address _oldStrategy, address _newStrategy) external onlyManager {
3
require(IGaugeStrategy(_oldStrategy).gauge() == IGaugeStrategy(_newStrategy).gauge(), '!gauge');
4
replacementStrategy[_oldStrategy] = _newStrategy;
5
}
6
7
// switch over whitelist from one strategy to another for a gauge
8
function upgradeStrategy(address _gauge) external onlyWhitelist(_gauge) {
9
whitelistedStrategy[_gauge] = replacementStrategy[msg.sender];
10
}
Copied!

Contracts

Critical functions are always managed via multi-sig transactions and timelocks. No funds are stored directly on the Gauge Staker and only the active whitelisted strategy can interact with funds stored in a gauge.