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": {
"contracts/v2/factory/TermMax4626Factory.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {StakingBuffer} from "../tokens/StakingBuffer.sol";
import {StableERC4626For4626} from "../tokens/StableERC4626For4626.sol";
import {StableERC4626ForAave} from "../tokens/StableERC4626ForAave.sol";
import {VariableERC4626ForAave} from "../tokens/VariableERC4626ForAave.sol";
import {FactoryEventsV2} from "../events/FactoryEventsV2.sol";
import {VersionV2} from "../VersionV2.sol";
contract TermMax4626Factory is VersionV2 {
using Clones for address;
address public immutable stableERC4626For4626Implementation;
address public immutable stableERC4626ForAaveImplementation;
address public immutable variableERC4626ForAaveImplementation;
constructor(address aavePool, uint16 aaveReferralCode) {
stableERC4626For4626Implementation = address(new StableERC4626For4626());
stableERC4626ForAaveImplementation = address(new StableERC4626ForAave(aavePool, aaveReferralCode));
variableERC4626ForAaveImplementation = address(new VariableERC4626ForAave(aavePool, aaveReferralCode));
emit FactoryEventsV2.TermMax4626FactoryInitialized(
aavePool,
aaveReferralCode,
stableERC4626For4626Implementation,
stableERC4626ForAaveImplementation,
variableERC4626ForAaveImplementation
);
}
function createStableERC4626For4626(
address admin,
address thirdPool,
StakingBuffer.BufferConfig memory bufferConfig
) external returns (StableERC4626For4626) {
StableERC4626For4626 instance = StableERC4626For4626(stableERC4626For4626Implementation.clone());
instance.initialize(admin, thirdPool, bufferConfig);
emit FactoryEventsV2.StableERC4626For4626Created(msg.sender, address(instance));
return instance;
}
function createStableERC4626ForAave(
address admin,
address underlying,
StakingBuffer.BufferConfig memory bufferConfig
) public returns (StableERC4626ForAave) {
StableERC4626ForAave instance = StableERC4626ForAave(stableERC4626ForAaveImplementation.clone());
instance.initialize(admin, underlying, bufferConfig);
emit FactoryEventsV2.StableERC4626ForAaveCreated(msg.sender, address(instance));
return instance;
}
function createVariableERC4626ForAave(
address admin,
address underlying,
StakingBuffer.BufferConfig memory bufferConfig
) public returns (VariableERC4626ForAave) {
VariableERC4626ForAave instance = VariableERC4626ForAave(variableERC4626ForAaveImplementation.clone());
instance.initialize(admin, underlying, bufferConfig);
emit FactoryEventsV2.VariableERC4626ForAaveCreated(msg.sender, address(instance));
return instance;
}
function createVariableAndStableERC4626ForAave(
address admin,
address underlying,
StakingBuffer.BufferConfig memory bufferConfig
) external returns (VariableERC4626ForAave, StableERC4626ForAave) {
VariableERC4626ForAave variableInstance = createVariableERC4626ForAave(admin, underlying, bufferConfig);
StableERC4626ForAave stableInstance = createStableERC4626ForAave(admin, underlying, bufferConfig);
return (variableInstance, stableInstance);
}
}
"
},
"dependencies/@openzeppelin-contracts-5.2.0/proxy/Clones.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/Clones.sol)
pragma solidity ^0.8.20;
import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*/
library Clones {
error CloneArgumentsTooLong();
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address implementation) internal returns (address instance) {
return clone(implementation, 0);
}
/**
* @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
* to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function clone(address implementation, uint256 value) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(value, 0x09, 0x37)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple times will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
return cloneDeterministic(implementation, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
* a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministic(
address implementation,
bytes32 salt,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(value, 0x09, 0x37, salt)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create opcode, which should never revert.
*/
function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
return cloneWithImmutableArgs(implementation, args, 0);
}
/**
* @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
* parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneWithImmutableArgs(
address implementation,
bytes memory args,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
assembly ("memory-safe") {
instance := create(value, add(bytecode, 0x20), mload(bytecode))
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
* `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
* at the same address.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
* but with a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
uint256 value
) internal returns (address instance) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.deploy(value, salt, bytecode);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.computeAddress(salt, keccak256(bytecode), deployer);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
}
/**
* @dev Get the immutable args attached to a clone.
*
* - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
* function will return an empty array.
* - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
* `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
* creation.
* - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
* function should only be used to check addresses that are known to be clones.
*/
function fetchCloneArgs(address instance) internal view returns (bytes memory) {
bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
assembly ("memory-safe") {
extcodecopy(instance, add(result, 32), 45, mload(result))
}
return result;
}
/**
* @dev Helper that prepares the initcode of the proxy with immutable args.
*
* An assembly variant of this function requires copying the `args` array, which can be efficiently done using
* `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
* abi.encodePacked is more expensive but also more portable and easier to review.
*
* NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
* With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
*/
function _cloneCodeWithImmutableArgs(
address implementation,
bytes memory args
) private pure returns (bytes memory) {
if (args.length > 24531) revert CloneArgumentsTooLong();
return
abi.encodePacked(
hex"61",
uint16(args.length + 45),
hex"3d81600a3d39f3363d3d373d3d3d363d73",
implementation,
hex"5af43d82803e903d91602b57fd5bf3",
args
);
}
}
"
},
"contracts/v2/tokens/StakingBuffer.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import {TransferUtilsV2, IERC20} from "../lib/TransferUtilsV2.sol";
import {VersionV2} from "../VersionV2.sol";
abstract contract StakingBuffer is VersionV2 {
using TransferUtilsV2 for IERC20;
error InvalidBuffer(uint256 minimumBuffer, uint256 maximumBuffer, uint256 buffer);
struct BufferConfig {
uint256 minimumBuffer;
uint256 maximumBuffer;
uint256 buffer;
}
function _depositWithBuffer(address assetAddr) internal {
uint256 assetBalance = IERC20(assetAddr).balanceOf(address(this));
BufferConfig memory bufferConfig = _bufferConfig(assetAddr);
if (assetBalance > bufferConfig.maximumBuffer) {
_depositToPool(assetAddr, assetBalance - bufferConfig.buffer);
}
}
function _withdrawWithBuffer(address assetAddr, address to, uint256 amount) internal {
if (amount == 0) return;
uint256 assetBalance = IERC20(assetAddr).balanceOf(address(this));
BufferConfig memory bufferConfig = _bufferConfig(assetAddr);
if (assetBalance >= amount && assetBalance - amount >= bufferConfig.minimumBuffer) {
// Sufficient buffer, transfer directly from contract balance
IERC20(assetAddr).safeTransfer(to, amount);
return;
}
// Not enough buffer, withdraw from pool
uint256 targetBalance = bufferConfig.buffer + amount;
uint256 amountFromPool = targetBalance - assetBalance;
uint256 assetInPool = _assetInPool(assetAddr);
if (amountFromPool > assetInPool) {
amountFromPool = assetInPool;
}
if (amountFromPool == amount) {
_withdrawFromPool(assetAddr, to, amountFromPool);
} else {
if (amountFromPool != 0) _withdrawFromPool(assetAddr, address(this), amountFromPool);
IERC20(assetAddr).safeTransfer(to, amount);
}
}
function _bufferConfig(address assetAddr) internal view virtual returns (BufferConfig memory);
function _depositToPool(address assetAddr, uint256 amount) internal virtual;
function _withdrawFromPool(address assetAddr, address to, uint256 amount) internal virtual;
function _assetInPool(address assetAddr) internal view virtual returns (uint256 amount);
function _checkBufferConfig(uint256 minimumBuffer, uint256 maximumBuffer, uint256 buffer) internal pure {
if (minimumBuffer > maximumBuffer || buffer < minimumBuffer || buffer > maximumBuffer) {
revert InvalidBuffer(minimumBuffer, maximumBuffer, buffer);
}
}
}
"
},
"contracts/v2/tokens/StableERC4626For4626.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import {
ERC4626Upgradeable,
Math,
IERC4626
} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {
OwnableUpgradeable,
Ownable2StepUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {IERC20Metadata, IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {TransferUtilsV2} from "../lib/TransferUtilsV2.sol";
import {StakingBuffer} from "./StakingBuffer.sol";
import {ERC4626TokenEvents} from "../events/ERC4626TokenEvents.sol";
import {ERC4626TokenErrors} from "../errors/ERC4626TokenErrors.sol";
contract StableERC4626For4626 is
StakingBuffer,
ERC4626Upgradeable,
Ownable2StepUpgradeable,
ReentrancyGuardUpgradeable
{
using TransferUtilsV2 for *;
IERC4626 public thirdPool;
IERC20 public underlying;
BufferConfig public bufferConfig;
uint256 internal withdawedIncomeAssets;
constructor() {
_disableInitializers();
}
function initialize(address admin, address thirdPool_, BufferConfig memory bufferConfig_) public initializer {
thirdPool = IERC4626(thirdPool_);
address underlying_ = thirdPool.asset();
underlying = IERC20(underlying_);
string memory name = string(abi.encodePacked("TermMax Stable ERC4626 ", IERC20Metadata(underlying_).name()));
string memory symbol = string(abi.encodePacked("tmse", IERC20Metadata(underlying_).symbol()));
__ERC20_init_unchained(name, symbol);
__Ownable_init_unchained(admin);
__ERC4626_init_unchained(IERC20(underlying_));
__ReentrancyGuard_init_unchained();
_updateBufferConfig(bufferConfig_);
emit ERC4626TokenEvents.ERC4626For4626Initialized(admin, underlying_, thirdPool_);
}
function totalAssets() public view virtual override returns (uint256) {
// share is 1:1 with underlying
return super.totalSupply();
}
function _deposit(address caller, address recipient, uint256 assets, uint256 shares)
internal
virtual
override
nonReentrant
{
IERC20 assetToken = IERC20(asset());
assetToken.safeTransferFrom(caller, address(this), assets);
_depositWithBuffer(address(assetToken));
_mint(recipient, shares);
emit Deposit(caller, recipient, assets, shares);
}
/**
* @dev Internal conversion function (from assets to shares) with support for rounding direction.
*/
function _convertToShares(uint256 assets, Math.Rounding) internal view virtual override returns (uint256) {
return assets;
}
/**
* @dev Internal conversion function (from shares to assets) with support for rounding direction.
*/
function _convertToAssets(uint256 shares, Math.Rounding) internal view virtual override returns (uint256) {
return shares;
}
function _withdraw(address caller, address recipient, address owner, uint256 assets, uint256 shares)
internal
virtual
override
nonReentrant
{
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
_burn(owner, shares);
_withdrawWithBuffer(address(underlying), recipient, assets);
emit Withdraw(caller, recipient, owner, assets, shares);
}
function totalIncomeAssets() external view returns (uint256) {
uint256 assetInPool = _assetInPool(address(0));
uint256 underlyingBalance = underlying.balanceOf(address(this));
uint256 totalSupply_ = totalSupply();
uint256 assetsWithIncome = assetInPool + underlyingBalance + withdawedIncomeAssets;
if (assetsWithIncome < totalSupply_) {
// If total assets with income is less than total supply, return 0
return 0;
} else {
return assetsWithIncome - totalSupply_;
}
}
function withdrawIncomeAssets(address asset, address to, uint256 amount) external nonReentrant onlyOwner {
uint256 assetInPool = _assetInPool(address(0));
uint256 underlyingBalance = underlying.balanceOf(address(this));
uint256 avaliableAmount = assetInPool + underlyingBalance - totalSupply();
require(avaliableAmount >= amount, ERC4626TokenErrors.InsufficientIncomeAmount(avaliableAmount, amount));
withdawedIncomeAssets += amount;
if (asset == address(underlying)) {
_withdrawWithBuffer(address(underlying), to, amount);
} else if (asset == address(thirdPool)) {
uint256 shares = thirdPool.previewWithdraw(amount);
thirdPool.safeTransfer(to, shares);
} else {
revert ERC4626TokenErrors.InvalidToken();
}
emit ERC4626TokenEvents.WithdrawIncome(to, amount);
}
function updateBufferConfigAndAddReserves(uint256 additionalReserves, BufferConfig memory bufferConfig_)
external
onlyOwner
{
// Admin may add additional reserves when liquidity is low
// to avoid the situation that the underlying liquidity is too low to withdraw
underlying.safeTransferFrom(msg.sender, address(this), additionalReserves);
_updateBufferConfig(bufferConfig_);
}
function _updateBufferConfig(BufferConfig memory bufferConfig_) internal {
_checkBufferConfig(bufferConfig_.minimumBuffer, bufferConfig_.maximumBuffer, bufferConfig_.buffer);
bufferConfig = BufferConfig(bufferConfig_.minimumBuffer, bufferConfig_.maximumBuffer, bufferConfig_.buffer);
emit ERC4626TokenEvents.UpdateBufferConfig(
bufferConfig_.minimumBuffer, bufferConfig_.maximumBuffer, bufferConfig_.buffer
);
}
function _bufferConfig(address) internal view virtual override returns (BufferConfig memory) {
return bufferConfig;
}
function _depositToPool(address assetAddr, uint256 amount) internal virtual override {
IERC20(assetAddr).safeIncreaseAllowance(address(thirdPool), amount);
thirdPool.deposit(amount, address(this));
}
function _withdrawFromPool(address, address to, uint256 amount) internal virtual override {
thirdPool.withdraw(amount, to, address(this));
}
function _assetInPool(address) internal view virtual override returns (uint256 amount) {
uint256 shares = thirdPool.balanceOf(address(this));
if (shares != 0) {
amount = thirdPool.convertToAssets(shares);
}
}
}
"
},
"contracts/v2/tokens/StableERC4626ForAave.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import {
ERC4626Upgradeable, Math
} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {
OwnableUpgradeable,
Ownable2StepUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {IERC20Metadata, IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {IAaveV3Pool} from "../extensions/aave/IAaveV3Pool.sol";
import {TransferUtilsV2} from "../lib/TransferUtilsV2.sol";
import {StakingBuffer} from "./StakingBuffer.sol";
import {ERC4626TokenEvents} from "../events/ERC4626TokenEvents.sol";
import {ERC4626TokenErrors} from "../errors/ERC4626TokenErrors.sol";
contract StableERC4626ForAave is
StakingBuffer,
ERC4626Upgradeable,
Ownable2StepUpgradeable,
ReentrancyGuardUpgradeable
{
using TransferUtilsV2 for IERC20;
IAaveV3Pool public immutable aavePool;
uint16 public immutable referralCode;
IERC20 public aToken;
IERC20 public underlying;
BufferConfig public bufferConfig;
uint256 internal withdawedIncomeAssets;
constructor(address aavePool_, uint16 referralCode_) {
aavePool = IAaveV3Pool(aavePool_);
referralCode = referralCode_;
_disableInitializers();
}
function initialize(address admin, address underlying_, BufferConfig memory bufferConfig_) public initializer {
underlying = IERC20(underlying_);
string memory name = string(abi.encodePacked("TermMax Stable AaveERC4626 ", IERC20Metadata(underlying_).name()));
string memory symbol = string(abi.encodePacked("tmsa", IERC20Metadata(underlying_).symbol()));
__ERC20_init_unchained(name, symbol);
__Ownable_init_unchained(admin);
__ERC4626_init_unchained(IERC20(underlying_));
__ReentrancyGuard_init_unchained();
_updateBufferConfig(bufferConfig_);
aToken = IERC20(aavePool.getReserveData(underlying_).aTokenAddress);
emit ERC4626TokenEvents.ERC4626ForAaveInitialized(admin, underlying_, true);
}
function totalAssets() public view virtual override returns (uint256) {
// share is 1:1 with underlying
return super.totalSupply();
}
function _deposit(address caller, address recipient, uint256 assets, uint256 shares)
internal
virtual
override
nonReentrant
{
IERC20 assetToken = IERC20(asset());
assetToken.safeTransferFrom(caller, address(this), assets);
_depositWithBuffer(address(assetToken));
_mint(recipient, shares);
emit Deposit(caller, recipient, assets, shares);
}
/**
* @dev Internal conversion function (from assets to shares) with support for rounding direction.
*/
function _convertToShares(uint256 assets, Math.Rounding) internal view virtual override returns (uint256) {
return assets;
}
/**
* @dev Internal conversion function (from shares to assets) with support for rounding direction.
*/
function _convertToAssets(uint256 shares, Math.Rounding) internal view virtual override returns (uint256) {
return shares;
}
function _withdraw(address caller, address recipient, address owner, uint256 assets, uint256 shares)
internal
virtual
override
nonReentrant
{
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
_burn(owner, shares);
_withdrawWithBuffer(address(underlying), recipient, assets);
emit Withdraw(caller, recipient, owner, assets, shares);
}
function burnToAToken(address to, uint256 amount) external nonReentrant {
_burn(msg.sender, amount);
aToken.safeTransfer(to, amount);
}
function totalIncomeAssets() external view returns (uint256) {
uint256 aTokenBalance = aToken.balanceOf(address(this));
uint256 underlyingBalance = underlying.balanceOf(address(this));
uint256 assetsWithHistoryIncome = aTokenBalance + underlyingBalance + withdawedIncomeAssets;
// assetsWithHistoryIncome might be smaller than totalSupply() if the vault is at a loss
return assetsWithHistoryIncome > totalSupply() ? assetsWithHistoryIncome - totalSupply() : 0;
}
function withdrawIncomeAssets(address asset, address to, uint256 amount) external nonReentrant onlyOwner {
uint256 aTokenBalance = aToken.balanceOf(address(this));
uint256 underlyingBalance = underlying.balanceOf(address(this));
uint256 avaliableAmount = aTokenBalance + underlyingBalance - totalSupply();
require(avaliableAmount >= amount, ERC4626TokenErrors.InsufficientIncomeAmount(avaliableAmount, amount));
withdawedIncomeAssets += amount;
if (asset == address(underlying)) {
_withdrawWithBuffer(address(underlying), to, amount);
} else if (asset == address(aToken)) {
aToken.safeTransfer(to, amount);
} else {
revert ERC4626TokenErrors.InvalidToken();
}
emit ERC4626TokenEvents.WithdrawIncome(to, amount);
}
function updateBufferConfigAndAddReserves(uint256 additionalReserves, BufferConfig memory bufferConfig_)
external
onlyOwner
{
// Admin may add additional reserves when liquidity is low
// to avoid the situation that the underlying liquidity is too low to withdraw
underlying.safeTransferFrom(msg.sender, address(this), additionalReserves);
_updateBufferConfig(bufferConfig_);
}
function _updateBufferConfig(BufferConfig memory bufferConfig_) internal {
_checkBufferConfig(bufferConfig_.minimumBuffer, bufferConfig_.maximumBuffer, bufferConfig_.buffer);
bufferConfig = BufferConfig(bufferConfig_.minimumBuffer, bufferConfig_.maximumBuffer, bufferConfig_.buffer);
emit ERC4626TokenEvents.UpdateBufferConfig(
bufferConfig_.minimumBuffer, bufferConfig_.maximumBuffer, bufferConfig_.buffer
);
}
function _bufferConfig(address) internal view virtual override returns (BufferConfig memory) {
return bufferConfig;
}
function _depositToPool(address assetAddr, uint256 amount) internal virtual override {
IERC20(assetAddr).safeIncreaseAllowance(address(aavePool), amount);
aavePool.supply(assetAddr, amount, address(this), referralCode);
}
function _withdrawFromPool(address assetAddr, address to, uint256 amount) internal virtual override {
uint256 receivedAmount = aavePool.withdraw(assetAddr, amount, to);
require(receivedAmount == amount, ERC4626TokenErrors.AaveWithdrawFailed(amount, receivedAmount));
}
function _assetInPool(address) internal view virtual override returns (uint256 amount) {
amount = aToken.balanceOf(address(this));
}
}
"
},
"contracts/v2/tokens/VariableERC4626ForAave.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import {ERC4626Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {
OwnableUpgradeable,
Ownable2StepUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {IERC20Metadata, IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {IAaveV3Pool} from "../extensions/aave/IAaveV3Pool.sol";
import {TransferUtilsV2} from "../lib/TransferUtilsV2.sol";
import {StakingBuffer} from "./StakingBuffer.sol";
import {ERC4626TokenEvents} from "../events/ERC4626TokenEvents.sol";
import {ERC4626TokenErrors} from "../errors/ERC4626TokenErrors.sol";
contract VariableERC4626ForAave is
StakingBuffer,
ERC4626Upgradeable,
Ownable2StepUpgradeable,
ReentrancyGuardUpgradeable
{
using TransferUtilsV2 for IERC20;
IAaveV3Pool public immutable aavePool;
uint16 public immutable referralCode;
IERC20 public aToken;
IERC20 public underlying;
BufferConfig public bufferConfig;
constructor(address aavePool_, uint16 referralCode_) {
aavePool = IAaveV3Pool(aavePool_);
referralCode = referralCode_;
_disableInitializers();
}
function initialize(address admin, address underlying_, BufferConfig memory bufferConfig_) public initializer {
underlying = IERC20(underlying_);
string memory name =
string(abi.encodePacked("TermMax Variable AaveERC4626 ", IERC20Metadata(underlying_).name()));
string memory symbol = string(abi.encodePacked("tmva", IERC20Metadata(underlying_).symbol()));
__ERC20_init_unchained(name, symbol);
__ERC4626_init_unchained(IERC20(underlying_));
__Ownable_init_unchained(admin);
__ReentrancyGuard_init_unchained();
_updateBufferConfig(bufferConfig_);
aToken = IERC20(aavePool.getReserveData(underlying_).aTokenAddress);
emit ERC4626TokenEvents.ERC4626ForAaveInitialized(admin, underlying_, false);
}
function totalAssets() public view override returns (uint256) {
return IERC20(asset()).balanceOf(address(this)) + _assetInPool(address(underlying));
}
function _deposit(address caller, address recipient, uint256 assets, uint256 shares)
internal
virtual
override
nonReentrant
{
IERC20 assetToken = IERC20(asset());
assetToken.safeTransferFrom(caller, address(this), assets);
_depositWithBuffer(address(assetToken));
_mint(recipient, shares);
emit Deposit(caller, recipient, assets, shares);
}
function _withdraw(address caller, address recipient, address owner, uint256 assets, uint256 shares)
internal
virtual
override
nonReentrant
{
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
_burn(owner, shares);
_withdrawWithBuffer(address(underlying), recipient, assets);
emit Withdraw(caller, recipient, owner, assets, shares);
}
function updateBufferConfig(BufferConfig memory bufferConfig_) external onlyOwner {
_updateBufferConfig(bufferConfig_);
}
function _updateBufferConfig(BufferConfig memory bufferConfig_) internal {
_checkBufferConfig(bufferConfig_.minimumBuffer, bufferConfig_.maximumBuffer, bufferConfig_.buffer);
bufferConfig = BufferConfig(bufferConfig_.minimumBuffer, bufferConfig_.maximumBuffer, bufferConfig_.buffer);
emit ERC4626TokenEvents.UpdateBufferConfig(
bufferConfig_.minimumBuffer, bufferConfig_.maximumBuffer, bufferConfig_.buffer
);
}
function _bufferConfig(address) internal view virtual override returns (BufferConfig memory) {
return bufferConfig;
}
function _depositToPool(address assetAddr, uint256 amount) internal virtual override {
IERC20(assetAddr).safeIncreaseAllowance(address(aavePool), amount);
aavePool.supply(assetAddr, amount, address(this), referralCode);
}
function _withdrawFromPool(address assetAddr, address to, uint256 amount) internal virtual override {
uint256 receivedAmount = aavePool.withdraw(assetAddr, amount, to);
require(receivedAmount == amount, ERC4626TokenErrors.AaveWithdrawFailed(amount, receivedAmount));
}
function _assetInPool(address) internal view virtual override returns (uint256 amount) {
amount = aToken.balanceOf(address(this));
}
}
"
},
"contracts/v2/events/FactoryEventsV2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {MarketInitialParams} from "../../v1/storage/TermMaxStorage.sol";
import {VaultInitialParamsV2} from "../storage/TermMaxStorageV2.sol";
/**
* @title Factory Events Interface V2
* @notice Events emitted by the TermMax factory contracts
*/
interface FactoryEventsV2 {
/**
* @notice Emitted when a new market is created
* @param market The address of the newly created market
* @param collateral The address of the collateral token
* @param debtToken The debt token interface
* @param params The initial parameters for the market
*/
event MarketCreated(
address indexed market, address indexed collateral, IERC20 indexed debtToken, MarketInitialParams params
);
/**
* @notice Emitted when a new vault is created
* @param vault The address of the newly created vault
* @param creator The address of the vault creator
* @param initialParams The initial parameters used to configure the vault
*/
event VaultCreated(address indexed vault, address indexed creator, VaultInitialParamsV2 initialParams);
/**
* @notice Emitted when a new price feed is created
* @param priceFeed The address of the newly created price feed contract
*/
event PriceFeedCreated(address indexed priceFeed);
// Events from TermMax4626Factory
/**
* @notice Emitted when TermMax4626Factory is initialized
* @param aavePool The Aave pool address
* @param aaveReferralCode The Aave referral code
* @param stableERC4626For4626Implementation The stable ERC4626For4626 implementation address
* @param stableERC4626ForAaveImplementation The stable ERC4626ForAave implementation address
* @param variableERC4626ForAaveImplementation The variable ERC4626ForAave implementation address
*/
event TermMax4626FactoryInitialized(
address indexed aavePool,
uint16 aaveReferralCode,
address stableERC4626For4626Implementation,
address stableERC4626ForAaveImplementation,
address variableERC4626ForAaveImplementation
);
/**
* @notice Emitted when a new StableERC4626For4626 is created
* @param caller The address that called the creation function
* @param stableERC4626For4626 The address of the created StableERC4626For4626
*/
event StableERC4626For4626Created(address indexed caller, address indexed stableERC4626For4626);
/**
* @notice Emitted when a new StableERC4626ForAave is created
* @param caller The address that called the creation function
* @param stableERC4626ForAave The address of the created StableERC4626ForAave
*/
event StableERC4626ForAaveCreated(address indexed caller, address indexed stableERC4626ForAave);
/**
* @notice Emitted when a new VariableERC4626ForAave is created
* @param caller The address that called the creation function
* @param variableERC4626ForAave The address of the created VariableERC4626ForAave
*/
event VariableERC4626ForAaveCreated(address indexed caller, address indexed variableERC4626ForAave);
}
"
},
"contracts/v2/VersionV2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
abstract contract VersionV2 {
// Function to get the version number
function getVersion() public pure virtual returns (string memory) {
return "2.0.0";
}
}
"
},
"dependencies/@openzeppelin-contracts-5.2.0/utils/Create2.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev There's no code to deploy.
*/
error Create2EmptyBytecode();
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
if (bytecode.length == 0) {
revert Create2EmptyBytecode();
}
assembly ("memory-safe") {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
// if no address was created, and returndata is not empty, bubble revert
if and(iszero(addr), not(iszero(returndatasize()))) {
let p := mload(0x40)
returndatacopy(p, 0, returndatasize())
revert(p, returndatasize())
}
}
if (addr == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
assembly ("memory-safe") {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
}
"
},
"dependencies/@openzeppelin-contracts-5.2.0/utils/Errors.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}
"
},
"contracts/v2/lib/TransferUtilsV2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
library TransferUtilsV2 {
using SafeERC20 for IERC20;
error CanNotTransferUintMax();
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
token.safeTransferFrom(from, to, value);
}
function safeTransfer(IERC20 token, address to, uint256 value) internal {
token.safeTransfer(to, value);
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
token.safeIncreaseAllowance(spender, value);
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
if (value == 0 || spender == address(this)) {
return;
}
token.safeDecreaseAllowance(spender, value);
}
function forceApprove(IERC20 token, address spender, uint256 value) internal {
token.forceApprove(spender, value);
}
function safeTransferFromWithCheck(IERC20 token, address from, address to, uint256 value) internal {
if (from == to || value == 0) {
return;
}
token.safeTransferFrom(from, to, value);
}
function safeTransferWithCheck(IERC20 token, address to, uint256 value) internal {
if (to == address(this) || value == 0) {
return;
}
token.safeTransfer(to, value);
}
function safeIncreaseAllowanceWithCheck(IERC20 token, address spender, uint256 value) internal {
if (value == 0 || spender == address(this)) {
return;
}
token.safeIncreaseAllowance(spender, value);
}
function safeDecreaseAllowanceWithCheck(IERC20 token, address spender, uint256 value) internal {
if (value == 0 || spender == address(this)) {
return;
}
token.safeDecreaseAllowance(spender, value);
}
function forceApproveWithCheck(IERC20 token, address spender, uint256 value) internal {
if (spender == address(this)) {
return;
}
token.forceApprove(spender, value);
}
}
"
},
"dependencies/@openzeppelin-contracts-upgradeable-5.2.0/token/ERC20/extensions/ERC4626Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC4626.sol)
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ERC20Upgradeable} from "../ERC20Upgradeable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Initializable} from "../../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the ERC-4626 "Tokenized Vault Standard" as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*
* This extension allows the minting and burning of "shares" (represented using the ERC-20 inheritance) in exchange for
* underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends
* the ERC-20 standard. Any additional extensions included along it would affect the "shares" token represented by this
* contract and not the "assets" token which is an independent contract.
*
* [CAUTION]
* ====
* In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning
* with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation
* attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial
* deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may
* similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by
* verifying the amount received is as expected, using a wrapper that performs these checks such as
* https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
*
* Since v4.9, this implementation introduces configurable virtual assets and shares to help developers mitigate that risk.
* The `_decimalsOffset()` corresponds to an offset in the decimal representation between the underlying asset's decimals
* and the vault decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which
* itself determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default
* offset (0) makes it non-profitable even if an attacker is able to capture value from multiple user deposits, as a result
* of the value being captured by the virtual shares (out of the attacker's donation) matching the attacker's expected gains.
* With a larger offset, the attack becomes orders of magnitude more expensive than it is profitable. More details about the
* underlying math can be found xref:erc4626.adoc#inflation-attack[here].
*
* The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued
* to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets
* will cause the first user to exit to experience reduced losses in detriment to the last users that will experience
* bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the
* `_convertToShares` and `_convertToAssets` functions.
*
* To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide].
* ====
*/
abstract contract ERC4626Upgradeable is Initializable, ERC20Upgradeable, IERC4626 {
using Math for uint256;
/// @custom:storage-location erc7201:openzeppelin.storage.ERC4626
struct ERC4626Storage {
IERC20 _asset;
uint8 _underlyingDecimals;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC4626")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ERC4626StorageLocation = 0x0773e532dfede91f04b12a73d3d2acd361424f41f76b4fb79f090161e36b4e00;
function _getERC4626Storage() private pure returns (ERC4626Storage storage $) {
assembly {
$.slot := ERC4626StorageLocation
}
}
/**
* @dev Attempted to deposit more assets than the max amount for `receiver`.
*/
error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max);
/**
* @dev Attempted to mint more shares than the max amount for `receiver`.
*/
error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max);
/**
* @dev Attempted to withdraw more assets than the max amount for `receiver`.
*/
error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max);
/**
* @dev Attempted to redeem more shares than the max amount for `receiver`.
*/
error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max);
/**
* @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC-20 or ERC-777).
*/
function __ERC4626_init(IERC20 asset_) internal onlyInitializing {
__ERC4626_init_unchained(asset_);
}
function __ERC4626_init_unchained(IERC20 asset_) internal onlyInitializing {
ERC4626Storage storage $ = _getERC4626Storage();
(bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
$._underlyingDecimals = success ? assetDecimals : 18;
$._asset = asset_;
}
/**
* @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way.
*/
function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool ok, uint8 assetDecimals) {
(bool success, bytes memory encodedDecimals) = address(asset_).staticcall(
abi.encodeCall(IERC20Metadata.decimals, ())
);
if (success && encodedDecimals.length >= 32) {
uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
if (returnedDecimals <= type(uint8).max) {
return (true, uint8(returnedDecimals));
}
}
return (false, 0);
}
/**
* @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This
* "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the
* asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals.
*
* See {IERC20Metadata-decimals}.
*/
function decimals() public view virtual override(IERC20Metadata, ERC20Upgradeable) returns (uint8) {
ERC4626Storage storage $ = _getERC4626Storage();
return $._underlyingDecimals + _decimalsOffset();
}
/** @dev See {IERC4626-asset}. */
function asset() public view virtual returns (address) {
ERC4626Storage storage $ = _getERC4626Storage();
return address($._asset);
}
/** @dev See {IERC4626-totalAssets}. */
function totalAssets() public view virtual returns (uint256) {
ERC4626Storage storage $ = _getERC4626Storage();
return $._asset.balanceOf(address(this));
}
/** @dev See {IERC4626-convertToShares}. */
function convertToShares(uint256 assets) public view virtual returns (uint256) {
return _convertToShares(assets, Math.Rounding.Floor);
}
/** @dev See {IERC4626-convertToAssets}. */
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
return _convertToAssets(shares, Math.Rounding.Floor);
}
/** @dev See {IERC4626-maxDeposit}. */
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
/** @dev See {IERC4626-maxMint}. */
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
/** @dev See {IERC4626-maxWithdraw}. */
function maxWithdraw(address owner) public view virtual returns (uint256) {
return _convertToAssets(balanceOf(owner), Math.Rounding.Floor);
}
/** @dev See {IERC4626-maxRedeem}. */
function maxRedeem(address owner) public view virtual returns (uint256) {
return balanceOf(owner);
}
/** @dev See {IERC4626-previewDeposit}. */
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return _convertToShares(assets, Math.Rounding.Floor);
}
/** @dev See {IERC4626-previewMint}. */
function previewMint(uint256 shares) public view virtual returns (uint256) {
return _convertToAssets(shares, Math.Rounding.Ceil);
}
/** @dev See {IERC4626-previewWithdraw}. */
function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
return _convertToShares(assets, Math.Rounding.Ceil);
}
/** @dev See {IERC4626-previewRedeem}. */
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
return _convertToAssets(shares, Math.Rounding.Floor);
}
/** @dev See {IERC4626-deposit}. */
function deposit(uint256 assets, address receiver) public virtual returns (uint256) {
uint256 maxAssets = maxDeposit(receiver);
if (assets > maxAssets) {
revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets);
}
uint256 shares = previewDeposit(assets);
_deposit(_msgSender(), receiver, assets, shares);
return shares;
}
/** @dev See {IERC4626-mint}. */
function mint(uint256 shares, address receiver) public virtual returns (uint256) {
uint256 maxShares = maxMint(receiver);
if (shares > maxShares) {
revert ERC4626ExceededMaxMint(receiver, shares, maxShares);
}
uint256 assets = previewMint(shares);
_deposit(_msgSender(), receiver, assets, shares);
return assets;
}
/** @dev See {IERC4626-withdraw}. */
function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) {
uint256 maxAssets = maxWithdraw(owner);
if (assets > maxAssets) {
revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets);
}
uint256 shares = previewWithdraw(assets);
_withdraw(_msgSender(), receiver, owner, assets, shares);
return shares;
}
/** @dev See {IERC4626-redeem}. */
function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) {
uint256 maxShares = maxRedeem(owner);
if (shares > maxShares) {
revert ERC4626ExceededMaxRedeem(owner, shares, maxShares);
}
uint256 assets = previewRedeem(shares);
_withdraw(_msgSender(), receiver, owner, assets, shares);
return assets;
}
/**
* @dev Internal conversion function (from assets to shares) with support for rounding direction.
*/
function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) {
return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding);
}
/**
* @dev Internal conversion function (from shares to assets) with support for rounding direction.
*/
function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) {
return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding);
}
/**
* @dev Deposit/mint common workflow.
*/
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual {
ERC4626Storage storage $ = _getERC4626Storage();
// If _asset is ERC-777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the
// `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
// calls the vault, which is assumed not malicious.
//
// Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
// assets are transferred and before the shares are minted, which is a valid state.
// slither-disable-next-line reentrancy-no-eth
SafeERC20.safeTransferFrom($._asset, caller, address(this), assets);
_mint(receiver, shares);
emit Deposit(caller, receiver, assets, shares);
}
/**
* @dev Withdraw/redeem common workflow.
*/
function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal virtual {
ERC4626Storage storage $ = _getERC4626Storage();
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
// If _asset is ERC-777, `transfer` can trigger a reentrancy AFTER the transfer happens through the
// `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
// calls the vault, which is assumed not malicious.
//
// Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
// shares are burned and after the assets are transferred, which is a valid state.
_burn(owner, shares);
SafeERC20.safeTransfer($._asset, receiver, assets);
emit Withdraw(caller, receiver, owner, assets, shares);
}
function _decimalsOffset() internal view virtual returns (uint8) {
return 0;
}
}
"
},
"dependencies/@openzeppelin-contracts-upgradeable-5.2.0/access/Ownable2StepUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.20;
import {OwnableUpgradeable} from "./OwnableUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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 Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable2Step
struct Ownable2StepStorage {
address _pendingOwner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable2Step")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant Ownable2StepStorageLocation = 0x237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00;
function _getOwnable2StepStorage() private pure returns (Ownable2StepStorage storage $) {
assembly {
$.slot := Ownable2StepStorageLocation
Submitted on: 2025-10-02 14:17:30
Comments
Log in to comment.
No comments yet.