Contract Source Code:
File 1 of 1 : Vault
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
abstract contract ReentrancyGuard {
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
receive() external payable {}
fallback() external payable {}
function _nonReentrantBefore() private {
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
_status = ENTERED;
}
function _nonReentrantAfter() private {
_status = NOT_ENTERED;
}
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
contract Vault is ReentrancyGuard {
ISnakePool public pool;
IRouter public router;
IOracle public gsnakeOracle;
IERC20 public token0;
IERC20 public token1;
IERC20 public stakeToken;
IERC20 public rewardToken;
IERC20 public wrappedSonic;
address private treasury;
address private creatorTreasury;
address private owner;
bool public active = true;
bool public emergencyWithdrawn = false;
uint256 public depositFee = 2;
uint256 public vaultFee;
uint256 public creatorFee;
uint256 private lpPerTicket;
uint256 public totalTickets;
uint256 public compoundedAt;
mapping(address => uint256) public userTickets; // Number of tickets per user
event Deposit(address indexed user, uint256 amount, uint256 tickets);
event Withdraw(address indexed user, uint256 amount);
event Compound(uint256 rewardAmount, uint256 lpAmount);
event ActiveStatusChanged(bool newStatus);
constructor(
address _treasury,
uint256 _vaultFee,
address _creatorTreasury,
uint256 _creatorFee,
address _pool,
address _router,
address _gsnakeOracle,
IERC20 _stakeToken,
IERC20 _rewardToken,
IERC20 _token0,
IERC20 _token1
) {
treasury = _treasury;
vaultFee = _vaultFee;
creatorTreasury = _creatorTreasury;
creatorFee = _creatorFee;
pool = ISnakePool(_pool);
router = IRouter(_router);
gsnakeOracle = IOracle(_gsnakeOracle);
stakeToken = _stakeToken;
rewardToken = _rewardToken;
owner = msg.sender;
lpPerTicket = 1e18;
token0 = _token0;
token1 = _token1;
wrappedSonic = IERC20(0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38);
require(_vaultFee + _creatorFee <= 100, "Invalid fee distribution");
rewardToken.approve(address(router), type(uint256).max);
token0.approve(address(router), type(uint256).max);
token1.approve(address(router), type(uint256).max);
stakeToken.approve(address(pool), type(uint256).max);
}
modifier whenActive() {
require(active, "Vault is not active");
_;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_;
}
function toggleActive() external onlyOwner {
active = !active;
emit ActiveStatusChanged(active);
}
function _swapRewardToSonic(uint256 rewardAmountIn) internal returns (uint256 sonicReceived) {
IRouter.route[] memory path = new IRouter.route[](1);
path[0] = IRouter.route(address(rewardToken), address(wrappedSonic), false);
uint[] memory amounts = router.swapExactTokensForTokens(
rewardAmountIn,
1,
path,
address(this),
block.timestamp
);
uint256 wrappedSonicReceived = amounts[amounts.length - 1];
IWETH(address(wrappedSonic)).withdraw(wrappedSonicReceived);
sonicReceived = wrappedSonicReceived;
}
function _harvest() internal returns (uint256) {
require(address(this).balance > 0, "No funds to harvest");
require(pool.pendingShareAndPendingRewards(0, address(this)) > pool.minClaimThreshold(), "No rewards to harvest");
uint256 balanceBefore = address(this).balance;
pool.harvest{value: balanceBefore}(0);
uint256 balanceAfter = address(this).balance;
return balanceBefore - balanceAfter;
}
function _getRewards() internal returns (uint256 feePaid, uint256 rewards) {
uint256 amountSonicToPay = _harvest();
feePaid = amountSonicToPay;
rewards = rewardToken.balanceOf(address(this));
}
function _swapToken0ForToken1(uint256 amount) internal returns (uint256 token1Received) {
require(amount > 0, "No token0 to swap");
IRouter.route[] memory path = new IRouter.route[](1);
path[0] = IRouter.route(address(token0), address(token1), true);
router.swapExactTokensForTokens(
amount,
1,
path,
address(this),
block.timestamp
);
token1Received = token1.balanceOf(address(this));
}
function _swapToken1ForToken0(uint256 amount) internal returns (uint256 token0Received) {
require(amount > 0, "No token1 to swap");
IRouter.route[] memory path = new IRouter.route[](1);
path[0] = IRouter.route(address(token1), address(token0), true);
router.swapExactTokensForTokens(
amount,
1,
path,
address(this),
block.timestamp
);
token0Received = token0.balanceOf(address(this));
}
function _swapLPToToken0(uint256 rewardAmount) internal returns (uint256) {
require(rewardAmount > 0, "No token to swap");
IRouter.route[] memory path = new IRouter.route[](1);
path[0] = IRouter.route(address(rewardToken), address(token0), false);
router.swapExactTokensForTokens(
rewardAmount,
1,
path,
address(this),
block.timestamp
);
return token0.balanceOf(address(this));
}
function _swapLPToToken1(uint256 rewardAmount) internal returns (uint256) {
require(rewardAmount > 0, "No token to swap");
IRouter.route[] memory path = new IRouter.route[](2);
path[0] = IRouter.route(address(rewardToken), address(token0), false);
path[1] = IRouter.route(address(token0), address(token1), true);
router.swapExactTokensForTokens(
rewardAmount,
1,
path,
address(this),
block.timestamp
);
return token1.balanceOf(address(this));
}
function _addLiquidity() internal returns (uint256) {
uint256 token0Balance = IERC20(token0).balanceOf(address(this));
uint256 token1Balance = IERC20(token1).balanceOf(address(this));
// add liquidity
require(token0Balance > 0, "Insufficient balance for Token0 to provide liquidity");
require(token1Balance > 0, "Insufficient balance for Token1 to provide liquidity");
router.addLiquidity(
address(token0),
address(token1),
true,
token0Balance,
token1Balance,
1,
1,
address(this),
block.timestamp
);
return IERC20(stakeToken).balanceOf(address(this));
}
function _recoverSonicFee(uint256 feePaid) internal returns (uint256, uint256) {
uint256 requiredSonic = (feePaid * 150) / 100;
IRouter.route[] memory path = new IRouter.route[](1);
path[0] = IRouter.route(address(rewardToken), address(wrappedSonic), false);
uint256[] memory amountsIn = router.getAmountsIn(requiredSonic, path);
uint256 rewardTokensForSonic = amountsIn[0];
uint256 sonicReceived = _swapRewardToSonic(rewardTokensForSonic);
require(sonicReceived >= requiredSonic, "Insufficient sonic received from swap");
return (sonicReceived, rewardTokensForSonic);
}
function addLeftoverLiquidity() external onlyOwner nonReentrant {
uint256 token0Bal = token0.balanceOf(address(this));
uint256 token1Bal = token1.balanceOf(address(this));
require(token0Bal > 0 && token1Bal > 0, "No leftovers to add liquidity");
(uint256 optimalToken0, uint256 optimalToken1, ) = router.quoteAddLiquidity(
address(token0),
address(token1),
true,
token0Bal,
token1Bal
);
if (token0Bal > optimalToken0) {
uint256 excessToken0 = token0Bal - optimalToken0;
_swapToken0ForToken1(excessToken0);
} else if (token1Bal > optimalToken1) {
uint256 excessToken1 = token1Bal - optimalToken1;
_swapToken1ForToken0(excessToken1);
}
uint256 stakeAmount = _addLiquidity();
require(stakeAmount > 0, "No LP tokens received");
_innerDeposit();
}
function _updateVault() internal returns (uint256) {
uint256 totalLp = totalDeposits();
uint256 netRewardTokens = 0;
if (totalTickets > 0 && totalLp > 0) {
uint256 rewardAmount = 0;
uint256 feePaid = 0;
if (pool.pendingShareAndPendingRewards(0, address(this)) > pool.minClaimThreshold()) {
(feePaid, rewardAmount) = _getRewards();
}
if (rewardAmount > 0) {
(, uint256 rewardTokensForSonic) = _recoverSonicFee(feePaid);
uint256 remainingReward = rewardAmount - rewardTokensForSonic;
uint256 compoundAmount = (remainingReward * vaultFee) / 1000;
uint256 creatorAmount = (remainingReward * creatorFee) / 1000;
rewardToken.transfer(creatorTreasury, creatorAmount);
rewardToken.transfer(treasury, compoundAmount);
remainingReward = remainingReward - (compoundAmount + creatorAmount);
(uint112 reserve0, uint112 reserve1, ) = IPair(address(stakeToken)).getReserves();
uint256 rewardForToken0 = (remainingReward * uint256(reserve0)) / (uint256(reserve0) + uint256(reserve1));
uint256 rewardForToken1 = remainingReward - rewardForToken0;
uint256 token0Amount = _swapLPToToken0(rewardForToken0);
uint256 token1Amount = _swapLPToToken1(rewardForToken1);
(uint256 optimalToken0, uint256 optimalToken1, ) = router.quoteAddLiquidity(
address(token0),
address(token1),
true,
token0Amount,
token1Amount
);
if (token0Amount > optimalToken0) {
uint256 excessToken0 = token0Amount - optimalToken0;
_swapToken0ForToken1(excessToken0);
} else if (token1Amount > optimalToken1) {
uint256 excessToken1 = token1Amount - optimalToken1;
_swapToken1ForToken0(excessToken1);
}
uint256 stakeAmount = _addLiquidity();
require(stakeAmount > 0, "No LP tokens received");
_innerDeposit();
compoundedAt = block.timestamp;
netRewardTokens = remainingReward;
}
}
return netRewardTokens;
}
function _innerDeposit() internal returns (uint256) {
uint256 lpAmount = stakeToken.balanceOf(address(this));
if (lpAmount > 0) {
pool.deposit(0, lpAmount);
uint256 totalDeps = totalDeposits();
lpPerTicket = (totalDeps * 1e18) / totalTickets;
}
return lpPerTicket;
}
function _withdraw(uint256 amount) internal {
if (totalTickets != 0) {
_updateVault();
}
uint256 tickets = userTickets[msg.sender];
require(tickets > 0, "No tickets to withdraw");
uint256 userLp = (tickets * lpPerTicket) / 1e18;
require(userLp >= amount, "Insufficient balance");
uint256 withdrawTickets = (amount * 1e18) / lpPerTicket;
userTickets[msg.sender] -= withdrawTickets;
totalTickets -= withdrawTickets;
pool.withdraw(0, amount);
stakeToken.transfer(msg.sender, amount);
emit Withdraw(msg.sender, amount);
}
function deposit(uint256 amount) external nonReentrant whenActive {
if (totalTickets != 0) {
_updateVault();
}
stakeToken.transferFrom(msg.sender, address(this), amount);
uint256 fee = (amount * depositFee) / 1000;
stakeToken.transfer(treasury, fee);
amount -= fee;
(uint256 amountBefore,) = pool.userInfo(0, address(this));
pool.deposit(0, amount);
(uint256 amountAfter ,) = pool.userInfo(0, address(this));
uint256 depositedAmount = amountAfter - amountBefore;
uint256 tickets = (depositedAmount * 1e18) / lpPerTicket;
userTickets[msg.sender] += tickets;
totalTickets += tickets;
emit Deposit(msg.sender, depositedAmount, tickets);
}
function withdraw(uint256 amount) external nonReentrant {
require(!emergencyWithdrawn, "Use userEmergencyWithdraw instead");
_withdraw(amount);
}
function withdrawAll() external nonReentrant {
require(!emergencyWithdrawn, "Use userEmergencyWithdraw instead");
uint256 tickets = userTickets[msg.sender];
uint256 userLp = (tickets * lpPerTicket) / 1e18;
_withdraw(userLp);
}
function userEmergencyWithdraw() external nonReentrant {
require(emergencyWithdrawn, "Emergency withdraw not enabled");
uint256 tickets = userTickets[msg.sender];
if (tickets == 0) return;
uint256 userLp = (tickets * lpPerTicket) / 1e18;
userTickets[msg.sender] = 0;
totalTickets -= tickets;
stakeToken.transfer(msg.sender, userLp);
emit Withdraw(msg.sender, userLp);
}
function emergencyWithdraw() external onlyOwner nonReentrant {
pool.emergencyWithdraw(0);
emergencyWithdrawn = true;
active = false;
emit ActiveStatusChanged(active);
}
function compound() external whenActive {
uint256 rewards = _updateVault();
require(rewards > 0, "No rewards to compound");
emit Compound(rewardToken.balanceOf(address(this)), rewards);
}
function totalDeposits() public view returns (uint256) {
(uint256 amount,) = pool.userInfo(0, address(this));
return amount;
}
function lpForUser(address _user) external view returns (uint256) {
uint256 tickets = userTickets[_user];
if (tickets == 0) return 0;
uint256 _lpPerTicket = lpPerTicket;
uint256 totalLp = totalDeposits();
if (totalTickets > 0 && totalLp > 0) {
uint256 rewardAmount = rewardToken.balanceOf(address(this));
if (rewardAmount > 0) {
_lpPerTicket += (rewardAmount * 1e18) / totalTickets;
}
}
return (tickets * _lpPerTicket) / 1e18;
}
function addFunds() external payable onlyOwner {
require(msg.value > 0, "No funds sent");
}
function takeFunds(uint256 amount) external onlyOwner nonReentrant {
require(address(this).balance >= amount, "Insufficient balance");
(bool sent, ) = payable(owner).call{value: amount}("");
require(sent, "Transfer failed");
}
function takeAllFunds() external onlyOwner nonReentrant {
uint256 balance = address(this).balance;
require(balance > 0, "No funds available");
(bool sent, ) = payable(owner).call{value: balance}("");
require(sent, "Transfer failed");
}
}
interface ISnakePool {
function deposit(uint _pid, uint _amount) external;
function withdraw(uint _pid, uint _amount) external;
function emergencyWithdraw(uint _pid) external;
function harvest(uint _pid) payable external;
function harvestAll() payable external;
function userInfo(uint256 _pid, address _user) external view returns (uint256 amount, uint256 rewardDebt);
function poolInfo(uint256 _pid) external view returns (ISnakePool.PoolInfo memory);
function pendingRewards(uint256 _pid, address _user) external view returns (uint256);
function pendingShareAndPendingRewards(uint _pid, address _user) external view returns (uint256);
function minClaimThreshold() external view returns (uint256);
function pegStabilityModuleFeeEnabled() external view returns (bool);
function pegStabilityModuleFee() external view returns (uint256);
enum GaugeDex {
NONE,
SHADOW,
SWAPX
}
struct GaugeInfo {
bool isGauge; // If this is a gauge
address gauge; // The gauge
GaugeDex gaugeDex; // Dex of the gauge
}
struct PoolInfo {
IERC20 token; // Address of LP token contract.
uint256 depFee; // deposit fee that is applied to created pool.
uint256 allocPoint; // How many allocation points assigned to this pool. GSNAKEs to distribute per block.
uint256 lastRewardTime; // Last time that GSNAKEs distribution occurs.
uint256 accGsnakePerShare; // Accumulated GSNAKEs per share, times 1e18. See below.
bool isStarted; // if lastRewardTime has passed
GaugeInfo gaugeInfo; // Gauge info (does this pool have a gauge and where is it)
uint256 poolGsnakePerSec; // rewards per second for pool (acts as allocPoint)
}
}
interface IOracle {
function update() external;
function consult(address _token, uint256 _amountIn) external view returns (uint256 amountOut);
function twap(address _token, uint256 _amountIn) external view returns (uint256 _amountOut);
}
interface IRouter {
struct route {
address from;
address to;
bool stable;
}
function addLiquidity(
address tokenA,
address tokenB,
bool stable,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (
uint amountA,
uint amountB,
uint liquidity
);
function getAmountsIn(
uint256 amountOut,
route[] memory routes
) external returns (
uint256[] memory amounts
);
function quoteAddLiquidity(
address tokenA,
address tokenB,
bool stable,
uint256 amountADesired,
uint256 amountBDesired
) external view returns (
uint256 amountA,
uint256 amountB,
uint256 liquidity
);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
route[] calldata routes,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
route[] memory routes,
address to,
uint256 deadline
) external;
}
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
interface IWETH is IERC20 {
function withdraw(uint256 wad) external;
}
interface IPair {
function getReserves()
external
view
returns (
uint112 _reserve0,
uint112 _reserve1,
uint32 _blockTimestampLast
);
}