Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/SparkVault.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.25;
import { ERC1967Utils } from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol";
import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import { AccessControlEnumerableUpgradeable }
from "openzeppelin-contracts-upgradeable/contracts/access/extensions/AccessControlEnumerableUpgradeable.sol";
import { UUPSUpgradeable } from "openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
import { ISparkVault } from "./ISparkVault.sol";
interface IERC1271 {
function isValidSignature(bytes32, bytes memory) external view returns (bytes4);
}
/*
███████╗██████╗ █████╗ ██████╗ ██╗ ██╗ ██╗ ██╗ █████╗ ██╗ ██╗██╗ ████████╗
██╔════╝██╔══██╗██╔══██╗██╔══██╗██║ ██╔╝ ██║ ██║██╔══██╗██║ ██║██║ ╚══██╔══╝
███████╗██████╔╝███████║██████╔╝█████╔╝ ██║ ██║███████║██║ ██║██║ ██║
╚════██║██╔═══╝ ██╔══██║██╔══██╗██╔═██╗ ╚██╗ ██╔╝██╔══██║██║ ██║██║ ██║
███████║██║ ██║ ██║██║ ██║██║ ██╗ ╚████╔╝ ██║ ██║╚██████╔╝███████╗██║
╚══════╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝
*/
/// @dev If the inheritance is updated, the functions in `initialize` must be updated as well.
/// Last updated for: `Initializable, UUPSUpgradeable, AccessControlEnumerableUpgradeable,
/// ISparkVault`.
contract SparkVault is AccessControlEnumerableUpgradeable, UUPSUpgradeable, ISparkVault {
/**********************************************************************************************/
/*** Constants ***/
/**********************************************************************************************/
// This corresponds to a 100% APY, verify here:
// bc -l <<< 'scale=27; e( l(2)/(60 * 60 * 24 * 365) )'
uint256 public constant MAX_VSR = 1.000000021979553151239153027e27;
uint256 public constant RAY = 1e27;
bytes32 public constant SETTER_ROLE = keccak256("SETTER_ROLE");
bytes32 public constant TAKER_ROLE = keccak256("TAKER_ROLE");
bytes32 public constant PERMIT_TYPEHASH = keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
);
string public constant version = "1";
uint8 public constant decimals = 18;
/**********************************************************************************************/
/*** Storage variables ***/
/**********************************************************************************************/
address public asset;
string public name;
string public symbol;
uint64 public rho; // Time of last drip [unix epoch time]
uint192 public chi; // The Rate Accumulator [ray]
uint256 public vsr; // The Vault Savings Rate [ray]
uint256 public minVsr; // The minimum Vault Savings Rate [ray]
uint256 public maxVsr; // The maximum Vault Savings Rate [ray]
uint256 public depositCap;
uint256 public totalSupply;
mapping (address => uint256) public balanceOf;
mapping (address => uint256) public nonces;
mapping (address => mapping (address => uint256)) public allowance;
/**********************************************************************************************/
/*** Initialization and upgradeability ***/
/**********************************************************************************************/
constructor() {
_disableInitializers(); // Avoid initializing in the context of the implementation
}
// NOTE: Neither UUPSUpgradeable nor AccessControlEnumerableUpgradeable
// require init functions to be called.
function initialize(address asset_, string memory name_, string memory symbol_, address admin)
initializer external
{
asset = asset_;
name = name_;
symbol = symbol_;
_grantRole(DEFAULT_ADMIN_ROLE, admin);
chi = uint192(RAY);
rho = uint64(block.timestamp);
vsr = RAY;
minVsr = RAY;
maxVsr = RAY;
}
// Only DEFAULT_ADMIN_ROLE can upgrade the implementation
function _authorizeUpgrade(address) internal view override onlyRole(DEFAULT_ADMIN_ROLE) {}
/**********************************************************************************************/
/*** Role-based external functions ***/
/**********************************************************************************************/
function setDepositCap(uint256 newCap) external onlyRole(DEFAULT_ADMIN_ROLE) {
emit DepositCapSet(depositCap, newCap);
depositCap = newCap;
}
function setVsrBounds(uint256 minVsr_, uint256 maxVsr_) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(minVsr_ >= RAY, "SparkVault/vsr-too-low");
require(maxVsr_ <= MAX_VSR, "SparkVault/vsr-too-high");
require(minVsr_ <= maxVsr_, "SparkVault/min-vsr-gt-max-vsr");
emit VsrBoundsSet(minVsr, maxVsr, minVsr_, maxVsr_);
minVsr = minVsr_;
maxVsr = maxVsr_;
}
function setVsr(uint256 newVsr) external onlyRole(SETTER_ROLE) {
require(newVsr >= minVsr, "SparkVault/vsr-too-low");
require(newVsr <= maxVsr, "SparkVault/vsr-too-high");
drip();
uint256 vsr_ = vsr;
vsr = newVsr;
emit VsrSet(msg.sender, vsr_, newVsr);
}
function take(uint256 value) external onlyRole(TAKER_ROLE) {
_pushAsset(msg.sender, value);
emit Take(msg.sender, value);
}
/**********************************************************************************************/
/*** Rate accumulation ***/
/**********************************************************************************************/
function drip() public returns (uint256 nChi) {
(uint256 chi_, uint256 rho_) = (chi, rho);
uint256 diff;
if (block.timestamp > rho_) {
nChi = _rpow(vsr, block.timestamp - rho_) * chi_ / RAY;
uint256 totalSupply_ = totalSupply;
diff = totalSupply_ * nChi / RAY - totalSupply_ * chi_ / RAY;
// Safe as nChi is limited to maxUint256/RAY (which is < maxUint192)
chi = uint192(nChi);
rho = uint64(block.timestamp);
} else {
nChi = chi_;
}
emit Drip(nChi, diff);
}
/**********************************************************************************************/
/*** ERC20 external mutating functions ***/
/**********************************************************************************************/
function approve(address spender, uint256 value) external returns (bool) {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function transfer(address to, uint256 value) external returns (bool) {
require(to != address(0) && to != address(this), "SparkVault/invalid-address");
uint256 balance = balanceOf[msg.sender];
require(balance >= value, "SparkVault/insufficient-balance");
// NOTE: Don't need an overflow check here b/c sum of all balances == totalSupply
unchecked {
balanceOf[msg.sender] = balance - value;
balanceOf[to] += value;
}
emit Transfer(msg.sender, to, value);
return true;
}
function transferFrom(address from, address to, uint256 value) external returns (bool) {
require(to != address(0) && to != address(this), "SparkVault/invalid-address");
uint256 balance = balanceOf[from];
require(balance >= value, "SparkVault/insufficient-balance");
if (from != msg.sender) {
uint256 allowed = allowance[from][msg.sender];
if (allowed != type(uint256).max) {
require(allowed >= value, "SparkVault/insufficient-allowance");
unchecked {
allowance[from][msg.sender] = allowed - value;
}
}
}
// NOTE: Don't need an overflow check here b/c sum of all balances == totalSupply
unchecked {
balanceOf[from] = balance - value;
balanceOf[to] += value;
}
emit Transfer(from, to, value);
return true;
}
/**********************************************************************************************/
/*** EIP712 external mutating functions ***/
/**********************************************************************************************/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
bytes memory signature
) public {
require(block.timestamp <= deadline, "SparkVault/permit-expired");
require(owner != address(0), "SparkVault/invalid-owner");
uint256 nonce;
unchecked { nonce = nonces[owner]++; }
bytes32 digest =
keccak256(abi.encodePacked(
"\x19\x01",
_calculateDomainSeparator(block.chainid),
keccak256(abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
nonce,
deadline
))
));
require(_isValidSignature(owner, digest, signature), "SparkVault/invalid-permit");
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
permit(owner, spender, value, deadline, abi.encodePacked(r, s, v));
}
/**********************************************************************************************/
/*** ERC4626 external mutating functions ***/
/**********************************************************************************************/
function deposit(uint256 assets, address receiver) public returns (uint256 shares) {
shares = assets * RAY / drip();
_mint(assets, shares, receiver);
}
function deposit(uint256 assets, address receiver, uint16 referral)
external returns (uint256 shares)
{
shares = deposit(assets, receiver);
emit Referral(referral, receiver, assets, shares);
}
function mint(uint256 shares, address receiver) public returns (uint256 assets) {
assets = _divup(shares * drip(), RAY);
_mint(assets, shares, receiver);
}
function mint(uint256 shares, address receiver, uint16 referral)
external returns (uint256 assets)
{
assets = mint(shares, receiver);
emit Referral(referral, receiver, assets, shares);
}
function redeem(uint256 shares, address receiver, address owner)
external returns (uint256 assets)
{
assets = shares * drip() / RAY;
_burn(assets, shares, receiver, owner);
}
function withdraw(uint256 assets, address receiver, address owner)
external returns (uint256 shares)
{
shares = _divup(assets * RAY, drip());
_burn(assets, shares, receiver, owner);
}
/**********************************************************************************************/
/*** ERC4626 external view functions ***/
/**********************************************************************************************/
function convertToAssets(uint256 shares) public view returns (uint256) {
return shares * nowChi() / RAY;
}
function convertToShares(uint256 assets) public view returns (uint256) {
return assets * RAY / nowChi();
}
function maxDeposit(address) external view returns (uint256) {
uint256 totalAssets_ = totalAssets();
uint256 depositCap_ = depositCap;
return depositCap_ <= totalAssets_ ? 0 : depositCap_ - totalAssets_;
}
function maxMint(address) external view returns (uint256) {
uint256 depositCap_ = depositCap;
// NOTE: Prevents overflow on (depositCap_ - totalAssets_) * RAY below, values above
// type(uint256).max / RAY are considered "infinite".
if (depositCap_ > type(uint256).max / RAY) return type(uint256).max;
uint256 totalAssets_ = totalAssets();
return depositCap_ <= totalAssets_ ? 0 : (depositCap_ - totalAssets_) * RAY / nowChi();
}
function maxRedeem(address owner) external view returns (uint256) {
// NOTE: Rounds down to be conservative. This may sometimes result in a maxRedeem result
// that is not actually the max. However, rounding up could sometimes result in a maxRedeem
// that is too high.
uint256 maxShares = IERC20(asset).balanceOf(address(this)) * RAY / nowChi();
uint256 userShares = balanceOf[owner];
return maxShares > userShares ? userShares : maxShares;
}
function maxWithdraw(address owner) external view returns (uint256) {
uint256 liquidity = IERC20(asset).balanceOf(address(this));
uint256 userAssets = assetsOf(owner);
return liquidity > userAssets ? userAssets : liquidity;
}
function previewDeposit(uint256 assets) external view returns (uint256) {
return convertToShares(assets);
}
function previewMint(uint256 shares) external view returns (uint256) {
return _divup(shares * nowChi(), RAY);
}
function previewRedeem(uint256 shares) external view returns (uint256 amount) {
amount = convertToAssets(shares);
require(
IERC20(asset).balanceOf(address(this)) >= amount,
"SparkVault/insufficient-liquidity"
);
}
function previewWithdraw(uint256 assets) external view returns (uint256) {
require(
IERC20(asset).balanceOf(address(this)) >= assets,
"SparkVault/insufficient-liquidity"
);
return _divup(assets * RAY, nowChi());
}
function totalAssets() public view returns (uint256) {
return convertToAssets(totalSupply);
}
/**********************************************************************************************/
/*** Convenience view functions ***/
/**********************************************************************************************/
function assetsOf(address owner) public view returns (uint256) {
return convertToAssets(balanceOf[owner]);
}
function assetsOutstanding() public view returns (uint256) {
uint256 liquidity_ = IERC20(asset).balanceOf(address(this));
uint256 totalAssets_ = totalAssets();
return totalAssets_ >= liquidity_ ? totalAssets_ - liquidity_ : 0;
}
function getImplementation() external view returns (address) {
return ERC1967Utils.getImplementation();
}
function nowChi() public view returns (uint256) {
return (block.timestamp > rho) ? _rpow(vsr, block.timestamp - rho) * chi / RAY : chi;
}
/**********************************************************************************************/
/*** Token transfer internal helper functions ***/
/**********************************************************************************************/
function _burn(uint256 assets, uint256 shares, address receiver, address owner) internal {
uint256 balance = balanceOf[owner];
require(balance >= shares, "SparkVault/insufficient-balance");
if (owner != msg.sender) {
uint256 allowed = allowance[owner][msg.sender];
if (allowed != type(uint256).max) {
require(allowed >= shares, "SparkVault/insufficient-allowance");
unchecked {
allowance[owner][msg.sender] = allowed - shares;
}
}
}
// NOTE: Don't need overflow checks as require(balance >= shares)
// and balance <= totalSupply
unchecked {
balanceOf[owner] = balance - shares;
totalSupply = totalSupply - shares;
}
_pushAsset(receiver, assets);
emit Transfer(owner, address(0), shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
}
function _mint(uint256 assets, uint256 shares, address receiver) internal {
require(receiver != address(0) && receiver != address(this), "SparkVault/invalid-address");
require(
!hasRole(TAKER_ROLE, msg.sender) && !hasRole(TAKER_ROLE, receiver),
"SparkVault/taker-cannot-deposit"
);
require(totalAssets() + assets <= depositCap, "SparkVault/deposit-cap-exceeded");
_pullAsset(msg.sender, assets);
totalSupply = totalSupply + shares;
// NOTE: balanceOf unchecked addition is secure as balanceOf[receiver] <= totalSupply.
unchecked {
balanceOf[receiver] = balanceOf[receiver] + shares;
}
emit Deposit(msg.sender, receiver, assets, shares);
emit Transfer(address(0), receiver, shares);
}
function _pullAsset(address from, uint256 value) internal {
SafeERC20.safeTransferFrom(IERC20(asset), from, address(this), value);
}
function _pushAsset(address to, uint256 value) internal {
require(
value <= IERC20(asset).balanceOf(address(this)),
"SparkVault/insufficient-liquidity"
);
SafeERC20.safeTransfer(IERC20(asset), to, value);
}
/**********************************************************************************************/
/*** EIP712 internal helper functions ***/
/**********************************************************************************************/
function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {
return keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256(bytes(version)),
chainId,
address(this)
)
);
}
function DOMAIN_SEPARATOR() external view returns (bytes32) {
return _calculateDomainSeparator(block.chainid);
}
function _isValidSignature(
address signer,
bytes32 digest,
bytes memory signature
) internal view returns (bool valid) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
if (signer == ecrecover(digest, v, r, s)) {
return true;
}
}
if (signer.code.length > 0) {
(bool success, bytes memory result) = signer.staticcall(
abi.encodeCall(IERC1271.isValidSignature, (digest, signature))
);
valid = (success &&
result.length == 32 &&
abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);
}
}
/**********************************************************************************************/
/*** General internal helper functions ***/
/**********************************************************************************************/
function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) {
// NOTE: _divup(0,0) will return 0 differing from natural solidity division
unchecked {
z = x != 0 ? ((x - 1) / y) + 1 : 0;
}
}
function _rpow(uint256 x, uint256 n) internal pure returns (uint256 z) {
assembly {
switch x case 0 {switch n case 0 {z := RAY} default {z := 0}}
default {
switch mod(n, 2) case 0 { z := RAY } default { z := x }
let half := div(RAY, 2) // for rounding.
for { n := div(n, 2) } n { n := div(n,2) } {
let xx := mul(x, x)
if iszero(eq(div(xx, x), x)) { revert(0,0) }
let xxRound := add(xx, half)
if lt(xxRound, xx) { revert(0,0) }
x := div(xxRound, RAY)
if mod(n,2) {
let zx := mul(z, x)
if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) }
let zxRound := add(zx, half)
if lt(zxRound, zx) { revert(0,0) }
z := div(zxRound, RAY)
}
}
}
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (proxy/ERC1967/ERC1967Utils.sol)
pragma solidity ^0.8.21;
import {IBeacon} from "../beacon/IBeacon.sol";
import {IERC1967} from "../../interfaces/IERC1967.sol";
import {Address} from "../../utils/Address.sol";
import {StorageSlot} from "../../utils/StorageSlot.sol";
/**
* @dev This library provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] slots.
*/
library ERC1967Utils {
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev The `implementation` of the proxy is invalid.
*/
error ERC1967InvalidImplementation(address implementation);
/**
* @dev The `admin` of the proxy is invalid.
*/
error ERC1967InvalidAdmin(address admin);
/**
* @dev The `beacon` of the proxy is invalid.
*/
error ERC1967InvalidBeacon(address beacon);
/**
* @dev An upgrade function sees `msg.value > 0` that may be lost.
*/
error ERC1967NonPayable();
/**
* @dev Returns the current implementation address.
*/
function getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the ERC-1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
if (newImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(newImplementation);
}
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Performs implementation upgrade with additional setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-Upgraded} event.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) internal {
_setImplementation(newImplementation);
emit IERC1967.Upgraded(newImplementation);
if (data.length > 0) {
Address.functionDelegateCall(newImplementation, data);
} else {
_checkNonPayable();
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the ERC-1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
if (newAdmin == address(0)) {
revert ERC1967InvalidAdmin(address(0));
}
StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {IERC1967-AdminChanged} event.
*/
function changeAdmin(address newAdmin) internal {
emit IERC1967.AdminChanged(getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the ERC-1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
if (newBeacon.code.length == 0) {
revert ERC1967InvalidBeacon(newBeacon);
}
StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
address beaconImplementation = IBeacon(newBeacon).implementation();
if (beaconImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(beaconImplementation);
}
}
/**
* @dev Change the beacon and trigger a setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-BeaconUpgraded} event.
*
* CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
* it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
* efficiency.
*/
function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
_setBeacon(newBeacon);
emit IERC1967.BeaconUpgraded(newBeacon);
if (data.length > 0) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
} else {
_checkNonPayable();
}
}
/**
* @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
* if an upgrade doesn't perform an initialization call.
*/
function _checkNonPayable() private {
if (msg.value > 0) {
revert ERC1967NonPayable();
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 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.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @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).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @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).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/access/extensions/AccessControlEnumerableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (access/extensions/AccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/IAccessControlEnumerable.sol";
import {AccessControlUpgradeable} from "../AccessControlUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerable, AccessControlUpgradeable {
using EnumerableSet for EnumerableSet.AddressSet;
/// @custom:storage-location erc7201:openzeppelin.storage.AccessControlEnumerable
struct AccessControlEnumerableStorage {
mapping(bytes32 role => EnumerableSet.AddressSet) _roleMembers;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControlEnumerable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlEnumerableStorageLocation = 0xc1f6fe24621ce81ec5827caf0253cadb74709b061630e6b55e82371705932000;
function _getAccessControlEnumerableStorage() private pure returns (AccessControlEnumerableStorage storage $) {
assembly {
$.slot := AccessControlEnumerableStorageLocation
}
}
function __AccessControlEnumerable_init() internal onlyInitializing {
}
function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
}
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].length();
}
/**
* @dev Return all accounts that have `role`
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function getRoleMembers(bytes32 role) public view virtual returns (address[] memory) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].values();
}
/**
* @dev Overload {AccessControl-_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
bool granted = super._grantRole(role, account);
if (granted) {
$._roleMembers[role].add(account);
}
return granted;
}
/**
* @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
bool revoked = super._revokeRole(role, account);
if (revoked) {
$._roleMembers[role].remove(account);
}
return revoked;
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.22;
import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*/
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
_checkProxy();
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
_checkNotDelegated();
_;
}
function __UUPSUpgradeable_init() internal onlyInitializing {
}
function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
}
/**
* @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
return ERC1967Utils.IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
/**
* @dev Reverts if the execution is not performed via delegatecall or the execution
* context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
*/
function _checkProxy() internal view virtual {
if (
address(this) == __self || // Must be called through delegatecall
ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
) {
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Reverts if the execution is performed via delegatecall.
* See {notDelegated}.
*/
function _checkNotDelegated() internal view virtual {
if (address(this) != __self) {
// Must not be called through delegatecall
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
/**
* @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
*
* As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
* is expected to be the implementation slot in ERC-1967.
*
* Emits an {IERC1967-Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} catch {
// The implementation is not UUPS
revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
}
}
}
"
},
"src/ISparkVault.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2021 Dai Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.0;
import { IAccessControlEnumerable } from "openzeppelin-contracts/contracts/access/extensions/IAccessControlEnumerable.sol";
import { IERC4626 } from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
import { IERC20Permit } from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol";
interface ISparkVault is IERC20Permit, IERC4626, IAccessControlEnumerable {
/**
* @notice Emitted every time drip() is called.
* @param chi The new rate accumulator value after the drip operation [ray]
* @param diff The difference in total assets due to the rate accumulation [asset units]
*/
event Drip(uint256 chi, uint256 diff);
/**
* @notice Emitted when assets are deposited or shares are minted with referral tracking.
* @param referral The referral ID (16-bit) used for tracking user acquisition
* @param owner The address receiving the minted shares
* @param assets The amount of underlying assets deposited/minted
* @param shares The amount of vault shares minted to the owner
*/
event Referral(uint16 indexed referral, address indexed owner, uint256 assets, uint256 shares);
/**
* @notice Emitted when the maximum deposit cap is updated.
* @param oldCap The previous maximum deposit cap [asset units]
* @param newCap The new maximum deposit cap [asset units]
*/
event DepositCapSet(uint256 oldCap, uint256 newCap);
/**
* @notice Emitted when the bounds for the Vault Savings Rate (VSR) are updated.
* @param oldMinVsr The previous minimum allowed VSR value [ray]
* @param oldMaxVsr The previous maximum allowed VSR value [ray]
* @param newMinVsr The new minimum allowed VSR value [ray]
* @param newMaxVsr The new maximum allowed VSR value [ray]
*/
event VsrBoundsSet(uint256 oldMinVsr, uint256 oldMaxVsr, uint256 newMinVsr, uint256 newMaxVsr);
/**
* @notice Emitted when the Vault Savings Rate (VSR) is updated.
* @param sender The address that called setVsr() to update the rate
* @param oldVsr The previous VSR value before the update [ray]
* @param newVsr The new VSR value after the update [ray]
*/
event VsrSet(address indexed sender, uint256 oldVsr, uint256 newVsr);
/**
* @notice Emitted when assets are withdrawn from the vault by accounts with TAKER_ROLE.
* @param to The address receiving the withdrawn assets
* @param value The amount of assets withdrawn from the vault [asset units]
*/
event Take(address indexed to, uint256 value);
/**
* @notice Returns the current rate accumulator (chi).
* @dev Chi represents the cumulative growth factor for all shares. It starts at 1e27 (RAY) and
* increases exponentially over time based on the Vault Savings Rate (VSR). The formula is:
* chi = chi_old * (vsr)^(time_delta) / RAY where time_delta is the time since last drip.
* User assets = user_shares * nowChi() / RAY
* @return The current rate accumulator value [ray]
*/
function chi() external view returns (uint192);
/**
* @notice Returns the current rate accumulator (chi) calculated up to the current block timestamp.
* @return The current rate accumulator value [ray]
*/
function nowChi() external view returns (uint256);
/**
* @notice Updates the rate accumulator and returns the new value.
* @dev This function calculates the new chi value based on the time elapsed since the last drip
* and the current VSR. The formula used is:
* new_chi = old_chi * (vsr)^(block.timestamp - rho) / RAY
* @return nChi The new Chi value [ray]
*/
function drip() external returns (uint256);
/**
* @notice Returns the timestamp of the last drip operation.
* @dev rho tracks when the rate accumulator was last updated.
* @return The timestamp of the last drip [unix epoch time]
*/
function rho() external view returns (uint64);
/**
* @notice Sets the deposit cap for the vault.
* @dev This function can only be called by accounts with DEFAULT_ADMIN_ROLE.
Deposits (and mints) are disabled if it would put totalAssets() above this value
* @param newCap The new deposit cap value [asset units]
*/
function setDepositCap(uint256 newCap) external;
/**
* @notice Sets the Vault Savings Rate (VSR) within the configured bounds.
* @dev This function can only be called by accounts with SETTER_ROLE.
* The VSR determines the rate at which user shares grow over time. A higher VSR
* means faster share growth and higher yields for depositors.
* @param newVsr The new VSR value [ray]
*/
function setVsr(uint256 newVsr) external;
/**
* @notice Sets the bounds for the Vault Savings Rate (VSR).
* @dev This function can only be called by accounts with DEFAULT_ADMIN_ROLE.
* @param minVsr_ The new minimum allowed VSR value [ray]
* @param maxVsr_ The new maximum allowed VSR value [ray]
*/
function setVsrBounds(uint256 minVsr_, uint256 maxVsr_) external;
/**
* @notice Returns the current Vault Savings Rate (VSR).
* @dev The VSR is the rate at which the vault's shares appreciate in value over time.
* It's expressed in ray (1e27).
* @return The current VSR value [ray]
*/
function vsr() external view returns (uint256);
/**
* @notice Deposits specified assets and mints shares.
* @param assets The amount of assets to deposit
* @param receiver The address to receive the minted shares
* @param referral The referral ID (16-bit) for tracking
* @return shares The amount of shares minted
*/
function deposit(uint256 assets, address receiver, uint16 referral) external returns (uint256 shares);
/**
* @notice Mints specified shares and pulls assets from the caller.
* @param shares The amount of shares to mint
* @param receiver The address to receive the minted shares
* @param referral The referral ID (16-bit) for tracking
* @return assets The amount of assets transferred from the caller.
*/
function mint(uint256 shares, address receiver, uint16 referral) external returns (uint256 assets);
/**
* @notice Allows authorized accounts to withdraw assets from the vault.
* @dev This function can only be called by accounts with TAKER_ROLE.
* The function transfers the specified amount of assets to the caller.
* @param value The amount of assets to withdraw
*/
function take(uint256 value) external;
/**
* @notice Returns the version of the vault implementation.
* @return The version string.
*/
function version() external view returns (string memory);
}
"
},
"lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (proxy/beacon/IBeacon.sol)
pragma solidity >=0.4.16;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {UpgradeableBeacon} will check that this address is a contract.
*/
function implementation() external view returns (address);
}
"
},
"lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1967.sol)
pragma solidity >=0.4.11;
/**
* @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
*/
interface IERC1967 {
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Address.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @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://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]
Submitted on: 2025-09-19 14:56:35
Comments
Log in to comment.
No comments yet.