Skip to content

Commit

Permalink
add WombatStrategy (#228)
Browse files Browse the repository at this point in the history
* add WombatStrategy

* update WombatStrategy

* add reservedWom property to WombatVoter
  • Loading branch information
midnight-commit authored Feb 15, 2024
1 parent 9a9eaaf commit 273a3e4
Show file tree
Hide file tree
Showing 9 changed files with 421 additions and 0 deletions.
200 changes: 200 additions & 0 deletions contracts/strategies/crosschain/wombat/WombatProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "./../../../interfaces/IERC20.sol";
import "./interfaces/IWombatVoter.sol";
import "./interfaces/IBoostedMasterWombat.sol";
import "./interfaces/IWombatWaddle.sol";

interface IWombatStrategy {
function pid() external view returns (uint256);
function masterWombat() external view returns (address);
}

library SafeProxy {
function safeExecute(IWombatVoter voter, address target, uint256 value, bytes memory data)
internal
returns (bytes memory)
{
(bool success, bytes memory returnValue) = voter.execute(target, value, data);
if (!success) revert("WombatProxy::safeExecute failed");
return returnValue;
}
}

contract WombatProxy {
using SafeProxy for IWombatVoter;

struct Reward {
address reward;
uint256 amount;
}

uint256 constant LOCK_DAYS = 1461;
uint256 internal constant BIPS_DIVISOR = 10000;

address public devAddr;
IWombatVoter public immutable voter;
IWombatWaddle public immutable wombatWaddle;
address immutable WOM;

// staking contract => pid => strategy
mapping(address => mapping(uint256 => address)) public approvedStrategies;
uint256 boostFeeBips;
uint256 minBoostAmount;

modifier onlyDev() {
require(msg.sender == devAddr, "WombatProxy::onlyDev");
_;
}

modifier onlyStrategy(address _stakingContract, uint256 _pid) {
require(approvedStrategies[_stakingContract][_pid] == msg.sender, "WombatProxy::onlyStrategy");
_;
}

constructor(
address _voter,
address _devAddr,
address _wombatWaddle,
uint256 _boostFeeBips,
uint256 _minBoostAmount
) {
require(_devAddr > address(0), "WombatProxy::Invalid dev address provided");
devAddr = _devAddr;
voter = IWombatVoter(_voter);
wombatWaddle = IWombatWaddle(_wombatWaddle);
WOM = wombatWaddle.wom();
boostFeeBips = _boostFeeBips;
minBoostAmount = _minBoostAmount;
}

/**
* @notice Update devAddr
* @param newValue address
*/
function updateDevAddr(address newValue) external onlyDev {
devAddr = newValue;
}

/**
* @notice Add an approved strategy
* @dev Very sensitive, restricted to devAddr
* @dev Can only be set once per PID and staking contract (reported by the strategy)
* @param _strategy address
*/
function approveStrategy(address _strategy) public onlyDev {
uint256 pid = IWombatStrategy(_strategy).pid();
address stakingContract = IWombatStrategy(_strategy).masterWombat();
require(approvedStrategies[stakingContract][pid] == address(0), "WombatProxy::Strategy for PID already added");
approvedStrategies[stakingContract][pid] = _strategy;
}

/**
* @notice Transfer from _amount WOM from sender and lock for veWOM
* @dev This contract needs approval to transfer _amount WOM before calling this method
* @param _amount WOM amount
*/
function boost(uint256 _amount) public onlyDev {
IERC20(WOM).transferFrom(msg.sender, address(voter), _amount);
_boost(_amount);
}

/**
* @notice Update additional/optional boost fee settins
* @param _boostFeeBips Boost fee bips, check BIPS_DIVISOR
* @param _minBoostAmount Minimum amount of WOM to create a new lock position
*/
function updateBoostFee(uint256 _boostFeeBips, uint256 _minBoostAmount) external onlyDev {
require(_boostFeeBips < BIPS_DIVISOR, "WombatProxy::Invalid boost fee");
boostFeeBips = _boostFeeBips;
minBoostAmount = _minBoostAmount;
}

function depositToStakingContract(address _masterWombat, uint256 _pid, address _token, uint256 _amount)
external
onlyStrategy(_masterWombat, _pid)
{
voter.safeExecute(_token, 0, abi.encodeWithSelector(IERC20.approve.selector, _masterWombat, _amount));
voter.safeExecute(
_masterWombat, 0, abi.encodeWithSelector(IBoostedMasterWombat.deposit.selector, _pid, _amount)
);
}

function withdrawFromStakingContract(address _masterWombat, uint256 _pid, address _token, uint256 _amount)
external
onlyStrategy(_masterWombat, _pid)
{
getRewards(_masterWombat, _pid);
voter.safeExecute(
_masterWombat, 0, abi.encodeWithSelector(IBoostedMasterWombat.withdraw.selector, _pid, _amount)
);
voter.safeExecute(_token, 0, abi.encodeWithSelector(IERC20.transfer.selector, msg.sender, _amount));
}

function pendingRewards(address _masterWombat, uint256 _pid) public view returns (Reward[] memory) {
(, address[] memory bonusTokenAddresses,, uint256[] memory pendingBonusRewards) =
IBoostedMasterWombat(_masterWombat).pendingTokens(_pid, address(voter));
Reward[] memory rewards = new Reward[](bonusTokenAddresses.length);
for (uint256 i = 0; i < bonusTokenAddresses.length; i++) {
uint256 boostFee = bonusTokenAddresses[i] == WOM ? _calculateBoostFee(pendingBonusRewards[i]) : 0;
rewards[i] = Reward({reward: bonusTokenAddresses[i], amount: pendingBonusRewards[i] - boostFee});
}
return rewards;
}

function getRewards(address _masterWombat, uint256 _pid) public onlyStrategy(_masterWombat, _pid) {
Reward[] memory rewards = pendingRewards(_masterWombat, _pid);
voter.safeExecute(_masterWombat, 0, abi.encodeWithSelector(IBoostedMasterWombat.deposit.selector, _pid, 0));
for (uint256 i; i < rewards.length; i++) {
uint256 reward = IERC20(rewards[i].reward).balanceOf(address(voter));

if (rewards[i].reward == WOM) {
uint256 reservedWom = voter.reservedWom();
reservedWom += _calculateBoostFee(reward - reservedWom);
reward -= reservedWom;
if (reservedWom > minBoostAmount) {
_boost(reservedWom);
reservedWom = 0;
}
voter.setReservedWom(reservedWom);
}

voter.safeExecute(
rewards[i].reward, 0, abi.encodeWithSelector(IERC20.transfer.selector, msg.sender, reward)
);
}
}

function _calculateBoostFee(uint256 _womAmount) internal view returns (uint256) {
return (_womAmount * boostFeeBips) / BIPS_DIVISOR;
}

function _boost(uint256 _amount) internal {
voter.safeExecute(
address(WOM), 0, abi.encodeWithSelector(IERC20.approve.selector, address(wombatWaddle), _amount)
);
voter.safeExecute(
address(wombatWaddle), 0, abi.encodeWithSelector(IWombatWaddle.mint.selector, _amount, LOCK_DAYS)
);
}

function totalDeposits(address _masterWombat, uint256 _pid) external view returns (uint256) {
(uint128 liquidity,,,) = IBoostedMasterWombat(_masterWombat).userInfo(_pid, address(voter));
return liquidity;
}

function emergencyWithdraw(address _masterWombat, uint256 _pid, address _token)
external
onlyStrategy(_masterWombat, _pid)
{
voter.safeExecute(
_masterWombat, 0, abi.encodeWithSelector(IBoostedMasterWombat.emergencyWithdraw.selector, _pid)
);
voter.safeExecute(
_token,
0,
abi.encodeWithSelector(IERC20.transfer.selector, msg.sender, IERC20(_token).balanceOf(address(voter)))
);
}
}
74 changes: 74 additions & 0 deletions contracts/strategies/crosschain/wombat/WombatStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "../../BaseStrategy.sol";
import "./interfaces/IBoostedMasterWombat.sol";
import "./interfaces/IWombatPool.sol";
import "./interfaces/IWombatAsset.sol";
import "./interfaces/IWombatProxy.sol";

contract WombatStrategy is BaseStrategy {
using SafeERC20 for IERC20;

IWombatPool public immutable wombatPool;
address public immutable masterWombat;
uint256 public immutable pid;
address public immutable underlyingToken;

IWombatProxy public proxy;

constructor(
address _wombatPool,
address _proxy,
BaseStrategySettings memory baseStrategySettings,
StrategySettings memory _strategySettings
) BaseStrategy(baseStrategySettings, _strategySettings) {
wombatPool = IWombatPool(_wombatPool);
masterWombat = wombatPool.masterWombat();
pid = IBoostedMasterWombat(masterWombat).getAssetPid(address(depositToken));
proxy = IWombatProxy(_proxy);
underlyingToken = IWombatAsset(address(depositToken)).underlyingToken();
}

function setProxy(address _proxy) external onlyDev {
proxy = IWombatProxy(_proxy);
}

function _depositToStakingContract(uint256 _amount, uint256) internal override {
depositToken.transfer(proxy.voter(), _amount);
proxy.depositToStakingContract(masterWombat, pid, address(depositToken), _amount);
}

function _withdrawFromStakingContract(uint256 _amount) internal override returns (uint256 _withdrawAmount) {
proxy.withdrawFromStakingContract(masterWombat, pid, address(depositToken), _amount);
return _amount;
}

function _emergencyWithdraw() internal override {
proxy.emergencyWithdraw(masterWombat, pid, address(depositToken));
}

function _pendingRewards() internal view override returns (Reward[] memory) {
return proxy.pendingRewards(masterWombat, pid);
}

function _getRewards() internal override {
proxy.getRewards(masterWombat, pid);
}

function _convertRewardTokenToDepositToken(uint256 _fromAmount) internal override returns (uint256) {
if (address(rewardToken) != underlyingToken) {
FormattedOffer memory offer = simpleRouter.query(_fromAmount, address(rewardToken), underlyingToken);
_fromAmount = _swap(offer);
}
if (_fromAmount > 0) {
IERC20(underlyingToken).approve(address(wombatPool), _fromAmount);
wombatPool.deposit(underlyingToken, _fromAmount, 0, address(proxy.voter()), block.timestamp, true);
}
return 0;
}

function totalDeposits() public view override returns (uint256) {
return proxy.totalDeposits(masterWombat, pid);
}
}
39 changes: 39 additions & 0 deletions contracts/strategies/crosschain/wombat/WombatVoter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "../../../lib/Ownable.sol";

import "./interfaces/IWombatVoter.sol";

contract WombatVoter is IWombatVoter, Ownable {
address public proxy;
uint256 public reservedWom;

modifier onlyProxy() {
require(msg.sender == proxy, "WombatVoter::onlyProxy");
_;
}

constructor(address _owner) {
transferOwnership(_owner);
}

function setProxy(address _proxy) external override onlyOwner {
proxy = _proxy;
}

function setReservedWom(uint256 _reservedWom) external override onlyProxy {
reservedWom = _reservedWom;
}

function execute(address target, uint256 value, bytes calldata data)
external
override
onlyProxy
returns (bool, bytes memory)
{
(bool success, bytes memory result) = target.call{value: value}(data);

return (success, result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

interface IBoostedMasterWombat {
function getAssetPid(address _asset) external view returns (uint256);

function deposit(uint256 _pid, uint256 _amount) external;

function withdraw(uint256 _pid, uint256 _amount) external;

function emergencyWithdraw(uint256 _pid) external;

function userInfo(uint256 _pid, address _user) external view returns (uint128, uint128, uint128, uint128);

function pendingTokens(uint256 _pid, address _user)
external
view
returns (
uint256 pendingRewards,
address[] memory bonusTokenAddresses,
string[] memory bonusTokenSymbols,
uint256[] memory pendingBonusRewards
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

interface IWombatAsset {
function pool() external view returns (address);
function underlyingToken() external view returns (address);
}
26 changes: 26 additions & 0 deletions contracts/strategies/crosschain/wombat/interfaces/IWombatPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

interface IWombatPool {
function addressOfAsset(address token) external view returns (address);

function masterWombat() external view returns (address);

function deposit(
address token,
uint256 amount,
uint256 minimumLiquidity,
address to,
uint256 deadline,
bool shouldStake
) external returns (uint256 liquidity);

function withdraw(address token, uint256 liquidity, uint256 minimumAmount, address to, uint256 deadline)
external
returns (uint256 amount);

function quotePotentialWithdraw(address token, uint256 liquidity)
external
view
returns (uint256 amount, uint256 fee);
}
15 changes: 15 additions & 0 deletions contracts/strategies/crosschain/wombat/interfaces/IWombatProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "./../../../BaseStrategy.sol";

interface IWombatProxy {
function voter() external view returns (address);
function depositToStakingContract(address _masterWombat, uint256 _pid, address _token, uint256 _amount) external;
function withdrawFromStakingContract(address _masterWombat, uint256 _pid, address _token, uint256 _amount)
external;
function emergencyWithdraw(address _masterWombat, uint256 _pid, address _token) external;
function pendingRewards(address _masterWombat, uint256 _pid) external view returns (BaseStrategy.Reward[] memory);
function getRewards(address _masterWombat, uint256 _pid) external;
function totalDeposits(address _masterWombat, uint256 _pid) external view returns (uint256);
}
Loading

0 comments on commit 273a3e4

Please sign in to comment.