Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/oracles/VaultPriceManager.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.28;
import { FixedPointMathLib } from "src/utils/FixedPointMathLib.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { IUltraVaultOracle, Price } from "src/interfaces/IUltraVaultOracle.sol";
import { IOwnable } from "src/interfaces/IOwnable.sol";
import { IERC20Supply } from "src/interfaces/IERC20Supply.sol";
import { IPausable } from "src/interfaces/IPausable.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
/// @notice Price update struct
struct PriceUpdate {
// The vault to update the price for
address vault;
// The asset to update the price for (the asset the vault is denominated in)
address asset;
// The share value in assets
uint256 shareValueInAssets;
}
/// @notice Safety limits for price updates
struct Limit {
// Maximum allowed price jump from one update to the next (1e18 = 100%)
uint256 jump;
// Maximum allowed drawdown from the highwaterMark (1e18 = 100%)
uint256 drawdown;
}
/// @title VaultPriceManager
/// @notice Contract managing vault price updates with limits
/// @dev Has built in safety mechanisms to pause vault upon sudden moves
contract VaultPriceManager is Ownable2Step {
using FixedPointMathLib for uint256;
////////////
// Events //
////////////
event VaultAdded(address indexed vault);
event OracleUpdated(address indexed oldOracle, address indexed newOracle);
event LimitsUpdated(address indexed vault, Limit oldLimit, Limit newLimit);
event AdminUpdated(address indexed vault, address indexed admin, bool isAdmin);
////////////
// Errors //
////////////
error ZeroOracleAddress();
error CannotAddNonEmptyVault();
error InputLengthMismatch();
error NotAdminOrOwner();
error InvalidLimit();
///////////////
// Constants //
///////////////
uint256 internal constant SCALE = 1e18;
uint256 public constant MAX_JUMP_LIMIT = SCALE;
uint256 public constant MAX_DRAWDOWN_LIMIT = SCALE;
/////////////
// Storage //
/////////////
IUltraVaultOracle public oracle;
mapping(address => uint256) public highwaterMarks; // vault => highwaterMark
mapping(address => Limit) public limits; // vault => Limit
mapping(address => mapping(address => bool)) public isAdmin; // vault => admin => isAdmin
/////////////////
// Constructor //
/////////////////
/// @notice Initialize controller with oracle and owner
/// @param _oracle Oracle contract address
/// @param _owner Owner address
constructor(address _oracle, address _owner) Ownable(_owner) {
require(_oracle != address(0), ZeroOracleAddress());
oracle = IUltraVaultOracle(_oracle);
}
//////////////////
// Oracle Logic //
//////////////////
/// @notice Add vault to controller
/// @param vault Vault address to add
/// @dev Initializes price to 1e18 (1:1)
/// @dev Must be called before vault receives deposits
function addVault(address vault) external onlyOwner {
// Ensure vault is empty
require(IERC20Supply(vault).totalSupply() == 0, CannotAddNonEmptyVault());
uint256 initialPrice = SCALE;
highwaterMarks[vault] = initialPrice;
oracle.setPrice(vault, address(IERC4626(vault).asset()), initialPrice);
emit VaultAdded(vault);
}
/// @notice Update vault price and highwaterMark
/// @param priceUpdate Price update data
/// @dev Pauses vault on large price jumps or drawdowns
function updatePriceInstantly(PriceUpdate calldata priceUpdate) external {
_updatePriceInstantly(priceUpdate);
}
/// @notice Update prices for multiple vaults
/// @param priceUpdates Array of price updates
function updatePricesInstantly(PriceUpdate[] calldata priceUpdates) external {
for (uint256 i; i < priceUpdates.length; i++) {
_updatePriceInstantly(priceUpdates[i]);
}
}
/// @notice Internal price update function
function _updatePriceInstantly(
PriceUpdate calldata priceUpdate
) internal onlyAdminOrOwner(priceUpdate.vault) {
_checkSuddenMovements(priceUpdate);
oracle.setPrice(
priceUpdate.vault,
priceUpdate.asset,
priceUpdate.shareValueInAssets
);
}
/// @notice Update vault price gradually over multiple blocks
/// @param priceUpdate Price update data
/// @param duration Vesting duration
/// @dev Pauses vault on large price jumps
function updatePriceWithVesting(
PriceUpdate calldata priceUpdate,
uint256 duration
) external {
_updatePriceWithVesting(priceUpdate, duration);
}
/// @notice Update prices for multiple vaults gradually
/// @param priceUpdates Array of price updates
/// @param durations Array of vesting durations
function updatePricesWithVesting(
PriceUpdate[] calldata priceUpdates,
uint256[] calldata durations
) external {
require(priceUpdates.length == durations.length, InputLengthMismatch());
for (uint256 i; i < priceUpdates.length; i++) {
_updatePriceWithVesting(
priceUpdates[i],
durations[i]
);
}
}
/// @notice Internal gradual price update function
function _updatePriceWithVesting(
PriceUpdate calldata priceUpdate,
uint256 duration
) internal onlyAdminOrOwner(priceUpdate.vault) {
_checkSuddenMovements(priceUpdate);
oracle.scheduleLinearPriceUpdate(
priceUpdate.vault,
priceUpdate.asset,
priceUpdate.shareValueInAssets,
duration
);
}
/// @notice Check price update for sudden price swings and update highwatermark
function _checkSuddenMovements(
PriceUpdate calldata priceUpdate
) internal {
uint256 lastPrice = oracle.getCurrentPrice(
priceUpdate.vault,
priceUpdate.asset
);
uint256 highwaterMark = highwaterMarks[priceUpdate.vault];
Limit memory limit = limits[priceUpdate.vault];
if (
// Sudden drop
priceUpdate.shareValueInAssets <
lastPrice.mulDivDown(SCALE - limit.jump, SCALE) ||
// Sudden increase
priceUpdate.shareValueInAssets >
lastPrice.mulDivDown(SCALE + limit.jump, SCALE) ||
// Drawdown check
priceUpdate.shareValueInAssets <
highwaterMark.mulDivDown(SCALE - limit.drawdown, SCALE)
) {
IPausable vault = IPausable(priceUpdate.vault);
if (!vault.paused()) {
vault.pause();
}
} else if (priceUpdate.shareValueInAssets > highwaterMark) {
highwaterMarks[priceUpdate.vault] = priceUpdate.shareValueInAssets;
}
}
///////////////////
// Oracle Update //
///////////////////
/// @notice Set oracle address
/// @param _newOracle new oracle address
function setOracle(
address _newOracle
) external onlyOwner {
if (address(oracle) != _newOracle) {
emit OracleUpdated(address(oracle), _newOracle);
oracle = IUltraVaultOracle(_newOracle);
}
}
//////////////////////
// Admin Role Logic //
//////////////////////
/// @notice Set vault admin
/// @param _vault Vault address
/// @param _admin Admin address
/// @param _isAdmin Whether to add or remove admin
function setAdmin(
address _vault,
address _admin,
bool _isAdmin
) external onlyOwner {
if (isAdmin[_vault][_admin] != _isAdmin) {
emit AdminUpdated(_vault, _admin, _isAdmin);
isAdmin[_vault][_admin] = _isAdmin;
}
}
/// @notice Modifier for admin/owner access
/// @param _vault Vault to check access for
modifier onlyAdminOrOwner(address _vault) {
require(msg.sender == owner() || isAdmin[_vault][msg.sender], NotAdminOrOwner());
_;
}
///////////////////
// Limits Update //
///////////////////
/// @notice Set vault price limits
/// @param _vault Vault address
/// @param _limit Price limits to set
function setLimits(address _vault, Limit memory _limit) external onlyOwner {
_setLimits(_vault, _limit);
}
/// @notice Set price limits for multiple vaults
/// @param _vaults Array of vault addresses
/// @param _limits Array of price limits
function setLimits(
address[] memory _vaults,
Limit[] memory _limits
) external onlyOwner {
require(_vaults.length == _limits.length, InputLengthMismatch());
for (uint256 i; i < _vaults.length; i++) {
_setLimits(_vaults[i], _limits[i]);
}
}
function _setLimits(address _vault, Limit memory _limit) internal {
require(_limit.jump <= MAX_JUMP_LIMIT && _limit.drawdown <= MAX_DRAWDOWN_LIMIT, InvalidLimit());
Limit memory oldLimit = limits[_vault];
if (
_limit.jump != oldLimit.jump ||
_limit.drawdown != oldLimit.drawdown
) {
emit LimitsUpdated(_vault, oldLimit, _limit);
limits[_vault] = _limit;
}
}
///////////
// Utils //
///////////
/// @notice Claim oracle ownership
function claimOracleOwnership() external onlyOwner {
IOwnable(address(oracle)).acceptOwnership();
}
}
"
},
"src/utils/FixedPointMathLib.sol": {
"content": "// 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))
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (interfaces/IERC4626.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
/**
* @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
"
},
"src/interfaces/IUltraVaultOracle.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
import { IPriceSource } from "./IPriceSource.sol";
/// @notice Price data for base/quote pair
/// @param price Current price
/// @param targetPrice Target price for gradual changes
/// @param timestampForFullVesting Target timestamp for full vesting
/// @param lastUpdatedTimestamp Last timestamp price was updated
struct Price {
uint256 price;
uint256 targetPrice;
uint256 timestampForFullVesting;
uint256 lastUpdatedTimestamp;
}
/// @title IUltraVaultOracle
/// @notice Interface for push-based price oracle
/// @dev Extends IPriceSource with price setting capabilities
interface IUltraVaultOracle is IPriceSource {
////////////
// Events //
////////////
event PriceUpdated(
address indexed base,
address indexed quote,
uint256 price,
uint256 targetPrice,
uint256 timestampForFullVesting
);
////////////
// Errors //
////////////
error NoPriceData(address base, address quote);
error InputLengthMismatch();
error InvalidVestingTime(address base, address quote, uint256 vestingTime);
error ZeroVestingStartPrice(address base, address quote);
error InvalidAssetsDecimals();
////////////////////
// View Functions //
////////////////////
/// @notice Get current price for base/quote pair
/// @param base The base asset
/// @param quote The quote asset
/// @return Current price of base in terms of quote
function getCurrentPrice(
address base,
address quote
) external view returns (uint256);
/// @notice Get price data for base/quote pair
/// @param base The base asset
/// @param quote The quote asset
/// @return Price data for the pair
function prices(
address base,
address quote
) external view returns (Price memory);
/////////////////////
// Write Functions //
/////////////////////
/// @notice Set base/quote pair price
/// @param base The base asset
/// @param quote The quote asset
/// @param price The price of the base in terms of the quote
function setPrice(
address base,
address quote,
uint256 price
) external;
/// @notice Set multiple base/quote pair prices
/// @param bases The base assets
/// @param quotes The quote assets
/// @param prices The prices of the bases in terms of the quotes
/// @dev Array lengths must match
function setPrices(
address[] memory bases,
address[] memory quotes,
uint256[] memory prices
) external;
/// @notice Set base/quote pair price with gradual change
/// @param base The base asset
/// @param quote The quote asset
/// @param targetPrice The target price of the base in terms of the quote
/// @param vestingTime The time over which vesting would occur
function scheduleLinearPriceUpdate(
address base,
address quote,
uint256 targetPrice,
uint256 vestingTime
) external;
/// @notice Set multiple base/quote pair prices with gradual changes
/// @param bases The base assets
/// @param quotes The quote assets
/// @param targetPrices The target prices of the bases in terms of the quotes
/// @param vestingTimes The times over which vesting would occur
/// @dev Array lengths must match
function scheduleLinearPricesUpdates(
address[] memory bases,
address[] memory quotes,
uint256[] memory targetPrices,
uint256[] memory vestingTimes
) external;
}
"
},
"src/interfaces/IOwnable.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
interface IOwnable {
////////////
// Events //
////////////
/// @dev Emitted when ownership is transferred.
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
////////////
// Errors //
////////////
/// @dev The caller account is not authorized to perform an operation.
error OwnableUnauthorizedAccount(address account);
/// @dev The owner is not a valid owner account. (eg. `address(0)`)
error OwnableInvalidOwner(address owner);
///////////////
// Functions //
///////////////
/// @dev Returns the address of the current owner.
function owner() external view returns (address);
/// @dev Returns the address of the pending owner.
function pendingOwner() external view returns (address);
/// @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
/// Can only be called by the current owner.
///
/// Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
function transferOwnership(address newOwner) external;
/// @dev The new owner accepts the ownership transfer.
function acceptOwnership() external;
}
"
},
"src/interfaces/IERC20Supply.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
/**
* @dev Concise interface of the ERC-20 token to allow balance fetching
*/
interface IERC20Supply {
/**
* @dev Returns the total supply of the token
*/
function totalSupply() external view returns (uint256);
}
"
},
"src/interfaces/IPausable.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
interface IPausable {
/// @notice Returns the paused status of the vault
/// @return paused The paused status of the vault
function paused() external view returns (bool);
/// @notice Pauses the contract
function pause() external;
/// @notice Unpauses the contract
function unpause() external;
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.20;
import {Ownable} from "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* This extension of the {Ownable} contract includes a two-step mechanism to transfer
* ownership, where the new owner must call {acceptOwnership} in order to replace the
* old one. This can help prevent common mistakes, such as transfers of ownership to
* incorrect accounts, or to contracts that are unable to interact with the
* permission system.
*
* The initial owner is specified at deployment time in the constructor for `Ownable`. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*
* Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
if (pendingOwner() != sender) {
revert OwnableUnauthorizedAccount(sender);
}
_transferOwnership(sender);
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @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/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
"
},
"src/interfaces/IPriceSource.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
interface IPriceSource {
/// @notice Get one-sided price quote
/// @param inAmount Amount of base token to convert
/// @param base Token being priced
/// @param quote Token used as unit of account
/// @return outAmount Amount of quote token equivalent to inAmount of base
/// @dev Assumes no price spread
function getQuote(
uint256 inAmount,
address base,
address quote
) external view returns (uint256 outAmount);
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
}
},
"settings": {
"remappings": [
"ds-test/=lib/forge-std/lib/ds-test/src/",
"forge-std/=lib/forge-std/src/",
"solady/=lib/solady/src/",
"@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/",
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
"openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/",
"ERC-7540/=lib/ERC-7540-Reference/src/",
"ERC-7540-Reference/=lib/ERC-7540-Reference/src/",
"erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
"halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/",
"solmate/=lib/ERC-7540-Reference/lib/solmate/src/"
],
"optimizer": {
"enabled": true,
"runs": 50
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "none",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false
}
}}
Submitted on: 2025-11-04 12:46:38
Comments
Log in to comment.
No comments yet.