Contract Source Code:
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OwnershipTransferred(address indexed user, address indexed newOwner);
/*//////////////////////////////////////////////////////////////
OWNERSHIP STORAGE
//////////////////////////////////////////////////////////////*/
address public owner;
modifier onlyOwner() virtual {
require(msg.sender == owner, "UNAUTHORIZED");
_;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address _owner) {
owner = _owner;
emit OwnershipTransferred(address(0), _owner);
}
/*//////////////////////////////////////////////////////////////
OWNERSHIP LOGIC
//////////////////////////////////////////////////////////////*/
function transferOwnership(address newOwner) public virtual onlyOwner {
owner = newOwner;
emit OwnershipTransferred(msg.sender, newOwner);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol";
/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
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
);
/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/
ERC20 public immutable asset;
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol, _asset.decimals()) {
asset = _asset;
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function withdraw(
uint256 assets,
address receiver,
address owner
) public virtual returns (uint256 shares) {
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
function redeem(
uint256 shares,
address receiver,
address owner
) public virtual returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
function totalAssets() public view virtual returns (uint256);
function convertToShares(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return convertToShares(assets);
}
function previewMint(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
}
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
return convertToAssets(shares);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxWithdraw(address owner) public view virtual returns (uint256) {
return convertToAssets(balanceOf[owner]);
}
function maxRedeem(address owner) public view virtual returns (uint256) {
return balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/
function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}
function afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
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;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
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]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
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 because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
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)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
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) {
/// @solidity memory-safe-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
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)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
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)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity ^0.8.13;
import { Owned } from "../lib/solmate/src/auth/Owned.sol";
import { ERC4626 } from "../lib/solmate/src/mixins/ERC4626.sol";
import { ILendingPool } from "./interfaces/ILendingPool.sol";
import { FixedPointMathLib } from "../lib/solmate/src/utils/FixedPointMathLib.sol";
import { ITranche } from "./interfaces/ITranche.sol";
import { IGuardian } from "./interfaces/IGuardian.sol";
/**
* @title Tranche
* @author Pragma Labs
* @notice The Tranche contract allows for lending of a specified ERC20 token, managed by a lending pool.
* @dev Protocol is according the ERC4626 standard, with a certain ERC20 as underlying
* @dev Implementation not vulnerable to ERC4626 inflation attacks,
* since totalAssets() cannot be manipulated by first minter when total amount of shares are low.
* For more information, see https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3706
*/
contract Tranche is ITranche, ERC4626, Owned {
using FixedPointMathLib for uint256;
ILendingPool public immutable lendingPool;
/* //////////////////////////////////////////////////////////////
STORAGE
////////////////////////////////////////////////////////////// */
// Flag indicating if the Tranche is locked or not.
bool public locked;
// Flag indicating if there are ongoing auctions or not.
bool public auctionInProgress;
/* //////////////////////////////////////////////////////////////
EVENTS
////////////////////////////////////////////////////////////// */
event LockSet(bool status);
event AuctionFlagSet(bool status);
/* //////////////////////////////////////////////////////////////
MODIFIERS
////////////////////////////////////////////////////////////// */
modifier notLocked() {
require(!locked, "TRANCHE: LOCKED");
_;
}
/**
* @dev Certain actions (depositing and withdrawing) can be halted on the most junior tranche while auctions are in progress.
* This prevents frontrunning both in the case there is bad debt (by pulling out the tranche before the bad debt is settled),
* as in the case there are big payouts to the LPs (mitigate Just In Time attacks, where MEV bots front-run the payout of
* Liquidation penalties to the most junior tranche and withdraw immediately after).
*/
modifier notDuringAuction() {
require(!auctionInProgress, "TRANCHE: AUCTION IN PROGRESS");
_;
}
/* //////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////// */
/**
* @notice The constructor for a tranche.
* @param lendingPool_ the Lending Pool of the underlying ERC-20 token, with the lending logic.
* @param prefix_ The prefix of the contract name (eg. Senior -> Mezzanine -> Junior).
* @param prefixSymbol_ The prefix of the contract symbol (eg. SR -> MZ -> JR).
* @dev The name and symbol of the tranche are automatically generated, based on the name and symbol of the underlying token.
*/
constructor(address lendingPool_, string memory prefix_, string memory prefixSymbol_)
ERC4626(
ERC4626(address(lendingPool_)).asset(),
string(abi.encodePacked(prefix_, " Arcadia ", ERC4626(lendingPool_).asset().name())),
string(abi.encodePacked(prefixSymbol_, "arc", ERC4626(lendingPool_).asset().symbol()))
)
Owned(msg.sender)
{
lendingPool = ILendingPool(lendingPool_);
}
/*//////////////////////////////////////////////////////////////
LOCKING LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Locks the tranche in case all liquidity of the tranche is written off due to bad debt.
* @dev Only the Lending Pool can call this function, only trigger is a severe default event.
*/
function lock() external {
require(msg.sender == address(lendingPool), "T_L: UNAUTHORIZED");
locked = true;
auctionInProgress = false;
emit LockSet(true);
emit AuctionFlagSet(false);
}
/**
* @notice Unlocks the tranche.
* @dev Only the Owner can call this function, since tranches are locked due to complete defaults,
* This function will only be called to partially refund existing share-holders after a default.
*/
function unLock() external onlyOwner {
locked = false;
emit LockSet(false);
}
/**
* @notice Locks the tranche when an auction is in progress.
* @param auctionInProgress_ Flag indicating if there are auctions in progress.
* @dev Only the Lending Pool can call this function.
* This function is to make sure no JIT liquidity is provided during a positive auction,
* and that no liquidity can be withdrawn during a negative auction.
*/
function setAuctionInProgress(bool auctionInProgress_) external {
require(msg.sender == address(lendingPool), "T_SAIP: UNAUTHORIZED");
auctionInProgress = auctionInProgress_;
emit AuctionFlagSet(auctionInProgress_);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Modification of the standard ERC-4626 deposit implementation.
* @param assets The amount of assets of the underlying ERC-20 token being deposited.
* @param receiver The address that receives the minted shares.
* @return shares The amount of shares minted.
* @dev This contract does not directly transfer the underlying assets from the sender to the receiver.
* Instead it calls the deposit of the Lending Pool which calls the transferFrom of the underlying assets.
* Hence the sender should not give this contract an allowance to transfer the underlying asset but the Lending Pool.
*/
function deposit(uint256 assets, address receiver)
public
override
notLocked
notDuringAuction
returns (uint256 shares)
{
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDepositAndSync(assets)) != 0, "T_D: ZERO_SHARES");
// Need to transfer (via lendingPool.depositInLendingPool()) before minting or ERC777s could reenter.
lendingPool.depositInLendingPool(assets, msg.sender);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}
/**
* @notice Modification of the standard ERC-4626 mint implementation.
* @param shares The amount of shares minted.
* @param receiver The address that receives the minted shares.
* @return assets The corresponding amount of assets of the underlying ERC-20 token being deposited.
* @dev This contract does not directly transfers the underlying assets from the sender to the receiver.
* Instead it calls the deposit of the Lending Pool which calls the transferFrom of the underlying assets.
* Hence the sender should not give this contract an allowance to transfer the underlying asset but the Lending Pool.
*/
function mint(uint256 shares, address receiver)
public
override
notLocked
notDuringAuction
returns (uint256 assets)
{
assets = previewMintAndSync(shares); // No need to check for rounding error, previewMint rounds up.
// Need to transfer (via lendingPool.depositInLendingPool()) before minting or ERC777s could reenter.
lendingPool.depositInLendingPool(assets, msg.sender);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}
/**
* @notice Modification of the standard ERC-4626 withdraw implementation.
* @param assets The amount of assets of the underlying ERC-20 token being withdrawn.
* @param receiver The address of the receiver of the underlying ERC-20 tokens.
* @param owner_ The address of the owner of the assets being withdrawn.
* @return shares The corresponding amount of shares redeemed.
*/
function withdraw(uint256 assets, address receiver, address owner_)
public
override
notLocked
notDuringAuction
returns (uint256 shares)
{
shares = previewWithdrawAndSync(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner_) {
uint256 allowed = allowance[owner_][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) {
allowance[owner_][msg.sender] = allowed - shares;
}
}
_burn(owner_, shares);
emit Withdraw(msg.sender, receiver, owner_, assets, shares);
lendingPool.withdrawFromLendingPool(assets, receiver);
}
/**
* @notice Modification of the standard ERC-4626 redeem implementation.
* @param shares the amount of shares being redeemed.
* @param receiver The address of the receiver of the underlying ERC-20 tokens.
* @param owner_ The address of the owner of the shares being redeemed.
* @return assets The corresponding amount of assets withdrawn.
*/
function redeem(uint256 shares, address receiver, address owner_)
public
override
notLocked
notDuringAuction
returns (uint256 assets)
{
if (msg.sender != owner_) {
uint256 allowed = allowance[owner_][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) {
allowance[owner_][msg.sender] = allowed - shares;
}
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeemAndSync(shares)) != 0, "T_R: ZERO_ASSETS");
_burn(owner_, shares);
emit Withdraw(msg.sender, receiver, owner_, assets, shares);
lendingPool.withdrawFromLendingPool(assets, receiver);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Returns the total amount of underlying assets, to which liquidity providers have a claim.
* @return assets The total amount of underlying assets, to which liquidity providers have a claim.
* @dev The Liquidity Pool does the accounting of the outstanding claim on liquidity per tranche.
*/
function totalAssets() public view override returns (uint256 assets) {
assets = lendingPool.liquidityOf(address(this));
}
/**
* @dev Modification of totalAssets() where interests are realised (state modification).
*/
function totalAssetsAndSync() public returns (uint256 assets) {
assets = lendingPool.liquidityOfAndSync(address(this));
}
/**
* @dev Modification of convertToShares() where interests are realised (state modification).
*/
function convertToSharesAndSync(uint256 assets) public returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssetsAndSync());
}
/**
* @dev Modification of convertToAssets() where interests are realised (state modification).
*/
function convertToAssetsAndSync(uint256 shares) public returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivDown(totalAssetsAndSync(), supply);
}
/**
* @dev Modification of previewDeposit() where interests are realised (state modification).
*/
function previewDepositAndSync(uint256 assets) public returns (uint256) {
return convertToSharesAndSync(assets);
}
/**
* @dev Modification of previewMint() where interests are realised (state modification).
*/
function previewMintAndSync(uint256 shares) public returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivUp(totalAssetsAndSync(), supply);
}
/**
* @dev Modification of previewWithdraw() where interests are realised (state modification).
*/
function previewWithdrawAndSync(uint256 assets) public returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssetsAndSync());
}
/**
* @dev Modification of previewRedeem() where interests are realised (state modification).
*/
function previewRedeemAndSync(uint256 shares) public returns (uint256) {
return convertToAssetsAndSync(shares);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @dev maxDeposit() according the EIP-4626 specification.
*/
function maxDeposit(address) public view override returns (uint256 maxAssets) {
if (locked || auctionInProgress || IGuardian(address(lendingPool)).depositPaused()) return 0;
uint256 supplyCap = lendingPool.supplyCap();
uint256 realisedLiquidity = lendingPool.totalRealisedLiquidity();
uint256 interests = lendingPool.calcUnrealisedDebt();
if (supplyCap > 0) {
if (realisedLiquidity + interests > supplyCap) return 0;
maxAssets = supplyCap - realisedLiquidity - interests;
} else {
maxAssets = type(uint128).max - realisedLiquidity - interests;
}
}
/**
* @dev maxMint() according the EIP-4626 specification.
*/
function maxMint(address) public view override returns (uint256 maxShares) {
if (locked || auctionInProgress || IGuardian(address(lendingPool)).depositPaused()) return 0;
uint256 supplyCap = lendingPool.supplyCap();
uint256 realisedLiquidity = lendingPool.totalRealisedLiquidity();
uint256 interests = lendingPool.calcUnrealisedDebt();
if (supplyCap > 0) {
if (realisedLiquidity + interests > supplyCap) return 0;
maxShares = convertToShares(supplyCap - realisedLiquidity - interests);
} else {
maxShares = convertToShares(type(uint128).max - realisedLiquidity - interests);
}
}
/**
* @dev maxWithdraw() according the EIP-4626 specification.
*/
function maxWithdraw(address owner_) public view override returns (uint256 maxAssets) {
if (locked || auctionInProgress || IGuardian(address(lendingPool)).withdrawPaused()) return 0;
uint256 availableAssets = asset.balanceOf(address(lendingPool));
uint256 claimableAssets = convertToAssets(balanceOf[owner_]);
maxAssets = availableAssets < claimableAssets ? availableAssets : claimableAssets;
}
/**
* @dev maxRedeem() according the EIP-4626 specification.
*/
function maxRedeem(address owner_) public view override returns (uint256 maxShares) {
if (locked || auctionInProgress || IGuardian(address(lendingPool)).withdrawPaused()) return 0;
uint256 claimableShares = balanceOf[owner_];
if (claimableShares == 0) return 0;
uint256 availableShares = convertToShares(asset.balanceOf(address(lendingPool)));
maxShares = availableShares < claimableShares ? availableShares : claimableShares;
}
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.8.13;
interface IGuardian {
/**
* @notice Returns if Withdrawals are paused or not.
* @return bool indicating if Withdrawals are paused or not.
*/
function withdrawPaused() external view returns (bool);
/**
* @notice Returns if Deposits are paused or not.
* @return bool indicating if Deposits are paused or not.
*/
function depositPaused() external view returns (bool);
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.8.13;
import { ERC20 } from "../../lib/solmate/src/tokens/ERC20.sol";
interface ILendingPool {
/**
* @notice returns the supply cap of the Lending Pool.
* @return supplyCap The supply cap of the Lending Pool.
*/
function supplyCap() external view returns (uint128);
/**
* @notice returns the total realised liquidity of the Lending Pool.
* @return totalRealisedLiquidity The total realised liquidity of the Lending Pool.
*/
function totalRealisedLiquidity() external view returns (uint128);
/**
* @notice Deposit assets in the Lending Pool.
* @param assets The amount of assets of the underlying ERC-20 token being deposited.
* @param from The address of the Liquidity Provider who deposits the underlying ERC-20 token via a Tranche.
*/
function depositInLendingPool(uint256 assets, address from) external;
/**
* @notice Withdraw assets from the Lending Pool.
* @param assets The amount of assets of the underlying ERC-20 tokens being withdrawn.
* @param receiver The address of the receiver of the underlying ERC-20 tokens.
*/
function withdrawFromLendingPool(uint256 assets, address receiver) external;
/**
* @notice Returns the redeemable amount of liquidity in the underlying asset of an address.
* @param owner The address of the liquidity provider.
* @return assets The redeemable amount of liquidity in the underlying asset.
*/
function liquidityOf(address owner) external view returns (uint256);
/**
* @notice liquidityOf, but syncs the unrealised interest first.
* @param owner The address of the liquidity provider.
* @return assets The redeemable amount of liquidity in the underlying asset.
*/
function liquidityOfAndSync(address owner) external returns (uint256);
/**
* @notice Calculates the unrealised debt (interests).
* @return unrealisedDebt The unrealised debt.
*/
function calcUnrealisedDebt() external view returns (uint256);
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity ^0.8.13;
interface ITranche {
/**
* @notice Locks the tranche in case all liquidity of the tranche is written of due to bad debt.
*/
function lock() external;
/**
* @notice Locks the tranche while an auction is in progress.
* @param auctionInProgress Flag indicating if there are auctions in progress.
*/
function setAuctionInProgress(bool auctionInProgress) external;
}