编译器
0.8.17+commit.8df45f5f
文件 1 的 17:Address.sol
pragma solidity ^0.8.1;
library Address {
function isContract(address account) internal view returns (bool) {
return account.code.length > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
文件 2 的 17:Calculations.sol
pragma solidity ^0.8.17;
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { Deposit, FCNVaultMetadata, OptionBarrierType, OptionBarrier, VaultStatus, Withdrawal } from "./Structs.sol";
import { IOracle } from "./interfaces/IOracle.sol";
import { ICegaState } from "./interfaces/ICegaState.sol";
library Calculations {
uint256 public constant DAYS_IN_YEAR = 365;
uint256 public constant SECONDS_TO_DAYS = 86400;
uint256 public constant BPS_DECIMALS = 10 ** 4;
uint256 public constant LARGE_CONSTANT = 10 ** 18;
uint256 public constant ORACLE_STALE_DELAY = 1 days;
function calculateCurrentYield(FCNVaultMetadata storage self) public {
require(self.vaultStatus == VaultStatus.Traded, "500:WS");
uint256 currentTime = block.timestamp;
if (currentTime > self.tradeExpiry) {
self.vaultStatus = VaultStatus.TradeExpired;
return;
}
uint256 numberOfDaysPassed = (currentTime - self.tradeDate) / SECONDS_TO_DAYS;
self.totalCouponPayoff = calculateCouponPayment(self.underlyingAmount, self.aprBps, numberOfDaysPassed);
}
function checkBarriers(FCNVaultMetadata storage self, address cegaStateAddress) public {
if (self.isKnockedIn == true) {
return;
}
require(self.vaultStatus == VaultStatus.Traded, "500:WS");
for (uint256 i = 0; i < self.optionBarriersCount; i++) {
OptionBarrier storage optionBarrier = self.optionBarriers[i];
if (optionBarrier.barrierType == OptionBarrierType.KnockIn) {
address oracle = getOracleAddress(optionBarrier, cegaStateAddress);
(, int256 answer, uint256 startedAt, , ) = IOracle(oracle).latestRoundData();
require(block.timestamp - ORACLE_STALE_DELAY <= startedAt, "400:T");
if (uint256(answer) <= optionBarrier.barrierAbsoluteValue) {
self.isKnockedIn = true;
}
}
}
}
function calculateVaultFinalPayoff(
FCNVaultMetadata storage self,
address cegaStateAddress
) public returns (uint256) {
uint256 totalPrincipal;
uint256 totalCouponPayment;
uint256 principalToReturnBps = BPS_DECIMALS;
require(
(self.vaultStatus == VaultStatus.TradeExpired || self.vaultStatus == VaultStatus.PayoffCalculated),
"500:WS"
);
totalCouponPayment = calculateCouponPayment(self.underlyingAmount, self.aprBps, self.tenorInDays);
if (self.isKnockedIn) {
principalToReturnBps = calculateKnockInRatio(self, cegaStateAddress);
}
totalPrincipal = (self.underlyingAmount * principalToReturnBps) / BPS_DECIMALS;
uint256 vaultFinalPayoff = totalPrincipal + totalCouponPayment;
self.totalCouponPayoff = totalCouponPayment;
self.vaultFinalPayoff = vaultFinalPayoff;
self.vaultStatus = VaultStatus.PayoffCalculated;
return vaultFinalPayoff;
}
function calculateKnockInRatio(
FCNVaultMetadata storage self,
address cegaStateAddress
) public view returns (uint256) {
OptionBarrier[] memory optionBarriers = self.optionBarriers;
uint256 optionBarriersCount = self.optionBarriersCount;
uint256 minRatioBps = LARGE_CONSTANT;
for (uint256 i = 0; i < optionBarriersCount; i++) {
OptionBarrier memory optionBarrier = optionBarriers[i];
address oracle = getOracleAddress(optionBarrier, cegaStateAddress);
(, int256 answer, uint256 startedAt, , ) = IOracle(oracle).latestRoundData();
require(block.timestamp - ORACLE_STALE_DELAY <= startedAt, "400:T");
if (optionBarrier.barrierType == OptionBarrierType.KnockIn) {
uint256 ratioBps = (uint256(answer) * LARGE_CONSTANT) / optionBarrier.strikeAbsoluteValue;
minRatioBps = Math.min(ratioBps, minRatioBps);
}
}
return ((minRatioBps * BPS_DECIMALS)) / LARGE_CONSTANT;
}
function calculateFees(
FCNVaultMetadata storage self,
uint256 managementFeeBps,
uint256 yieldFeeBps
) public view returns (uint256, uint256, uint256) {
uint256 totalFee = 0;
uint256 managementFee = 0;
uint256 yieldFee = 0;
uint256 underlyingAmount = self.underlyingAmount;
uint256 numberOfDaysPassed = (self.tradeExpiry - self.vaultStart) / SECONDS_TO_DAYS;
managementFee =
(underlyingAmount * numberOfDaysPassed * managementFeeBps * LARGE_CONSTANT) /
DAYS_IN_YEAR /
BPS_DECIMALS /
LARGE_CONSTANT;
if (self.vaultFinalPayoff > underlyingAmount) {
uint256 profit = self.vaultFinalPayoff - underlyingAmount;
yieldFee = (profit * yieldFeeBps) / BPS_DECIMALS;
}
totalFee = managementFee + yieldFee;
return (totalFee, managementFee, yieldFee);
}
function calculateCouponPayment(
uint256 underlyingAmount,
uint256 aprBps,
uint256 daysPassed
) private pure returns (uint256) {
return (underlyingAmount * daysPassed * aprBps * LARGE_CONSTANT) / DAYS_IN_YEAR / BPS_DECIMALS / LARGE_CONSTANT;
}
function getOracleAddress(
OptionBarrier memory optionBarrier,
address cegaStateAddress
) private view returns (address) {
ICegaState cegaState = ICegaState(cegaStateAddress);
address oracle = cegaState.oracleAddresses(optionBarrier.oracleName);
require(oracle != address(0), "400:Unregistered");
return oracle;
}
}
文件 3 的 17:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 4 的 17:ERC20.sol
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
function name() public view virtual override returns (string memory) {
return _name;
}
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
function decimals() public view virtual override returns (uint8) {
return 18;
}
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
function _transfer(
address from,
address to,
uint256 amount
) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}
文件 5 的 17:FCNProduct.sol
pragma solidity ^0.8.17;
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import { ICegaState } from "./interfaces/ICegaState.sol";
import { Deposit, FCNVaultMetadata, OptionBarrierType, OptionBarrier, VaultStatus, Withdrawal } from "./Structs.sol";
import { FCNVault } from "./FCNVault.sol";
import { Calculations } from "./Calculations.sol";
contract FCNProduct is ReentrancyGuard {
using SafeERC20 for IERC20;
using Calculations for FCNVaultMetadata;
event FCNProductCreated(
address indexed cegaState,
address indexed asset,
string name,
uint256 managementFeeBps,
uint256 yieldFeeBps,
uint256 maxDepositAmountLimit,
uint256 minDepositAmount,
uint256 minWithdrawalAmount
);
event ManagementFeeBpsUpdated(uint256 managementFeeBps);
event YieldFeeBpsUpdated(uint256 yieldFeeBps);
event MinDepositAmountUpdated(uint256 minDepositAmount);
event MinWithdrawalAmountUpdated(uint256 minWithdrawalAmount);
event IsDepositQueueOpenUpdated(bool isDepositQueueOpen);
event MaxDepositAmountLimitUpdated(uint256 maxDepositAmountLimit);
event VaultCreated(address indexed vaultAddress, string _tokenSymbol, string _tokenName, uint256 _vaultStart);
event VaultMetadataUpdated(address indexed vaultAddress);
event VaultRemoved(address indexed vaultAddress);
event TradeDataSet(
address indexed vaultAddress,
uint256 _tradeDate,
uint256 _tradeExpiry,
uint256 _aprBps,
uint256 _tenorInDays
);
event OptionBarrierAdded(
address indexed vaultAddress,
uint256 barrierBps,
uint256 barrierAbsoluteValue,
uint256 strikeBps,
uint256 strikeAbsoluteValue,
string asset,
string oracleName,
OptionBarrierType barrierType
);
event OptionBarrierUpated(
address indexed vaultAddress,
uint256 index,
string _asset,
uint256 _strikeAbsoluteValue,
uint256 _barrierAbsoluteValue
);
event OptionBarrierOracleUpdated(address indexed vaultAddress, uint256 index, string _asset, string _oracleName);
event OptionBarrierRemoved(address indexed vaultAddress, uint256 index, string asset);
event VaultStatusUpdated(address indexed vaultAddress, VaultStatus vaultStatus);
event DepositQueued(address indexed receiver, uint256 amount);
event DepositProcessed(address indexed vaultAddress, address indexed receiver, uint256 amount);
event KnockInStatusUpdated(address indexed vaultAddress, bool isKnockIn);
event FeesCollected(
address indexed vaultAddress,
uint256 managementFee,
uint256 yieldFee,
uint256 totalFee,
VaultStatus vaultStatus
);
event WithdrawalQueued(address indexed vaultAddress, address indexed receiver, uint256 amountShares);
event WithdrawalProcessed(
address indexed vaultAddress,
address indexed receiver,
uint256 amountShares,
uint256 amountAssets
);
event VaultRollover(address indexed vaultAddress, uint256 vaultStart, VaultStatus vaultStatus);
event VaultFinalPayoffCalculated(address indexed vaultAddress, uint256 finalPayoffAmount, VaultStatus vaultStatus);
event BarriersChecked(address indexed vaultAddress, bool isKnockedIn);
event AssetsReceivedFromCegaState(address indexed vaultAddress, uint256 amount);
event AssetsSentToTrade(
address indexed vaultAddress,
address indexed receiver,
uint256 amount,
VaultStatus vaultStatus
);
ICegaState public cegaState;
address public immutable asset;
string public name;
uint256 public managementFeeBps;
uint256 public yieldFeeBps;
bool public isDepositQueueOpen;
uint256 public maxDepositAmountLimit;
uint256 public minDepositAmount;
uint256 public minWithdrawalAmount;
uint256 public sumVaultUnderlyingAmounts;
uint256 public queuedDepositsTotalAmount;
uint256 public queuedDepositsCount;
mapping(address => FCNVaultMetadata) public vaults;
address[] public vaultAddresses;
Deposit[] public depositQueue;
mapping(address => Withdrawal[]) public withdrawalQueues;
constructor(
address _cegaState,
address _asset,
string memory _name,
uint256 _managementFeeBps,
uint256 _yieldFeeBps,
uint256 _maxDepositAmountLimit,
uint256 _minDepositAmount,
uint256 _minWithdrawalAmount
) {
require(_managementFeeBps < 1e4, "400:IB");
require(_yieldFeeBps < 1e4, "400:IB");
require(_minDepositAmount > 0, "400:IU");
require(_minWithdrawalAmount > 0, "400:IU");
cegaState = ICegaState(_cegaState);
asset = _asset;
name = _name;
managementFeeBps = _managementFeeBps;
yieldFeeBps = _yieldFeeBps;
maxDepositAmountLimit = _maxDepositAmountLimit;
isDepositQueueOpen = false;
minDepositAmount = _minDepositAmount;
minWithdrawalAmount = _minWithdrawalAmount;
emit FCNProductCreated(
_cegaState,
_asset,
_name,
_managementFeeBps,
_yieldFeeBps,
_maxDepositAmountLimit,
_minDepositAmount,
_minWithdrawalAmount
);
}
modifier onlyDefaultAdmin() {
require(cegaState.isDefaultAdmin(msg.sender), "403:DA");
_;
}
modifier onlyTraderAdmin() {
require(cegaState.isTraderAdmin(msg.sender), "403:TA");
_;
}
modifier onlyOperatorAdmin() {
require(cegaState.isOperatorAdmin(msg.sender), "403:OA");
_;
}
modifier onlyValidVault(address vaultAddress) {
require(vaults[vaultAddress].vaultStart != 0, "400:VA");
_;
}
function getVaultAddresses() public view returns (address[] memory) {
return vaultAddresses;
}
function getVaultMetadata(address vaultAddress) public view returns (FCNVaultMetadata memory) {
return vaults[vaultAddress];
}
function setManagementFeeBps(uint256 _managementFeeBps) public onlyOperatorAdmin {
require(_managementFeeBps < 1e4, "400:IB");
managementFeeBps = _managementFeeBps;
emit ManagementFeeBpsUpdated(_managementFeeBps);
}
function setYieldFeeBps(uint256 _yieldFeeBps) public onlyOperatorAdmin {
require(_yieldFeeBps < 1e4, "400:IB");
yieldFeeBps = _yieldFeeBps;
emit YieldFeeBpsUpdated(_yieldFeeBps);
}
function setMinDepositAmount(uint256 _minDepositAmount) public onlyOperatorAdmin {
require(_minDepositAmount > 0, "400:IU");
minDepositAmount = _minDepositAmount;
emit MinDepositAmountUpdated(_minDepositAmount);
}
function setMinWithdrawalAmount(uint256 _minWithdrawalAmount) public onlyOperatorAdmin {
require(_minWithdrawalAmount > 0, "400:IU");
minWithdrawalAmount = _minWithdrawalAmount;
emit MinWithdrawalAmountUpdated(_minWithdrawalAmount);
}
function setIsDepositQueueOpen(bool _isDepositQueueOpen) public onlyOperatorAdmin {
isDepositQueueOpen = _isDepositQueueOpen;
emit IsDepositQueueOpenUpdated(_isDepositQueueOpen);
}
function setMaxDepositAmountLimit(uint256 _maxDepositAmountLimit) public onlyTraderAdmin {
require(queuedDepositsTotalAmount + sumVaultUnderlyingAmounts <= _maxDepositAmountLimit, "400:TooSmall");
maxDepositAmountLimit = _maxDepositAmountLimit;
emit MaxDepositAmountLimitUpdated(_maxDepositAmountLimit);
}
function createVault(
string memory _tokenName,
string memory _tokenSymbol,
uint256 _vaultStart
) public onlyTraderAdmin returns (address vaultAddress) {
require(_vaultStart != 0, "400:VS");
FCNVault vault = new FCNVault(asset, _tokenName, _tokenSymbol);
address newVaultAddress = address(vault);
vaultAddresses.push(newVaultAddress);
FCNVaultMetadata storage vaultMetadata = vaults[newVaultAddress];
vaultMetadata.vaultStart = _vaultStart;
vaultMetadata.vaultAddress = newVaultAddress;
vaultMetadata.leverage = 1;
emit VaultCreated(newVaultAddress, _tokenSymbol, _tokenName, _vaultStart);
return newVaultAddress;
}
function setVaultMetadata(
address vaultAddress,
FCNVaultMetadata calldata metadata
) public onlyDefaultAdmin onlyValidVault(vaultAddress) {
require(metadata.vaultStart > 0, "400:VS");
require(metadata.leverage == 1, "400:L");
vaults[vaultAddress] = metadata;
emit VaultMetadataUpdated(vaultAddress);
}
function removeVault(uint256 i) public onlyDefaultAdmin {
address vaultAddress = vaultAddresses[i];
vaultAddresses[i] = vaultAddresses[vaultAddresses.length - 1];
vaultAddresses.pop();
delete vaults[vaultAddress];
emit VaultRemoved(vaultAddress);
}
function setTradeData(
address vaultAddress,
uint256 _tradeDate,
uint256 _tradeExpiry,
uint256 _aprBps,
uint256 _tenorInDays
) public onlyTraderAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage metadata = vaults[vaultAddress];
require(metadata.vaultStatus == VaultStatus.NotTraded, "500:WS");
require(_tradeDate >= metadata.vaultStart, "400:TD");
require(_tradeExpiry > _tradeDate, "400:TE");
uint256 derivedDays = (_tradeExpiry - _tradeDate) / 1 days;
if (derivedDays < _tenorInDays) {
require(_tenorInDays - derivedDays <= 1, "400:TN");
} else {
require(derivedDays - _tenorInDays <= 1, "400:TN");
}
metadata.tradeDate = _tradeDate;
metadata.tradeExpiry = _tradeExpiry;
metadata.aprBps = _aprBps;
metadata.tenorInDays = _tenorInDays;
emit TradeDataSet(vaultAddress, _tradeDate, _tradeExpiry, _aprBps, _tenorInDays);
}
function addOptionBarrier(
address vaultAddress,
OptionBarrier calldata optionBarrier
) public onlyTraderAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage metadata = vaults[vaultAddress];
require(
metadata.vaultStatus == VaultStatus.DepositsClosed || metadata.vaultStatus == VaultStatus.NotTraded,
"500:WS"
);
metadata.optionBarriers.push(optionBarrier);
metadata.optionBarriersCount++;
emit OptionBarrierAdded(
vaultAddress,
optionBarrier.barrierBps,
optionBarrier.barrierAbsoluteValue,
optionBarrier.strikeBps,
optionBarrier.strikeAbsoluteValue,
optionBarrier.asset,
optionBarrier.oracleName,
optionBarrier.barrierType
);
}
function updateOptionBarrier(
address vaultAddress,
uint256 index,
string calldata _asset,
uint256 _strikeAbsoluteValue,
uint256 _barrierAbsoluteValue
) public onlyTraderAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
require(_strikeAbsoluteValue > 0, "400:SV");
require(_barrierAbsoluteValue > 0, "400:BV");
OptionBarrier storage optionBarrier = vaultMetadata.optionBarriers[index];
require(keccak256(abi.encodePacked(optionBarrier.asset)) == keccak256(abi.encodePacked(_asset)), "400:AS");
optionBarrier.strikeAbsoluteValue = _strikeAbsoluteValue;
optionBarrier.barrierAbsoluteValue = _barrierAbsoluteValue;
emit OptionBarrierUpated(vaultAddress, index, _asset, _strikeAbsoluteValue, _barrierAbsoluteValue);
}
function updateOptionBarrierOracle(
address vaultAddress,
uint256 index,
string calldata _asset,
string memory newOracleName
) public onlyOperatorAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
require(
vaultMetadata.vaultStatus == VaultStatus.DepositsClosed ||
vaultMetadata.vaultStatus == VaultStatus.NotTraded,
"500:WS"
);
OptionBarrier storage optionBarrier = vaultMetadata.optionBarriers[index];
require(keccak256(abi.encodePacked(optionBarrier.asset)) == keccak256(abi.encodePacked(_asset)), "400:AS");
require(cegaState.oracleAddresses(newOracleName) != address(0), "400:OR");
optionBarrier.oracleName = newOracleName;
emit OptionBarrierOracleUpdated(vaultAddress, index, _asset, newOracleName);
}
function removeOptionBarrier(
address vaultAddress,
uint256 index,
string calldata _asset
) public onlyTraderAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
require(
vaultMetadata.vaultStatus == VaultStatus.DepositsClosed ||
vaultMetadata.vaultStatus == VaultStatus.NotTraded,
"500:WS"
);
OptionBarrier[] storage optionBarriers = vaultMetadata.optionBarriers;
require(
keccak256(abi.encodePacked(optionBarriers[index].asset)) == keccak256(abi.encodePacked(_asset)),
"400:AS"
);
optionBarriers[index] = optionBarriers[optionBarriers.length - 1];
optionBarriers.pop();
vaultMetadata.optionBarriersCount -= 1;
emit OptionBarrierRemoved(vaultAddress, index, _asset);
}
function setVaultStatus(
address vaultAddress,
VaultStatus _vaultStatus
) public onlyOperatorAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage metadata = vaults[vaultAddress];
metadata.vaultStatus = _vaultStatus;
emit VaultStatusUpdated(vaultAddress, _vaultStatus);
}
function openVaultDeposits(address vaultAddress) public onlyTraderAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
require(vaultMetadata.vaultStatus == VaultStatus.DepositsClosed, "500:WS");
vaultMetadata.vaultStatus = VaultStatus.DepositsOpen;
emit VaultStatusUpdated(vaultAddress, VaultStatus.DepositsOpen);
}
function setKnockInStatus(
address vaultAddress,
bool newState
) public onlyDefaultAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
vaultMetadata.isKnockedIn = newState;
emit KnockInStatusUpdated(vaultAddress, newState);
}
function addToDepositQueue(uint256 amount) public nonReentrant {
require(isDepositQueueOpen, "500:NotOpen");
require(amount >= minDepositAmount, "400:DA");
queuedDepositsCount += 1;
queuedDepositsTotalAmount += amount;
require(queuedDepositsTotalAmount + sumVaultUnderlyingAmounts <= maxDepositAmountLimit, "500:TooBig");
IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
depositQueue.push(Deposit({ amount: amount, receiver: msg.sender }));
emit DepositQueued(msg.sender, amount);
}
function processDepositQueue(
address vaultAddress,
uint256 maxProcessCount
) public nonReentrant onlyTraderAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
require(vaultMetadata.vaultStatus == VaultStatus.DepositsOpen, "500:WS");
FCNVault vault = FCNVault(vaultAddress);
require(!(vaultMetadata.underlyingAmount == 0 && vault.totalSupply() > 0), "500:Z");
uint256 processCount = Math.min(queuedDepositsCount, maxProcessCount);
Deposit storage deposit;
while (processCount > 0) {
deposit = depositQueue[queuedDepositsCount - 1];
queuedDepositsTotalAmount -= deposit.amount;
vault.deposit(deposit.amount, deposit.receiver);
vaultMetadata.underlyingAmount += deposit.amount;
sumVaultUnderlyingAmounts += deposit.amount;
vaultMetadata.currentAssetAmount += deposit.amount;
depositQueue.pop();
queuedDepositsCount -= 1;
processCount -= 1;
emit DepositProcessed(vaultAddress, deposit.receiver, deposit.amount);
}
if (queuedDepositsCount == 0) {
vaultMetadata.vaultStatus = VaultStatus.NotTraded;
emit VaultStatusUpdated(vaultAddress, VaultStatus.NotTraded);
}
}
function addToWithdrawalQueue(
address vaultAddress,
uint256 amountShares
) public nonReentrant onlyValidVault(vaultAddress) {
require(amountShares >= minWithdrawalAmount, "400:WA");
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
IERC20(vaultAddress).safeTransferFrom(msg.sender, address(this), amountShares);
Withdrawal[] storage withdrawalQueue = withdrawalQueues[vaultAddress];
withdrawalQueue.push(Withdrawal({ amountShares: amountShares, receiver: msg.sender }));
vaultMetadata.queuedWithdrawalsCount += 1;
vaultMetadata.queuedWithdrawalsSharesAmount += amountShares;
emit WithdrawalQueued(vaultAddress, msg.sender, amountShares);
}
function checkBarriers(address vaultAddress) public onlyValidVault(vaultAddress) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
vaultMetadata.checkBarriers(address(cegaState));
emit BarriersChecked(vaultAddress, vaultMetadata.isKnockedIn);
}
function calculateVaultFinalPayoff(
address vaultAddress
) public onlyValidVault(vaultAddress) returns (uint256 vaultFinalPayoff) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
vaultFinalPayoff = vaultMetadata.calculateVaultFinalPayoff(address(cegaState));
emit VaultFinalPayoffCalculated(vaultAddress, vaultFinalPayoff, VaultStatus.PayoffCalculated);
}
function calculateKnockInRatio(address vaultAddress) public view returns (uint256 knockInRatio) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
return vaultMetadata.calculateKnockInRatio(address(cegaState));
}
function receiveAssetsFromCegaState(
address vaultAddress,
uint256 amount
) public nonReentrant onlyValidVault(vaultAddress) {
require(msg.sender == address(cegaState), "403:CS");
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
vaultMetadata.currentAssetAmount += amount;
emit AssetsReceivedFromCegaState(vaultAddress, amount);
}
function calculateFees(
address vaultAddress
) public view returns (uint256 totalFee, uint256 managementFee, uint256 yieldFee) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
return vaultMetadata.calculateFees(managementFeeBps, yieldFeeBps);
}
function collectFees(address vaultAddress) public nonReentrant onlyTraderAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
require(vaultMetadata.vaultStatus == VaultStatus.PayoffCalculated, "500:WS");
(uint256 totalFees, uint256 managementFee, uint256 yieldFee) = calculateFees(vaultAddress);
totalFees = Math.min(totalFees, vaultMetadata.vaultFinalPayoff);
IERC20(asset).safeTransfer(cegaState.feeRecipient(), totalFees);
vaultMetadata.currentAssetAmount -= totalFees;
vaultMetadata.vaultStatus = VaultStatus.FeesCollected;
sumVaultUnderlyingAmounts -= vaultMetadata.underlyingAmount;
vaultMetadata.underlyingAmount = vaultMetadata.vaultFinalPayoff - totalFees;
sumVaultUnderlyingAmounts += vaultMetadata.underlyingAmount;
emit FeesCollected(vaultAddress, managementFee, yieldFee, totalFees, VaultStatus.FeesCollected);
}
function processWithdrawalQueue(
address vaultAddress,
uint256 maxProcessCount
) public nonReentrant onlyTraderAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
require(
vaultMetadata.vaultStatus == VaultStatus.FeesCollected || vaultMetadata.vaultStatus == VaultStatus.Zombie,
"500:WS"
);
Withdrawal[] storage withdrawalQueue = withdrawalQueues[vaultAddress];
FCNVault vault = FCNVault(vaultAddress);
uint256 processCount = Math.min(vaultMetadata.queuedWithdrawalsCount, maxProcessCount);
uint256 amountAssets;
Withdrawal memory withdrawal;
while (processCount > 0) {
withdrawal = withdrawalQueue[vaultMetadata.queuedWithdrawalsCount - 1];
amountAssets = vault.redeem(withdrawal.amountShares);
vaultMetadata.underlyingAmount -= amountAssets;
sumVaultUnderlyingAmounts -= amountAssets;
vaultMetadata.queuedWithdrawalsSharesAmount -= withdrawal.amountShares;
IERC20(asset).safeTransfer(withdrawal.receiver, amountAssets);
vaultMetadata.currentAssetAmount -= amountAssets;
withdrawalQueue.pop();
vaultMetadata.queuedWithdrawalsCount -= 1;
processCount -= 1;
emit WithdrawalProcessed(vaultAddress, withdrawal.receiver, withdrawal.amountShares, amountAssets);
}
if (vaultMetadata.queuedWithdrawalsCount == 0) {
if (vaultMetadata.underlyingAmount == 0 && vault.totalSupply() > 0) {
vaultMetadata.vaultStatus = VaultStatus.Zombie;
emit VaultStatusUpdated(vaultAddress, VaultStatus.Zombie);
} else {
vaultMetadata.vaultStatus = VaultStatus.WithdrawalQueueProcessed;
emit VaultStatusUpdated(vaultAddress, VaultStatus.WithdrawalQueueProcessed);
}
}
}
function rolloverVault(address vaultAddress) public onlyTraderAdmin onlyValidVault(vaultAddress) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
require(vaultMetadata.vaultStatus == VaultStatus.WithdrawalQueueProcessed, "500:WS");
require(vaultMetadata.tradeExpiry != 0, "400:TE");
vaultMetadata.vaultStart = vaultMetadata.tradeExpiry;
vaultMetadata.tradeDate = 0;
vaultMetadata.tradeExpiry = 0;
vaultMetadata.aprBps = 0;
vaultMetadata.vaultStatus = VaultStatus.DepositsClosed;
vaultMetadata.totalCouponPayoff = 0;
vaultMetadata.vaultFinalPayoff = 0;
vaultMetadata.isKnockedIn = false;
emit VaultRollover(vaultAddress, vaultMetadata.vaultStart, VaultStatus.DepositsClosed);
}
function sendAssetsToTrade(
address vaultAddress,
address receiver,
uint256 amount
) public nonReentrant onlyTraderAdmin onlyValidVault(vaultAddress) {
require(cegaState.marketMakerAllowList(receiver), "400:NotAllowed");
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
require(amount <= vaultMetadata.currentAssetAmount, "400:TooBig");
IERC20(asset).safeTransfer(receiver, amount);
vaultMetadata.currentAssetAmount = vaultMetadata.currentAssetAmount - amount;
vaultMetadata.vaultStatus = VaultStatus.Traded;
emit AssetsSentToTrade(vaultAddress, receiver, amount, VaultStatus.Traded);
}
function calculateCurrentYield(address vaultAddress) public onlyValidVault(vaultAddress) {
FCNVaultMetadata storage vaultMetadata = vaults[vaultAddress];
vaultMetadata.calculateCurrentYield();
}
}
文件 6 的 17:FCNVault.sol
pragma solidity ^0.8.13;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { FCNProduct } from "./FCNProduct.sol";
import { FCNVaultMetadata, VaultStatus } from "./Structs.sol";
contract FCNVault is ERC20, Ownable {
using SafeERC20 for ERC20;
address public asset;
FCNProduct public fcnProduct;
constructor(address _asset, string memory _tokenName, string memory _tokenSymbol) ERC20(_tokenName, _tokenSymbol) {
asset = _asset;
fcnProduct = FCNProduct(owner());
}
function decimals() public view virtual override returns (uint8) {
return 6;
}
function totalAssets() public view returns (uint256) {
(, , , , , uint256 underlyingAmount, , , , , , , , , , ) = fcnProduct.vaults(address(this));
return underlyingAmount;
}
function convertToAssets(uint256 shares) public view returns (uint256) {
uint256 _totalSupply = totalSupply();
if (_totalSupply == 0) return 0;
return (shares * totalAssets()) / _totalSupply;
}
function convertToShares(uint256 assets) public view returns (uint256) {
uint256 _totalSupply = totalSupply();
uint256 _totalAssets = totalAssets();
if (_totalAssets == 0 || _totalSupply == 0) return assets;
return (assets * _totalSupply) / _totalAssets;
}
function deposit(uint256 assets, address receiver) public onlyOwner returns (uint256) {
uint256 shares = convertToShares(assets);
_mint(receiver, shares);
return shares;
}
function redeem(uint256 shares) external onlyOwner returns (uint256) {
uint256 assets = convertToAssets(shares);
_burn(msg.sender, shares);
return assets;
}
}
文件 7 的 17:IAggregatorV3.sol
pragma solidity ^0.8.17;
interface IAggregatorV3 {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(
uint80 _roundId
)
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
文件 8 的 17:ICegaState.sol
pragma solidity ^0.8.17;
interface ICegaState {
function marketMakerAllowList(address marketMaker) external view returns (bool);
function products(string memory productName) external view returns (address);
function oracleAddresses(string memory oracleName) external view returns (address);
function oracleNames() external view returns (string[] memory);
function productNames() external view returns (string[] memory);
function feeRecipient() external view returns (address);
function isDefaultAdmin(address sender) external view returns (bool);
function isTraderAdmin(address sender) external view returns (bool);
function isOperatorAdmin(address sender) external view returns (bool);
function isServiceAdmin(address sender) external view returns (bool);
function getOracleNames() external view returns (string[] memory);
function addOracle(string memory oracleName, address oracleAddress) external;
function removeOracle(string memory oracleName) external;
function getProductNames() external view returns (string[] memory);
function addProduct(string memory productName, address product) external;
function removeProduct(string memory productName) external;
function updateMarketMakerPermission(address marketMaker, bool allow) external;
function setFeeRecipient(address _feeRecipient) external;
function moveAssetsToProduct(string memory productName, address vaultAddress, uint256 amount) external;
}
文件 9 的 17:IERC20.sol
pragma solidity ^0.8.0;
import "../token/ERC20/IERC20.sol";
文件 10 的 17:IERC20Metadata.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
interface IERC20Metadata is IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
}
文件 11 的 17:IOracle.sol
pragma solidity ^0.8.17;
import { IAggregatorV3 } from "./IAggregatorV3.sol";
import { RoundData } from "../Structs.sol";
interface IOracle is IAggregatorV3 {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function cegaState() external view returns (address);
function oracleData() external view returns (RoundData[] memory);
function nextRoundId() external view returns (uint80);
function addNextRoundData(RoundData calldata _roundData) external;
function updateRoundData(uint80 roundId, RoundData calldata _roundData) external;
function getRoundData(
uint80 _roundId
)
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
文件 12 的 17:Math.sol
pragma solidity ^0.8.0;
library Math {
enum Rounding {
Down,
Up,
Zero
}
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function average(uint256 a, uint256 b) internal pure returns (uint256) {
return (a & b) + (a ^ b) / 2;
}
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
return a == 0 ? 0 : (a - 1) / b + 1;
}
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
uint256 prod0;
uint256 prod1;
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
if (prod1 == 0) {
return prod0 / denominator;
}
require(denominator > prod1);
uint256 remainder;
assembly {
remainder := mulmod(x, y, denominator)
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
uint256 twos = denominator & (~denominator + 1);
assembly {
denominator := div(denominator, twos)
prod0 := div(prod0, twos)
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
uint256 inverse = (3 * denominator) ^ 2;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
result = prod0 * inverse;
return result;
}
}
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 result = 1 << (log2(a) >> 1);
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10**64) {
value /= 10**64;
result += 64;
}
if (value >= 10**32) {
value /= 10**32;
result += 32;
}
if (value >= 10**16) {
value /= 10**16;
result += 16;
}
if (value >= 10**8) {
value /= 10**8;
result += 8;
}
if (value >= 10**4) {
value /= 10**4;
result += 4;
}
if (value >= 10**2) {
value /= 10**2;
result += 2;
}
if (value >= 10**1) {
result += 1;
}
}
return result;
}
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
}
}
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
}
}
}
文件 13 的 17:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 14 的 17:ReentrancyGuard.sol
pragma solidity ^0.8.0;
abstract contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
_status = _ENTERED;
}
function _nonReentrantAfter() private {
_status = _NOT_ENTERED;
}
}
文件 15 的 17:SafeERC20.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
文件 16 的 17:Structs.sol
pragma solidity ^0.8.17;
enum OptionBarrierType {
None,
KnockIn
}
struct Deposit {
uint256 amount;
address receiver;
}
struct Withdrawal {
uint256 amountShares;
address receiver;
}
enum VaultStatus {
DepositsClosed,
DepositsOpen,
NotTraded,
Traded,
TradeExpired,
PayoffCalculated,
FeesCollected,
WithdrawalQueueProcessed,
Zombie
}
struct OptionBarrier {
uint256 barrierBps;
uint256 barrierAbsoluteValue;
uint256 strikeBps;
uint256 strikeAbsoluteValue;
string asset;
string oracleName;
OptionBarrierType barrierType;
}
struct FCNVaultMetadata {
uint256 vaultStart;
uint256 tradeDate;
uint256 tradeExpiry;
uint256 aprBps;
uint256 tenorInDays;
uint256 underlyingAmount;
uint256 currentAssetAmount;
uint256 totalCouponPayoff;
uint256 vaultFinalPayoff;
uint256 queuedWithdrawalsSharesAmount;
uint256 queuedWithdrawalsCount;
uint256 optionBarriersCount;
uint256 leverage;
address vaultAddress;
VaultStatus vaultStatus;
bool isKnockedIn;
OptionBarrier[] optionBarriers;
}
struct RoundData {
int256 answer;
uint256 startedAt;
uint256 updatedAt;
uint80 answeredInRound;
}
文件 17 的 17:draft-IERC20Permit.sol
pragma solidity ^0.8.0;
interface IERC20Permit {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
{
"compilationTarget": {
"contracts/FCNVault.sol": "FCNVault"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [],
"viaIR": true
}
[{"inputs":[{"internalType":"address","name":"_asset","type":"address"},{"internalType":"string","name":"_tokenName","type":"string"},{"internalType":"string","name":"_tokenSymbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fcnProduct","outputs":[{"internalType":"contract FCNProduct","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]