Skip to content

Instantly share code, notes, and snippets.

@xhad
Created June 15, 2024 12:00
Show Gist options
  • Save xhad/2cf2f0c4e910bab9f9ae832a80b53e45 to your computer and use it in GitHub Desktop.
Save xhad/2cf2f0c4e910bab9f9ae832a80b53e45 to your computer and use it in GitHub Desktop.
Index Vault Example
// SPDX-License-Identifier: GPL-3.0
pragma solidity =0.8.18;
import {VerifiableAddressArray} from "src/lib/VArray.sol";
import {IVault} from "src/interfaces/IVault.sol";
import {TokenInfo} from "src/Common.sol";
import {IIndexToken} from "src/interfaces/IIndexToken.sol";
import {SCALAR, fdiv, fmul, finv} from "./lib/FixedPoint.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
contract Vault is Ownable2Step, IVault {
using VerifiableAddressArray for VerifiableAddressArray.VerifiableArray;
using SafeERC20 for IERC20;
IIndexToken public immutable indexToken;
bool public emergency;
address public issuance;
address public rebalancer;
address public feeRecipient;
address public emergencyResponder;
uint256 public inflationRate;
uint256 public lastKnownTimestamp;
VerifiableAddressArray.VerifiableArray internal _underlying;
mapping(address => uint256) internal _nominals;
///////////////////////// MODIFIERS / CONSTRUCTOR /////////////////////////
modifier onlyInvokers() {
if (msg.sender != issuance && msg.sender != rebalancer) {
revert AMKTVaultOnlyInvokers();
}
_;
}
modifier only(address who) {
if (msg.sender != who) {
revert AMKTVaultOnly(who);
}
_;
}
modifier whenNotEmergency() {
if (emergency) {
revert AMKTVaultEmergency();
}
_;
}
/// @notice Constructor
/// @param _indexToken The index token address
/// @param _owner The owner of the vault
/// @param _feeRecipient The recipient of the fee
/// @param _emergencyResponder The emergency responder, can set emergency flag
/// @param _inflationRate The per second inflation rate
constructor(
IIndexToken _indexToken,
address _owner,
address _feeRecipient,
address _emergencyResponder,
uint256 _inflationRate
) {
if (_owner == address(0)) revert VaultZeroCheck();
if (_feeRecipient == address(0)) revert VaultZeroCheck();
if (_emergencyResponder == address(0)) revert VaultZeroCheck();
if (_inflationRate > SCALAR) {
revert AMKTVaultInflationRateTooLarge();
}
indexToken = _indexToken;
_transferOwnership(_owner);
emergencyResponder = _emergencyResponder;
feeRecipient = _feeRecipient;
inflationRate = _inflationRate;
lastKnownTimestamp = block.timestamp;
}
///////////////////////// CONFIG /////////////////////////
/// @notice Set the issuance module for the vault
/// @param _issuance The issuance module address
/// @dev only owner
function setIssuance(address _issuance) external only(owner()) {
if (_issuance == address(0)) revert VaultZeroCheck();
issuance = _issuance;
emit VaultIssuanceSet(_issuance);
}
/// @notice Set the rebalancer module for the vault
/// @param _rebalancer The rebalancer module address
/// @dev only owner
function setRebalancer(address _rebalancer) external only(owner()) {
if (_rebalancer == address(0)) revert VaultZeroCheck();
rebalancer = _rebalancer;
emit VaultRebalancerSet(_rebalancer);
}
/// @notice Set the fee recipient
/// @param _feeRecipient The fee recipient address
/// @dev only owner
function setFeeRecipient(address _feeRecipient) external only(owner()) {
if (_feeRecipient == address(0)) revert VaultZeroCheck();
feeRecipient = _feeRecipient;
emit VaultFeeRecipientSet(_feeRecipient);
}
/// @notice Set the emergency responder
/// @param _emergencyResponder The emergency responder address
/// @dev only owner
function setEmergencyResponder(
address _emergencyResponder
) external only(owner()) {
if (_emergencyResponder == address(0)) revert VaultZeroCheck();
emergencyResponder = _emergencyResponder;
emit VaultEmergencyResponderSet(_emergencyResponder);
}
/// @notice Set the inflation rate
/// @param _inflationRate The per second inflation rate
/// @dev only owner & accrues inflation
function setInflationRate(uint256 _inflationRate) external only(owner()) {
if (_inflationRate > SCALAR) {
revert AMKTVaultInflationRateTooLarge();
}
inflationRate = _inflationRate;
emit VaultInflationRateSet(_inflationRate);
}
/// @notice Set the emergency flag
/// @param _emergency The emergency flag
/// @dev only emergency responder
function setEmergency(bool _emergency) external only(emergencyResponder) {
emergency = _emergency;
emit VaultEmergencySet(_emergency);
}
///////////////////////// INFLATION /////////////////////////
/// @notice Try to accrue inflation
function tryInflation() external only(feeRecipient) {
if (block.timestamp < lastKnownTimestamp + 1 days)
revert AMKTVaultFeeTooEarly();
uint256 startingSupply = indexToken.totalSupply();
uint256 timestampDiff = block.timestamp - lastKnownTimestamp;
uint256 inflation = fmul(startingSupply, timestampDiff * inflationRate);
if (inflation == 0) revert AMKTVaultFeeTooSmall();
lastKnownTimestamp = block.timestamp;
uint256 valueMultiplier = fdiv(
startingSupply,
startingSupply + inflation
);
TokenInfo[] memory tokens = virtualUnits();
for (uint256 i = 0; i < tokens.length; i++) {
_setNominal(
SetNominalArgs({
token: tokens[i].token,
virtualUnits: fmul(tokens[i].units, valueMultiplier)
})
);
}
indexToken.mint(feeRecipient, inflation);
invariantCheck();
emit VaultFeeMinted(feeRecipient, inflation);
}
///////////////////////// INVOKE /////////////////////////
/// @notice Set the nominal units of more than one token
/// @param args The SetNominalArgs[]
/// @dev only rebalancer
function invokeSetNominals(
SetNominalArgs[] calldata args
) external whenNotEmergency only(rebalancer) {
for (uint256 i; i < args.length; i++) {
_setNominal(args[i]);
}
}
/// @notice Invoke ERC20 transfers
/// @param args The InvokeERC20Args[]
/// @dev only invokers
function invokeERC20s(
InvokeERC20Args[] calldata args
) external onlyInvokers {
for (uint256 i; i < args.length; i++) {
_transferERC20(args[i]);
}
}
/// @notice Mint index tokens
/// @param to The recipient of the index tokens
/// @param amount The amount of index tokens to mint
/// @dev only issuance
function invokeMint(
address to,
uint256 amount
) external whenNotEmergency only(issuance) {
indexToken.mint(to, amount);
}
/// @notice Burn index tokens
/// @param from The owner of the index tokens
/// @param amount The amount of index tokens to burn
/// @dev only issuance
function invokeBurn(address from, uint256 amount) external only(issuance) {
indexToken.burn(from, amount);
}
///////////////////////// VIEW ////////////////////////
/// @notice Returns the underlying tokens
/// @return address[] memory of underlying tokens with nominal units > 0
function underlying() external view returns (address[] memory) {
return _underlying.toMemoryArray();
}
/// @notice Returns the underlying tokens and their nominal units
/// @return the number of tokens backing the index
function underlyingLength() external view returns (uint256) {
return _underlying.size();
}
/// @notice Returns true if the token is an underlying
/// @param _token address
/// @return bool true if underlying
function isUnderlying(address _token) public view returns (bool) {
return _underlying.includes(_token);
}
/// @notice Returns the virtual units of a token
/// @dev warning! does not revert on non-underlying token
/// @param token address
/// @return uint256
function virtualUnits(address token) public view returns (uint256) {
return _nominals[token];
}
/// @notice Returns the virtual units of all tokens
/// @return TokenInfo[] memory
function virtualUnits() public view returns (TokenInfo[] memory) {
address[] storage stor = _underlying.toStorageArray();
uint256 len = stor.length;
TokenInfo[] memory info = new TokenInfo[](len);
for (uint256 i; i < len; i++) {
address token = stor[i];
info[i] = TokenInfo({token: token, units: _nominals[token]});
}
return info;
}
/// @notice Checks that the vault is in a valid state
/// @notice i.e. we can wind down to 0 safely
/// @notice reverts if the check fails
function invariantCheck() public view {
TokenInfo[] memory tokens = virtualUnits();
for (uint256 i; i < tokens.length; ) {
uint256 expectedAmount = fmul(
tokens[i].units,
indexToken.totalSupply()
);
if (
IERC20(tokens[i].token).balanceOf(address(this)) <
expectedAmount
) revert VaultInvariant();
unchecked {
++i;
}
}
}
///////////////////////// INTERNAL /////////////////////////
function _setNominal(SetNominalArgs memory args) internal {
address token = args.token;
uint256 _virtualUnits = args.virtualUnits;
if (_virtualUnits == 0) {
delete _nominals[token];
_underlying.remove(token);
return;
}
if (!isUnderlying(token)) {
_underlying.add(token);
}
_nominals[token] = _virtualUnits;
}
function _transferERC20(InvokeERC20Args memory args) internal {
IERC20(args.token).safeTransfer(args.to, args.amount);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment