// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.7.6;
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint size;
// solhint-disable-next-line no-inline-assembly
assembly {
size := extcodesize(account)
}
return size > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data)
internal
returns (bytes memory)
{
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint value
) internal returns (bytes memory) {
return
functionCallWithValue(
target,
data,
value,
"Address: low-level call with value failed"
);
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint value,
string memory errorMessage
) internal returns (bytes memory) {
require(
address(this).balance >= value,
"Address: insufficient balance for call"
);
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{value: value}(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data)
internal
view
returns (bytes memory)
{
return
functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data)
internal
returns (bytes memory)
{
return
functionDelegateCall(
target,
data,
"Address: low-level delegate call failed"
);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function _verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) private pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
interface BaseRewardPool {
function balanceOf(address _account) external view returns (uint);
function getReward(address _account, bool _claimExtras) external returns (bool);
function withdrawAndUnwrap(uint amount, bool claim) external returns (bool);
}
interface Booster {
function poolInfo(uint _pid)
external
view
returns (
address lptoken,
address token,
address gauge,
address crvRewards,
address stash,
bool shutdown
);
function deposit(
uint _pid,
uint _amount,
bool _stake
) external returns (bool);
function withdraw(uint _pid, uint _amount) external returns (bool);
}
interface DepositZapAlUsd3Crv {
// Curve.fi Factory USD Metapool v2
function add_liquidity(
address _pool,
uint[4] calldata _amounts,
uint _min_mint_amount
) external returns (uint);
function remove_liquidity_one_coin(
address _pool,
uint _burn_amount,
int128 _i,
uint _min_amount
) external returns (uint);
function calc_withdraw_one_coin(
address _pool,
uint _amount,
int128 _i
) external view returns (uint);
}
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint value);
}
interface IFundManager {
function token() external view returns (address);
function borrow(uint amount) external returns (uint);
function repay(uint amount) external returns (uint);
function report(uint gain, uint loss) external;
function getDebt(address strategy) external view returns (uint);
}
library SafeERC20 {
using SafeMath for uint;
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transfer.selector, to, value)
);
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
);
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(
token,
abi.encodeWithSelector(token.approve.selector, spender, value)
);
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint value
) internal {
uint newAllowance = token.allowance(address(this), spender).add(value);
_callOptionalReturn(
token,
abi.encodeWithSelector(token.approve.selector, spender, newAllowance)
);
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint value
) internal {
uint newAllowance = token.allowance(address(this), spender).sub(
value,
"SafeERC20: decreased allowance below zero"
);
_callOptionalReturn(
token,
abi.encodeWithSelector(token.approve.selector, spender, newAllowance)
);
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(
data,
"SafeERC20: low-level call failed"
);
if (returndata.length > 0) {
// Return data is optional
// solhint-disable-next-line max-line-length
require(
abi.decode(returndata, (bool)),
"SafeERC20: ERC20 operation did not succeed"
);
}
}
}
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint a, uint b) internal pure returns (bool, uint) {
uint c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint a, uint b) internal pure returns (bool, uint) {
if (b > a) return (false, 0);
return (true, a - b);
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint a, uint b) internal pure returns (bool, uint) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint a, uint b) internal pure returns (bool, uint) {
if (b == 0) return (false, 0);
return (true, a / b);
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint a, uint b) internal pure returns (bool, uint) {
if (b == 0) return (false, 0);
return (true, a % b);
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint a, uint b) internal pure returns (uint) {
uint c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint a, uint b) internal pure returns (uint) {
require(b <= a, "SafeMath: subtraction overflow");
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint a, uint b) internal pure returns (uint) {
if (a == 0) return 0;
uint c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint a, uint b) internal pure returns (uint) {
require(b > 0, "SafeMath: division by zero");
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint a, uint b) internal pure returns (uint) {
require(b > 0, "SafeMath: modulo by zero");
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(
uint a,
uint b,
string memory errorMessage
) internal pure returns (uint) {
require(b <= a, errorMessage);
return a - b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryDiv}.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(
uint a,
uint b,
string memory errorMessage
) internal pure returns (uint) {
require(b > 0, errorMessage);
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(
uint a,
uint b,
string memory errorMessage
) internal pure returns (uint) {
require(b > 0, errorMessage);
return a % b;
}
}
interface StableSwapAlUsd3Crv {
function coins(uint _i) external view returns (address);
function get_virtual_price() external view returns (uint);
}
abstract contract Strategy {
using SafeERC20 for IERC20;
using SafeMath for uint;
event SetNextTimeLock(address nextTimeLock);
event AcceptTimeLock(address timeLock);
event SetAdmin(address admin);
event Authorize(address addr, bool authorized);
event SetTreasury(address treasury);
event SetFundManager(address fundManager);
event Deposit(uint amount, uint borrowed);
event Repay(uint amount, uint repaid);
event Withdraw(uint amount, uint withdrawn, uint loss);
event ClaimRewards(uint profit);
event Skim(uint total, uint debt, uint profit);
event Report(uint gain, uint loss, uint free, uint total, uint debt);
// Privilege - time lock >= admin >= authorized addresses
address public timeLock;
address public nextTimeLock;
address public admin;
address public treasury; // Profit is sent to this address
// authorization other than time lock and admin
mapping(address => bool) public authorized;
IERC20 public immutable token;
IFundManager public fundManager;
// Performance fee sent to treasury
uint public perfFee = 1000;
uint private constant PERF_FEE_CAP = 2000; // Upper limit to performance fee
uint internal constant PERF_FEE_MAX = 10000;
bool public claimRewardsOnMigrate = true;
constructor(
address _token,
address _fundManager,
address _treasury
) {
// Don't allow accidentally sending perf fee to 0 address
require(_treasury != address(0), "treasury = 0 address");
timeLock = msg.sender;
admin = msg.sender;
treasury = _treasury;
require(
IFundManager(_fundManager).token() == _token,
"fund manager token != token"
);
fundManager = IFundManager(_fundManager);
token = IERC20(_token);
IERC20(_token).safeApprove(_fundManager, type(uint).max);
}
modifier onlyTimeLock() {
require(msg.sender == timeLock, "!time lock");
_;
}
modifier onlyTimeLockOrAdmin() {
require(msg.sender == timeLock || msg.sender == admin, "!auth");
_;
}
modifier onlyAuthorized() {
require(
msg.sender == timeLock || msg.sender == admin || authorized[msg.sender],
"!auth"
);
_;
}
modifier onlyFundManager() {
require(msg.sender == address(fundManager), "!fund manager");
_;
}
/*
@notice Set next time lock
@param _nextTimeLock Address of next time lock
@dev nextTimeLock can become timeLock by calling acceptTimeLock()
*/
function setNextTimeLock(address _nextTimeLock) external onlyTimeLock {
// Allow next time lock to be zero address (cancel next time lock)
nextTimeLock = _nextTimeLock;
emit SetNextTimeLock(_nextTimeLock);
}
/*
@notice Set timeLock to msg.sender
@dev msg.sender must be nextTimeLock
*/
function acceptTimeLock() external {
require(msg.sender == nextTimeLock, "!next time lock");
timeLock = msg.sender;
nextTimeLock = address(0);
emit AcceptTimeLock(msg.sender);
}
/*
@notice Set admin
@param _admin Address of admin
*/
function setAdmin(address _admin) external onlyTimeLockOrAdmin {
require(_admin != address(0), "admin = 0 address");
admin = _admin;
emit SetAdmin(_admin);
}
/*
@notice Set authorization
@param _addr Address to authorize
@param _authorized Boolean
*/
function authorize(address _addr, bool _authorized) external onlyTimeLockOrAdmin {
require(_addr != address(0), "addr = 0 address");
authorized[_addr] = _authorized;
emit Authorize(_addr, _authorized);
}
/*
@notice Set treasury
@param _treasury Address of treasury
*/
function setTreasury(address _treasury) external onlyTimeLockOrAdmin {
// Don't allow accidentally sending perf fee to 0 address
require(_treasury != address(0), "treasury = 0 address");
treasury = _treasury;
emit SetTreasury(_treasury);
}
/*
@notice Set performance fee
@param _fee Performance fee
*/
function setPerfFee(uint _fee) external onlyTimeLockOrAdmin {
require(_fee <= PERF_FEE_CAP, "fee > cap");
perfFee = _fee;
}
function setFundManager(address _fundManager) external onlyTimeLock {
if (address(fundManager) != address(0)) {
token.safeApprove(address(fundManager), 0);
}
require(
IFundManager(_fundManager).token() == address(token),
"new fund manager token != token"
);
fundManager = IFundManager(_fundManager);
token.safeApprove(_fundManager, type(uint).max);
emit SetFundManager(_fundManager);
}
/*
@notice Set `claimRewardsOnMigrate`. If `false` skip call to `claimRewards`
when `migrate` is called.
@param _claimRewards Boolean to call or skip call to `claimRewards`
*/
function setClaimRewardsOnMigrate(bool _claimRewards) external onlyTimeLockOrAdmin {
claimRewardsOnMigrate = _claimRewards;
}
/*
@notice Transfer funds from `_from` address. Used for migration.
@param _from Address to transfer token from
@param _amount Amount of token to transfer
*/
function transferTokenFrom(address _from, uint _amount) external onlyAuthorized {
token.safeTransferFrom(_from, address(this), _amount);
}
/*
@notice Returns approximate amount of token locked in this contract
@dev Output may vary depending on price pulled from external DeFi contracts
*/
function totalAssets() external view virtual returns (uint);
/*
@notice Deposit into strategy
@param _amount Amount of token to deposit from fund manager
@param _min Minimum amount borrowed
*/
function deposit(uint _amount, uint _min) external virtual;
/*
@notice Withdraw token from this contract
@dev Only callable by fund manager
@dev Returns current loss = debt to fund manager - total assets
*/
function withdraw(uint _amount) external virtual returns (uint);
/*
@notice Repay fund manager
@param _amount Amount of token to repay to fund manager
@param _min Minimum amount repaid
@dev Call report after this to report any loss
*/
function repay(uint _amount, uint _min) external virtual;
/*
@notice Claim any reward tokens, sell for token
@param _minProfit Minumum amount of token to gain from selling rewards
*/
function claimRewards(uint _minProfit) external virtual;
/*
@notice Free up any profit over debt
*/
function skim() external virtual;
/*
@notice Report gain or loss back to fund manager
@param _minTotal Minimum value of total assets.
Used to protect against price manipulation.
@param _maxTotal Maximum value of total assets Used
Used to protect against price manipulation.
*/
function report(uint _minTotal, uint _maxTotal) external virtual;
/*
@notice Claim rewards, skim and report
@param _minProfit Minumum amount of token to gain from selling rewards
@param _minTotal Minimum value of total assets.
Used to protect against price manipulation.
@param _maxTotal Maximum value of total assets Used
Used to protect against price manipulation.
*/
function harvest(
uint _minProfit,
uint _minTotal,
uint _maxTotal
) external virtual;
/*
@notice Migrate to new version of this strategy
@param _strategy Address of new strategy
@dev Only callable by fund manager
*/
function migrate(address _strategy) external virtual;
/*
@notice Transfer token accidentally sent here back to admin
@param _token Address of token to transfer
*/
function sweep(address _token) external virtual;
}
contract StrategyConvexAlUsd is Strategy {
using SafeERC20 for IERC20;
using SafeMath for uint;
// Uniswap and Sushiswap //
// UNISWAP = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
// SUSHISWAP = 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F;
address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
uint private constant NUM_REWARDS = 3;
// address of DEX (uniswap or sushiswap) to use for selling reward tokens
// CRV, CVX, ALCX
address[NUM_REWARDS] public dex;
address private constant CRV = 0xD533a949740bb3306d119CC777fa900bA034cd52;
address private constant CVX = 0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B;
address private constant ALCX = 0xdBdb4d16EdA451D0503b854CF79D55697F90c8DF;
// Solc 0.7 cannot create constant arrays
address[NUM_REWARDS] private REWARDS = [CRV, CVX, ALCX];
// Convex //
Booster private constant BOOSTER =
Booster(0xF403C135812408BFbE8713b5A23a04b3D48AAE31);
// pool id
uint private constant PID = 36;
BaseRewardPool private constant REWARD =
BaseRewardPool(0x02E2151D4F351881017ABdF2DD2b51150841d5B3);
bool public shouldClaimExtras = true;
// Curve //
// DepositZap alUSD + 3CRV
DepositZapAlUsd3Crv private constant ZAP =
DepositZapAlUsd3Crv(0xA79828DF1850E8a3A3064576f380D90aECDD3359);
// StableSwap alUSD + 3CRV (meta pool)
StableSwapAlUsd3Crv private constant CURVE_POOL =
StableSwapAlUsd3Crv(0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c);
// LP token for curve pool (same contract as CURVE_POOL)
IERC20 private constant CURVE_LP =
IERC20(0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c);
// prevent slippage from deposit / withdraw
uint public slip = 100;
uint private constant SLIP_MAX = 10000;
/*
0 - alUSD
1 - DAI
2 - USDC
3 - USDT
*/
// multipliers to normalize token decimals to 10 ** 18
uint[4] private MULS = [1, 1, 1e12, 1e12];
uint private immutable MUL; // multiplier of token
uint private immutable INDEX; // index of token
// DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F
// USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
// USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7
constructor(
address _token,
address _fundManager,
address _treasury,
uint _index
) Strategy(_token, _fundManager, _treasury) {
// disable alUSD
require(_index > 0, "index = 0");
INDEX = _index;
MUL = MULS[_index];
(address lptoken, , , address crvRewards, , ) = BOOSTER.poolInfo(PID);
require(address(CURVE_LP) == lptoken, "curve pool lp != pool info lp");
require(address(REWARD) == crvRewards, "reward != pool info reward");
IERC20(_token).safeApprove(address(ZAP), type(uint).max);
// deposit into BOOSTER
CURVE_LP.safeApprove(address(BOOSTER), type(uint).max);
// withdraw from ZAP
CURVE_LP.safeApprove(address(ZAP), type(uint).max);
_setDex(0, 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F); // CRV - sushiswap
_setDex(1, 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F); // CVX - sushiswap
_setDex(2, 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F); // ALCX - sushiswap
}
function _setDex(uint _i, address _dex) private {
IERC20 reward = IERC20(REWARDS[_i]);
// disallow previous dex
if (dex[_i] != address(0)) {
reward.safeApprove(dex[_i], 0);
}
dex[_i] = _dex;
// approve new dex
reward.safeApprove(_dex, type(uint).max);
}
function setDex(uint _i, address _dex) external onlyTimeLock {
require(_dex != address(0), "dex = 0 address");
_setDex(_i, _dex);
}
/*
@notice Set max slippage for deposit and withdraw from Curve pool
@param _slip Max amount of slippage allowed
*/
function setSlip(uint _slip) external onlyAuthorized {
require(_slip <= SLIP_MAX, "slip > max");
slip = _slip;
}
// @dev Claim extra rewards (ALCX) from Convex
function setShouldClaimExtras(bool _shouldClaimExtras) external onlyAuthorized {
shouldClaimExtras = _shouldClaimExtras;
}
function _totalAssets() private view returns (uint total) {
/*
s0 = shares in meta pool
p0 = price per share of meta pool
s1 = shares in base pool
p1 = price per share of base pool
a = amount of tokens (DAI, USDC, USDT)
s1 = s0 * p0
a = s1 * p1
a = s0 * p0 * p1
*/
// amount of Curve LP tokens in Convex
uint lpBal = REWARD.balanceOf(address(this));
// amount of alUSD or DAI, USDC, USDT converted from Curve LP
// BASE_POOL.get_virtual_price is included in CURVE_POOL.get_virtual_price
// so CURVE_POOL.get_virtual_price = p0 * p1
total = lpBal.mul(CURVE_POOL.get_virtual_price()) / (MUL * 1e18);
total = total.add(token.balanceOf(address(this)));
}
function totalAssets() external view override returns (uint) {
return _totalAssets();
}
function _deposit() private {
uint bal = token.balanceOf(address(this));
if (bal > 0) {
uint[4] memory amounts;
amounts[INDEX] = bal;
/*
shares = token amount * multiplier * 1e18 / price per share
*/
uint pricePerShare = CURVE_POOL.get_virtual_price();
uint shares = bal.mul(MUL).mul(1e18).div(pricePerShare);
uint min = shares.mul(SLIP_MAX - slip) / SLIP_MAX;
ZAP.add_liquidity(address(CURVE_POOL), amounts, min);
}
uint lpBal = CURVE_LP.balanceOf(address(this));
if (lpBal > 0) {
require(BOOSTER.deposit(PID, lpBal, true), "deposit failed");
}
}
function deposit(uint _amount, uint _min) external override onlyAuthorized {
require(_amount > 0, "deposit = 0");
uint borrowed = fundManager.borrow(_amount);
require(borrowed >= _min, "borrowed < min");
_deposit();
emit Deposit(_amount, borrowed);
}
function _calcSharesToWithdraw(
uint _amount,
uint _total,
uint _totalShares
) private pure returns (uint) {
/*
calculate shares to withdraw
a = amount of token to withdraw
T = total amount of token locked in external liquidity pool
s = shares to withdraw
P = total shares deposited into external liquidity pool
a / T = s / P
s = a / T * P
*/
if (_total > 0) {
// avoid rounding errors and cap shares to be <= total shares
if (_amount >= _total) {
return _totalShares;
}
return _amount.mul(_totalShares) / _total;
}
return 0;
}
function _withdraw(uint _amount) private returns (uint) {
uint bal = token.balanceOf(address(this));
if (_amount <= bal) {
return _amount;
}
uint total = _totalAssets();
if (_amount >= total) {
_amount = total;
}
uint need = _amount - bal;
uint totalShares = REWARD.balanceOf(address(this));
// total assets is always >= bal
uint shares = _calcSharesToWithdraw(need, total - bal, totalShares);
// withdraw from Convex
if (shares > 0) {
// true = claim CRV and ALCX
require(REWARD.withdrawAndUnwrap(shares, false), "reward withdraw failed");
}
// withdraw from Curve
uint lpBal = CURVE_LP.balanceOf(address(this));
if (shares > lpBal) {
shares = lpBal;
}
if (shares > 0) {
uint min = need.mul(SLIP_MAX - slip) / SLIP_MAX;
ZAP.remove_liquidity_one_coin(
address(CURVE_POOL),
shares,
int128(INDEX),
min
);
}
uint balAfter = token.balanceOf(address(this));
if (balAfter < _amount) {
return balAfter;
}
// balAfter >= _amount >= total
// requested to withdraw all so return balAfter
if (_amount >= total) {
return balAfter;
}
// requested withdraw < all
return _amount;
}
function withdraw(uint _amount)
external
override
onlyFundManager
returns (uint loss)
{
require(_amount > 0, "withdraw = 0");
// availabe <= _amount
uint available = _withdraw(_amount);
uint debt = fundManager.getDebt(address(this));
uint total = _totalAssets();
if (debt > total) {
loss = debt - total;
}
if (available > 0) {
token.safeTransfer(msg.sender, available);
}
emit Withdraw(_amount, available, loss);
}
function repay(uint _amount, uint _min) external override onlyAuthorized {
require(_amount > 0, "repay = 0");
// availabe <= _amount
uint available = _withdraw(_amount);
uint repaid = fundManager.repay(available);
require(repaid >= _min, "repaid < min");
emit Repay(_amount, repaid);
}
/*
@dev Uniswap fails with zero address so no check is necessary here
*/
function _swap(
address _dex,
address _tokenIn,
address _tokenOut,
uint _amount
) private {
// create dynamic array with 3 elements
address[] memory path = new address[](3);
path[0] = _tokenIn;
path[1] = WETH;
path[2] = _tokenOut;
UniswapV2Router(_dex).swapExactTokensForTokens(
_amount,
1,
path,
address(this),
block.timestamp
);
}
function _claimRewards(uint _minProfit) private {
// calculate profit = balance of token after - balance of token before
uint diff = token.balanceOf(address(this));
require(
REWARD.getReward(address(this), shouldClaimExtras),
"get reward failed"
);
for (uint i = 0; i < NUM_REWARDS; i++) {
uint rewardBal = IERC20(REWARDS[i]).balanceOf(address(this));
if (rewardBal > 0) {
_swap(dex[i], REWARDS[i], address(token), rewardBal);
}
}
diff = token.balanceOf(address(this)) - diff;
require(diff >= _minProfit, "profit < min");
// transfer performance fee to treasury
if (diff > 0) {
uint fee = diff.mul(perfFee) / PERF_FEE_MAX;
if (fee > 0) {
token.safeTransfer(treasury, fee);
diff = diff.sub(fee);
}
}
emit ClaimRewards(diff);
}
function claimRewards(uint _minProfit) external override onlyAuthorized {
_claimRewards(_minProfit);
}
function _skim() private {
uint total = _totalAssets();
uint debt = fundManager.getDebt(address(this));
require(total > debt, "total <= debt");
uint profit = total - debt;
// reassign to actual amount withdrawn
profit = _withdraw(profit);
emit Skim(total, debt, profit);
}
function skim() external override onlyAuthorized {
_skim();
}
function _report(uint _minTotal, uint _maxTotal) private {
uint total = _totalAssets();
require(total >= _minTotal, "total < min");
require(total <= _maxTotal, "total > max");
uint gain = 0;
uint loss = 0;
uint free = 0; // balance of token
uint debt = fundManager.getDebt(address(this));
if (total > debt) {
gain = total - debt;
free = token.balanceOf(address(this));
if (gain > free) {
gain = free;
}
} else {
loss = debt - total;
}
if (gain > 0 || loss > 0) {
fundManager.report(gain, loss);
}
emit Report(gain, loss, free, total, debt);
}
function report(uint _minTotal, uint _maxTotal) external override onlyAuthorized {
_report(_minTotal, _maxTotal);
}
function harvest(
uint _minProfit,
uint _minTotal,
uint _maxTotal
) external override onlyAuthorized {
_claimRewards(_minProfit);
_skim();
_report(_minTotal, _maxTotal);
}
function migrate(address _strategy) external override onlyFundManager {
Strategy strat = Strategy(_strategy);
require(address(strat.token()) == address(token), "strategy token != token");
require(
address(strat.fundManager()) == address(fundManager),
"strategy fund manager != fund manager"
);
if (claimRewardsOnMigrate) {
_claimRewards(1);
}
uint bal = _withdraw(type(uint).max);
token.safeApprove(_strategy, bal);
strat.transferTokenFrom(address(this), bal);
}
/*
@notice Transfer token accidentally sent here to admin
@param _token Address of token to transfer
*/
function sweep(address _token) external override onlyAuthorized {
require(_token != address(token), "protected token");
for (uint i = 0; i < NUM_REWARDS; i++) {
require(_token != REWARDS[i], "protected token");
}
IERC20(_token).safeTransfer(admin, IERC20(_token).balanceOf(address(this)));
}
}
interface UniswapV2Router {
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactTokensForETH(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
}
contract StrategyConvexAlUsdUsdt is StrategyConvexAlUsd {
constructor(address _fundManager, address _treasury)
StrategyConvexAlUsd(
0xdAC17F958D2ee523a2206206994597C13D831ec7,
_fundManager,
_treasury,
3
)
{}
}
{
"compilationTarget": {
"StrategyConvexAlUsdUsdt.sol": "StrategyConvexAlUsdUsdt"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_fundManager","type":"address"},{"internalType":"address","name":"_treasury","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"timeLock","type":"address"}],"name":"AcceptTimeLock","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"},{"indexed":false,"internalType":"bool","name":"authorized","type":"bool"}],"name":"Authorize","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"profit","type":"uint256"}],"name":"ClaimRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"borrowed","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"repaid","type":"uint256"}],"name":"Repay","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"gain","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"loss","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"free","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"total","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"debt","type":"uint256"}],"name":"Report","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"admin","type":"address"}],"name":"SetAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"fundManager","type":"address"}],"name":"SetFundManager","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"nextTimeLock","type":"address"}],"name":"SetNextTimeLock","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"treasury","type":"address"}],"name":"SetTreasury","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"total","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"debt","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"profit","type":"uint256"}],"name":"Skim","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"withdrawn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"loss","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"acceptTimeLock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"},{"internalType":"bool","name":"_authorized","type":"bool"}],"name":"authorize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"authorized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minProfit","type":"uint256"}],"name":"claimRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimRewardsOnMigrate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_min","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"dex","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fundManager","outputs":[{"internalType":"contract IFundManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minProfit","type":"uint256"},{"internalType":"uint256","name":"_minTotal","type":"uint256"},{"internalType":"uint256","name":"_maxTotal","type":"uint256"}],"name":"harvest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_strategy","type":"address"}],"name":"migrate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nextTimeLock","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"perfFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_min","type":"uint256"}],"name":"repay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minTotal","type":"uint256"},{"internalType":"uint256","name":"_maxTotal","type":"uint256"}],"name":"report","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_claimRewards","type":"bool"}],"name":"setClaimRewardsOnMigrate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_i","type":"uint256"},{"internalType":"address","name":"_dex","type":"address"}],"name":"setDex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_fundManager","type":"address"}],"name":"setFundManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_nextTimeLock","type":"address"}],"name":"setNextTimeLock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_fee","type":"uint256"}],"name":"setPerfFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_shouldClaimExtras","type":"bool"}],"name":"setShouldClaimExtras","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_slip","type":"uint256"}],"name":"setSlip","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_treasury","type":"address"}],"name":"setTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"shouldClaimExtras","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"skim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"slip","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"sweep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"timeLock","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"transferTokenFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"loss","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]