Skip to content
Open

Slp #83

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions contracts/interfaces/wrapped-assets/IWrappedAsset.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ pragma solidity 0.7.6;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
* @dev For upgradeable/clones contracts use inheritance from IWrappedAssetUpgradeable.
* @dev For dependencies in unit protocol use this interface
* @dev todo on update wsslp replace body with IWrappedAssetInternal
*/
interface IWrappedAsset is IERC20 /* IERC20WithOptional */ {

event Deposit(address indexed user, uint256 amount);
Expand Down
65 changes: 65 additions & 0 deletions contracts/interfaces/wrapped-assets/IWrappedAssetInternal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: bsl-1.1

/*
Copyright 2022 Unit Protocol: Artem Zakharov (az@unit.xyz).
*/
pragma solidity 0.7.6;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
* @dev wrapped assets methods/events. To inherit by IWrappedAsset* interfaces
*/
interface IWrappedAssetInternal {

event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
event PositionMoved(address indexed userFrom, address indexed userTo, uint256 amount);

event EmergencyWithdraw(address indexed user, uint256 amount);
event TokenWithdraw(address indexed user, address token, uint256 amount);

event FeeChanged(uint256 newFeePercent);
event FeeReceiverChanged(address newFeeReceiver);
event AllowedBoneLockerSelectorAdded(address boneLocker, bytes4 selector);
event AllowedBoneLockerSelectorRemoved(address boneLocker, bytes4 selector);

/**
* @notice Get underlying token
*/
function getUnderlyingToken() external view returns (IERC20);

/**
* @notice deposit underlying token and send wrapped token to user
* @dev Important! Only user or trusted contracts must be able to call this method
*/
function deposit(address _userAddr, uint256 _amount) external;

/**
* @notice get wrapped token and return underlying
* @dev Important! Only user or trusted contracts must be able to call this method
*/
function withdraw(address _userAddr, uint256 _amount) external;

/**
* @notice get pending reward amount for user if reward is supported
*/
function pendingReward(address _userAddr) external view returns (uint256);

/**
* @notice claim pending reward for user if reward is supported
*/
function claimReward(address _userAddr) external;

/**
* @notice Manually move position (or its part) to another user (for example in case of liquidation)
* @dev Important! Only trusted contracts must be able to call this method
*/
function movePosition(address _userAddrFrom, address _userAddrTo, uint256 _amount) external;

/**
* @dev function for checks that asset is unitprotocol wrapped asset.
* @dev For wrapped assets must return keccak256("UnitProtocolWrappedAsset")
*/
function isUnitProtocolWrappedAsset() external view returns (bytes32);
}
16 changes: 16 additions & 0 deletions contracts/interfaces/wrapped-assets/IWrappedAssetUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: bsl-1.1

/*
Copyright 2021 Unit Protocol: Artem Zakharov (az@unit.xyz).
*/
pragma solidity 0.7.6;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "./IWrappedAssetInternal.sol";

/**
* @dev for usage with upgradeable/clones contracts. Methods/events must be the same as in IWrappedAsset
* @dev For dependencies in unit protocol use IWrappedAsset
*/
interface IWrappedAssetUpgradeable is IERC20Upgradeable, IWrappedAssetInternal /* IERC20WithOptional */ {
}
23 changes: 23 additions & 0 deletions contracts/interfaces/wrapped-assets/sushi/IMasterChef.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: bsl-1.1

/*
Copyright 2021 Unit Protocol: Artem Zakharov (az@unit.xyz).
*/
pragma solidity 0.7.6;


import "./ISushiToken.sol";

interface IMasterChef {
function sushi() external view returns (ISushiToken);
function poolInfo(uint256) external view returns (IERC20, uint256, uint256, uint256);
function poolLength() external view returns (uint256);
function userInfo(uint256, address) external view returns (uint256, uint256);

function pendingSushi(uint256 _pid, address _user) external view returns (uint256);
function deposit(uint256 _pid, uint256 _amount) external;
function withdraw(uint256 _pid, uint256 _amount) external;

function emergencyWithdraw(uint256 _pid) external;

}
12 changes: 12 additions & 0 deletions contracts/interfaces/wrapped-assets/sushi/ISushiToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: bsl-1.1

/*
Copyright 2021 Unit Protocol: Artem Zakharov (az@unit.xyz).
*/
pragma solidity 0.7.6;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface ISushiToken is IERC20 {
function mint(address _to, uint256 _amount) external;
}
20 changes: 20 additions & 0 deletions contracts/interfaces/wrapped-assets/sushi/IWSLPFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: bsl-1.1

/*
Copyright 2022 Unit Protocol: Artem Zakharov (az@unit.xyz).
*/
pragma solidity 0.7.6;

interface IWSLPFactory {
struct FeeInfo {
address feeReceiver;
uint8 feePercent;
}

event FeeChanged(address feeReceiver, uint8 feePercent);
event WrappedLpDeployed(address wrappedLp, uint rewardDistributorPoolId);

function feeInfo() external view returns (address feeReceiver, uint8 feePercent);
function setFee(address _feeReceiver, uint8 _feePercent) external;
function deploy(uint256 _rewardDistributorPoolId) external returns (address wrappedLp);
}
82 changes: 82 additions & 0 deletions contracts/wrapped-assets/sushi/WSLPFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: bsl-1.1

/*
Copyright 2022 Unit Protocol: Artem Zakharov (az@unit.xyz).
*/
pragma solidity 0.7.6;

import "@openzeppelin/contracts/proxy/Clones.sol";

import "../../interfaces/wrapped-assets/sushi/IWSLPFactory.sol";
import "../../interfaces/wrapped-assets/sushi/IMasterChef.sol";
import "../../interfaces/IVault.sol";
import "../../interfaces/IVaultParameters.sol";
import "./WSLPUserProxy.sol";
import "./WrappedSushiSwapLp.sol";


/**
* @title WSLPFactory
**/
contract WSLPFactory is IWSLPFactory, Auth2 {

// these variables stored just for info
IMasterChef public immutable rewardDistributor;
IERC20 public immutable rewardToken;

address public immutable wrappedSushiSwapLpImplementation;
address public immutable userProxyImplementation;

FeeInfo public override feeInfo;

mapping(uint => address) public wrappedLpByPoolId;

constructor(
IVaultParameters _vaultParameters,
IMasterChef _rewardDistributor,
address _feeReceiver,
uint8 _feePercent
)
Auth2(address(_vaultParameters))
{
IERC20 rewardTokenInternal = _rewardDistributor.sushi();
rewardToken = rewardTokenInternal;

rewardDistributor = _rewardDistributor;

feeInfo = FeeInfo(_feeReceiver, _feePercent);

address userProxyImplementationInternal = address(new WSLPUserProxy(this, _rewardDistributor));
userProxyImplementation = userProxyImplementationInternal;

WrappedSushiSwapLp wrappedSushiSwapLpImplementationInternal = new WrappedSushiSwapLp(
_vaultParameters, _rewardDistributor, rewardTokenInternal, userProxyImplementationInternal
);
wrappedSushiSwapLpImplementation = address(wrappedSushiSwapLpImplementationInternal);


// initialize implementations just not to allow to do it by somebody else
(IERC20 lpToken,,,) = _rewardDistributor.poolInfo(0);
WSLPUserProxy(userProxyImplementationInternal).initialize(0, lpToken);

wrappedSushiSwapLpImplementationInternal.initialize(0);

}

function setFee(address _feeReceiver, uint8 _feePercent) public override onlyManager {
require(_feePercent <= 50, "Unit Protocol Wrapped Assets: INVALID_FEE");
feeInfo = FeeInfo(_feeReceiver, _feePercent);

emit FeeChanged(_feeReceiver, _feePercent);
}

function deploy(uint256 _rewardDistributorPoolId) public override onlyManager returns (address wrappedLp) {
require(wrappedLpByPoolId[_rewardDistributorPoolId] == address(0), "Unit Protocol Wrapped Assets: ALREADY_DEPLOYED");

wrappedLp = Clones.clone(wrappedSushiSwapLpImplementation);
WrappedSushiSwapLp(wrappedLp).initialize(_rewardDistributorPoolId);

wrappedLpByPoolId[_rewardDistributorPoolId] = wrappedLp;
emit WrappedLpDeployed(wrappedLp, _rewardDistributorPoolId);
}
}
120 changes: 120 additions & 0 deletions contracts/wrapped-assets/sushi/WSLPUserProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: bsl-1.1

/*
Copyright 2022 Unit Protocol: Artem Zakharov (az@unit.xyz).
*/
pragma solidity 0.7.6;

import "@openzeppelin/contracts/math/SafeMath.sol"; // have to use OZ safemath since it is used in WSLP

import "../../interfaces/wrapped-assets/sushi/IMasterChef.sol";
import "../../interfaces/wrapped-assets/sushi/IWSLPFactory.sol";
import "../../helpers/TransferHelper.sol";

/**
* @title WSLPUserProxy
**/
contract WSLPUserProxy {
using SafeMath for uint256;

IWSLPFactory immutable factory;

IMasterChef public immutable rewardDistributor;
IERC20 public immutable rewardToken;

// to store in one slot rewardDistributorPoolId was reduced to uint96. 7*10^28 pools are quite enough
address public manager;
uint96 public rewardDistributorPoolId;

modifier onlyManager() {
require(msg.sender == manager, "Unit Protocol Wrapped Assets: AUTH_FAILED");
_;
}

constructor(IWSLPFactory _factory, IMasterChef _rewardDistributor) {
factory = _factory;
rewardDistributor = _rewardDistributor;

rewardToken = _rewardDistributor.sushi();
}

function initialize(uint96 _rewardDistributorPoolId, IERC20 _lpToken) public {
require(manager == address(0), "Unit Protocol Wrapped Assets: ALREADY_INITIALIZED");

manager = msg.sender;
rewardDistributorPoolId = _rewardDistributorPoolId;

TransferHelper.safeApprove(address(_lpToken), address(rewardDistributor), type(uint256).max);
}

/**
* @dev in case of change lp
*/
function approveLpToRewardDistributor(IERC20 _lpToken) public onlyManager {
TransferHelper.safeApprove(address(_lpToken), address(rewardDistributor), type(uint256).max);
}

function deposit(uint256 _amount) public onlyManager {
rewardDistributor.deposit(rewardDistributorPoolId, _amount);
}

function withdraw(IERC20 _lpToken, uint256 _amount, address _sentTokensTo) public onlyManager {
rewardDistributor.withdraw(rewardDistributorPoolId, _amount);
TransferHelper.safeTransfer(address(_lpToken), _sentTokensTo, _amount);
}

function pendingReward() public view returns (uint) {
uint balance = rewardToken.balanceOf(address(this));
uint pending = rewardDistributor.pendingSushi(rewardDistributorPoolId, address(this));

(uint amountWithoutFee,,) = _calcFee(balance.add(pending));
return amountWithoutFee;
}

function claimReward(address _user) public onlyManager {
rewardDistributor.deposit(rewardDistributorPoolId, 0); // get current reward (no separate methods)

_sendAllRewardTokensToUser(_user);
}

function _calcFee(uint _amount) internal view returns (uint amountWithoutFee, uint fee, address feeReceiver) {
(address _feeReceiver, uint8 _feePercent) = factory.feeInfo();
if (_feePercent == 0 || _feeReceiver == address(0)) {
return (_amount, 0, address(0));
}

fee = _amount.mul(_feePercent).div(100);
return (_amount.sub(fee), fee, _feeReceiver);
}

function _sendAllRewardTokensToUser(address _user) internal {
uint balance = rewardToken.balanceOf(address(this));

_sendRewardTokensToUser(_user, balance);
}

function _sendRewardTokensToUser(address _user, uint _amount) internal {
(uint amountWithoutFee, uint fee, address feeReceiver) = _calcFee(_amount);

if (fee > 0) {
TransferHelper.safeTransfer(address(rewardToken), feeReceiver, fee);
}
TransferHelper.safeTransfer(address(rewardToken), _user, amountWithoutFee);
}

function emergencyWithdraw() public onlyManager {
rewardDistributor.emergencyWithdraw(rewardDistributorPoolId);
}

function withdrawToken(address _token, address _user, uint _amount) public onlyManager {
if (_token == address(rewardToken)) {
_sendRewardTokensToUser(_user, _amount);
} else {
TransferHelper.safeTransfer(_token, _user, _amount);
}
}

function getDepositedAmount() public view returns (uint amount) {
(amount, ) = rewardDistributor.userInfo(rewardDistributorPoolId, address (this));
}
}
Loading