文件 1 的 10:BlockDelay.sol
pragma solidity 0.8.9;
abstract contract BlockDelay {
uint256 public blockDelay;
uint256 internal constant MAX_BLOCK_DELAY = 10;
mapping(address => uint256) public lastBlock;
error AboveMaxBlockDelay();
error BeforeBlockDelay();
constructor(uint8 _delay) {
_setBlockDelay(_delay);
}
function _setBlockDelay(uint8 _newDelay) internal {
if (_newDelay > MAX_BLOCK_DELAY) revert AboveMaxBlockDelay();
blockDelay = _newDelay;
}
modifier useBlockDelay(address _address) {
if (block.number < lastBlock[_address] + blockDelay) revert BeforeBlockDelay();
lastBlock[_address] = block.number;
_;
}
}
文件 2 的 10:ERC20.sol
pragma solidity >=0.8.0;
abstract contract ERC20 {
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
string public name;
string public symbol;
uint8 public immutable decimals;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender];
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
文件 3 的 10:FixedPointMathLib.sol
pragma solidity >=0.8.0;
library FixedPointMathLib {
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18;
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD);
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD);
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y);
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y);
}
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
assembly {
switch x
case 0 {
switch n
case 0 {
z := scalar
}
default {
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
z := scalar
}
default {
z := x
}
let half := shr(1, scalar)
for {
n := shr(1, n)
} n {
n := shr(1, n)
} {
if shr(128, x) {
revert(0, 0)
}
let xx := mul(x, x)
let xxRound := add(xx, half)
if lt(xxRound, xx) {
revert(0, 0)
}
x := div(xxRound, scalar)
if mod(n, 2) {
let zx := mul(z, x)
if iszero(eq(div(zx, x), z)) {
if iszero(iszero(x)) {
revert(0, 0)
}
}
let zxRound := add(zx, half)
if lt(zxRound, zx) {
revert(0, 0)
}
z := div(zxRound, scalar)
}
}
}
}
}
function sqrt(uint256 x) internal pure returns (uint256 z) {
assembly {
let y := x
z := 181
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
z := shr(18, mul(z, add(y, 65536)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
assembly {
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
文件 4 的 10:IERC4626.sol
pragma solidity 0.8.9;
import 'solmate/tokens/ERC20.sol';
interface IERC4626 {
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
function asset() external view returns (ERC20);
function totalAssets() external view returns (uint256 assets);
function convertToShares(uint256 assets) external view returns (uint256 shares);
function convertToAssets(uint256 shares) external view returns (uint256 assets);
function maxDeposit(address receiver) external view returns (uint256 assets);
function previewDeposit(uint256 assets) external view returns (uint256 shares);
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
function maxMint(address receiver) external view returns (uint256 shares);
function previewMint(uint256 shares) external view returns (uint256 assets);
function mint(uint256 shares, address receiver) external returns (uint256 assets);
function maxWithdraw(address owner) external view returns (uint256 assets);
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
function withdraw(
uint256 assets,
address receiver,
address owner
) external returns (uint256 shares);
function maxRedeem(address owner) external view returns (uint256 shares);
function previewRedeem(uint256 shares) external view returns (uint256 assets);
function redeem(
uint256 shares,
address receiver,
address owner
) external returns (uint256 assets);
}
文件 5 的 10:Ownership.sol
pragma solidity 0.8.9;
abstract contract Ownership {
address public owner;
address public nominatedOwner;
address public admin;
mapping(address => bool) public authorized;
event OwnerChanged(address indexed previousOwner, address indexed newOwner);
event AuthAdded(address indexed newAuth);
event AuthRemoved(address indexed oldAuth);
error Unauthorized();
error AlreadyRole();
error NotRole();
constructor(
address _nominatedOwner,
address _admin,
address[] memory _authorized
) {
owner = msg.sender;
nominatedOwner = _nominatedOwner;
admin = _admin;
for (uint8 i = 0; i < _authorized.length; ++i) {
authorized[_authorized[i]] = true;
emit AuthAdded(_authorized[i]);
}
}
function acceptOwnership() external {
if (msg.sender != nominatedOwner) revert Unauthorized();
emit OwnerChanged(owner, msg.sender);
owner = msg.sender;
nominatedOwner = address(0);
}
function nominateOwnership(address _newOwner) external onlyOwner {
nominatedOwner = _newOwner;
}
function setAdmin(address _newAdmin) external onlyOwner {
if (admin == _newAdmin) revert AlreadyRole();
admin = _newAdmin;
}
function addAuthorized(address _authorized) external onlyAdmins {
if (authorized[_authorized]) revert AlreadyRole();
authorized[_authorized] = true;
emit AuthAdded(_authorized);
}
function removeAuthorized(address _authorized) external onlyAdmins {
if (!authorized[_authorized]) revert NotRole();
authorized[_authorized] = false;
emit AuthRemoved(_authorized);
}
modifier onlyOwner() {
if (msg.sender != owner) revert Unauthorized();
_;
}
modifier onlyAdmins() {
if (msg.sender != owner && msg.sender != admin) revert Unauthorized();
_;
}
modifier onlyAuthorized() {
if (msg.sender != owner && msg.sender != admin && !authorized[msg.sender]) revert Unauthorized();
_;
}
}
文件 6 的 10:SafeTransferLib.sol
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
library SafeTransferLib {
function safeTransferETH(address to, uint256 amount) internal {
bool success;
assembly {
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
assembly {
let freeMemoryPointer := mload(0x40)
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from)
mstore(add(freeMemoryPointer, 36), to)
mstore(add(freeMemoryPointer, 68), amount)
success := and(
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
let freeMemoryPointer := mload(0x40)
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to)
mstore(add(freeMemoryPointer, 36), amount)
success := and(
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
let freeMemoryPointer := mload(0x40)
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to)
mstore(add(freeMemoryPointer, 36), amount)
success := and(
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
文件 7 的 10:Strategy.sol
pragma solidity 0.8.9;
import './Vault.sol';
abstract contract Strategy is Ownership {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
Vault public immutable vault;
ERC20 public immutable asset;
address public treasury;
uint16 public fee = 1_000;
uint16 internal constant MAX_FEE = 1_000;
uint16 internal constant FEE_BASIS = 10_000;
uint16 public slip = 9_900;
uint16 internal constant SLIP_BASIS = 10_000;
event FeeChanged(uint16 newFee);
event SlipChanged(uint16 newSlip);
event TreasuryChanged(address indexed newTreasury);
error Zero();
error NotVault();
error InvalidValue();
error AlreadyValue();
constructor(
Vault _vault,
address _treasury,
address _nominatedOwner,
address _admin,
address[] memory _authorized
) Ownership(_nominatedOwner, _admin, _authorized) {
vault = _vault;
asset = vault.asset();
treasury = _treasury;
}
function totalAssets() public view virtual returns (uint256);
function withdraw(uint256 _assets)
external
onlyVault
returns (
uint256 received,
uint256 slippage,
uint256 bonus
)
{
uint256 total = totalAssets();
if (total == 0) revert Zero();
uint256 assets = _assets > total ? total : _assets;
received = _withdraw(assets);
unchecked {
if (assets > received) slippage = assets - received;
else if (received > assets) {
bonus = received - assets;
received = assets;
}
}
}
function harvest() external onlyAdminOrVault returns (uint256 received) {
_harvest();
received = asset.balanceOf(address(this));
if (fee > 0) {
uint256 feeAmount = _calculateFee(received);
received -= feeAmount;
asset.safeTransfer(treasury, feeAmount);
}
asset.safeTransfer(address(vault), received);
}
function invest() external onlyAdminOrVault {
_invest();
}
function setFee(uint16 _fee) external onlyOwner {
if (_fee > MAX_FEE) revert InvalidValue();
if (_fee == fee) revert AlreadyValue();
fee = _fee;
emit FeeChanged(_fee);
}
function setTreasury(address _treasury) external onlyOwner {
if (_treasury == treasury) revert AlreadyValue();
treasury = _treasury;
emit TreasuryChanged(_treasury);
}
function setSlip(uint16 _slip) external onlyAdmins {
if (_slip > SLIP_BASIS) revert InvalidValue();
if (_slip == slip) revert AlreadyValue();
slip = _slip;
emit SlipChanged(_slip);
}
function _withdraw(uint256 _assets) internal virtual returns (uint256 received);
function _harvest() internal virtual;
function _invest() internal virtual;
function _calculateSlippage(uint256 _amount) internal view returns (uint256) {
return _amount.mulDivDown(slip, SLIP_BASIS);
}
function _calculateFee(uint256 _amount) internal view returns (uint256) {
return _amount.mulDivDown(fee, FEE_BASIS);
}
modifier onlyVault() {
if (msg.sender != address(vault)) revert NotVault();
_;
}
modifier onlyAdminOrVault() {
if (msg.sender != owner && msg.sender != admin && msg.sender != address(vault)) revert Unauthorized();
_;
}
}
文件 8 的 10:Vault.sol
pragma solidity 0.8.9;
import 'solmate/tokens/ERC20.sol';
import 'solmate/utils/SafeTransferLib.sol';
import 'solmate/utils/FixedPointMathLib.sol';
import './libraries/Ownership.sol';
import './libraries/BlockDelay.sol';
import './interfaces/IERC4626.sol';
import './Strategy.sol';
contract Vault is ERC20, IERC4626, Ownership, BlockDelay {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
ERC20 public immutable asset;
bool public paused;
uint256 private _lockedProfit;
uint256 public lastReport;
uint256 public lockedProfitDuration = 24 hours;
uint256 internal constant MAX_LOCKED_PROFIT_DURATION = 3 days;
uint256 private _maxDeposit = type(uint256).max;
struct StrategyParams {
bool added;
uint256 debt;
uint256 debtRatio;
}
Strategy[] private _queue;
mapping(Strategy => StrategyParams) public strategies;
uint8 internal constant MAX_QUEUE_LENGTH = 20;
uint256 public totalDebt;
uint256 public floatDebtRatio;
uint256 public totalDebtRatio;
uint256 internal constant MAX_TOTAL_DEBT_RATIO = 1_000;
event Report(Strategy indexed strategy, uint256 harvested, uint256 gain, uint256 loss);
event Lend(Strategy indexed strategy, uint256 assets, uint256 slippage);
event Collect(Strategy indexed strategy, uint256 received, uint256 slippage, uint256 bonus);
event StrategyAdded(Strategy indexed strategy, uint256 debtRatio);
event StrategyDebtRatioChanged(Strategy indexed strategy, uint256 newDebtRatio);
event StrategyRemoved(Strategy indexed strategy);
event StrategyQueuePositionsSwapped(uint8 i, uint8 j, Strategy indexed newI, Strategy indexed newJ);
event LockedProfitDurationChanged(uint256 newDuration);
event MaxDepositChanged(uint256 newMaxDeposit);
event FloatDebtRatioChanged(uint256 newFloatDebtRatio);
error Zero();
error BelowMinimum(uint256);
error AboveMaximum(uint256);
error AboveMaxDeposit();
error AlreadyStrategy();
error NotStrategy();
error StrategyDoesNotBelongToQueue();
error StrategyQueueFull();
error AlreadyValue();
error Paused();
constructor(
ERC20 _asset,
uint8 _blockDelay,
uint256 _floatDebtRatio,
address _nominatedOwner,
address _admin,
address[] memory _authorized
)
ERC20(
string(abi.encodePacked('Unagii ', _asset.name(), ' Vault v3')),
string(abi.encodePacked('u', _asset.symbol(), 'v3')),
_asset.decimals()
)
Ownership(_nominatedOwner, _admin, _authorized)
BlockDelay(_blockDelay)
{
asset = _asset;
_setFloatDebtRatio(_floatDebtRatio);
}
function queue() external view returns (Strategy[] memory) {
return _queue;
}
function totalAssets() public view returns (uint256 assets) {
return asset.balanceOf(address(this)) + totalDebt;
}
function lockedProfit() public view returns (uint256 lockedAssets) {
uint256 last = lastReport;
uint256 duration = lockedProfitDuration;
unchecked {
if (block.timestamp >= last + duration) return 0;
return _lockedProfit - _lockedProfit.mulDivDown(block.timestamp - last, duration);
}
}
function freeAssets() public view returns (uint256 assets) {
return totalAssets() - lockedProfit();
}
function convertToShares(uint256 _assets) public view returns (uint256 shares) {
uint256 supply = totalSupply;
return supply == 0 ? _assets : _assets.mulDivDown(supply, totalAssets());
}
function convertToAssets(uint256 _shares) public view returns (uint256 assets) {
uint256 supply = totalSupply;
return supply == 0 ? _shares : _shares.mulDivDown(totalAssets(), supply);
}
function maxDeposit(address) external view returns (uint256 assets) {
return _maxDeposit;
}
function previewDeposit(uint256 _assets) public view returns (uint256 shares) {
return convertToShares(_assets);
}
function maxMint(address) external view returns (uint256 shares) {
return convertToShares(_maxDeposit);
}
function previewMint(uint256 shares) public view returns (uint256 assets) {
uint256 supply = totalSupply;
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
function maxWithdraw(address owner) external view returns (uint256 assets) {
return convertToAssets(balanceOf[owner]);
}
function previewWithdraw(uint256 assets) public view returns (uint256 shares) {
uint256 supply = totalSupply;
return supply == 0 ? assets : assets.mulDivUp(supply, freeAssets());
}
function maxRedeem(address _owner) external view returns (uint256 shares) {
return balanceOf[_owner];
}
function previewRedeem(uint256 shares) public view returns (uint256 assets) {
uint256 supply = totalSupply;
return supply == 0 ? shares : shares.mulDivDown(freeAssets(), supply);
}
function safeDeposit(
uint256 _assets,
address _receiver,
uint256 _minShares
) external returns (uint256 shares) {
shares = deposit(_assets, _receiver);
if (shares < _minShares) revert BelowMinimum(shares);
}
function safeMint(
uint256 _shares,
address _receiver,
uint256 _maxAssets
) external returns (uint256 assets) {
assets = mint(_shares, _receiver);
if (assets > _maxAssets) revert AboveMaximum(assets);
}
function safeWithdraw(
uint256 _assets,
address _receiver,
address _owner,
uint256 _maxShares
) external returns (uint256 shares) {
shares = withdraw(_assets, _receiver, _owner);
if (shares > _maxShares) revert AboveMaximum(shares);
}
function safeRedeem(
uint256 _shares,
address _receiver,
address _owner,
uint256 _minAssets
) external returns (uint256 assets) {
assets = redeem(_shares, _receiver, _owner);
if (assets < _minAssets) revert BelowMinimum(assets);
}
function deposit(uint256 _assets, address _receiver) public whenNotPaused returns (uint256 shares) {
if ((shares = previewDeposit(_assets)) == 0) revert Zero();
_deposit(_assets, shares, _receiver);
}
function mint(uint256 _shares, address _receiver) public whenNotPaused returns (uint256 assets) {
if (_shares == 0) revert Zero();
assets = previewMint(_shares);
_deposit(assets, _shares, _receiver);
}
function withdraw(
uint256 _assets,
address _receiver,
address _owner
) public returns (uint256 shares) {
if (_assets == 0) revert Zero();
shares = previewWithdraw(_assets);
_withdraw(_assets, shares, _owner, _receiver);
}
function redeem(
uint256 _shares,
address _receiver,
address _owner
) public returns (uint256 assets) {
if ((assets = previewRedeem(_shares)) == 0) revert Zero();
return _withdraw(assets, _shares, _owner, _receiver);
}
function addStrategy(Strategy _strategy, uint256 _debtRatio) external onlyOwner {
if (_strategy.vault() != this) revert StrategyDoesNotBelongToQueue();
if (strategies[_strategy].added) revert AlreadyStrategy();
if (_queue.length >= MAX_QUEUE_LENGTH) revert StrategyQueueFull();
totalDebtRatio += _debtRatio;
if (totalDebtRatio > MAX_TOTAL_DEBT_RATIO) revert AboveMaximum(totalDebtRatio);
strategies[_strategy] = StrategyParams({added: true, debt: 0, debtRatio: _debtRatio});
_queue.push(_strategy);
emit StrategyAdded(_strategy, _debtRatio);
}
function removeStrategy(
Strategy _strategy,
bool _shouldHarvest,
uint256 _minReceived
) external onlyAdmins returns (uint256 received) {
if (!strategies[_strategy].added) revert NotStrategy();
_setDebtRatio(_strategy, 0);
uint256 balanceBefore = asset.balanceOf(address(this));
if (_shouldHarvest) _harvest(_strategy);
else _report(_strategy, 0);
received = asset.balanceOf(address(this)) - balanceBefore;
if (received < _minReceived) revert BelowMinimum(received);
Strategy[] memory newQueue = new Strategy[](_queue.length - 1);
bool found;
uint8 length = uint8(newQueue.length);
for (uint8 i = 0; i < length; ++i) {
if (_queue[i] == _strategy) found = true;
if (found) newQueue[i] = _queue[i + 1];
else newQueue[i] = _queue[i];
}
delete strategies[_strategy];
_queue = newQueue;
emit StrategyRemoved(_strategy);
}
function swapQueuePositions(uint8 _i, uint8 _j) external onlyAdmins {
Strategy s1 = _queue[_i];
Strategy s2 = _queue[_j];
_queue[_i] = s2;
_queue[_j] = s1;
emit StrategyQueuePositionsSwapped(_i, _j, s2, s1);
}
function setDebtRatio(Strategy _strategy, uint256 _newDebtRatio) external onlyAdmins {
if (!strategies[_strategy].added) revert NotStrategy();
_setDebtRatio(_strategy, _newDebtRatio);
}
function setLockedProfitDuration(uint256 _newDuration) external onlyAdmins {
if (_newDuration > MAX_LOCKED_PROFIT_DURATION) revert AboveMaximum(_newDuration);
if (_newDuration == lockedProfitDuration) revert AlreadyValue();
lockedProfitDuration = _newDuration;
emit LockedProfitDurationChanged(_newDuration);
}
function setBlockDelay(uint8 _newDelay) external onlyAdmins {
_setBlockDelay(_newDelay);
}
function suspendStrategy(Strategy _strategy) external onlyAuthorized {
if (!strategies[_strategy].added) revert NotStrategy();
_setDebtRatio(_strategy, 0);
}
function collectFromStrategy(
Strategy _strategy,
uint256 _assets,
uint256 _minReceived
) external onlyAuthorized returns (uint256 received) {
if (!strategies[_strategy].added) revert NotStrategy();
(received, ) = _collect(_strategy, _assets, address(this));
if (received < _minReceived) revert BelowMinimum(received);
}
function pause() external onlyAuthorized {
if (paused) revert AlreadyValue();
paused = true;
}
function unpause() external onlyAuthorized {
if (!paused) revert AlreadyValue();
paused = false;
}
function setMaxDeposit(uint256 _newMaxDeposit) external onlyAuthorized {
if (_maxDeposit == _newMaxDeposit) revert AlreadyValue();
_maxDeposit = _newMaxDeposit;
emit MaxDepositChanged(_newMaxDeposit);
}
function harvestAll() external onlyAuthorized updateLastReport {
uint8 length = uint8(_queue.length);
for (uint8 i = 0; i < length; ++i) {
_harvest(_queue[i]);
}
}
function reportAll() external onlyAuthorized updateLastReport {
uint8 length = uint8(_queue.length);
for (uint8 i = 0; i < length; ++i) {
_report(_queue[i], 0);
}
}
function harvest(Strategy _strategy) external onlyAuthorized updateLastReport {
if (!strategies[_strategy].added) revert NotStrategy();
_harvest(_strategy);
}
function report(Strategy _strategy) external onlyAuthorized updateLastReport {
if (!strategies[_strategy].added) revert NotStrategy();
_report(_strategy, 0);
}
function setFloatDebtRatio(uint256 _floatDebtRatio) external onlyAuthorized {
_setFloatDebtRatio(_floatDebtRatio);
}
function _mint(address _to, uint256 _amount) internal override useBlockDelay(_to) {
if (_to == address(0)) revert Zero();
ERC20._mint(_to, _amount);
}
function _burn(address _from, uint256 _amount) internal override useBlockDelay(_from) {
ERC20._burn(_from, _amount);
}
function transfer(address _to, uint256 _amount)
public
override
useBlockDelay(msg.sender)
useBlockDelay(_to)
returns (bool)
{
return ERC20.transfer(_to, _amount);
}
function transferFrom(
address _from,
address _to,
uint256 _amount
) public override useBlockDelay(_from) useBlockDelay(_to) returns (bool) {
return ERC20.transferFrom(_from, _to, _amount);
}
function _deposit(
uint256 _assets,
uint256 _shares,
address _receiver
) internal {
if (_assets > _maxDeposit) revert AboveMaxDeposit();
asset.safeTransferFrom(msg.sender, address(this), _assets);
_mint(_receiver, _shares);
emit Deposit(msg.sender, _receiver, _assets, _shares);
}
function _withdraw(
uint256 _assets,
uint256 _shares,
address _owner,
address _receiver
) internal returns (uint256 received) {
if (msg.sender != _owner) {
uint256 allowed = allowance[_owner][msg.sender];
if (allowed != type(uint256).max) allowance[_owner][msg.sender] = allowed - _shares;
}
_burn(_owner, _shares);
emit Withdraw(msg.sender, _receiver, _owner, _assets, _shares);
uint256 balance = asset.balanceOf(address(this));
if (balance > 0) {
uint256 amount = _assets > balance ? balance : _assets;
asset.safeTransfer(_receiver, amount);
_assets -= amount;
received += amount;
}
uint8 length = uint8(_queue.length);
for (uint8 i = 0; i < length; ++i) {
if (_assets == 0) break;
(uint256 receivedFromStrategy, uint256 slippage) = _collect(_queue[i], _assets, _receiver);
_assets -= receivedFromStrategy + slippage;
received += receivedFromStrategy;
}
}
function _lend(Strategy _strategy, uint256 _assets) internal {
uint256 balance = asset.balanceOf(address(this));
uint256 amount = _assets > balance ? balance : _assets;
asset.safeTransfer(address(_strategy), amount);
_strategy.invest();
uint256 debtBefore = strategies[_strategy].debt;
uint256 debtAfter = _strategy.totalAssets();
uint256 diff = debtAfter - debtBefore;
uint256 slippage = amount > diff ? amount - diff : 0;
uint256 debt = amount - slippage;
strategies[_strategy].debt += debt;
totalDebt += debt;
emit Lend(_strategy, amount, slippage);
}
function _collect(
Strategy _strategy,
uint256 _assets,
address _receiver
) internal returns (uint256 received, uint256 slippage) {
uint256 bonus;
(received, slippage, bonus) = _strategy.withdraw(_assets);
uint256 total = received + slippage;
uint256 debt = strategies[_strategy].debt;
uint256 amount = debt > total ? received : total;
strategies[_strategy].debt -= amount;
totalDebt -= amount;
if (_receiver == address(this)) emit Collect(_strategy, received, slippage, bonus);
else asset.safeTransfer(_receiver, received);
}
function _harvest(Strategy _strategy) internal {
_report(_strategy, _strategy.harvest());
}
function _report(Strategy _strategy, uint256 _harvested) internal {
uint256 assets = _strategy.totalAssets();
uint256 debt = strategies[_strategy].debt;
strategies[_strategy].debt = assets;
uint256 gain;
uint256 loss;
uint256 lockedProfitBefore = lockedProfit();
if (assets > debt) {
unchecked {
gain = assets - debt;
}
totalDebt += gain;
_lockedProfit = lockedProfitBefore + gain + _harvested;
} else if (debt > assets) {
unchecked {
loss = debt - assets;
totalDebt -= loss;
_lockedProfit = lockedProfitBefore + _harvested > loss ? lockedProfitBefore + _harvested - loss : 0;
}
}
uint256 possibleDebt = totalDebtRatio == 0
? 0
: totalAssets().mulDivDown(strategies[_strategy].debtRatio, totalDebtRatio);
if (possibleDebt > assets) _lend(_strategy, possibleDebt - assets);
else if (assets > possibleDebt) _collect(_strategy, assets - possibleDebt, address(this));
emit Report(_strategy, _harvested, gain, loss);
}
function _setDebtRatio(Strategy _strategy, uint256 _newDebtRatio) internal {
uint256 currentDebtRatio = strategies[_strategy].debtRatio;
if (_newDebtRatio == currentDebtRatio) revert AlreadyValue();
uint256 newTotalDebtRatio = totalDebtRatio + _newDebtRatio - currentDebtRatio;
if (newTotalDebtRatio > MAX_TOTAL_DEBT_RATIO) revert AboveMaximum(newTotalDebtRatio);
strategies[_strategy].debtRatio = _newDebtRatio;
totalDebtRatio = newTotalDebtRatio;
emit StrategyDebtRatioChanged(_strategy, _newDebtRatio);
}
function _setFloatDebtRatio(uint256 _floatDebtRatio) internal {
uint256 newTotalDebtRatio = totalDebtRatio + _floatDebtRatio - floatDebtRatio;
if (newTotalDebtRatio > MAX_TOTAL_DEBT_RATIO) revert AboveMaximum(newTotalDebtRatio);
floatDebtRatio = _floatDebtRatio;
totalDebtRatio = newTotalDebtRatio;
emit FloatDebtRatioChanged(_floatDebtRatio);
}
modifier updateLastReport() {
_;
lastReport = block.timestamp;
}
modifier whenNotPaused() {
if (paused) revert Paused();
_;
}
}
文件 9 的 10:WETH.sol
pragma solidity >=0.8.0;
import {ERC20} from "./ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
contract WETH is ERC20("Wrapped Ether", "WETH", 18) {
using SafeTransferLib for address;
event Deposit(address indexed from, uint256 amount);
event Withdrawal(address indexed to, uint256 amount);
function deposit() public payable virtual {
_mint(msg.sender, msg.value);
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint256 amount) public virtual {
_burn(msg.sender, amount);
emit Withdrawal(msg.sender, amount);
msg.sender.safeTransferETH(amount);
}
receive() external payable virtual {
deposit();
}
}
文件 10 的 10:WethZap.sol
pragma solidity 0.8.9;
import 'solmate/tokens/WETH.sol';
import 'solmate/utils/SafeTransferLib.sol';
import '../Vault.sol';
contract WethZap {
using SafeTransferLib for WETH;
Vault public immutable vault;
WETH public immutable WETH9;
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
error NoDepositETH();
constructor(Vault _vault) {
vault = _vault;
WETH9 = WETH(payable(address(_vault.asset())));
}
receive() external payable {
if (msg.sender != address(WETH9)) revert NoDepositETH();
}
function safeDepositETH(address _receiver, uint256 _minShares) external payable returns (uint256 shares) {
WETH9.deposit{value: msg.value}();
WETH9.safeApprove(address(vault), msg.value);
shares = vault.safeDeposit(msg.value, _receiver, _minShares);
emit Deposit(msg.sender, _receiver, msg.value, shares);
}
function depositETH(address _receiver) external payable returns (uint256 shares) {
WETH9.deposit{value: msg.value}();
WETH9.safeApprove(address(vault), msg.value);
shares = vault.deposit(msg.value, _receiver);
emit Deposit(msg.sender, _receiver, msg.value, shares);
}
function safeRedeemETH(
uint256 _shares,
address _receiver,
address _owner,
uint256 _maxShares
) external returns (uint256 assets) {
assets = vault.safeRedeem(_shares, address(this), _owner, _maxShares);
WETH9.withdraw(assets);
SafeTransferLib.safeTransferETH(_receiver, assets);
emit Withdraw(msg.sender, _receiver, _owner, assets, _shares);
}
function redeemETH(
uint256 _shares,
address _receiver,
address _owner
) external returns (uint256 assets) {
assets = vault.redeem(_shares, address(this), _owner);
WETH9.withdraw(assets);
SafeTransferLib.safeTransferETH(_receiver, assets);
emit Withdraw(msg.sender, _receiver, _owner, assets, _shares);
}
}
{
"compilationTarget": {
"src/zaps/WethZap.sol": "WethZap"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 10000
},
"remappings": [
":ds-test/=lib/solmate/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":solmate/=lib/solmate/src/"
]
}
[{"inputs":[{"internalType":"contract Vault","name":"_vault","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"NoDepositETH","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"contract WETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_receiver","type":"address"}],"name":"depositETH","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_shares","type":"uint256"},{"internalType":"address","name":"_receiver","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"name":"redeemETH","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_receiver","type":"address"},{"internalType":"uint256","name":"_minShares","type":"uint256"}],"name":"safeDepositETH","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_shares","type":"uint256"},{"internalType":"address","name":"_receiver","type":"address"},{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_maxShares","type":"uint256"}],"name":"safeRedeemETH","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"contract Vault","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]