Feature Tip: Add private address tag to any address under My Name Tag !
This is a portfolio address for WBTC++ created through Endaoment.
Overview
ETH Balance
0 ETH
Eth Value
$0.00Token Holdings
More Info
Private Name Tags
ContractCreator
TokenTracker
Latest 1 from a total of 1 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
0x61016060 | 18727247 | 356 days ago | IN | 0 ETH | 0.13892868 |
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0xB31b5b5B...229133E2B The constructor portion of the code might be different and could alter the actual behaviour of the contract
Contract Name:
SingleTokenPortfolio
Compiler Version
v0.8.13+commit.abaa5c0e
Optimization Enabled:
Yes with 9999999 runs
Other Settings:
london EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
//SPDX-License-Identifier: BSD 3-Clause pragma solidity 0.8.13; import {Registry} from "../Registry.sol"; import {Portfolio} from "../Portfolio.sol"; import {ISwapWrapper} from "../interfaces/ISwapWrapper.sol"; import {Math} from "../lib/Math.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; contract SingleTokenPortfolio is Portfolio { using SafeTransferLib for ERC20; using Math for uint256; /// @notice Percentage error margin for caller's redemption assetOut estimation as WAD (e.g. .01e18 means error tolerance of 1%). uint256 public errorMarginPct; /// @notice Emitted when caller's redemption assetOut estimation is off by more than errorMarginPct. error ErrorMarginExceeded(uint256 expectedAssetsOut, uint256 actualAssetsOut, uint256 margin); /// @notice Emitted when errorMarginPct is set. event ErrorMarginPctSet(uint256 newErrorMarginPct); /** * @param _registry Endaoment registry. * @param _receiptAsset Underlying ERC20 asset token for portfolio. * @param _shareTokenName Name of ERC20 portfolio share token. * @param _shareTokenSymbol Symbol of ERC20 portfolio share token. * @param _cap Amount of baseToken that this portfolio's asset balance should not exceed. * @param _feeTreasury Address of treasury that should receive fees. * @param _depositFee Percentage fee as ZOC that should go to treasury on deposit. (100 = 1%). * @param _redemptionFee Percentage fee as ZOC that should go to treasury on redemption. (100 = 1%). * @param _aumRate Percentage fee per second (as WAD) that should accrue to treasury as AUM fee. (1e16 = 1%). * @param _errorMarginPct Tolerable error percentage (as WAD) that caller's redemption asset estimation can be off by. */ constructor( Registry _registry, address _receiptAsset, string memory _shareTokenName, string memory _shareTokenSymbol, uint256 _cap, address _feeTreasury, uint256 _depositFee, uint256 _redemptionFee, uint256 _aumRate, uint256 _errorMarginPct ) Portfolio( _registry, _receiptAsset, _shareTokenName, _shareTokenSymbol, _cap, _feeTreasury, _depositFee, _redemptionFee, _aumRate ) { if (_errorMarginPct > Math.WAD) revert PercentageOver100(); errorMarginPct = _errorMarginPct; emit ErrorMarginPctSet(_errorMarginPct); } /** * @notice Role authed method to set the tolerance of error for caller's redemption assetOut estimation. * @param _pct New percentage error margin as WAD. */ function setErrorMarginPct(uint256 _pct) public requiresAuth { if (_pct > Math.WAD) revert PercentageOver100(); errorMarginPct = _pct; emit ErrorMarginPctSet(_pct); } /** * @inheritdoc Portfolio */ function _getAsset(address _receiptAsset) internal pure override returns (address) { return _receiptAsset; } /** * @inheritdoc Portfolio */ function _checkCap() internal view override { // bypass cap check; we will check it in _deposit } /** * @inheritdoc Portfolio * @dev In SingleTokenPortfolio we consider `asset` and `receiptAsset` to be the same. This portfolio merely * holds the `asset` in its custody until redemption. */ function convertReceiptAssetsToAssets(uint256 _receiptAssets) public pure override returns (uint256) { return _receiptAssets; } /** * @notice Like `convertToAssets`, converts shares to assets, but factors in AUM fee delta as well. * Users will need to use this method to calculate their expected assets out for redemption. * @param _shares Amount of shares to convert to assets. * @param _elapsed Upper bound of time expected to elapse between calculation and inclusion on chain. * @dev Rounding down in both of these favors the portfolio, so the user gets slightly less and the portfolio gets slightly more, * that way it prevents a situation where the user is owed x but the vault only has x - epsilon, where epsilon is some tiny number * due to rounding error. * @return Amount of assets that `_shares` is worth after `_elapsed` seconds. */ function convertToAssetsLessFutureFees(uint256 _shares, uint256 _elapsed) external view returns (uint256) { uint256 _supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. uint256 _totalAssets = totalAssets(); uint256 _feeAmount = _calculateAumFee(_totalAssets, block.timestamp + _elapsed - timestampAumFeesTaken); if (_feeAmount > _totalAssets) return 0; return _supply == 0 ? _shares : _shares.mulDivDown(_totalAssets - _feeAmount, _supply); } /** * @notice This method is needed to calculate shares deposit because we already possess the assets that we want to convert. * @dev Rounding down favors the portfolio, so the user gets slightly less and the portfolio gets slightly more, that way it prevents * a situation where the user is owed x but the vault only has x - epsilon, where epsilon is some tiny number due to rounding error. * @return Amount of shares that `_assets` is worth. */ function _convertToSharesLessAssets(uint256 _assets) private view returns (uint256) { uint256 _supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. return _supply == 0 ? _assets : _assets.mulDivDown(_supply, totalAssets() - _assets); } /** * @inheritdoc Portfolio * @dev We convert `baseToken` to `asset` via a swap wrapper. * `_data` should be the ABI-encoded `uint minSharesOut`, plus a packed swap wrapper address concatenated with the bytes payload your * swap wrapper expects. i.e. `abi.encodePacked(uint _minSharesOut, abi.encodePacked(address swapWrapper), SWAP_WRAPPER_BYTES)`. * To determine if this deposit exceeds the cap, we get the asset/baseToken exchange rate and multiply it by `totalAssets`. */ function _deposit(uint256 _amountBaseToken, bytes calldata _data) internal override returns (uint256, uint256, uint256) { // check swap wrapper ISwapWrapper _swapWrapper = ISwapWrapper(address(bytes20(_data[32:32 + 20]))); if (!registry.isSwapperSupported(_swapWrapper)) revert InvalidSwapper(); // transfer in baseToken, transfer out fees, and swap to asset (uint256 _amountIn, uint256 _amountFee) = _calculateFee(_amountBaseToken, depositFee); baseToken.safeTransferFrom(msg.sender, address(this), _amountBaseToken); baseToken.safeTransfer(feeTreasury, _amountFee); baseToken.safeApprove(address(_swapWrapper), 0); baseToken.safeApprove(address(_swapWrapper), _amountIn); uint256 _assets = _swapWrapper.swap(address(baseToken), asset, address(this), _amountIn, _data[32 + 20:]); // Convert totalAssets to baseToken unit to measure against cap. if (totalAssets() * _amountIn / _assets > cap) revert ExceedsCap(); uint256 _shares = _convertToSharesLessAssets(_assets); return (_shares, _assets, _amountFee); } /** * @inheritdoc Portfolio * @dev After converting `shares` to `assets`, we convert `assets` to `baseToken` via a swap wrapper. * * `_data` should consist of: * 1.) An encoded uint256 `expectedAssetsOut` that we'll use as your redemption value. It must be within a tolerance of * `errorMarginPct` from the exact asset amount your shares are entitled to. This is necessary because the correct asset value * changes by a small amount every second, due to AUM fees or in some cases rebasing tokens. But to swap the assets back to USDC, * the asset amount must match the amountIn of the swap calldata. Therefore, we introduce this error tolerance. It's recommended * to use `convertToAssetsLessFutureFees` to calculate an `expectedAssetsOut` that accounts for the time elapsed between * calculation and inclusion on chain. * 2.) A packed swap wrapper address concatenated with the bytes payload your swap wrapper expects. * * _data payload example: * `abi.encodePacked(uint expectedAssetsOut, abi.encodePacked(address swapWrapper), SWAP_WRAPPER_BYTES)`. */ function _redeem(uint256 _amountShares, bytes calldata _data) internal override returns (uint256 assetsOut, uint256 baseTokenOut) { uint256 _assetsOut = convertToAssets(_amountShares); (uint256 _actualAssetsOut, uint256 _baseTokenOut) = _exit(_assetsOut, _data); return (_actualAssetsOut, _baseTokenOut); } /** * @inheritdoc Portfolio */ function _exit(uint256 _assetsOut, bytes calldata _data) internal override returns (uint256 actualAssetsOut, uint256 baseTokenOut) { ISwapWrapper _swapWrapper = ISwapWrapper(address(bytes20(_data[32:32 + 20]))); if (!registry.isSwapperSupported(_swapWrapper)) revert InvalidSwapper(); // is specified _expectedAssetsOut within reasonable margin of error? uint256 _expectedAssetsOut = abi.decode(_data, (uint256)); /** * Because _assetsOut changes every second due to AUM fees, we need to allow for some error margin. * We do this by allowing _expectedAssetsOut to be within a certain percentage of _assetsOut. * `errorMarginPct` should be calibrated to allow for, say, 30 minutes of elapsed time between calculation and * inclusion on chain. And the user should account for this 30 minutes of elapsed time via `convertToAssetsLessFutureFees` * to calculate _expectedAssetsOut. * In the case where `asset` is a rebasing token, the `errorMarginPct` should be calibrated with the rebase rate in mind. */ // _margin is the absolute amount that _expectedAssetsOut can be off by uint256 _margin = _assetsOut.mulWadDown(errorMarginPct); if ( _expectedAssetsOut > _assetsOut // Make sure user isn't redeeming more than entitled to; also prevent overflow on next line || _assetsOut - _expectedAssetsOut > _margin // If off by more than _margin, revert ) { revert ErrorMarginExceeded(_expectedAssetsOut, _assetsOut, _margin); } ERC20(asset).safeApprove(address(_swapWrapper), 0); ERC20(asset).safeApprove(address(_swapWrapper), _expectedAssetsOut); uint256 _baseTokenOut = _swapWrapper.swap(asset, address(baseToken), address(this), _expectedAssetsOut, _data[32 + 20:]); return (_expectedAssetsOut, _baseTokenOut); } }
//SPDX-License-Identifier: BSD 3-Clause pragma solidity 0.8.13; import {Math} from "./lib/Math.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {Auth, Authority} from "./lib/auth/Auth.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {RegistryAuth} from "./RegistryAuth.sol"; import {Entity} from "./Entity.sol"; import {ISwapWrapper} from "./interfaces/ISwapWrapper.sol"; import {Portfolio} from "./Portfolio.sol"; // --- Errors --- error Unauthorized(); error UnsupportedSwapper(); /** * @notice Registry entity - manages Factory and Entity state info. */ contract Registry is RegistryAuth { // --- Storage --- /// @notice Treasury address can receives fees. address public treasury; /// @notice Base Token address is the stable coin contract used throughout the system. ERC20 public immutable baseToken; /// @notice Mapping of approved factory contracts that are allowed to register new Entities. mapping(address => bool) public isApprovedFactory; /// @notice Mapping of active status of entities. mapping(Entity => bool) public isActiveEntity; /// @notice Maps entity type to donation fee percentage stored as a zoc, where type(uint32).max represents 0. mapping(uint8 => uint32) defaultDonationFee; /// @notice Maps specific entity receiver to donation fee percentage stored as a zoc. mapping(Entity => uint32) donationFeeReceiverOverride; /// @notice Maps entity type to payout fee percentage stored as a zoc, where type(uint32).max represents 0. mapping(uint8 => uint32) defaultPayoutFee; /// @notice Maps specific entity sender to payout fee percentage stored as a zoc. mapping(Entity => uint32) payoutFeeOverride; /// @notice Maps sender entity type to receiver entity type to fee percentage as a zoc. mapping(uint8 => mapping(uint8 => uint32)) defaultTransferFee; /// @notice Maps specific entity sender to receiver entity type to fee percentage as a zoc. mapping(Entity => mapping(uint8 => uint32)) transferFeeSenderOverride; /// @notice Maps sender entity type to specific entity receiver to fee percentage as a zoc. mapping(uint8 => mapping(Entity => uint32)) transferFeeReceiverOverride; /// @notice Maps swap wrappers to their enabled/disabled status. mapping(ISwapWrapper => bool) public isSwapperSupported; /// @notice Maps portfolios to their enabled/disabled status. mapping(Portfolio => bool) public isActivePortfolio; // --- Events --- /// @notice The event emitted when a factory is approved (whitelisted) or has it's approval removed. event FactoryApprovalSet(address indexed factory, bool isApproved); /// @notice The event emitted when an entity is set active or inactive. event EntityStatusSet(address indexed entity, bool isActive); /// @notice The event emitted when a swap wrapper is set active or inactive. event SwapWrapperStatusSet(address indexed swapWrapper, bool isSupported); /// @notice The event emitted when a portfolio is set active or inactive. event PortfolioStatusSet(address indexed portfolio, bool isActive); /// @notice Emitted when a default donation fee is set for an entity type. event DefaultDonationFeeSet(uint8 indexed entityType, uint32 fee); /// @notice Emitted when a donation fee override is set for a specific receiving entity. event DonationFeeReceiverOverrideSet(address indexed entity, uint32 fee); /// @notice Emitted when a default payout fee is set for an entity type. event DefaultPayoutFeeSet(uint8 indexed entityType, uint32 fee); /// @notice Emitted when a payout fee override is set for a specific sender entity. event PayoutFeeOverrideSet(address indexed entity, uint32 fee); /// @notice Emitted when a default transfer fee is set for transfers between entity types. event DefaultTransferFeeSet(uint8 indexed fromEntityType, uint8 indexed toEntityType, uint32 fee); /// @notice Emitted when a transfer fee override is set for transfers from an entity to a specific entityType. event TransferFeeSenderOverrideSet(address indexed fromEntity, uint8 indexed toEntityType, uint32 fee); /// @notice Emitted when a transfer fee override is set for transfers from an entityType to an entity. event TransferFeeReceiverOverrideSet(uint8 indexed fromEntityType, address indexed toEntity, uint32 fee); /// @notice Emitted when the registry treasury contract is changed event TreasuryChanged(address oldTreasury, address indexed newTreasury); /** * @notice Modifier for methods that require auth and that the manager cannot access. * @dev Overridden from Auth.sol. Reason: use custom error. */ modifier requiresAuth() override { if (!isAuthorized(msg.sender, msg.sig)) revert Unauthorized(); _; } // --- Constructor --- constructor(address _admin, address _treasury, ERC20 _baseToken) RegistryAuth(_admin, Authority(address(this))) { treasury = _treasury; emit TreasuryChanged(address(0), _treasury); baseToken = _baseToken; } // --- Internal fns --- /** * @notice Fee parsing to convert the special "type(uint32).max" value to zero, and zero to the "max". * @dev After converting, "type(uint32).max" will cause overflow/revert when used as a fee percentage multiplier and zero will mean no fee. * @param _value The value to be converted. * @return The parsed fee to use. */ function _parseFeeWithFlip(uint32 _value) private pure returns (uint32) { if (_value == 0) { return type(uint32).max; } else if (_value == type(uint32).max) { return 0; } else { return _value; } } // --- External fns --- /** * @notice Sets a new Endaoment treasury address. * @param _newTreasury The new treasury. */ function setTreasury(address _newTreasury) external requiresAuth { emit TreasuryChanged(treasury, _newTreasury); treasury = _newTreasury; } /** * @notice Sets the approval state of a factory. Grants the factory permissions to set entity status. * @param _factory The factory whose approval state is to be updated. * @param _isApproved True if the factory should be approved, false otherwise. */ function setFactoryApproval(address _factory, bool _isApproved) external requiresAuth { isApprovedFactory[_factory] = _isApproved; emit FactoryApprovalSet(address(_factory), _isApproved); } /** * @notice Sets the enable/disable state of an Entity. * @param _entity The entity whose active state is to be updated. * @param _isActive True if the entity should be active, false otherwise. */ function setEntityStatus(Entity _entity, bool _isActive) external requiresAuth { isActiveEntity[_entity] = _isActive; emit EntityStatusSet(address(_entity), _isActive); } /** * @notice Sets Entity as active. This is a special method to be called only by approved factories. * Other callers should use `setEntityStatus` instead. * @param _entity The entity. */ function setEntityActive(Entity _entity) external { if (!isApprovedFactory[msg.sender]) revert Unauthorized(); isActiveEntity[_entity] = true; emit EntityStatusSet(address(_entity), true); } /** * @notice Sets the enable/disable state of a Portfolio. * @param _portfolio Portfolio. * @param _isActive True if setting portfolio to active, false otherwise. */ function setPortfolioStatus(Portfolio _portfolio, bool _isActive) external requiresAuth { isActivePortfolio[_portfolio] = _isActive; emit PortfolioStatusSet(address(_portfolio), _isActive); } /** * @notice Gets default donation fee pct (as a zoc) for an Entity. * @param _entity The receiving entity of the donation for which the fee is being fetched. * @return uint32 The default donation fee for the entity's type. * @dev Makes use of _parseFeeWithFlip, so if no default exists, "max" will be returned. */ function getDonationFee(Entity _entity) external view returns (uint32) { return _parseFeeWithFlip(defaultDonationFee[_entity.entityType()]); } /** * @notice Gets lowest possible donation fee pct (as a zoc) for an Entity, among default and override. * @param _entity The receiving entity of the donation for which the fee is being fetched. * @return uint32 The minimum of the default donation fee and the receiver's fee override. * @dev Makes use of _parseFeeWithFlip, so if no default or override exists, "max" will be returned. */ function getDonationFeeWithOverrides(Entity _entity) external view returns (uint32) { uint32 _default = _parseFeeWithFlip(defaultDonationFee[_entity.entityType()]); uint32 _receiverOverride = _parseFeeWithFlip(donationFeeReceiverOverride[_entity]); return _receiverOverride < _default ? _receiverOverride : _default; } /** * @notice Gets default payout fee pct (as a zoc) for an Entity. * @param _entity The sender entity of the payout for which the fee is being fetched. * @return uint32 The default payout fee for the entity's type. * @dev Makes use of _parseFeeWithFlip, so if no default exists, "max" will be returned. */ function getPayoutFee(Entity _entity) external view returns (uint32) { return _parseFeeWithFlip(defaultPayoutFee[_entity.entityType()]); } /** * @notice Gets lowest possible payout fee pct (as a zoc) for an Entity, among default and override. * @param _entity The sender entity of the payout for which the fee is being fetched. * @return uint32 The minimum of the default payout fee and the sender's fee override. * @dev Makes use of _parseFeeWithFlip, so if no default or override exists, "max" will be returned. */ function getPayoutFeeWithOverrides(Entity _entity) external view returns (uint32) { uint32 _default = _parseFeeWithFlip(defaultPayoutFee[_entity.entityType()]); uint32 _senderOverride = _parseFeeWithFlip(payoutFeeOverride[_entity]); return _senderOverride < _default ? _senderOverride : _default; } /** * @notice Gets default transfer fee pct (as a zoc) between sender & receiver Entities. * @param _sender The sending entity of the transfer for which the fee is being fetched. * @param _receiver The receiving entity of the transfer for which the fee is being fetched. * @return uint32 The default transfer fee. * @dev Makes use of _parseFeeWithFlip, so if no default exists, "type(uint32).max" will be returned. */ function getTransferFee(Entity _sender, Entity _receiver) external view returns (uint32) { return _parseFeeWithFlip(defaultTransferFee[_sender.entityType()][_receiver.entityType()]); } /** * @notice Gets lowest possible transfer fee pct (as a zoc) between sender & receiver Entities, among default and overrides. * @param _sender The sending entity of the transfer for which the fee is being fetched. * @param _receiver The receiving entity of the transfer for which the fee is being fetched. * @return uint32 The minimum of the default transfer fee, and sender and receiver overrides. * @dev Makes use of _parseFeeWithFlip, so if no default or overrides exist, "type(uint32).max" will be returned. */ function getTransferFeeWithOverrides(Entity _sender, Entity _receiver) external view returns (uint32) { uint32 _default = _parseFeeWithFlip(defaultTransferFee[_sender.entityType()][_receiver.entityType()]); uint32 _senderOverride = _parseFeeWithFlip(transferFeeSenderOverride[_sender][_receiver.entityType()]); uint32 _receiverOverride = _parseFeeWithFlip(transferFeeReceiverOverride[_sender.entityType()][_receiver]); uint32 _lowestFee = _default; _lowestFee = _senderOverride < _lowestFee ? _senderOverride : _lowestFee; _lowestFee = _receiverOverride < _lowestFee ? _receiverOverride : _lowestFee; return _lowestFee; } /** * @notice Sets the default donation fee for an entity type. * @param _entityType Entity type. * @param _fee The fee percentage to be set (a zoc). */ function setDefaultDonationFee(uint8 _entityType, uint32 _fee) external requiresAuth { defaultDonationFee[_entityType] = _parseFeeWithFlip(_fee); emit DefaultDonationFeeSet(_entityType, _fee); } /** * @notice Sets the donation fee receiver override for a specific entity. * @param _entity Entity. * @param _fee The overriding fee (a zoc). */ function setDonationFeeReceiverOverride(Entity _entity, uint32 _fee) external requiresAuth { donationFeeReceiverOverride[_entity] = _parseFeeWithFlip(_fee); emit DonationFeeReceiverOverrideSet(address(_entity), _fee); } /** * @notice Sets the default payout fee for an entity type. * @param _entityType Entity type. * @param _fee The fee percentage to be set (a zoc). */ function setDefaultPayoutFee(uint8 _entityType, uint32 _fee) external requiresAuth { defaultPayoutFee[_entityType] = _parseFeeWithFlip(_fee); emit DefaultPayoutFeeSet(_entityType, _fee); } /** * @notice Sets the payout fee override for a specific entity. * @param _entity Entity. * @param _fee The overriding fee (a zoc). */ function setPayoutFeeOverride(Entity _entity, uint32 _fee) external requiresAuth { payoutFeeOverride[_entity] = _parseFeeWithFlip(_fee); emit PayoutFeeOverrideSet(address(_entity), _fee); } /** * @notice Sets the default transfer fee for transfers from one specific entity type to another. * @param _fromEntityType The entityType making the transfer. * @param _toEntityType The receiving entityType. * @param _fee The transfer fee percentage (a zoc). */ function setDefaultTransferFee(uint8 _fromEntityType, uint8 _toEntityType, uint32 _fee) external requiresAuth { defaultTransferFee[_fromEntityType][_toEntityType] = _parseFeeWithFlip(_fee); emit DefaultTransferFeeSet(_fromEntityType, _toEntityType, _fee); } /** * @notice Sets the transfer fee override for transfers from one specific entity to entities of a given type. * @param _fromEntity The entity making the transfer. * @param _toEntityType The receiving entityType. * @param _fee The overriding fee percentage (a zoc). */ function setTransferFeeSenderOverride(Entity _fromEntity, uint8 _toEntityType, uint32 _fee) external requiresAuth { transferFeeSenderOverride[_fromEntity][_toEntityType] = _parseFeeWithFlip(_fee); emit TransferFeeSenderOverrideSet(address(_fromEntity), _toEntityType, _fee); } /** * @notice Sets the transfer fee override for transfers from entities of a given type to a specific entity. * @param _fromEntityType The entityType making the transfer. * @param _toEntity The receiving entity. * @param _fee The overriding fee percentage (a zoc). */ function setTransferFeeReceiverOverride(uint8 _fromEntityType, Entity _toEntity, uint32 _fee) external requiresAuth { transferFeeReceiverOverride[_fromEntityType][_toEntity] = _parseFeeWithFlip(_fee); emit TransferFeeReceiverOverrideSet(_fromEntityType, address(_toEntity), _fee); } /** * @notice Sets the enable/disable state of a SwapWrapper. System owners must ensure meticulous review of SwapWrappers before approving them. * @param _swapWrapper A contract that implements ISwapWrapper. * @param _supported `true` if supported, `false` if unsupported. */ function setSwapWrapperStatus(ISwapWrapper _swapWrapper, bool _supported) external requiresAuth { isSwapperSupported[_swapWrapper] = _supported; emit SwapWrapperStatusSet(address(_swapWrapper), _supported); } }
//SPDX-License-Identifier: BSD 3-Clause pragma solidity >=0.8.0; import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {Registry} from "./Registry.sol"; import {Entity} from "./Entity.sol"; import {EndaomentAuth} from "./lib/auth/EndaomentAuth.sol"; import {Math} from "./lib/Math.sol"; abstract contract Portfolio is ERC20, EndaomentAuth, ReentrancyGuard { using Math for uint256; using SafeTransferLib for ERC20; Registry public immutable registry; uint256 public cap; address public feeTreasury; uint256 public depositFee; uint256 public redemptionFee; address public immutable asset; address public immutable receiptAsset; ERC20 public immutable baseToken; bool public didShutdown; uint256 public timestampAumFeesTaken; uint256 public aumRate; uint256 internal constant MAX_AUM_RATE = 3168808782; error InvalidSwapper(); error InvalidRate(); error TransferDisallowed(); error DepositAfterShutdown(); error DidShutdown(); error NotEntity(); error BadCheckCapImplementation(); error ExceedsCap(); error PercentageOver100(); error RoundsToZero(); error Slippage(); error CallFailed(bytes response); /// @notice `sender` has exchanged `assets` (after fees) for `shares`, and transferred those `shares` to `receiver`. /// The sender paid a total of `depositAmount` and was charged `fee` for the transaction. event Deposit( address indexed sender, address indexed receiver, uint256 assets, uint256 shares, uint256 depositAmount, uint256 fee ); /// @notice `sender` has exchanged `shares` for `assets`, and transferred those `assets` to `receiver`. /// The sender received a net of `redeemedAmount` after the conversion of `assets` into base tokens /// and was charged `fee` for the transaction. event Redeem( address indexed sender, address indexed receiver, uint256 assets, uint256 shares, uint256 redeemedAmount, uint256 fee ); /// @notice Event emitted when `cap` is set. event CapSet(uint256 cap); /// @notice Event emitted when `depositFee` is set. event DepositFeeSet(uint256 fee); /// @notice Event emitted when `redemptionFee` is set. event RedemptionFeeSet(uint256 fee); /// @notice Event emitted when `feeTreasury` is set. event FeeTreasurySet(address feeTreasury); /// @notice Event emitted when management takes fees. event FeesTaken(uint256 amount); /// @notice Event emitted when AUM fees are taken. event AumFeesTaken(uint256 feeAmount, uint256 timeDelta); /// @notice Event emitted when `aumRate` is set. event AumRateSet(uint256 rate); /// @notice Event emitted when admin forcefully swaps portfolio asset balance for baseToken. event Shutdown(uint256 assetAmount, uint256 baseTokenOut); /** * @param _registry Endaoment registry. * @param _receiptAsset Address of token that the portfolio receives from a deposit. * @param _name Name of the ERC20 Portfolio share tokens. * @param _symbol Symbol of the ERC20 Portfolio share tokens. * @param _cap Amount in baseToken that value of totalAssets should not exceed. * @param _depositFee Percentage fee as ZOC that will go to treasury on asset deposit. * @param _redemptionFee Percentage fee as ZOC that will go to treasury on share redemption. * @param _aumRate Percentage fee per second (as WAD) that should accrue to treasury as AUM fee. (1e16 = 1%). */ constructor( Registry _registry, address _receiptAsset, string memory _name, string memory _symbol, uint256 _cap, address _feeTreasury, uint256 _depositFee, uint256 _redemptionFee, uint256 _aumRate ) ERC20(_name, _symbol, ERC20(_getAsset(_receiptAsset)).decimals()) { __initEndaomentAuth(_registry, "portfolio"); registry = _registry; feeTreasury = _feeTreasury; emit FeeTreasurySet(_feeTreasury); if (_redemptionFee > Math.ZOC) revert PercentageOver100(); redemptionFee = _redemptionFee; emit RedemptionFeeSet(_redemptionFee); if (_depositFee > Math.ZOC) revert PercentageOver100(); depositFee = _depositFee; emit DepositFeeSet(_depositFee); cap = _cap; emit CapSet(_cap); receiptAsset = _receiptAsset; asset = _getAsset(_receiptAsset); baseToken = registry.baseToken(); if (_aumRate > MAX_AUM_RATE) revert InvalidRate(); aumRate = _aumRate; emit AumRateSet(_aumRate); timestampAumFeesTaken = block.timestamp; } /** * @notice Returns the underlying asset for the `receiptAsset`. * @param _receiptAsset Address of token that the portfolio receives from a deposit. * @return Address of the underlying asset. */ function _getAsset(address _receiptAsset) internal view virtual returns (address); /** * @notice Function used to determine whether an Entity is active on the registry. * @param _entity The Entity. */ function _isEntity(Entity _entity) internal view returns (bool) { return registry.isActiveEntity(_entity); } /** * @notice Set the Portfolio cap. * @param _amount Amount, denominated in baseToken. */ function setCap(uint256 _amount) external requiresAuth { cap = _amount; emit CapSet(_amount); } /** * @notice Set deposit fee. * @param _pct Percentage as ZOC (e.g. 1000 = 10%). */ function setDepositFee(uint256 _pct) external requiresAuth { if (_pct > Math.ZOC) revert PercentageOver100(); depositFee = _pct; emit DepositFeeSet(_pct); } /** * @notice Set redemption fee. * @param _pct Percentage as ZOC (e.g. 1000 = 10%). */ function setRedemptionFee(uint256 _pct) external requiresAuth { if (_pct > Math.ZOC) revert PercentageOver100(); redemptionFee = _pct; emit RedemptionFeeSet(_pct); } /** * @notice Set fee treasury. * @param _feeTreasury Address of the treasury that should receive fees. * */ function setFeeTreasury(address _feeTreasury) external requiresAuth { feeTreasury = _feeTreasury; emit FeeTreasurySet(_feeTreasury); } /** * @notice Set AUM rate. * @param _pct Percentage *per second* as WAD (e.g. .01e18 / 365.25 days = 1% per year). */ function setAumRate(uint256 _pct) external requiresAuth { // check to make sure _pct isn't above 10% over a year (.1e18 / 365.25 days = 3168808782 per second) if (_pct > MAX_AUM_RATE) revert InvalidRate(); takeAumFees(); aumRate = _pct; emit AumRateSet(_pct); } /** * @notice Total amount of the underlying asset that is managed by the Portfolio. * @return Total amount of the underlying asset. */ function totalAssets() public view returns (uint256) { return convertReceiptAssetsToAssets(totalReceiptAssets()); } /** * @notice Total amount of the receipt asset that is managed by the Portfolio. * @return Total amount of the receipt asset. */ function totalReceiptAssets() public view returns (uint256) { return ERC20(receiptAsset).balanceOf(address(this)); } /** * @notice Calculates the equivalent amount of assets for the given amount of receipt assets. * @param _receiptAssets Amount of receipt assets to convert. * @return Amount of assets. */ function convertReceiptAssetsToAssets(uint256 _receiptAssets) public view virtual returns (uint256); /** * @notice Takes some amount of receipt assets from this portfolio as management fee. * @param _amountReceiptAssets Amount of receipt assets to take. */ function takeFees(uint256 _amountReceiptAssets) external requiresAuth { ERC20(receiptAsset).safeTransfer(feeTreasury, _amountReceiptAssets); emit FeesTaken(_amountReceiptAssets); } /** * @notice Takes accrued percentage of assets from this portfolio as AUM fee. */ function takeAumFees() public { if (didShutdown) return _takeAumFeesShutdown(); uint256 _totalReceiptAssets = totalReceiptAssets(); uint256 _period = block.timestamp - timestampAumFeesTaken; uint256 _feeAmount = _calculateAumFee(_totalReceiptAssets, _period); if (_feeAmount > _totalReceiptAssets) _feeAmount = _totalReceiptAssets; if (_feeAmount > 0 || totalSupply == 0) { // in either case, we want to set `timestampAumFeesTaken`... timestampAumFeesTaken = block.timestamp; // but we only want to transfer/emit on non-zero amount if (_feeAmount > 0) { ERC20(receiptAsset).safeTransfer(feeTreasury, _feeAmount); emit AumFeesTaken(_feeAmount, _period); } } } /** * @notice Takes accrued percentage of post-shutdown baseToken from this portfolio as AUM fee. */ function _takeAumFeesShutdown() internal { uint256 _totalAssets = baseToken.balanceOf(address(this)); uint256 _period = block.timestamp - timestampAumFeesTaken; uint256 _feeAmount = _calculateAumFee(_totalAssets, _period); if (_feeAmount > _totalAssets) _feeAmount = _totalAssets; // in `takeAumFees`, the following conditional checks totalSupply as well, solving a first deposit corner case. // In this case, we don't need to check, because deposits aren't allowed after shutdown. if (_feeAmount > 0) { timestampAumFeesTaken = block.timestamp; baseToken.safeTransfer(feeTreasury, _feeAmount); emit AumFeesTaken(_feeAmount, _period); } } /** * @notice Exchange `_amountBaseToken` for some amount of Portfolio shares. * @param _amountBaseToken The amount of the Entity's baseToken to deposit. * @param _data Data that the portfolio needs to make the deposit. In some cases, this will be swap parameters. * The first 32 bytes of this data should be the ABI-encoded `minSharesOut`. * @return shares The amount of shares that this deposit yields to the Entity. */ function deposit(uint256 _amountBaseToken, bytes calldata _data) external nonReentrant returns (uint256) { // all portfolios should revert on deposit after shutdown if (didShutdown) revert DepositAfterShutdown(); // all portfolios should revert on a deposit from a non-entity (or inactive one) if (!_isEntity(Entity(payable(msg.sender)))) revert NotEntity(); // all portfolios should take AUM fees takeAumFees(); // all portfolios should make a deposit // all transferring of baseToken and share calculation should occur inside _deposit (uint256 _shares, uint256 _assets, uint256 _fee) = _deposit(_amountBaseToken, _data); if (_shares < abi.decode(_data, (uint256))) revert Slippage(); if (_shares == 0) revert RoundsToZero(); // mint shares _mint(msg.sender, _shares); // and check cap _checkCap(); // and emit an event emit Deposit(msg.sender, msg.sender, _assets, _shares, _amountBaseToken, _fee); return _shares; } /** * @notice Check to make sure the cap has not been exceeded. * @dev Most portfolios have the same asset and baseToken, so the _checkCap implementation here is written to accomodate * that situation. For portfolios where that is not the case, this method needs to be overwritten to ensure the cap * (denominated in baseToken) is properly compared to the number of assets. */ function _checkCap() internal virtual { if (asset != address(baseToken)) revert BadCheckCapImplementation(); if (totalAssets() > cap) revert ExceedsCap(); } /** * @notice Exchange `_amountIn` for some amount of Portfolio shares. * @dev Should include the transferring of baseToken and conversion to shares. * @param _amountIn The amount of the Entity's baseToken to deposit. * @param _data Data that the portfolio needs to make the deposit. In some cases, this will be swap parameters. * @return shares The amount of shares that this deposit yields to the Entity. * @return assets The amount of assets that this deposit yields to the portfolio. * @return fee The baseToken fee that this deposit yields to the treasury. */ function _deposit(uint256 _amountIn, bytes calldata _data) internal virtual returns (uint256 shares, uint256 assets, uint256 fee); /** * @notice Exchange `_amountShares` for some amount of baseToken. * @param _amountShares The amount of the Entity's portfolio shares to exchange. * @param _data Data that the portfolio needs to make the redemption. In some cases, this will be swap parameters. * @return baseTokenOut The amount of baseToken that this redemption yields to the Entity. */ function redeem(uint256 _amountShares, bytes calldata _data) external nonReentrant returns (uint256 baseTokenOut) { takeAumFees(); if (didShutdown) return _redeemShutdown(_amountShares); (uint256 _assetsOut, uint256 _baseTokenOut) = _redeem(_amountShares, _data); if (_assetsOut == 0) revert RoundsToZero(); _burn(msg.sender, _amountShares); (uint256 _netAmount, uint256 _fee) = _calculateFee(_baseTokenOut, redemptionFee); baseToken.safeTransfer(feeTreasury, _fee); baseToken.safeTransfer(msg.sender, _netAmount); emit Redeem(msg.sender, msg.sender, _assetsOut, _amountShares, _netAmount, _fee); return _netAmount; } /** * @notice Exchange `_amountShares` for some amount of Portfolio assets. * @param _amountShares The amount of portfolio shares to exchange. * @param _data Data that the portfolio needs to redeem the assets. In some cases, this will be swap parameters. * @return assetsOut The amount of assets that this redemption yielded (and then converted to baseToken). * @return baseTokenOut Amount in baseToken to which these assets were converted. */ function _redeem(uint256 _amountShares, bytes calldata _data) internal virtual returns (uint256 assetsOut, uint256 baseTokenOut); /** * @notice Handles redemption after shutdown, exchanging shares for baseToken. * @param _amountShares Shares being redeemed. * @return Amount of baseToken received. */ function _redeemShutdown(uint256 _amountShares) internal returns (uint256) { uint256 _baseTokenOut = convertToAssetsShutdown(_amountShares); _burn(msg.sender, _amountShares); (uint256 _netAmount, uint256 _fee) = _calculateFee(_baseTokenOut, redemptionFee); baseToken.safeTransfer(feeTreasury, _fee); baseToken.safeTransfer(msg.sender, _netAmount); emit Redeem(msg.sender, msg.sender, _baseTokenOut, _amountShares, _netAmount, _fee); return _netAmount; } /** * @notice Calculates the amount of shares that the Portfolio should exchange for the amount of assets provided. * @param _assets Amount of assets. * @return Amount of shares. */ function convertToShares(uint256 _assets) public view returns (uint256) { uint256 _supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. return _supply == 0 ? _assets : _assets.mulDivDown(_supply, totalAssets()); } /** * @notice Calculates the amount of assets that the Portfolio should exchange for the amount of shares provided. * @param _shares Amount of shares. * @return Amount of assets. */ function convertToAssets(uint256 _shares) public view returns (uint256) { uint256 _supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. return _supply == 0 ? _shares : _shares.mulDivDown(totalAssets(), _supply); } /** * @notice Calculates the amount of baseToken that the Portfolio should exchange for the amount of shares provided. * Used only if the Portfolio has shut down. * @dev Rounding down here favors the portfolio, so the user gets slightly less and the portfolio gets slightly more, * that way it prevents a situation where the user is owed x but the vault only has x - epsilon, where epsilon is * some tiny number due to rounding error. * @param _shares Amount of shares. * @return Amount of baseToken. */ function convertToAssetsShutdown(uint256 _shares) public view returns (uint256) { uint256 _supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. return _supply == 0 ? _shares : _shares.mulDivDown(baseToken.balanceOf(address(this)), _supply); } /** * @notice Exit out all assets of portfolio for baseToken. Must persist a mechanism for entities to redeem their shares for baseToken. * @param _data Data that the portfolio needs to exit from asset. Consult the portfolio's `_exit` method to determine * the correct format for this data. * @return baseTokenOut The amount of baseToken that this exit yielded. */ function shutdown(bytes calldata _data) external requiresAuth returns (uint256 baseTokenOut) { if (didShutdown) revert DidShutdown(); didShutdown = true; uint256 _assetsOut = totalAssets(); // In most cases, _actualAssetsOut will equal _assetsOut, but in SingleTokenPortfolio, it may be less. (uint256 _actualAssetsOut, uint256 _baseTokenOut) = _exit(_assetsOut, _data); emit Shutdown(_actualAssetsOut, _baseTokenOut); return _baseTokenOut; } /** * @notice Convert some amount of asset into baseToken, either partially or fully exiting the portfolio asset. * @dev This method is used in `redeem` and `shutdown` calls. * @param _amount The amount of the Entity's portfolio asset to exchange. * @param _data Data that the portfolio needs to exit from asset. In some cases, this will be swap parameters. Consult the portfolio's * `_exit` method to determine the correct format for this data. * @return actualAssetsOut The amount of assets that were exited. In most cases, this will be equal to `_amount`, but may differ * by some errorMarginPct in SingleTokenPortfolio. * @return baseTokenOut The amount of baseToken that this exit yielded. */ function _exit(uint256 _amount, bytes calldata _data) internal virtual returns (uint256 actualAssetsOut, uint256 baseTokenOut); /// @notice `transfer` disabled on Portfolio tokens. function transfer( address, // to uint256 // amount ) public pure override returns (bool) { revert TransferDisallowed(); } /// @notice `transferFrom` disabled on Portfolio tokens. function transferFrom(address, /* from */ address, /* to */ uint256 /* amount */ ) public pure override returns (bool) { revert TransferDisallowed(); } /// @notice `approve` disabled on Portfolio tokens. function approve(address, /* to */ uint256 /* amount */ ) public pure override returns (bool) { revert TransferDisallowed(); } /// @notice `permit` disabled on Portfolio tokens. function permit( address, /* owner */ address, /* spender */ uint256, /* value */ uint256, /* deadline */ uint8, /* v */ bytes32, /* r */ bytes32 /* s */ ) public pure override { revert TransferDisallowed(); } /** * @notice Permissioned method that allows Endaoment admin to make arbitrary calls acting as this Portfolio. * @param _target The address to which the call will be made. * @param _value The ETH value that should be forwarded with the call. * @param _data The calldata that will be sent with the call. * @return _return The data returned by the call. */ function callAsPortfolio(address _target, uint256 _value, bytes memory _data) external payable requiresAuth returns (bytes memory) { (bool _success, bytes memory _response) = payable(_target).call{value: _value}(_data); if (!_success) revert CallFailed(_response); return _response; } /** * @notice Internal helper method to calculate the fee on a base token amount for a given fee multiplier. * @param _amount Amount of baseToken. * @param _feeMultiplier Multiplier (as zoc) to apply to the amount. * @return _netAmount The amount of baseToken after the fee is applied. * @return _fee The amount of baseToken to be taken as a fee. */ function _calculateFee(uint256 _amount, uint256 _feeMultiplier) internal pure returns (uint256 _netAmount, uint256 _fee) { if (_feeMultiplier > Math.ZOC) revert PercentageOver100(); unchecked { // unchecked as no possibility of overflow with baseToken precision _fee = _amount.zocmul(_feeMultiplier); // unchecked as the _feeMultiplier check with revert above protects against overflow _netAmount = _amount - _fee; } } /** * @notice Helper method to calculate AUM fee based on assets and time elapsed. * @param _totalAssets Assets over which to calculate AUM fee. * @param _period Seconds elapsed since AUM fee was last taken. * @dev We chose to calculate using simple interest rather than compound interest because the error was small and * simple interest is easier to calculate, reason about, and test. * @return _aumFee The amount of baseToken to be taken as AUM fee. */ function _calculateAumFee(uint256 _totalAssets, uint256 _period) internal view returns (uint256) { if (_totalAssets == 0 || aumRate == 0 || _period == 0) return 0; // _period * aumRate is safe; max expected aum rate * 10 years of seconds is just over 1 WAD return _totalAssets.mulWadDown(_period * aumRate); } }
//SPDX-License-Identifier: BSD 3-Clause pragma solidity >=0.8.0; error ETHAmountInMismatch(); /** * @notice ISwapWrapper is the interface that all swap wrappers should implement. * This will be used to support swap protocols like Uniswap V2 and V3, Sushiswap, 1inch, etc. */ interface ISwapWrapper { /// @notice Event emitted after a successful swap. event WrapperSwapExecuted( address indexed tokenIn, address indexed tokenOut, address sender, address indexed recipient, uint256 amountIn, uint256 amountOut ); /// @notice Name of swap wrapper for UX readability. function name() external returns (string memory); /** * @notice Swap function. Generally we expect the implementer to call some exactAmountIn-like swap method, and so the documentation * is written with this in mind. However, the method signature is general enough to support exactAmountOut swaps as well. * @param _tokenIn Token to be swapped (or 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for ETH). * @param _tokenOut Token to receive (or 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for ETH). * @param _recipient Receiver of `_tokenOut`. * @param _amount Amount of `_tokenIn` that should be swapped. * @param _data Additional data that the swap wrapper may require to execute the swap. * @return Amount of _tokenOut received. */ function swap(address _tokenIn, address _tokenOut, address _recipient, uint256 _amount, bytes calldata _data) external payable returns (uint256); }
// SPDX-License-Identifier: BSD 3-Clause pragma solidity 0.8.13; library Math { uint256 internal constant ZOC = 1e4; /** * @dev Multiply 2 numbers where at least one is a zoc, return product in original units of the other number. */ function zocmul(uint256 x, uint256 y) internal pure returns (uint256 z) { z = x * y; unchecked { z /= ZOC; } } // Below is WAD math from solmate's FixedPointMathLib. // https://github.com/Rari-Capital/solmate/blob/c8278b3cb948cffda3f1de5a401858035f262060/src/utils/FixedPointMathLib.sol uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s. function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down. } function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down. } // For tokens with 6 decimals like USDC, these scale by 1e6 (one million). function mulMilDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, y, 1e6); // Equivalent to (x * y) / 1e6 rounded down. } function divMilDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, 1e6, y); // Equivalent to (x * 1e6) / y rounded down. } /*////////////////////////////////////////////////////////////// LOW LEVEL FIXED POINT OPERATIONS //////////////////////////////////////////////////////////////*/ function mulDivDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) { assembly { // Store x * y in z for now. z := mul(x, y) // Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y)) if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) { revert(0, 0) } // Divide z by the denominator. z := div(z, denominator) } } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. abstract contract ERC20 { /*/////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 amount); /*/////////////////////////////////////////////////////////////// METADATA STORAGE //////////////////////////////////////////////////////////////*/ string public name; string public symbol; uint8 public immutable decimals; /*/////////////////////////////////////////////////////////////// ERC20 STORAGE //////////////////////////////////////////////////////////////*/ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; /*/////////////////////////////////////////////////////////////// EIP-2612 STORAGE //////////////////////////////////////////////////////////////*/ bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); uint256 internal immutable INITIAL_CHAIN_ID; bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; /*/////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor( string memory _name, string memory _symbol, uint8 _decimals ) { name = _name; symbol = _symbol; decimals = _decimals; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); } /*/////////////////////////////////////////////////////////////// ERC20 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 amount) public virtual returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); return true; } function transferFrom( address from, address to, uint256 amount ) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount); return true; } /*/////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { bytes32 digest = keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)) ) ); address recoveredAddress = ecrecover(digest, v, r, s); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); } /*/////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 amount) internal virtual { totalSupply += amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal virtual { balanceOf[from] -= amount; // Cannot underflow because a user's balance // will never be larger than the total supply. unchecked { totalSupply -= amount; } emit Transfer(from, address(0), amount); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "../tokens/ERC20.sol"; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol) /// @author Modified from Gnosis (https://github.com/gnosis/gp-v2-contracts/blob/main/src/contracts/libraries/GPv2SafeERC20.sol) /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. library SafeTransferLib { /*/////////////////////////////////////////////////////////////// ETH OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferETH(address to, uint256 amount) internal { bool callStatus; assembly { // Transfer the ETH and store if it succeeded or not. callStatus := call(gas(), to, amount, 0, 0, 0, 0) } require(callStatus, "ETH_TRANSFER_FAILED"); } /*/////////////////////////////////////////////////////////////// ERC20 OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferFrom( ERC20 token, address from, address to, uint256 amount ) internal { bool callStatus; assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata to memory piece by piece: mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) // Begin with the function selector. mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "from" argument. mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument. mstore(add(freeMemoryPointer, 68), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value. // Call the token and store if it succeeded or not. // We use 100 because the calldata length is 4 + 32 * 3. callStatus := call(gas(), token, 0, freeMemoryPointer, 100, 0, 0) } require(didLastOptionalReturnCallSucceed(callStatus), "TRANSFER_FROM_FAILED"); } function safeTransfer( ERC20 token, address to, uint256 amount ) internal { bool callStatus; assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata to memory piece by piece: mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) // Begin with the function selector. mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value. // Call the token and store if it succeeded or not. // We use 68 because the calldata length is 4 + 32 * 2. callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0) } require(didLastOptionalReturnCallSucceed(callStatus), "TRANSFER_FAILED"); } function safeApprove( ERC20 token, address to, uint256 amount ) internal { bool callStatus; assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata to memory piece by piece: mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) // Begin with the function selector. mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value. // Call the token and store if it succeeded or not. // We use 68 because the calldata length is 4 + 32 * 2. callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0) } require(didLastOptionalReturnCallSucceed(callStatus), "APPROVE_FAILED"); } /*/////////////////////////////////////////////////////////////// INTERNAL HELPER LOGIC //////////////////////////////////////////////////////////////*/ function didLastOptionalReturnCallSucceed(bool callStatus) private pure returns (bool success) { assembly { // Get how many bytes the call returned. let returnDataSize := returndatasize() // If the call reverted: if iszero(callStatus) { // Copy the revert message into memory. returndatacopy(0, 0, returnDataSize) // Revert with the same message. revert(0, returnDataSize) } switch returnDataSize case 32 { // Copy the return data into memory. returndatacopy(0, 0, returnDataSize) // Set success to whether it returned true. success := iszero(iszero(mload(0))) } case 0 { // There was no return data. success := 1 } default { // It returned some malformed input. success := 0 } } } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; // This contract is modified from Solmate only to make requiresAuth virtual on line 26 /// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Auth.sol) /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol) abstract contract Auth { event OwnerUpdated(address indexed user, address indexed newOwner); event AuthorityUpdated(address indexed user, Authority indexed newAuthority); address public owner; Authority public authority; constructor(address _owner, Authority _authority) { owner = _owner; authority = _authority; emit OwnerUpdated(msg.sender, _owner); emit AuthorityUpdated(msg.sender, _authority); } modifier requiresAuth() virtual { require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED"); _; } function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) { Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas. // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be // aware that this makes protected functions uncallable even to the owner if the authority is out of order. return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner; } function setAuthority(Authority newAuthority) public virtual { // We check if the caller is the owner first because we want to ensure they can // always swap out the authority even if it's reverting or using up a lot of gas. require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig)); authority = newAuthority; emit AuthorityUpdated(msg.sender, newAuthority); } function setOwner(address newOwner) public virtual requiresAuth { owner = newOwner; emit OwnerUpdated(msg.sender, newOwner); } } /// @notice A generic interface for a contract which provides authorization data to an Auth instance. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Auth.sol) /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol) interface Authority { function canCall(address user, address target, bytes4 functionSig) external view returns (bool); }
//SPDX-License-Identifier: BSD 3-Clause pragma solidity 0.8.13; import {Auth, Authority} from "./lib/auth/Auth.sol"; import {RolesAuthority} from "./lib/auth/authorities/RolesAuthority.sol"; // --- Errors --- error OwnershipInvalid(); /** * @notice RegistryAuth - contract to control ownership of the Registry. */ contract RegistryAuth is RolesAuthority { /// @notice Emitted when the first step of an ownership transfer (proposal) is done. event OwnershipTransferProposed(address indexed user, address indexed newOwner); /// @notice Emitted when the second step of an ownership transfer (claim) is done. event OwnershipChanged(address indexed owner, address indexed newOwner); // --- Storage --- /// @notice Pending owner for 2 step ownership transfer address public pendingOwner; // --- Constructor --- constructor(address _owner, Authority _authority) RolesAuthority(_owner, _authority) {} /** * @notice Starts the 2 step process of transferring registry authorization to a new owner. * @param _newOwner Proposed new owner of registry authorization. */ function transferOwnership(address _newOwner) external requiresAuth { pendingOwner = _newOwner; emit OwnershipTransferProposed(msg.sender, _newOwner); } /** * @notice Completes the 2 step process of transferring registry authorization to a new owner. * This function must be called by the proposed new owner. */ function claimOwnership() external { if (msg.sender != pendingOwner) revert OwnershipInvalid(); emit OwnershipChanged(owner, pendingOwner); owner = pendingOwner; pendingOwner = address(0); } /** * @notice Old approach of setting a new owner in a single step. * @dev This function throws an error to force use of the new 2-step approach. */ function setOwner(address /*newOwner*/ ) public view override requiresAuth { revert OwnershipInvalid(); } }
//SPDX-License-Identifier: BSD 3-Clause pragma solidity >=0.8.0; import "solmate/tokens/ERC20.sol"; import "solmate/utils/SafeTransferLib.sol"; import "./lib/ReentrancyGuard.sol"; import {Registry} from "./Registry.sol"; import {ISwapWrapper} from "./interfaces/ISwapWrapper.sol"; import {EndaomentAuth} from "./lib/auth/EndaomentAuth.sol"; import {Portfolio} from "./Portfolio.sol"; import {Math} from "./lib/Math.sol"; error EntityInactive(); error PortfolioInactive(); error InsufficientFunds(); error InvalidAction(); error BalanceMismatch(); error CallFailed(bytes response); /** * @notice Entity contract inherited by Org and Fund contracts (and all future kinds of Entities). */ abstract contract Entity is EndaomentAuth, ReentrancyGuard { using Math for uint256; using SafeTransferLib for ERC20; /// @notice The base registry to which the entity is connected. Registry public registry; /// @notice The entity's manager. address public manager; // @notice The base token used for tracking the entity's fund balance. ERC20 public baseToken; /// @notice The current balance for the entity, denominated in the base token's units. uint256 public balance; /// @notice Placeholder address used in swapping method to denote usage of ETH instead of a token. address public constant ETH_PLACEHOLDER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @notice Emitted when manager is set. event EntityManagerSet(address indexed oldManager, address indexed newManager); /// @notice Emitted when a donation is made. event EntityDonationReceived( address indexed from, address indexed to, address indexed tokenIn, uint256 amountIn, uint256 amountReceived, uint256 amountFee ); /// @notice Emitted when a payout is made from an entity. event EntityValuePaidOut(address indexed from, address indexed to, uint256 amountSent, uint256 amountFee); /// @notice Emitted when a transfer is made between entities. event EntityValueTransferred(address indexed from, address indexed to, uint256 amountReceived, uint256 amountFee); /// @notice Emitted when a base token reconciliation completes event EntityBalanceReconciled(address indexed entity, uint256 amountReceived, uint256 amountFee); /// @notice Emitted when a base token balance is used to correct the internal contract balance. event EntityBalanceCorrected(address indexed entity, uint256 newBalance); /// @notice Emitted when a portfolio deposit is made. event EntityDeposit(address indexed portfolio, uint256 baseTokenDeposited, uint256 sharesReceived); /// @notice Emitted when a portfolio share redemption is made. event EntityRedeem(address indexed portfolio, uint256 sharesRedeemed, uint256 baseTokenReceived); /// @notice Emitted when ether is received. event EntityEthReceived(address indexed sender, uint256 amount); /** * @notice Modifier for methods that require auth and that the manager can access. * @dev Uses the same condition as `requiresAuth` but with added manager access. */ modifier requiresManager() { if (msg.sender != manager && !isAuthorized(msg.sender, msg.sig)) revert Unauthorized(); _; } /// @notice Each entity will implement this function to allow a caller to interrogate what kind of entity it is. function entityType() public pure virtual returns (uint8); /** * @notice One time method to be called at deployment to configure the contract. Required so Entity * contracts can be deployed as minimal proxies (clones). * @param _registry The registry to host the Entity. * @param _manager The address of the Entity's manager. */ function __initEntity(Registry _registry, address _manager) internal { // Call to EndaomentAuth's initialize function ensures that this can't be called again __initEndaomentAuth(_registry, bytes20(bytes.concat("entity", bytes1(entityType())))); __initReentrancyGuard(); registry = _registry; manager = _manager; baseToken = _registry.baseToken(); } /** * @notice Set a new manager for this entity. * @param _manager Address of new manager. * @dev Callable by current manager or permissioned role. */ function setManager(address _manager) external virtual requiresManager { emit EntityManagerSet(manager, _manager); manager = _manager; } /** * @notice Receives a donated amount of base tokens to be added to the entity's balance. Transfers default fee to treasury. * @param _amount Amount donated in base token. * @dev Reverts if the donation fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). * @dev Reverts if the token transfer fails. */ function donate(uint256 _amount) external virtual { uint32 _feeMultiplier = registry.getDonationFee(this); _donateWithFeeMultiplier(_amount, _feeMultiplier); } /** * @notice Receives a donated amount of base tokens to be added to the entity's balance. Transfers default or overridden fee to treasury. * @param _amount Amount donated in base token. * @dev Reverts if the donation fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). * @dev Reverts if the token transfer fails. */ function donateWithOverrides(uint256 _amount) external virtual { uint32 _feeMultiplier = registry.getDonationFeeWithOverrides(this); _donateWithFeeMultiplier(_amount, _feeMultiplier); } /** * @notice Receives a donated amount of base tokens to be added to the entity's balance. * This method can be called by permissioned actors to make a donation with a manually specified fee. * @param _amount Amount donated in base token. * @param _feeOverride Fee percentage as zoc. * @dev Reverts if the transfer fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). * @dev Reverts if the token transfer fails. * @dev Reverts with `Unauthorized` if the `msg.sender` is not a privileged role. */ function donateWithAdminOverrides(uint256 _amount, uint32 _feeOverride) external virtual requiresAuth { _donateWithFeeMultiplier(_amount, _feeOverride); } /** * @notice Receives a donated amount of base tokens to be added to the entity's balance. Transfers fee calculated by fee multiplier to treasury. * @param _amount Amount donated in base token. * @param _feeMultiplier Value indicating the percentage of the Endaoment donation fee to go to the Endaoment treasury. * @dev Reverts if the donation fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). * @dev Reverts if the token transfer fails. */ function _donateWithFeeMultiplier(uint256 _amount, uint32 _feeMultiplier) internal virtual { (uint256 _netAmount, uint256 _fee) = _calculateFee(_amount, _feeMultiplier); baseToken.safeTransferFrom(msg.sender, registry.treasury(), _fee); baseToken.safeTransferFrom(msg.sender, address(this), _netAmount); unchecked { // unchecked as no possibility of overflow with baseToken precision balance += _netAmount; } emit EntityDonationReceived(msg.sender, address(this), address(baseToken), _amount, _amount, _fee); } /** * @notice Receive a donated amount of ETH or ERC20 tokens, swaps them to base tokens, and adds the output to the * entity's balance. Fee calculated using default rate and sent to treasury. * @param _swapWrapper The swap wrapper to use for the donation. Must be whitelisted on the Registry. * @param _tokenIn The address of the ERC20 token to swap and donate, or ETH_PLACEHOLDER if donating ETH. * @param _amountIn The amount of tokens or ETH being swapped and donated. * @param _data Additional call data required by the ISwapWrapper being used. */ function swapAndDonate(ISwapWrapper _swapWrapper, address _tokenIn, uint256 _amountIn, bytes calldata _data) external payable virtual { uint32 _feeMultiplier = registry.getDonationFee(this); _swapAndDonateWithFeeMultiplier(_swapWrapper, _tokenIn, _amountIn, _data, _feeMultiplier); } /** * @notice Receive a donated amount of ETH or ERC20 tokens, swaps them to base tokens, and adds the output to the * entity's balance. Fee calculated using override rate and sent to treasury. * @param _swapWrapper The swap wrapper to use for the donation. Must be whitelisted on the Registry. * @param _tokenIn The address of the ERC20 token to swap and donate, or ETH_PLACEHOLDER if donating ETH. * @param _amountIn The amount of tokens or ETH being swapped and donated. * @param _data Additional call data required by the ISwapWrapper being used. */ function swapAndDonateWithOverrides( ISwapWrapper _swapWrapper, address _tokenIn, uint256 _amountIn, bytes calldata _data ) external payable virtual { uint32 _feeMultiplier = registry.getDonationFeeWithOverrides(this); _swapAndDonateWithFeeMultiplier(_swapWrapper, _tokenIn, _amountIn, _data, _feeMultiplier); } /// @dev Internal helper implementing swap and donate functionality for any fee multiplier provided. function _swapAndDonateWithFeeMultiplier( ISwapWrapper _swapWrapper, address _tokenIn, uint256 _amountIn, bytes calldata _data, uint32 _feeMultiplier ) internal virtual nonReentrant { if (!registry.isSwapperSupported(_swapWrapper)) revert InvalidAction(); // THINK: do we need a re-entrancy guard on this method? if (_tokenIn != ETH_PLACEHOLDER) { ERC20(_tokenIn).safeTransferFrom(msg.sender, address(this), _amountIn); ERC20(_tokenIn).safeApprove(address(_swapWrapper), 0); ERC20(_tokenIn).safeApprove(address(_swapWrapper), _amountIn); } uint256 _amountOut = _swapWrapper.swap{value: msg.value}(_tokenIn, address(baseToken), address(this), _amountIn, _data); (uint256 _netAmount, uint256 _fee) = _calculateFee(_amountOut, _feeMultiplier); baseToken.safeTransfer(registry.treasury(), _fee); unchecked { // unchecked as no possibility of overflow with baseToken precision balance += _netAmount; } if (balance > baseToken.balanceOf(address(this))) revert BalanceMismatch(); emit EntityDonationReceived(msg.sender, address(this), _tokenIn, _amountIn, _amountOut, _fee); } /** * @notice Transfers an amount of base tokens from one entity to another. Transfers default fee to treasury. * @param _to The entity to receive the tokens. * @param _amount Contains the amount being donated (denominated in the base token's units). * @dev Reverts if the entity is inactive or if the token transfer fails. * @dev Reverts if the transfer fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). * @dev Reverts with `Unauthorized` if the `msg.sender` is not the entity manager or a privileged role. * @dev Renamed from `transfer` to distinguish from ERC20 transfer in 3rd party tools. */ function transferToEntity(Entity _to, uint256 _amount) external virtual requiresManager { uint32 _feeMultiplier = registry.getTransferFee(this, _to); _transferWithFeeMultiplier(_to, _amount, _feeMultiplier); } /** * @notice Transfers an amount of base tokens from one entity to another. Transfers default or overridden fee to treasury. * @param _to The entity to receive the tokens. * @param _amount Contains the amount being donated (denominated in the base token's units). * @dev Reverts if the entity is inactive or if the token transfer fails. * @dev Reverts if the transfer fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). * @dev Reverts with `Unauthorized` if the `msg.sender` is not the entity manager or a privileged role. */ function transferToEntityWithOverrides(Entity _to, uint256 _amount) external virtual requiresManager { uint32 _feeMultiplier = registry.getTransferFeeWithOverrides(this, _to); _transferWithFeeMultiplier(_to, _amount, _feeMultiplier); } /** * @notice Transfers an amount of base tokens from one entity to another. Transfers fee specified by a privileged role. * @param _to The entity to receive the tokens. * @param _amount Contains the amount being donated (denominated in the base token's units). * @param _feeOverride Admin override configured by an Admin * @dev Reverts if the entity is inactive or if the token transfer fails. * @dev Reverts if the transfer fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). * @dev Reverts with `Unauthorized` if the `msg.sender` is not a privileged role. */ function transferToEntityWithAdminOverrides(Entity _to, uint256 _amount, uint32 _feeOverride) external virtual requiresAuth { _transferWithFeeMultiplier(_to, _amount, _feeOverride); } /** * @notice Transfers an amount of base tokens from one entity to another. Transfers fee calculated by fee multiplier to treasury. * @param _to The entity to receive the tokens. * @param _amount Contains the amount being donated (denominated in the base token's units). * @param _feeMultiplier Value indicating the percentage of the Endaoment donation fee to go to the Endaoment treasury. * @dev Reverts with 'Inactive' if the entity sending the transfer or the entity receiving the transfer is inactive. * @dev Reverts if the transfer fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). * @dev Reverts with `Unauthorized` if the `msg.sender` is not the entity manager or a privileged role. * @dev Reverts if the token transfer fails. */ function _transferWithFeeMultiplier(Entity _to, uint256 _amount, uint32 _feeMultiplier) internal virtual { if (!registry.isActiveEntity(this) || !registry.isActiveEntity(_to)) revert EntityInactive(); if (balance < _amount) revert InsufficientFunds(); (uint256 _netAmount, uint256 _fee) = _calculateFee(_amount, _feeMultiplier); baseToken.safeTransfer(registry.treasury(), _fee); baseToken.safeTransfer(address(_to), _netAmount); unchecked { // unchecked as no possibility of overflow with baseToken precision balance -= _amount; _to.receiveTransfer(_netAmount); } emit EntityValueTransferred(address(this), address(_to), _amount, _fee); } /** * @notice Updates the receiving entity balance on a transfer. * @param _transferAmount The amount being received on the transfer. * @dev This function is external, but is restricted such that it can only be called by other entities. */ function receiveTransfer(uint256 _transferAmount) external virtual { if (!registry.isActiveEntity(Entity(payable(msg.sender)))) revert EntityInactive(); unchecked { // Cannot overflow with realistic balances. balance += _transferAmount; } } /** * @notice Deposits an amount of Entity's `baseToken` into an Endaoment-approved Portfolio. * @param _portfolio An Endaoment-approved portfolio. * @param _amount Amount of `baseToken` to deposit into the portfolio. * @param _data Data required by a portfolio to deposit. * @return _shares Amount of portfolio share tokens Entity received as a result of this deposit. */ function portfolioDeposit(Portfolio _portfolio, uint256 _amount, bytes calldata _data) external virtual requiresManager returns (uint256) { if (!registry.isActivePortfolio(_portfolio)) revert PortfolioInactive(); balance -= _amount; baseToken.safeApprove(address(_portfolio), _amount); uint256 _shares = _portfolio.deposit(_amount, _data); emit EntityDeposit(address(_portfolio), _amount, _shares); return _shares; } /** * @notice Redeems an amount of Entity's portfolio shares for an amount of `baseToken`. * @param _portfolio An Endaoment-approved portfolio. * @param _shares Amount of share tokens to redeem. * @param _data Data required by a portfolio to redeem. * @return _received Amount of `baseToken` Entity received as a result of this redemption. */ function portfolioRedeem(Portfolio _portfolio, uint256 _shares, bytes calldata _data) external virtual requiresManager returns (uint256) { if (!registry.isActivePortfolio(_portfolio)) revert PortfolioInactive(); uint256 _received = _portfolio.redeem(_shares, _data); // unchecked: a realistic balance can never overflow a uint256 unchecked { balance += _received; } emit EntityRedeem(address(_portfolio), _shares, _received); return _received; } /** * @notice This method should be called to reconcile the Entity's internal baseToken accounting with the baseToken contract's accounting. * There are a 2 situations where calling this method is appropriate: * 1. To process amounts of baseToken that arrived at this Entity through methods besides Entity:donate or Entity:transfer. For example, * if this Entity receives a normal ERC20 transfer of baseToken, the amount received will be unavailable for Entity use until this method * is called to adjust the balance and process fees. OrgFundFactory.sol:_donate makes use of this method to do this as well. * 2. Unusually, the Entity's perspective of balance could be lower than `baseToken.balanceOf(this)`. This could happen if * Entity:callAsEntity is used to transfer baseToken. In this case, this method provides a way of correcting the Entity's internal balance. */ function reconcileBalance() external virtual { uint256 _tokenBalance = baseToken.balanceOf(address(this)); if (_tokenBalance >= balance) { uint256 _sweepAmount = _tokenBalance - balance; uint32 _feeMultiplier = registry.getDonationFeeWithOverrides(this); (uint256 _netAmount, uint256 _fee) = _calculateFee(_sweepAmount, _feeMultiplier); baseToken.safeTransfer(registry.treasury(), _fee); unchecked { balance += _netAmount; } emit EntityBalanceReconciled(address(this), _sweepAmount, _fee); } else { // Handle abnormal scenario where _tokenBalance < balance (see method docs) balance = _tokenBalance; emit EntityBalanceCorrected(address(this), _tokenBalance); } } /** * @notice Takes stray tokens or ETH sent directly to this Entity, swaps them for base token, then adds them to the * Entity's balance after paying the appropriate fee to the treasury. * @param _swapWrapper The swap wrapper to use to convert the assets. Must be whitelisted on the Registry. * @param _tokenIn The address of the ERC20 token to swap, or ETH_PLACEHOLDER if ETH. * @param _amountIn The amount of tokens or ETH being swapped and added to the balance. * @param _data Additional call data required by the ISwapWrapper being used. */ function swapAndReconcileBalance( ISwapWrapper _swapWrapper, address _tokenIn, uint256 _amountIn, bytes calldata _data ) external virtual nonReentrant requiresManager { if (!registry.isSwapperSupported(_swapWrapper)) revert InvalidAction(); uint32 _feeMultiplier = registry.getDonationFeeWithOverrides(this); if (_tokenIn != ETH_PLACEHOLDER) { ERC20(_tokenIn).safeApprove(address(_swapWrapper), 0); ERC20(_tokenIn).safeApprove(address(_swapWrapper), _amountIn); } // Send value only if token in is ETH uint256 _value = _tokenIn == ETH_PLACEHOLDER ? _amountIn : 0; uint256 _amountOut = _swapWrapper.swap{value: _value}(_tokenIn, address(baseToken), address(this), _amountIn, _data); (uint256 _netAmount, uint256 _fee) = _calculateFee(_amountOut, _feeMultiplier); baseToken.safeTransfer(registry.treasury(), _fee); unchecked { // unchecked as no possibility of overflow with baseToken precision balance += _netAmount; } if (balance > baseToken.balanceOf(address(this))) revert BalanceMismatch(); emit EntityBalanceReconciled(address(this), _amountOut, _fee); } /** * @notice Permissioned method that allows Endaoment admin to make arbitrary calls acting as this Entity. * @param _target The address to which the call will be made. * @param _value The ETH value that should be forwarded with the call. * @param _data The calldata that will be sent with the call. * @return _return The data returned by the call. */ function callAsEntity(address _target, uint256 _value, bytes memory _data) external payable virtual requiresAuth returns (bytes memory) { (bool _success, bytes memory _response) = payable(_target).call{value: _value}(_data); if (!_success) revert CallFailed(_response); return _response; } /** * @notice Pays out an amount of base tokens from the entity to an address. Transfers the fee calculated by the * default fee multiplier to the treasury. * @param _to The address to receive the tokens. * @param _amount Amount donated in base token. * @dev Reverts with `Unauthorized` if the `msg.sender` is not a privileged role. * @dev Reverts if the fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). * @dev Reverts if the token transfer fails. */ function payout(address _to, uint256 _amount) external virtual requiresAuth { uint32 _feeMultiplier = registry.getPayoutFee(this); _payoutWithFeeMultiplier(_to, _amount, _feeMultiplier); } /** * @notice Pays out an amount of base tokens from the entity to an address. Transfers the fee calculated by the * default fee multiplier to the treasury. * @param _amount Amount donated in base token. * @dev Reverts with `Unauthorized` if the `msg.sender` is not a privileged role. * @dev Reverts if the fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). * @dev Reverts if the token transfer fails. */ function payoutWithOverrides(address _to, uint256 _amount) external virtual requiresAuth { uint32 _feeMultiplier = registry.getPayoutFeeWithOverrides(this); _payoutWithFeeMultiplier(_to, _amount, _feeMultiplier); } /** * @notice Pays out an amount of base tokens from the entity to an address. Transfers fee specified by a privileged role. * @param _amount Amount donated in base token. * @param _feeOverride Payout override configured by an Admin * @dev Reverts with `Unauthorized` if the `msg.sender` is not a privileged role. * @dev Reverts if the fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). * @dev Reverts if the token transfer fails. */ function payoutWithAdminOverrides(address _to, uint256 _amount, uint32 _feeOverride) external virtual requiresAuth { _payoutWithFeeMultiplier(_to, _amount, _feeOverride); } /** * @notice Pays out an amount of base tokens from the entity to an address. Transfers the fee calculated by fee multiplier to the treasury. * @param _to The address to receive the tokens. * @param _amount Contains the amount being paid out (denominated in the base token's units). * @param _feeMultiplier Value indicating the percentage of the Endaoment fee to go to the Endaoment treasury. * @dev Reverts if the token transfer fails. * @dev Reverts if the fee percentage is larger than 100% (equal to 1e4 when represented as a zoc). */ function _payoutWithFeeMultiplier(address _to, uint256 _amount, uint32 _feeMultiplier) internal virtual { if (balance < _amount) revert InsufficientFunds(); (uint256 _netAmount, uint256 _fee) = _calculateFee(_amount, _feeMultiplier); baseToken.safeTransfer(registry.treasury(), _fee); baseToken.safeTransfer(address(_to), _netAmount); unchecked { // unchecked because we've already validated that amount is less than or equal to the balance balance -= _amount; } emit EntityValuePaidOut(address(this), _to, _amount, _fee); } /// @dev Internal helper method to calculate the fee on a base token amount for a given fee multiplier. function _calculateFee(uint256 _amount, uint256 _feeMultiplier) internal pure virtual returns (uint256 _netAmount, uint256 _fee) { if (_feeMultiplier > Math.ZOC) revert InvalidAction(); unchecked { // unchecked as no possibility of overflow with baseToken precision _fee = _amount.zocmul(_feeMultiplier); // unchecked as the _feeMultiplier check with revert above protects against overflow _netAmount = _amount - _fee; } } receive() external payable virtual { emit EntityEthReceived(msg.sender, msg.value); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Gas optimized reentrancy protection for smart contracts. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol) abstract contract ReentrancyGuard { uint256 private reentrancyStatus = 1; modifier nonReentrant() { require(reentrancyStatus == 1, "REENTRANCY"); reentrancyStatus = 2; _; reentrancyStatus = 1; } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {RolesAuthority} from "./authorities/RolesAuthority.sol"; /** * @notice An abstract Auth that contracts in the Endaoment ecosystem can inherit from. It is based on * the `Auth.sol` contract from Solmate, but does not inherit from it. Most of the functionality * is either slightly different, or not needed. In particular: * - EndaomentAuth uses an initializer such that it can be deployed with minimal proxies. * - EndaomentAuth contracts reference a RolesAuthority, not just an Authority, when looking up permissions. * In the Endaoment ecosystem, this is assumed to be the Registry. * - EndaomentAuth contracts do not have an owner, but instead grant ubiquitous permission to its RoleAuthority's * owner. In the Endaoment ecosystem, this is assumed to be the board of directors multi-sig. * - EndaomentAuth contracts can optionally declare themselves a "special target" at deploy time. Instead of passing * their address to the authority when looking up their permissions, they'll instead pass the special target bytes. * See documentation on `specialTarget` for more information. * */ abstract contract EndaomentAuth { /// @notice Thrown when an account without proper permissions calls a privileged method. error Unauthorized(); /// @notice Thrown if there is an attempt to deploy with address 0 as the authority. error InvalidAuthority(); /// @notice Thrown if there is a second call to initialize. error AlreadyInitialized(); /// @notice The contract used to source permissions for accounts targeting this contract. RolesAuthority public authority; /** * @notice If set to a non-zero value, this contract will pass these byes as the target contract * to the RolesAuthority's `canCall` method, rather than its own contract. This allows a single * RolesAuthority permission to manage permissions simultaneously for a group of contracts that * identify themselves as a certain type. For example: set a permission for all "entity" contracts. */ bytes20 public specialTarget; /** * @notice One time method to be called at deployment to configure the contract. Required so EndaomentAuth * contracts can be deployed as minimal proxies (clones). * @param _authority Contract that will be used to source permissions for accounts targeting this contract. * @param _specialTarget The bytes that this contract will pass as the "target" when looking up permissions * from the authority. If set to empty bytes, this contract will pass its own address instead. */ function __initEndaomentAuth(RolesAuthority _authority, bytes20 _specialTarget) internal virtual { if (address(_authority) == address(0)) revert InvalidAuthority(); if (address(authority) != address(0)) revert AlreadyInitialized(); authority = _authority; specialTarget = _specialTarget; } /** * @notice Modifier for methods that require authorization to execute. */ modifier requiresAuth() virtual { if (!isAuthorized(msg.sender, msg.sig)) revert Unauthorized(); _; } /** * @notice Internal method that asks the authority whether the caller has permission to execute a method. * @param user The account attempting to call a permissioned method on this contract * @param functionSig The signature hash of the permissioned method being invoked. */ function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) { RolesAuthority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas. address _target = specialTarget == "" ? address(this) : address(specialTarget); // The caller has permission on authority, or the caller is the RolesAuthority owner return auth.canCall(user, _target, functionSig) || user == auth.owner(); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; // This contract is modified from Solmate only to import modified Auth.sol on line 5 import {Auth, Authority} from "../Auth.sol"; /// @notice Role based Authority that supports up to 256 roles. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/authorities/RolesAuthority.sol) /// @author Modified from Dappsys (https://github.com/dapphub/ds-roles/blob/master/src/roles.sol) contract RolesAuthority is Auth, Authority { /*/////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event UserRoleUpdated(address indexed user, uint8 indexed role, bool enabled); event PublicCapabilityUpdated(address indexed target, bytes4 indexed functionSig, bool enabled); event RoleCapabilityUpdated(uint8 indexed role, address indexed target, bytes4 indexed functionSig, bool enabled); /*/////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor(address _owner, Authority _authority) Auth(_owner, _authority) {} /*/////////////////////////////////////////////////////////////// ROLE/USER STORAGE //////////////////////////////////////////////////////////////*/ mapping(address => bytes32) public getUserRoles; mapping(address => mapping(bytes4 => bool)) public isCapabilityPublic; mapping(address => mapping(bytes4 => bytes32)) public getRolesWithCapability; function doesUserHaveRole(address user, uint8 role) public view virtual returns (bool) { return (uint256(getUserRoles[user]) >> role) & 1 != 0; } function doesRoleHaveCapability(uint8 role, address target, bytes4 functionSig) public view virtual returns (bool) { return (uint256(getRolesWithCapability[target][functionSig]) >> role) & 1 != 0; } /*/////////////////////////////////////////////////////////////// AUTHORIZATION LOGIC //////////////////////////////////////////////////////////////*/ function canCall(address user, address target, bytes4 functionSig) public view virtual override returns (bool) { return isCapabilityPublic[target][functionSig] || bytes32(0) != getUserRoles[user] & getRolesWithCapability[target][functionSig]; } /*/////////////////////////////////////////////////////////////// ROLE CAPABILITY CONFIGURATION LOGIC //////////////////////////////////////////////////////////////*/ function setPublicCapability(address target, bytes4 functionSig, bool enabled) public virtual requiresAuth { isCapabilityPublic[target][functionSig] = enabled; emit PublicCapabilityUpdated(target, functionSig, enabled); } function setRoleCapability(uint8 role, address target, bytes4 functionSig, bool enabled) public virtual requiresAuth { if (enabled) { getRolesWithCapability[target][functionSig] |= bytes32(1 << role); } else { getRolesWithCapability[target][functionSig] &= ~bytes32(1 << role); } emit RoleCapabilityUpdated(role, target, functionSig, enabled); } /*/////////////////////////////////////////////////////////////// USER ROLE ASSIGNMENT LOGIC //////////////////////////////////////////////////////////////*/ function setUserRole(address user, uint8 role, bool enabled) public virtual requiresAuth { if (enabled) { getUserRoles[user] |= bytes32(1 << role); } else { getUserRoles[user] &= ~bytes32(1 << role); } emit UserRoleUpdated(user, role, enabled); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Gas optimized reentrancy protection for smart contracts. /// @author Modified Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol) abstract contract ReentrancyGuard { uint256 private reentrancyStatus; error Reentrancy(); function __initReentrancyGuard() internal { if (reentrancyStatus != 0) revert Reentrancy(); reentrancyStatus = 1; } modifier nonReentrant() { if (reentrancyStatus != 1) revert Reentrancy(); reentrancyStatus = 2; _; reentrancyStatus = 1; } }
{ "remappings": [ "ds-test/=lib/ds-test/src/", "forge-std/=lib/forge-std/src/", "murky/=lib/murky/src/", "openzeppelin-contracts/=lib/openzeppelin-contracts/", "solmate/=lib/solmate/src/", "weird-erc20/=lib/solmate/lib/weird-erc20/src/" ], "optimizer": { "enabled": true, "runs": 9999999 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs" }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "london", "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"contract Registry","name":"_registry","type":"address"},{"internalType":"address","name":"_receiptAsset","type":"address"},{"internalType":"string","name":"_shareTokenName","type":"string"},{"internalType":"string","name":"_shareTokenSymbol","type":"string"},{"internalType":"uint256","name":"_cap","type":"uint256"},{"internalType":"address","name":"_feeTreasury","type":"address"},{"internalType":"uint256","name":"_depositFee","type":"uint256"},{"internalType":"uint256","name":"_redemptionFee","type":"uint256"},{"internalType":"uint256","name":"_aumRate","type":"uint256"},{"internalType":"uint256","name":"_errorMarginPct","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"BadCheckCapImplementation","type":"error"},{"inputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"name":"CallFailed","type":"error"},{"inputs":[],"name":"DepositAfterShutdown","type":"error"},{"inputs":[],"name":"DidShutdown","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedAssetsOut","type":"uint256"},{"internalType":"uint256","name":"actualAssetsOut","type":"uint256"},{"internalType":"uint256","name":"margin","type":"uint256"}],"name":"ErrorMarginExceeded","type":"error"},{"inputs":[],"name":"ExceedsCap","type":"error"},{"inputs":[],"name":"InvalidAuthority","type":"error"},{"inputs":[],"name":"InvalidRate","type":"error"},{"inputs":[],"name":"InvalidSwapper","type":"error"},{"inputs":[],"name":"NotEntity","type":"error"},{"inputs":[],"name":"PercentageOver100","type":"error"},{"inputs":[],"name":"RoundsToZero","type":"error"},{"inputs":[],"name":"Slippage","type":"error"},{"inputs":[],"name":"TransferDisallowed","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"feeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timeDelta","type":"uint256"}],"name":"AumFeesTaken","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"rate","type":"uint256"}],"name":"AumRateSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"cap","type":"uint256"}],"name":"CapSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"depositAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"DepositFeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newErrorMarginPct","type":"uint256"}],"name":"ErrorMarginPctSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"feeTreasury","type":"address"}],"name":"FeeTreasurySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeesTaken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"redeemedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"RedemptionFeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"assetAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"baseTokenOut","type":"uint256"}],"name":"Shutdown","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":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"aumRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"authority","outputs":[{"internalType":"contract RolesAuthority","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseToken","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_target","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"callAsPortfolio","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"cap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_receiptAssets","type":"uint256"}],"name":"convertReceiptAssetsToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","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":"_shares","type":"uint256"},{"internalType":"uint256","name":"_elapsed","type":"uint256"}],"name":"convertToAssetsLessFutureFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_shares","type":"uint256"}],"name":"convertToAssetsShutdown","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":"uint256","name":"_amountBaseToken","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"didShutdown","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"errorMarginPct","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeTreasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint8","name":"","type":"uint8"},{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"receiptAsset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountShares","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"baseTokenOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"redemptionFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"registry","outputs":[{"internalType":"contract Registry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pct","type":"uint256"}],"name":"setAumRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"setCap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pct","type":"uint256"}],"name":"setDepositFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pct","type":"uint256"}],"name":"setErrorMarginPct","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_feeTreasury","type":"address"}],"name":"setFeeTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pct","type":"uint256"}],"name":"setRedemptionFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"shutdown","outputs":[{"internalType":"uint256","name":"baseTokenOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"specialTarget","outputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"takeAumFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountReceiptAssets","type":"uint256"}],"name":"takeFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"timestampAumFeesTaken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalReceiptAssets","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":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"}]
Deployed Bytecode
0x6080604052600436106102fd5760003560e01c80635d3035191161018f578063bfa37e37116100e1578063e37d15bf1161008a578063f06dfacb11610064578063f06dfacb14610919578063fef784971461092f578063ff5ac9a41461094257600080fd5b8063e37d15bf146108c3578063e77c646d146108d9578063f029748d146108f957600080fd5b8063cbb94359116100bb578063cbb943591461084b578063d505accf1461086b578063dd62ed3e1461088b57600080fd5b8063bfa37e37146107d7578063c55dae63146107f7578063c6e6f5921461082b57600080fd5b80637dbc1df01161014357806399aa73481161011d57806399aa734814610795578063a9059cbb1461036c578063bf7e214f146107aa57600080fd5b80637dbc1df0146107335780637ecebe001461075357806395d89b411461078057600080fd5b806367a527931161017457806367a52793146106bc57806370a08231146106d25780637b103999146106ff57600080fd5b80635d3035191461066f57806360dc23401461068f57600080fd5b806323b872dd1161025357806338d52e0f116101fc57806347786d37116101d657806347786d371461060f578063490ae2101461062f5780635bae19ef1461064f57600080fd5b806338d52e0f1461056c5780634288d871146105c5578063458f5815146105f957600080fd5b8063313ce5671161022d578063313ce567146104fb578063355274ea146105415780633644e5151461055757600080fd5b806323b872dd1461048c578063280398b5146104a757806330adf81f146104c757600080fd5b8063106f276f116102b55780631e7ee2371161028f5780631e7ee237146104055780632031ee951461041f57806322dc29091461046a57600080fd5b8063106f276f146103b157806318160ddd146103d15780631e500759146103e757600080fd5b806307a2d13a116102e657806307a2d13a1461034c578063095ea7b31461036c5780630a9d55141461039c57600080fd5b806301e1d1141461030257806306fdde031461032a575b600080fd5b34801561030e57600080fd5b50610317610958565b6040519081526020015b60405180910390f35b34801561033657600080fd5b5061033f61096a565b6040516103219190612b03565b34801561035857600080fd5b50610317610367366004612b16565b6109f8565b34801561037857600080fd5b5061038c610387366004612b54565b610a25565b6040519015158152602001610321565b3480156103a857600080fd5b50610317610a59565b3480156103bd57600080fd5b506103176103cc366004612bc9565b610b0a565b3480156103dd57600080fd5b5061031760025481565b3480156103f357600080fd5b50610317610402366004612b16565b90565b34801561041157600080fd5b50600d5461038c9060ff1681565b34801561042b57600080fd5b506007546104399060601b81565b6040517fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009091168152602001610321565b34801561047657600080fd5b5061048a610485366004612b16565b610c3c565b005b34801561049857600080fd5b5061038c610387366004612c0b565b3480156104b357600080fd5b506103176104c2366004612c4c565b610d1e565b3480156104d357600080fd5b506103177f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b34801561050757600080fd5b5061052f7f000000000000000000000000000000000000000000000000000000000000000881565b60405160ff9091168152602001610321565b34801561054d57600080fd5b5061031760095481565b34801561056357600080fd5b50610317610d95565b34801561057857600080fd5b506105a07f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59981565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610321565b3480156105d157600080fd5b506105a07f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59981565b34801561060557600080fd5b50610317600c5481565b34801561061b57600080fd5b5061048a61062a366004612b16565b610deb565b34801561063b57600080fd5b5061048a61064a366004612b16565b610e84565b34801561065b57600080fd5b5061048a61066a366004612b16565b610f59565b34801561067b57600080fd5b5061031761068a366004612c6e565b611038565b34801561069b57600080fd5b50600a546105a09073ffffffffffffffffffffffffffffffffffffffff1681565b3480156106c857600080fd5b50610317600b5481565b3480156106de57600080fd5b506103176106ed366004612cba565b60036020526000908152604090205481565b34801561070b57600080fd5b506105a07f00000000000000000000000094106ca9c7e567109a1d39413052887d1f41218381565b34801561073f57600080fd5b5061048a61074e366004612b16565b611232565b34801561075f57600080fd5b5061031761076e366004612cba565b60056020526000908152604090205481565b34801561078c57600080fd5b5061033f611307565b3480156107a157600080fd5b5061048a611314565b3480156107b657600080fd5b506006546105a09073ffffffffffffffffffffffffffffffffffffffff1681565b3480156107e357600080fd5b5061048a6107f2366004612cba565b611404565b34801561080357600080fd5b506105a07f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4881565b34801561083757600080fd5b50610317610846366004612b16565b6114db565b34801561085757600080fd5b5061048a610866366004612b16565b6114fb565b34801561087757600080fd5b5061048a610886366004612cd7565b6115d6565b34801561089757600080fd5b506103176108a6366004612d4e565b600460209081526000928352604080842090915290825290205481565b3480156108cf57600080fd5b50610317600f5481565b3480156108e557600080fd5b506103176108f4366004612c6e565b611608565b34801561090557600080fd5b50610317610914366004612b16565b6117ec565b34801561092557600080fd5b5061031760105481565b61033f61093d366004612db6565b6118aa565b34801561094e57600080fd5b50610317600e5481565b6000610965610402610a59565b905090565b6000805461097790612ea1565b80601f01602080910402602001604051908101604052809291908181526020018280546109a390612ea1565b80156109f05780601f106109c5576101008083540402835291602001916109f0565b820191906000526020600020905b8154815290600101906020018083116109d357829003601f168201915b505050505081565b6002546000908015610a1c57610a17610a0f610958565b8490836119c2565b610a1e565b825b9392505050565b60006040517fc31c949300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59973ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa158015610ae6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109659190612ef4565b6000610b3a336000357fffffffff00000000000000000000000000000000000000000000000000000000166119e1565b610b70576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d5460ff1615610bad576040517f2a542da400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790556000610be2610958565b9050600080610bf2838787611bbd565b60408051838152602081018390529294509092507fbf74a2393b907f335184e6dbeb4daa93812b077b8e034c2e61c8e6864002dda7910160405180910390a1925050505b92915050565b610c6a336000357fffffffff00000000000000000000000000000000000000000000000000000000166119e1565b610ca0576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b670de0b6b3a7640000811115610ce2576040517ff08319c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60108190556040518181527f1b93bc9809c55f88e4bd6c7309c9ee1954771b4b87fcf5df7e4593b9ab0e1c9c906020015b60405180910390a150565b60025460009081610d2d610958565b90506000610d5382600e548742610d449190612f3c565b610d4e9190612f54565b611eb8565b905081811115610d695760009350505050610c36565b8215610d8957610d84610d7c8284612f54565b8790856119c2565b610d8b565b855b9695505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000014614610dc657610965611ef5565b507fe45680e4091e9ca385ce3912f39c59473efabc4c9c57b29a6d58f026d6748ba290565b610e19336000357fffffffff00000000000000000000000000000000000000000000000000000000166119e1565b610e4f576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60098190556040518181527f9872d5eb566b79923d043f1b59aca655ca80a2bb5b6bca4824e515b0e398902f90602001610d13565b610eb2336000357fffffffff00000000000000000000000000000000000000000000000000000000166119e1565b610ee8576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612710811115610f24576040517ff08319c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600b8190556040518181527f12865465a7036a0232cbf9fb63ce880a3ee54f702775fe7abbc3c416e7968cd590602001610d13565b610f87336000357fffffffff00000000000000000000000000000000000000000000000000000000166119e1565b610fbd576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b63bce02f4e811115610ffb576040517f6a43f8d100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611003611314565b600f8190556040518181527fad2a1b372bf4c8402696d0ed78880c15faa5b69298ede2615a228810a52dc6c490602001610d13565b60006008546001146110ab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e43590000000000000000000000000000000000000000000060448201526064015b60405180910390fd5b6002600855600d5460ff16156110ed576040517f326eab7500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110f633611f8f565b61112c576040517f184849cf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611134611314565b6000806000611144878787612044565b9194509250905061115785870187612b16565b831015611190576040517f7dd37f7000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b826000036111ca576040517fc440e0aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6111d433846123bd565b604080518381526020810185905290810188905260608101829052339081907f5f971bd00bf3ffbca8a6d72cdd4fd92cfd4f62636161921d1e5a64f0b64ccb6d9060800160405180910390a350909150505b60016008559392505050565b611260336000357fffffffff00000000000000000000000000000000000000000000000000000000166119e1565b611296576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6127108111156112d2576040517ff08319c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c8190556040518181527f91cc643d187eb250905520d3dae0b1017edd16961d27ab9a4a61fea5e38f717d90602001610d13565b6001805461097790612ea1565b600d5460ff161561132957611327612436565b565b6000611333610a59565b90506000600e54426113459190612f54565b905060006113538383611eb8565b9050828111156113605750815b600081118061136f5750600254155b156113ff5742600e5580156113ff57600a546113c59073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5998116911683612561565b60408051828152602081018490527fc5bd0ea7a1520a37af6ec1d295e0b4f9820e066c078ae82f4abcc49286ab1028910160405180910390a15b505050565b611432336000357fffffffff00000000000000000000000000000000000000000000000000000000166119e1565b611468576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600a80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040519081527ffa91b2d5be9caed9cc205220f1d5f3c5fee252dea3faf893928e0e639c0ccc7090602001610d13565b6002546000908015610a1c57610a17816114f3610958565b8591906119c2565b611529336000357fffffffff00000000000000000000000000000000000000000000000000000000166119e1565b61155f576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600a546115a69073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5998116911683612561565b6040518181527ffba1cbbf893e6a61412440b48fca1c80a5bf24d2f7deb6d5544717745077a36090602001610d13565b6040517fc31c949300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600854600114611676576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e43590000000000000000000000000000000000000000000060448201526064016110a2565b6002600855611683611314565b600d5460ff161561169e576116978461262e565b9050611226565b6000806116ac868686612736565b91509150816000036116ea576040517fc440e0aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6116f43387612763565b60008061170383600c546127f1565b600a54919350915061174f9073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb488116911683612561565b61179073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48163384612561565b60408051858152602081018a905290810183905260608101829052339081907f8caf04742286d017f9ac3924388e188c73e6e5094311c5e59a61a7ef86dda8bf9060800160405180910390a35060016008559695505050505050565b6002546000908015610a1c576040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152610a17907f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4873ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa158015611886573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a0f9190612ef4565b60606118da336000357fffffffff00000000000000000000000000000000000000000000000000000000166119e1565b611910576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000808573ffffffffffffffffffffffffffffffffffffffff1685856040516119399190612f6b565b60006040518083038185875af1925050503d8060008114611976576040519150601f19603f3d011682016040523d82523d6000602084013e61197b565b606091505b5091509150816119b957806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016110a29190612b03565b95945050505050565b8282028115158415858304851417166119da57600080fd5b0492915050565b60065460075460009173ffffffffffffffffffffffffffffffffffffffff1690829060601b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001615611a4b5760075473ffffffffffffffffffffffffffffffffffffffff16611a4d565b305b6040517fb700961300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff878116600483015280831660248301527fffffffff00000000000000000000000000000000000000000000000000000000871660448301529192509083169063b700961390606401602060405180830381865afa158015611aed573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b119190612f87565b806119b957508173ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611b62573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b869190612fa9565b73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161495945050505050565b60008080611bcf603460208688612fc6565b611bd891612ff0565b6040517f6ab3905700000000000000000000000000000000000000000000000000000000815260609190911c60048201819052915073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000094106ca9c7e567109a1d39413052887d1f4121831690636ab3905790602401602060405180830381865afa158015611c6b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c8f9190612f87565b611cc5576040517fc94f7a9a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611cd385870187612b16565b90506000611cec6010548961284590919063ffffffff16565b905087821180611d04575080611d02838a612f54565b115b15611d4c576040517f07a7129e0000000000000000000000000000000000000000000000000000000081526004810183905260248101899052604481018290526064016110a2565b611d8e73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5991684600061285a565b611dcf73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59916848461285a565b60008373ffffffffffffffffffffffffffffffffffffffff1663da1452637f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5997f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4830878d8d6034908092611e4493929190612fc6565b6040518763ffffffff1660e01b8152600401611e6596959493929190613038565b6020604051808303816000875af1158015611e84573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ea89190612ef4565b9299929850919650505050505050565b6000821580611ec75750600f54155b80611ed0575081155b15611edd57506000610c36565b610a1e600f5483611eee91906130c0565b8490612845565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6000604051611f2791906130fd565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b6040517fea3fff6800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82811660048301526000917f00000000000000000000000094106ca9c7e567109a1d39413052887d1f4121839091169063ea3fff6890602401602060405180830381865afa158015612020573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c369190612f87565b6000808080612057603460208789612fc6565b61206091612ff0565b6040517f6ab3905700000000000000000000000000000000000000000000000000000000815260609190911c60048201819052915073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000094106ca9c7e567109a1d39413052887d1f4121831690636ab3905790602401602060405180830381865afa1580156120f3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121179190612f87565b61214d576040517fc94f7a9a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008061215c89600b546127f1565b90925090506121a373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb481633308c612921565b600a546121ea9073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb488116911683612561565b61222c73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb481684600061285a565b61226d73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4816848461285a565b60008373ffffffffffffffffffffffffffffffffffffffff1663da1452637f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb487f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59930878e8e60349080926122e293929190612fc6565b6040518763ffffffff1660e01b815260040161230396959493929190613038565b6020604051808303816000875af1158015612322573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123469190612ef4565b90506009548184612355610958565b61235f91906130c0565b61236991906131cf565b11156123a1576040517f2edaff4300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006123ac82612a0b565b9b919a509198509650505050505050565b80600260008282546123cf9190612f3c565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91015b60405180910390a35050565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4873ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa1580156124c3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124e79190612ef4565b90506000600e54426124f99190612f54565b905060006125078383611eb8565b9050828111156125145750815b80156113ff5742600e55600a546113c59073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4881169116835b60006040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841660048201528260248201526000806044836000895af19150506125c281612a2e565b612628576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c4544000000000000000000000000000000000060448201526064016110a2565b50505050565b60008061263a836117ec565b90506126463384612763565b60008061265583600c546127f1565b600a5491935091506126a19073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb488116911683612561565b6126e273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48163384612561565b604080518481526020810187905290810183905260608101829052339081907f8caf04742286d017f9ac3924388e188c73e6e5094311c5e59a61a7ef86dda8bf9060800160405180910390a3509392505050565b6000806000612744866109f8565b9050600080612754838888611bbd565b90999098509650505050505050565b73ffffffffffffffffffffffffffffffffffffffff821660009081526003602052604081208054839290612798908490612f54565b909155505060028054829003905560405181815260009073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200161242a565b600080612710831115612830576040517ff08319c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61283a8484612a75565b938490039492505050565b6000610a1e8383670de0b6b3a76400006119c2565b60006040517f095ea7b300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841660048201528260248201526000806044836000895af19150506128bb81612a2e565b612628576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f415050524f56455f4641494c454400000000000000000000000000000000000060448201526064016110a2565b60006040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8516600482015273ffffffffffffffffffffffffffffffffffffffff8416602482015282604482015260008060648360008a5af191505061299e81612a2e565b612a04576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c454400000000000000000000000060448201526064016110a2565b5050505050565b6002546000908015610a1c57610a178184612a24610958565b6114f39190612f54565b60003d82612a4057806000803e806000fd5b8060208114612a58578015612a695760009250612a6e565b816000803e60005115159250612a6e565b600192505b5050919050565b6000612a8182846130c0565b61271090049392505050565b60005b83811015612aa8578181015183820152602001612a90565b838111156126285750506000910152565b60008151808452612ad1816020860160208601612a8d565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610a1e6020830184612ab9565b600060208284031215612b2857600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff81168114612b5157600080fd5b50565b60008060408385031215612b6757600080fd5b8235612b7281612b2f565b946020939093013593505050565b60008083601f840112612b9257600080fd5b50813567ffffffffffffffff811115612baa57600080fd5b602083019150836020828501011115612bc257600080fd5b9250929050565b60008060208385031215612bdc57600080fd5b823567ffffffffffffffff811115612bf357600080fd5b612bff85828601612b80565b90969095509350505050565b600080600060608486031215612c2057600080fd5b8335612c2b81612b2f565b92506020840135612c3b81612b2f565b929592945050506040919091013590565b60008060408385031215612c5f57600080fd5b50508035926020909101359150565b600080600060408486031215612c8357600080fd5b83359250602084013567ffffffffffffffff811115612ca157600080fd5b612cad86828701612b80565b9497909650939450505050565b600060208284031215612ccc57600080fd5b8135610a1e81612b2f565b600080600080600080600060e0888a031215612cf257600080fd5b8735612cfd81612b2f565b96506020880135612d0d81612b2f565b95506040880135945060608801359350608088013560ff81168114612d3157600080fd5b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215612d6157600080fd5b8235612d6c81612b2f565b91506020830135612d7c81612b2f565b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080600060608486031215612dcb57600080fd5b8335612dd681612b2f565b925060208401359150604084013567ffffffffffffffff80821115612dfa57600080fd5b818601915086601f830112612e0e57600080fd5b813581811115612e2057612e20612d87565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715612e6657612e66612d87565b81604052828152896020848701011115612e7f57600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b600181811c90821680612eb557607f821691505b602082108103612eee577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b600060208284031215612f0657600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115612f4f57612f4f612f0d565b500190565b600082821015612f6657612f66612f0d565b500390565b60008251612f7d818460208701612a8d565b9190910192915050565b600060208284031215612f9957600080fd5b81518015158114610a1e57600080fd5b600060208284031215612fbb57600080fd5b8151610a1e81612b2f565b60008085851115612fd657600080fd5b83861115612fe357600080fd5b5050820193919092039150565b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000081358181169160148510156130305780818660140360031b1b83161692505b505092915050565b600073ffffffffffffffffffffffffffffffffffffffff8089168352808816602084015280871660408401525084606083015260a060808301528260a0830152828460c0840137600060c0848401015260c07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501168301019050979650505050505050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156130f8576130f8612f0d565b500290565b600080835481600182811c91508083168061311957607f831692505b60208084108203613151577f4e487b710000000000000000000000000000000000000000000000000000000086526022600452602486fd5b8180156131655760018114613194576131c1565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008616895284890196506131c1565b60008a81526020902060005b868110156131b95781548b8201529085019083016131a0565b505084890196505b509498975050505050505050565b600082613205577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b50049056fea2646970667358221220fa20f74abe87ca8ddf17a878afa311510d37ea822582c7b0c73b89672f28e0f764736f6c634300080d0033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|---|---|---|---|---|
ETH | 100.00% | $93,186 | 13.0749 | $1,218,401.26 |
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.