// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)pragmasolidity 0.8.13;/**
* @dev Collection of functions related to the address type
*/libraryAddress{
/**
* @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
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/functionisContract(address account) internalviewreturns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0// for contracts in construction, since the code is only stored at the end// of the constructor execution.return account.code.length>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].
*/functionsendValue(addresspayable recipient, uint256 amount) internal{
require(address(this).balance>= amount, "Address: insufficient balance");
(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._
*/functionfunctionCall(address target, bytesmemory data) internalreturns (bytesmemory) {
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._
*/functionfunctionCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
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._
*/functionfunctionCallWithValue(address target,
bytesmemory data,
uint256 value
) internalreturns (bytesmemory) {
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._
*/functionfunctionCallWithValue(address target,
bytesmemory data,
uint256 value,
stringmemory errorMessage
) internalreturns (bytesmemory) {
require(address(this).balance>= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytesmemory 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._
*/functionfunctionStaticCall(address target, bytesmemory data) internalviewreturns (bytesmemory) {
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._
*/functionfunctionStaticCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalviewreturns (bytesmemory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytesmemory 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._
*/functionfunctionDelegateCall(address target, bytesmemory data) internalreturns (bytesmemory) {
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._
*/functionfunctionDelegateCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytesmemory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/functionverifyCallResult(bool success,
bytesmemory returndata,
stringmemory errorMessage
) internalpurereturns (bytesmemory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if presentif (returndata.length>0) {
// The easiest way to bubble the revert reason is using memory via assemblyassembly {
let returndata_size :=mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
Contract Source Code
File 2 of 17: BaseStrategy.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"../YakStrategyV3.sol";
import"../interfaces/IWGAS.sol";
import"../lib/SafeERC20.sol";
import"./../interfaces/ISimpleRouter.sol";
/**
* @notice BaseStrategy
*/abstractcontractBaseStrategyisYakStrategyV3{
usingSafeERC20forIERC20;
IWGAS internalimmutable WGAS;
structBaseStrategySettings {
address gasToken;
address[] rewards;
address simpleRouter;
}
structReward {
address reward;
uint256 amount;
}
address[] public supportedRewards;
ISimpleRouter public simpleRouter;
eventAddReward(address rewardToken);
eventRemoveReward(address rewardToken);
eventUpdateRouter(address oldRouter, address newRouter);
constructor(BaseStrategySettings memory _settings, StrategySettings memory _strategySettings)
YakStrategyV3(_strategySettings)
{
WGAS = IWGAS(_settings.gasToken);
supportedRewards = _settings.rewards;
simpleRouter = ISimpleRouter(_settings.simpleRouter);
emit Reinvest(0, 0);
}
functionupdateRouter(address _router) publiconlyDev{
emit UpdateRouter(address(simpleRouter), _router);
simpleRouter = ISimpleRouter(_router);
}
functionaddReward(address _rewardToken) publiconlyDev{
bool found;
for (uint256 i =0; i < supportedRewards.length; i++) {
if (_rewardToken == supportedRewards[i]) {
found =true;
}
}
require(!found, "BaseStrategy::Reward already configured!");
supportedRewards.push(_rewardToken);
emit AddReward(_rewardToken);
}
functionremoveReward(address _rewardToken) publiconlyDev{
bool found;
for (uint256 i =0; i < supportedRewards.length; i++) {
if (_rewardToken == supportedRewards[i]) {
found =true;
supportedRewards[i] = supportedRewards[supportedRewards.length-1];
}
}
require(found, "BaseStrategy::Reward not configured!");
supportedRewards.pop();
emit RemoveReward(_rewardToken);
}
functiongetSupportedRewardsLength() publicviewreturns (uint256) {
return supportedRewards.length;
}
functioncalculateDepositFee(uint256 _amount) publicviewreturns (uint256) {
return _calculateDepositFee(_amount);
}
functioncalculateWithdrawFee(uint256 _amount) publicviewreturns (uint256) {
return _calculateWithdrawFee(_amount);
}
/**
* @notice Deposit tokens to receive receipt tokens
* @param _amount Amount of tokens to deposit
*/functiondeposit(uint256 _amount) externaloverride{
_deposit(msg.sender, _amount);
}
/**
* @notice Deposit using Permit
* @param _amount Amount of tokens to deposit
* @param _deadline The time at which to expire the signature
* @param _v The recovery byte of the signature
* @param _r Half of the ECDSA signature pair
* @param _s Half of the ECDSA signature pair
*/functiondepositWithPermit(uint256 _amount, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s)
externaloverride{
depositToken.permit(msg.sender, address(this), _amount, _deadline, _v, _r, _s);
_deposit(msg.sender, _amount);
}
functiondepositFor(address _account, uint256 _amount) externaloverride{
_deposit(_account, _amount);
}
function_deposit(address _account, uint256 _amount) internal{
require(DEPOSITS_ENABLED ==true, "BaseStrategy::Deposits disabled");
_reinvest(true);
require(
depositToken.transferFrom(msg.sender, address(this), _amount), "BaseStrategy::Deposit token transfer failed"
);
uint256 depositFee = _calculateDepositFee(_amount);
_mint(_account, getSharesForDepositTokens(_amount - depositFee));
_stakeDepositTokens(_amount, depositFee);
emit Deposit(_account, _amount);
}
/**
* @notice Deposit fee bips from underlying farm
*/function_getDepositFeeBips() internalviewvirtualreturns (uint256) {
return0;
}
/**
* @notice Calculate deposit fee of underlying farm
* @dev Override if deposit fee is calculated dynamically
*/function_calculateDepositFee(uint256 _amount) internalviewvirtualreturns (uint256) {
uint256 depositFeeBips = _getDepositFeeBips();
return (_amount * depositFeeBips) / _bip();
}
functionwithdraw(uint256 _amount) externaloverride{
uint256 depositTokenAmount = getDepositTokensForShares(_amount);
require(depositTokenAmount >0, "BaseStrategy::Withdraw amount too low");
uint256 withdrawAmount = _withdrawFromStakingContract(depositTokenAmount);
uint256 withdrawFee = _calculateWithdrawFee(depositTokenAmount);
depositToken.safeTransfer(msg.sender, withdrawAmount - withdrawFee);
_burn(msg.sender, _amount);
emit Withdraw(msg.sender, depositTokenAmount);
}
/**
* @notice Withdraw fee bips from underlying farm
* @dev Important: Do not override if withdraw fee is deducted from the amount returned by _withdrawFromStakingContract
*/function_getWithdrawFeeBips() internalviewvirtualreturns (uint256) {
return0;
}
/**
* @notice Calculate withdraw fee of underlying farm
* @dev Override if withdraw fee is calculated dynamically
* @dev Important: Do not override if withdraw fee is deducted from the amount returned by _withdrawFromStakingContract
*/function_calculateWithdrawFee(uint256 _amount) internalviewvirtualreturns (uint256) {
uint256 withdrawFeeBips = _getWithdrawFeeBips();
return (_amount * withdrawFeeBips) / _bip();
}
functionreinvest() externaloverrideonlyEOA{
_reinvest(false);
}
function_convertPoolRewardsToRewardToken() privatereturns (uint256) {
_getRewards();
uint256 rewardTokenAmount = rewardToken.balanceOf(address(this));
uint256 count = supportedRewards.length;
for (uint256 i =0; i < count; i++) {
address reward = supportedRewards[i];
if (reward ==address(WGAS)) {
uint256 balance =address(this).balance;
if (balance >0) {
WGAS.deposit{value: balance}();
}
if (address(rewardToken) ==address(WGAS)) {
rewardTokenAmount += balance;
continue;
}
}
uint256 amount = IERC20(reward).balanceOf(address(this));
if (amount >0&& reward !=address(rewardToken)) {
FormattedOffer memory offer = simpleRouter.query(amount, reward, address(rewardToken));
rewardTokenAmount += _swap(offer);
}
}
return rewardTokenAmount;
}
/**
* @notice Reinvest rewards from staking contract
* @param userDeposit Controls whether or not a gas refund is payed to msg.sender
*/function_reinvest(bool userDeposit) private{
uint256 amount = _convertPoolRewardsToRewardToken();
if (amount > MIN_TOKENS_TO_REINVEST) {
uint256 devFee = (amount * DEV_FEE_BIPS) / BIPS_DIVISOR;
if (devFee >0) {
rewardToken.safeTransfer(feeCollector, devFee);
}
uint256 reinvestFee = userDeposit ? 0 : (amount * REINVEST_REWARD_BIPS) / BIPS_DIVISOR;
if (reinvestFee >0) {
rewardToken.safeTransfer(msg.sender, reinvestFee);
}
uint256 depositTokenAmount = _convertRewardTokenToDepositToken(amount - devFee - reinvestFee);
if (depositTokenAmount >0) {
uint256 depositFee = _calculateDepositFee(depositTokenAmount);
_stakeDepositTokens(depositTokenAmount, depositFee);
}
}
emit Reinvest(totalDeposits(), totalSupply);
}
function_convertRewardTokenToDepositToken(uint256 _fromAmount) internalvirtualreturns (uint256 toAmount) {
if (address(rewardToken) ==address(depositToken)) return _fromAmount;
FormattedOffer memory offer = simpleRouter.query(_fromAmount, address(rewardToken), address(depositToken));
return _swap(offer);
}
function_stakeDepositTokens(uint256 _amount, uint256 _depositFee) private{
require(_amount >0, "BaseStrategy::Stake amount too low");
_depositToStakingContract(_amount, _depositFee);
}
function_swap(FormattedOffer memory _offer) internalreturns (uint256 amountOut) {
if (_offer.amounts.length>0&& _offer.amounts[_offer.amounts.length-1] >0) {
IERC20(_offer.path[0]).approve(address(simpleRouter), _offer.amounts[0]);
return simpleRouter.swap(_offer);
}
return0;
}
functioncheckReward() publicviewoverridereturns (uint256) {
Reward[] memory rewards = _pendingRewards();
uint256 estimatedTotalReward = rewardToken.balanceOf(address(this));
for (uint256 i =0; i < rewards.length; i++) {
address reward = rewards[i].reward;
if (reward ==address(WGAS)) {
rewards[i].amount +=address(this).balance;
}
if (reward ==address(rewardToken)) {
estimatedTotalReward += rewards[i].amount;
} elseif (reward >address(0)) {
uint256 balance = IERC20(reward).balanceOf(address(this));
uint256 amount = balance + rewards[i].amount;
if (amount >0) {
FormattedOffer memory offer = simpleRouter.query(amount, reward, address(rewardToken));
estimatedTotalReward += offer.amounts.length>1 ? offer.amounts[offer.amounts.length-1] : 0;
}
}
}
return estimatedTotalReward;
}
functionrescueDeployedFunds(uint256 _minReturnAmountAccepted) externaloverrideonlyOwner{
uint256 balanceBefore = depositToken.balanceOf(address(this));
_emergencyWithdraw();
uint256 balanceAfter = depositToken.balanceOf(address(this));
require(
balanceAfter - balanceBefore >= _minReturnAmountAccepted,
"BaseStrategy::Emergency withdraw minimum return amount not reached"
);
emit Reinvest(totalDeposits(), totalSupply);
if (DEPOSITS_ENABLED ==true) {
disableDeposits();
}
}
function_bip() internalviewvirtualreturns (uint256) {
return10000;
}
/* ABSTRACT */function_depositToStakingContract(uint256 _amount, uint256 _depositFee) internalvirtual;
function_withdrawFromStakingContract(uint256 _amount) internalvirtualreturns (uint256 withdrawAmount);
function_emergencyWithdraw() internalvirtual;
function_getRewards() internalvirtual;
function_pendingRewards() internalviewvirtualreturns (Reward[] memory);
}
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/abstractcontractContext{
function_msgSender() internalviewvirtualreturns (addresspayable) {
returnpayable(msg.sender);
}
function_msgData() internalviewvirtualreturns (bytesmemory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691returnmsg.data;
}
}
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"./Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/abstractcontractOwnableisContext{
addressprivate _owner;
eventOwnershipTransferred(addressindexed previousOwner, addressindexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/constructor() {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/functionowner() publicviewvirtualreturns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/modifieronlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/functionrenounceOwnership() publicvirtualonlyOwner{
emit OwnershipTransferred(_owner, address(0));
_owner =address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/functiontransferOwnership(address newOwner) publicvirtualonlyOwner{
require(newOwner !=address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
Contract Source Code
File 15 of 17: SafeERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)pragmasolidity 0.8.13;import"../interfaces/IERC20.sol";
import"./Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/librarySafeERC20{
usingAddressforaddress;
functionsafeTransfer(
IERC20 token,
address to,
uint256 value
) internal{
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
functionsafeTransferFrom(
IERC20 token,
addressfrom,
address to,
uint256 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.
*/functionsafeApprove(
IERC20 token,
address spender,
uint256 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'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));
}
functionsafeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal{
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
functionsafeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal{
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_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, bytesmemory 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.bytesmemory returndata =address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length>0) {
// Return data is optionalrequire(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
Contract Source Code
File 16 of 17: YakERC20.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"./interfaces/IERC20.sol";
abstractcontractYakERC20{
stringpublic name ="Yield Yak";
stringpublic symbol ="YRT";
uint8publicconstant decimals =18;
uint256public totalSupply;
mapping(address=>mapping(address=>uint256)) internal allowances;
mapping(address=>uint256) internal balances;
/// @dev keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")bytes32publicconstant DOMAIN_TYPEHASH =0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
/// @dev keccak256("1");bytes32publicconstant VERSION_HASH =0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;
/// @dev keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");bytes32publicconstant PERMIT_TYPEHASH =0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address=>uint256) public nonces;
eventTransfer(addressindexedfrom, addressindexed to, uint256 value);
eventApproval(addressindexed owner, addressindexed spender, uint256 value);
constructor() {}
/**
* @notice Get the number of tokens `spender` is approved to spend on behalf of `account`
* @param account The address of the account holding the funds
* @param spender The address of the account spending the funds
* @return The number of tokens approved
*/functionallowance(address account, address spender) externalviewreturns (uint256) {
return allowances[account][spender];
}
/**
* @notice Approve `spender` to transfer up to `amount` from `src`
* @dev This will overwrite the approval amount for `spender`
* and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
* It is recommended to use increaseAllowance and decreaseAllowance instead
* @param spender The address of the account which may transfer tokens
* @param amount The number of tokens that are approved (2^256-1 means infinite)
* @return Whether or not the approval succeeded
*/functionapprove(address spender, uint256 amount) externalreturns (bool) {
_approve(msg.sender, spender, amount);
returntrue;
}
/**
* @notice Get the number of tokens held by the `account`
* @param account The address of the account to get the balance of
* @return The number of tokens held
*/functionbalanceOf(address account) externalviewreturns (uint256) {
return balances[account];
}
/**
* @notice Transfer `amount` tokens from `msg.sender` to `dst`
* @param dst The address of the destination account
* @param amount The number of tokens to transfer
* @return Whether or not the transfer succeeded
*/functiontransfer(address dst, uint256 amount) externalreturns (bool) {
_transferTokens(msg.sender, dst, amount);
returntrue;
}
/**
* @notice Transfer `amount` tokens from `src` to `dst`
* @param src The address of the source account
* @param dst The address of the destination account
* @param amount The number of tokens to transfer
* @return Whether or not the transfer succeeded
*/functiontransferFrom(address src,
address dst,
uint256 amount
) externalreturns (bool) {
address spender =msg.sender;
uint256 spenderAllowance = allowances[src][spender];
if (spender != src && spenderAllowance !=type(uint256).max) {
uint256 newAllowance = spenderAllowance - amount;
allowances[src][spender] = newAllowance;
emit Approval(src, spender, newAllowance);
}
_transferTokens(src, dst, amount);
returntrue;
}
/**
* @notice Approval implementation
* @param owner The address of the account which owns tokens
* @param spender The address of the account which may transfer tokens
* @param amount The number of tokens that are approved (2^256-1 means infinite)
*/function_approve(address owner,
address spender,
uint256 amount
) internal{
require(owner !=address(0), "_approve::owner zero address");
require(spender !=address(0), "_approve::spender zero address");
allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @notice Transfer implementation
* @param from The address of the account which owns tokens
* @param to The address of the account which is receiving tokens
* @param value The number of tokens that are being transferred
*/function_transferTokens(addressfrom,
address to,
uint256 value
) internal{
require(to !=address(0), "_transferTokens: cannot transfer to the zero address");
balances[from] = balances[from] - value;
balances[to] = balances[to] + value;
emit Transfer(from, to, value);
}
function_mint(address to, uint256 value) internal{
require(value >0, "_mint::zero shares");
totalSupply = totalSupply + value;
balances[to] = balances[to] + value;
emit Transfer(address(0), to, value);
}
function_burn(addressfrom, uint256 value) internal{
balances[from] = balances[from] - value;
totalSupply = totalSupply - value;
emit Transfer(from, address(0), value);
}
/**
* @notice Triggers an approval from owner to spender
* @param owner The address to approve from
* @param spender The address to be approved
* @param value The number of tokens that are approved (2^256-1 means infinite)
* @param deadline The time at which to expire the signature
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
*/functionpermit(address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external{
require(deadline >=block.timestamp, "permit::expired");
bytes32 encodeData =keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline));
_validateSignedData(owner, encodeData, v, r, s);
_approve(owner, spender, value);
}
/**
* @notice Recovers address from signed data and validates the signature
* @param signer Address that signed the data
* @param encodeData Data signed by the address
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
*/function_validateSignedData(address signer,
bytes32 encodeData,
uint8 v,
bytes32 r,
bytes32 s
) internalview{
bytes32 digest =keccak256(abi.encodePacked("\x19\x01", getDomainSeparator(), encodeData));
address recoveredAddress =ecrecover(digest, v, r, s);
// Explicitly disallow authorizations for address(0) as ecrecover returns address(0) on malformed messagesrequire(recoveredAddress !=address(0) && recoveredAddress == signer, "Arch::validateSig: invalid signature");
}
/**
* @notice EIP-712 Domain separator
* @return Separator
*/functiongetDomainSeparator() publicviewreturns (bytes32) {
returnkeccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), VERSION_HASH, _getChainId(), address(this)));
}
/**
* @notice Current id of the chain where this contract is deployed
* @return Chain id
*/function_getChainId() internalviewreturns (uint256) {
uint256 chainId;
assembly {
chainId :=chainid()
}
return chainId;
}
}
Contract Source Code
File 17 of 17: YakStrategyV3.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"./YakERC20.sol";
import"./lib/Ownable.sol";
import"./lib/SafeERC20.sol";
import"./interfaces/IERC20.sol";
/**
* @notice YakStrategy should be inherited by new strategies
*/abstractcontractYakStrategyV3isYakERC20, Ownable{
usingSafeERC20forIERC20;
structStrategySettings {
string name;
address owner;
address dev;
address feeCollector;
address depositToken;
address rewardToken;
uint256 minTokensToReinvest;
uint256 devFeeBips;
uint256 reinvestRewardBips;
}
IERC20 publicimmutable depositToken;
IERC20 publicimmutable rewardToken;
addresspublic devAddr;
addresspublic feeCollector;
uint256public MIN_TOKENS_TO_REINVEST;
boolpublic DEPOSITS_ENABLED;
uint256public REINVEST_REWARD_BIPS;
uint256public DEV_FEE_BIPS;
uint256internalconstant BIPS_DIVISOR =10000;
eventDeposit(addressindexed account, uint256 amount);
eventWithdraw(addressindexed account, uint256 amount);
eventReinvest(uint256 newTotalDeposits, uint256 newTotalSupply);
eventRecovered(address token, uint256 amount);
eventUpdateDevFee(uint256 oldValue, uint256 newValue);
eventUpdateReinvestReward(uint256 oldValue, uint256 newValue);
eventUpdateMinTokensToReinvest(uint256 oldValue, uint256 newValue);
eventUpdateMaxTokensToDepositWithoutReinvest(uint256 oldValue, uint256 newValue);
eventUpdateDevAddr(address oldValue, address newValue);
eventUpdateFeeCollector(address oldValue, address newValue);
eventDepositsEnabled(bool newValue);
/**
* @notice Throws if called by smart contract
*/modifieronlyEOA() {
require(tx.origin==msg.sender, "YakStrategy::onlyEOA");
_;
}
/**
* @notice Only called by dev
*/modifieronlyDev() {
require(msg.sender== devAddr, "YakStrategy::onlyDev");
_;
}
constructor(StrategySettings memory _strategySettings) {
name = _strategySettings.name;
depositToken = IERC20(_strategySettings.depositToken);
rewardToken = IERC20(_strategySettings.rewardToken);
devAddr =msg.sender;
updateMinTokensToReinvest(_strategySettings.minTokensToReinvest);
updateDevFee(_strategySettings.devFeeBips);
updateReinvestReward(_strategySettings.reinvestRewardBips);
updateFeeCollector(_strategySettings.feeCollector);
updateDevAddr(_strategySettings.dev);
enableDeposits();
transferOwnership(_strategySettings.owner);
}
/**
* @notice Deposit and deploy deposits tokens to the strategy
* @dev Must mint receipt tokens to `msg.sender`
* @param amount deposit tokens
*/functiondeposit(uint256 amount) externalvirtual;
/**
* @notice Deposit using Permit
* @dev Should revert for tokens without Permit
* @param amount Amount of tokens to deposit
* @param deadline The time at which to expire the signature
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
*/functiondepositWithPermit(uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) externalvirtual;
/**
* @notice Deposit on behalf of another account
* @dev Must mint receipt tokens to `account`
* @param account address to receive receipt tokens
* @param amount deposit tokens
*/functiondepositFor(address account, uint256 amount) externalvirtual;
/**
* @notice Redeem receipt tokens for deposit tokens
* @param amount receipt tokens
*/functionwithdraw(uint256 amount) externalvirtual;
/**
* @notice Reinvest reward tokens into deposit tokens
*/functionreinvest() externalvirtual;
/**
* @notice Estimate reinvest reward
* @return reward tokens
*/functionestimateReinvestReward() externalviewreturns (uint256) {
uint256 unclaimedRewards = checkReward();
if (unclaimedRewards >= MIN_TOKENS_TO_REINVEST) {
return (unclaimedRewards * REINVEST_REWARD_BIPS) / BIPS_DIVISOR;
}
return0;
}
/**
* @notice Reward tokens available to strategy, including balance
* @return reward tokens
*/functioncheckReward() publicviewvirtualreturns (uint256);
/**
* @notice Rescue all available deployed deposit tokens back to Strategy
* @param minReturnAmountAccepted min deposit tokens to receive
*/functionrescueDeployedFunds(uint256 minReturnAmountAccepted) externalvirtual;
/**
* @notice This function returns a snapshot of last available quotes
* @return total deposits available on the contract
*/functiontotalDeposits() publicviewvirtualreturns (uint256);
/**
* @notice Calculate receipt tokens for a given amount of deposit tokens
* @dev If contract is empty, use 1:1 ratio
* @dev Could return zero shares for very low amounts of deposit tokens
* @param amount deposit tokens
* @return receipt tokens
*/functiongetSharesForDepositTokens(uint256 amount) publicviewreturns (uint256) {
uint256 tDeposits = totalDeposits();
uint256 tSupply = totalSupply;
if (tSupply ==0|| tDeposits ==0) {
return amount;
}
return (amount * tSupply) / tDeposits;
}
/**
* @notice Calculate deposit tokens for a given amount of receipt tokens
* @param amount receipt tokens
* @return deposit tokens
*/functiongetDepositTokensForShares(uint256 amount) publicviewreturns (uint256) {
uint256 tDeposits = totalDeposits();
uint256 tSupply = totalSupply;
if (tSupply ==0|| tDeposits ==0) {
return0;
}
return (amount * tDeposits) / tSupply;
}
// Dev protected/**
* @notice Revoke token allowance
* @param token address
* @param spender address
*/functionrevokeAllowance(address token, address spender) externalonlyDev{
require(IERC20(token).approve(spender, 0));
}
/**
* @notice Disable deposits
*/functiondisableDeposits() publiconlyDev{
require(DEPOSITS_ENABLED);
DEPOSITS_ENABLED =false;
emit DepositsEnabled(false);
}
/**
* @notice Update reinvest min threshold
* @param newValue threshold
*/functionupdateMinTokensToReinvest(uint256 newValue) publiconlyDev{
emit UpdateMinTokensToReinvest(MIN_TOKENS_TO_REINVEST, newValue);
MIN_TOKENS_TO_REINVEST = newValue;
}
/**
* @notice Update developer fee
* @param newValue fee in BIPS
*/functionupdateDevFee(uint256 newValue) publiconlyDev{
require(newValue + REINVEST_REWARD_BIPS <= BIPS_DIVISOR);
emit UpdateDevFee(DEV_FEE_BIPS, newValue);
DEV_FEE_BIPS = newValue;
}
/**
* @notice Update reinvest reward
* @param newValue fee in BIPS
*/functionupdateReinvestReward(uint256 newValue) publiconlyDev{
require(newValue + DEV_FEE_BIPS <= BIPS_DIVISOR);
emit UpdateReinvestReward(REINVEST_REWARD_BIPS, newValue);
REINVEST_REWARD_BIPS = newValue;
}
// Owner protected/**
* @notice Enable deposits
*/functionenableDeposits() publiconlyOwner{
require(!DEPOSITS_ENABLED);
DEPOSITS_ENABLED =true;
emit DepositsEnabled(true);
}
/**
* @notice Update devAddr
* @param newValue address
*/functionupdateDevAddr(address newValue) publiconlyOwner{
emit UpdateDevAddr(devAddr, newValue);
devAddr = newValue;
}
/**
* @notice Update feeCollector
* @param newValue address
*/functionupdateFeeCollector(address newValue) publiconlyOwner{
emit UpdateFeeCollector(feeCollector, newValue);
feeCollector = newValue;
}
/**
* @notice Recover ERC20 from contract
* @param tokenAddress token address
* @param tokenAmount amount to recover
*/functionrecoverERC20(address tokenAddress, uint256 tokenAmount) externalonlyOwner{
require(tokenAmount >0);
IERC20(tokenAddress).safeTransfer(msg.sender, tokenAmount);
emit Recovered(tokenAddress, tokenAmount);
}
/**
* @notice Recover GAS from contract
* @param amount amount
*/functionrecoverGas(uint256 amount) externalonlyOwner{
require(amount >0);
payable(msg.sender).transfer(amount);
emit Recovered(address(0), amount);
}
}