Source Code
Overview
ETH Balance
0 ETH
Eth Value
$0.00Latest 1 from a total of 1 transactions
| Transaction Hash |
Method
|
Block
|
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
| Set Epoch Manage... | 19140217 | 761 days ago | IN | 0 ETH | 0.00105274 |
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
LiquidateNegativeCollectionManualTrigger
Compiler Version
v0.8.17+commit.8df45f5f
Optimization Enabled:
Yes with 200 runs
Other Settings:
london EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {EpochManaged} from '@floor/utils/EpochManaged.sol';
import {CannotSetNullAddress} from '@floor/utils/Errors.sol';
import {StrategyFactory} from '@floor/strategies/StrategyFactory.sol';
import {IEpochEndTriggered} from '@floor-interfaces/utils/EpochEndTriggered.sol';
import {ISweepWars} from '@floor-interfaces/voting/SweepWars.sol';
/**
* When an epoch ends, the vote with the most negative votes will be liquidated to an amount
* relative to the number of negative votes it received. The amounts will be transferred to
* the {Treasury} and will need to be subsequently liquidated by a trusted TREASURY_MANAGER.
*/
contract LiquidateNegativeCollectionManualTrigger is EpochManaged, IEpochEndTriggered {
/// Event fired when losing collection strategy is liquidated
event CollectionTokensLiquidated(address _worstCollection, address[] _strategies, uint _percentage);
/// The sweep war contract used by this contract
ISweepWars public immutable sweepWars;
/// Internal strategies
StrategyFactory public immutable strategyFactory;
/// A threshold percentage that would be worth us working with
uint public constant THRESHOLD = 1_000; // 1%
/**
* Sets our internal contracts.
*/
constructor(address _sweepWars, address _strategyFactory) {
// Prevent any zero-address contracts from being set
if (_sweepWars == address(0) || _strategyFactory == address(0)) {
revert CannotSetNullAddress();
}
sweepWars = ISweepWars(_sweepWars);
strategyFactory = StrategyFactory(_strategyFactory);
}
/**
* When the epoch ends, we check to see if any collections received negative votes. If
* we do, then we find the collection with the most negative votes and liquidate a percentage
* of the position for that collection based on a formula.
*/
function endEpoch(uint /* epoch */) external onlyEpochManager {
address worstCollection;
int negativeCollectionVotes;
int grossVotes;
// Get a list of all collections that are part of the vote
address[] memory collectionAddrs = sweepWars.voteOptions();
// Get the number of collections to save gas in loops
uint length = collectionAddrs.length;
// Iterate over our collections and get the votes
for (uint i; i < length;) {
// Get the number of votes at the current epoch that is closing
int votes = sweepWars.votes(collectionAddrs[i]);
// If we have less votes, update our worst collection
if (votes < negativeCollectionVotes) {
negativeCollectionVotes = votes;
worstCollection = collectionAddrs[i];
}
// Keep track of the gross number of votes for calculation purposes
grossVotes += (votes >= 0) ? votes : -votes;
unchecked {
++i;
}
}
// If we have no gross votes, then we cannot calculate a percentage
if (grossVotes == 0) {
return;
}
// We then need to calculate the amount we exit our position by, depending on the number
// of negative votes.
uint percentage = uint(((negativeCollectionVotes * 10000) / grossVotes) * -1);
// Ensure we have a negative vote that is past a threshold
if (percentage < THRESHOLD) {
return;
}
// We need to determine the holdings across our strategies and exit our positions sufficiently
// and then subsequently sell against this position for ETH.
address[] memory strategies = strategyFactory.collectionStrategies(worstCollection);
emit CollectionTokensLiquidated(worstCollection, strategies, percentage);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {CannotSetNullAddress} from '@floor/utils/Errors.sol';
import {IEpochManager} from '@floor-interfaces/EpochManager.sol';
abstract contract EpochManaged is Ownable {
/// Emits when {EpochManager} is updated
event EpochManagerUpdated(address epochManager);
/// Stores the current {EpochManager} contract
IEpochManager public epochManager;
/**
* Allows an updated {EpochManager} address to be set.
*/
function setEpochManager(address _epochManager) external virtual onlyOwner {
_setEpochManager(_epochManager);
}
/**
* Allows an updated {EpochManager} address to be set by an inheriting contract.
*/
function _setEpochManager(address _epochManager) internal virtual {
if (_epochManager == address(0)) revert CannotSetNullAddress();
epochManager = IEpochManager(_epochManager);
emit EpochManagerUpdated(_epochManager);
}
/**
* Gets the current epoch from our {EpochManager}.
*/
function currentEpoch() internal view virtual returns (uint) {
return epochManager.currentEpoch();
}
/**
* Checks that the contract caller is the {EpochManager}.
*/
modifier onlyEpochManager() {
require(msg.sender == address(epochManager), 'Only EpochManager can call');
_;
}
}// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; /** * A collection of generic errors that can be referenced across multiple * contracts. Contract-specific errors should still be stored in their * individual Solidity files. */ /// If a NULL address tries to be stored which should not be accepted error CannotSetNullAddress(); /// If the caller has entered an insufficient amount to process the action. This /// will likely be a zero amount. error InsufficientAmount(); /// If the caller enters a percentage value that is too high for the requirements error PercentageTooHigh(uint amount); /// If a required ETH or token `transfer` call fails error TransferFailed(); /// If a user calls a deposit related function with a zero amount error CannotDepositZeroAmount(); /// If a user calls a withdrawal related function with a zero amount error CannotWithdrawZeroAmount(); /// If there are no rewards available to be claimed error NoRewardsAvailableToClaim(); /// If the requested collection is not approved /// @param collection Address of the collection requested error CollectionNotApproved(address collection); /// If the requested strategy implementation is not approved /// @param strategyImplementation Address of the strategy implementation requested error StrategyNotApproved(address strategyImplementation);
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {Clones} from '@openzeppelin/contracts/proxy/Clones.sol';
import {AuthorityControl} from '@floor/authorities/AuthorityControl.sol';
import {CannotSetNullAddress, CollectionNotApproved, StrategyNotApproved} from '@floor/utils/Errors.sol';
import {ICollectionRegistry} from '@floor-interfaces/collections/CollectionRegistry.sol';
import {IBaseStrategy} from '@floor-interfaces/strategies/BaseStrategy.sol';
import {IStrategyFactory} from '@floor-interfaces/strategies/StrategyFactory.sol';
import {IStrategyRegistry} from '@floor-interfaces/strategies/StrategyRegistry.sol';
import {ITreasury} from '@floor-interfaces/Treasury.sol';
// No empty names, that's just silly
error StrategyNameCannotBeEmpty();
/**
* Allows for strategies to be created, pairing them with an approved collection. The strategy
* creation script needs to be as highly optimised as possible to ensure that the gas
* costs are kept down.
*
* This factory will keep an index of created strategies and secondary information to ensure
* that external applications can display and maintain a list of available strategies.
*/
contract StrategyFactory is AuthorityControl, IStrategyFactory {
/// Maintains an array of all strategies created
address[] private _strategies;
/// Store our Treasury address
address public treasury;
/// Contract mappings to our approved collections
ICollectionRegistry public immutable collectionRegistry;
/// Contract mappings to our approved strategy implementations
IStrategyRegistry public immutable strategyRegistry;
/// Mappings to aide is discoverability
mapping(uint => address) private _strategyIds;
/// Mapping of collection to strategy addresses
mapping(address => address[]) private _collectionStrategies;
/// Stores a list of bypassed strategies
mapping(address => bool) private _bypassStrategy;
/**
* Store our registries, mapped to their interfaces.
*
* @param _authority {AuthorityRegistry} contract address
* @param _collectionRegistry Address of our {CollectionRegistry}
*/
constructor(address _authority, address _collectionRegistry, address _strategyRegistry) AuthorityControl(_authority) {
if (_collectionRegistry == address(0)) revert CannotSetNullAddress();
if (_strategyRegistry == address(0)) revert CannotSetNullAddress();
// Type-cast our interfaces and store our registry contracts
collectionRegistry = ICollectionRegistry(_collectionRegistry);
strategyRegistry = IStrategyRegistry(_strategyRegistry);
}
/**
* Provides a list of all strategies created.
*
* @return Array of all strategies created by the {StrategyFactory}
*/
function strategies() external view returns (address[] memory) {
return _strategies;
}
/**
* Returns an array of all strategies that belong to a specific collection.
*
* @param _collection The address of the collection to query
*
* @return address[] Array of strategy addresses
*/
function collectionStrategies(address _collection) external view returns (address[] memory) {
return _collectionStrategies[_collection];
}
/**
* Provides a strategy against the provided `strategyId` (index). If the index does not exist,
* then address(0) will be returned.
*
* @param _strategyId ID of the strategy to retrieve
*
* @return Address of the strategy
*/
function strategy(uint _strategyId) external view returns (address) {
return _strategyIds[_strategyId];
}
/**
* Creates a strategy with an approved collection.
*
* @dev The strategy is not created using Clones as there are complications when
* allocated roles and permissions.
*
* @param _name Human-readable name of the strategy
* @param _strategy The strategy implemented by the strategy
* @param _strategyInitData Bytes data required by the {Strategy} for initialization
* @param _collection The address of the collection attached to the strategy
*
* @return strategyId_ ID of the newly created strategy
* @return strategyAddr_ Address of the newly created strategy
*/
function deployStrategy(bytes32 _name, address _strategy, bytes calldata _strategyInitData, address _collection)
external
onlyRole(STRATEGY_MANAGER)
returns (uint strategyId_, address strategyAddr_)
{
// No empty names, that's just silly
if (_name == '') revert StrategyNameCannotBeEmpty();
// Make sure the strategy implementation is approved
if (!strategyRegistry.isApproved(_strategy)) revert StrategyNotApproved(_strategy);
// Make sure the collection is approved
if (!collectionRegistry.isApproved(_collection)) revert CollectionNotApproved(_collection);
// Capture our `strategyId`, before we increment the array length
strategyId_ = _strategies.length;
// Deploy a new {Strategy} instance using the clone mechanic
strategyAddr_ = Clones.cloneDeterministic(_strategy, bytes32(strategyId_));
// We then need to instantiate the strategy using our supplied `strategyInitData`
IBaseStrategy(strategyAddr_).initialize(_name, strategyId_, _strategyInitData);
// Add our strategies to our internal tracking
_strategies.push(strategyAddr_);
// Add our mappings for onchain discoverability
_strategyIds[strategyId_] = strategyAddr_;
_collectionStrategies[_collection].push(strategyAddr_);
// Finally we can emit our event to notify watchers of a new strategy
emit StrategyCreated(strategyId_, strategyAddr_, _collection);
}
/**
* Allows individual strategies to be paused, meaning that assets can no longer be deposited,
* although staked assets can always be withdrawn.
*
* @dev Events are fired within the strategy to allow listeners to update.
*
* @param _strategyId strategy ID to be updated
* @param _paused If the strategy should be paused or unpaused
*/
function pause(uint _strategyId, bool _paused) public onlyRole(STRATEGY_MANAGER) {
IBaseStrategy(_strategyIds[_strategyId]).pause(_paused);
}
/**
* Reads the yield generated by all strategies since the last time that this
* function was called.
*/
function snapshot(uint _epoch)
external
onlyRole(STRATEGY_MANAGER)
returns (address[] memory strategies_, uint[] memory amounts_, uint totalAmount_)
{
// Get our underlying WETH address
address weth = address(ITreasury(treasury).weth());
// Prefine some variables
address[] memory tokens;
uint[] memory amounts;
uint tokensLength;
// Get the number of strategies and define our returned array lengths
uint strategiesLength = _strategies.length;
strategies_ = new address[](strategiesLength);
amounts_ = new uint[](strategiesLength);
// Iterate over strategies to pull out yield
for (uint i; i < strategiesLength;) {
// Prevent a bypassed strategy from snapshotting
if (!_bypassStrategy[_strategies[i]]) {
// Snapshot our strategy
(tokens, amounts) = IBaseStrategy(_strategies[i]).snapshot();
// Capture the strategy address, even if we receive no WETH yield
strategies_[i] = _strategies[i];
// Iterate over tokens to just find WETH amounts
tokensLength = tokens.length;
for (uint l; l < tokensLength;) {
// Ensure that we only handle WETH tokens with amounts
if (tokens[l] == address(weth) && amounts[l] != 0) {
// Capture the WETH yield relative to the strategy
amounts_[i] = amounts[l];
// Keep a tally of the total amount of WETH earned
totalAmount_ += amounts[l];
}
unchecked { ++l; }
}
}
unchecked { ++i; }
}
emit StrategySnapshot(_epoch, strategies_, amounts_);
}
/**
* Harvest available reward yield from the strategy. This won't affect the amount
* depositted into the contract and should only harvest rewards directly into the
* {Treasury}.
*
* @param _strategyId Strategy ID to be harvested
*/
function harvest(uint _strategyId) external onlyRole(STRATEGY_MANAGER) {
if (_bypassStrategy[_strategyIds[_strategyId]]) return;
IBaseStrategy(_strategyIds[_strategyId]).harvest(treasury);
}
/**
* Makes a call to a strategy withdraw function by passing the strategy ID and
* `abi.encodeWithSelector` to build the bytes `_data` parameter. This will then
* pass the data on to the strategy function and inject the treasury recipient
* address within the call as the first function parameter.
*
* @dev It is required for the transaction to return a successful call, otherwise
* the transaction will be reverted. The error response will be standardised so
* debugging will require a trace, rather than just the end message.
*
* @param _strategyId Strategy ID to be withdrawn from
* @param _data Strategy withdraw function call, using `encodeWithSelector`
*/
function withdraw(uint _strategyId, bytes calldata _data) external onlyRole(STRATEGY_MANAGER) {
// If we are bypassing the strategy, then skip this call
if (_bypassStrategy[_strategyIds[_strategyId]]) return;
// Extract the selector from data
bytes4 _selector = bytes4(_data);
// Create a replication of the bytes data that removes the selector
bytes memory _newData = new bytes(_data.length - 4);
for (uint i; i < _data.length - 4; i++) {
_newData[i] = _data[i + 4];
}
// Make a call to our strategy that passes on our withdrawal data
(bool success,) = _strategyIds[_strategyId].call(
// Sandwich the selector against the recipient and remaining data
abi.encodePacked(abi.encodeWithSelector(_selector, treasury), _newData)
);
// If our call failed, return a standardised message rather than decoding
require(success, 'Unable to withdraw');
}
/**
* Makes a call to a strategy withdraw function.
*
* @param _strategy Strategy address to be updated
* @param _percentage The percentage of position to withdraw from
*/
function withdrawPercentage(address _strategy, uint _percentage)
external
onlyRole(STRATEGY_MANAGER)
returns (address[] memory tokens_, uint[] memory amounts_)
{
// Ensure our percentage is valid (less than 100% to 2 decimal places)
require(_percentage > 0, 'Invalid percentage');
require(_percentage <= 100_00, 'Invalid percentage');
// Prevent a bypassed strategy from parsing withdrawal calculations
if (_bypassStrategy[_strategy]) {
return (tokens_, amounts_);
}
// Calls our strategy to withdraw a percentage of the holdings
return IBaseStrategy(_strategy).withdrawPercentage(msg.sender, _percentage);
}
/**
* Allow a strategy to be skipped when being processing. This is beneficial if a
* strategy becomes corrupted at an external point and would otherwise prevent an
* epoch from ending.
*
* @dev This does not shutdown the strategy as it can be undone. If a strategy wants
* to wind down, then it should also be paused and a full withdraw made.
*/
function bypassStrategy(address _strategy, bool _bypass) external onlyRole(STRATEGY_MANAGER) {
_bypassStrategy[_strategy] = _bypass;
}
/**
* Allows the {Treasury} contract address to be updated. All withdrawals will
* be requested to be sent to this address when the `withdraw` is called.
*
* @dev This address is dynamically injected into the subsequent strategy
* withdraw call.
*
* @param _treasury The new {Treasury} contract address
*/
function setTreasury(address _treasury) public onlyRole(TREASURY_MANAGER) {
if (_treasury == address(0)) revert CannotSetNullAddress();
treasury = _treasury;
emit TreasuryUpdated(_treasury);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IEpochEndTriggered {
/**
* Function that is triggered when an epoch ends.
*/
function endEpoch(uint epoch) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* The GWV will allow users to assign their veFloor position to a strategy, or
* optionally case it to a veFloor, which will use a constant value. As the
* strategies will be rendered as an address, the veFloor vote will take a NULL
* address value.
*
* At point of development this can take influence from:
* https://github.com/saddle-finance/saddle-contract/blob/master/contracts/tokenomics/gauges/GaugeController.vy
*/
interface ISweepWars {
/// Sent when a user casts or revokes their vote
event VoteCast(address sender, address collection, int amount);
/// Sent when a user has revoked their vote, or it is revoked on their behalf
event VotesRevoked(address account, address collection, uint forVotesRevoked, uint againstVotesRevoked);
/// Sent when the Sample Size is updated
event SampleSizeUpdated(uint size);
/// Sent when the {NftStaking} contract address is updated
event NftStakingUpdated(address nftStaking);
/**
* Gets the number of votes for a collection at the current epoch.
*/
function votes(address) external view returns (int);
/**
* The total voting power of a user, regardless of if they have cast votes
* or not.
*/
function userVotingPower(address _user) external view returns (uint);
/**
* The total number of votes that a user has available, calculated by:
*
* ```
* votesAvailable_ = balanceOf(_user) - SUM(userVotes.votes_)
* ```
*/
function userVotesAvailable(address _user) external view returns (uint votesAvailable_);
/**
* Provides a list of collection addresses that can be voted on.
*/
function voteOptions() external view returns (address[] memory collections_);
/**
* Allows a user to cast a vote using their veFloor allocation. We don't
* need to monitor transfers as veFloor can only be minted or burned, and
* we check the voters balance during the `snapshot` call.
*
* A user can vote with a partial amount of their veFloor holdings, and when
* it comes to calculating their voting power this will need to be taken into
* consideration that it will be:
*
* ```
* staked balance + (gains from staking * (total balance - staked balance)%)
* ```
*
* The {Treasury} cannot vote with it's holdings, as it shouldn't be holding
* any staked Floor.
*/
function vote(address _collection, int _amount) external;
/**
* Allows a user to revoke their votes from strategies. This will free up the
* user's available votes that can subsequently be voted again with.
*/
function revokeVotes(address[] memory _collection) external;
/**
* Allows an authorised contract or wallet to revoke all user votes. This
* can be called when the veFLOOR balance is reduced.
*/
function revokeAllUserVotes(address _account) external;
/**
* The snapshot function will need to iterate over all strategies that have
* more than 0 votes against them. With that we will need to find each
* strategy percentage share in relation to other strategies.
*
* This percentage share will instruct the {Treasury} on how much additional
* FLOOR to allocate to the users staked in the strategies. These rewards will
* become available in the {RewardLedger}.
*
* +----------------+-----------------+-------------------+-------------------+
* | Voter | veFloor | Vote Weight | Strategy |
* +----------------+-----------------+-------------------+-------------------+
* | Alice | 30 | 40 | 1 |
* | Bob | 20 | 30 | 2 |
* | Carol | 40 | 55 | 3 |
* | Dave | 20 | 40 | 2 |
* | Emily | 25 | 35 | 0 |
* +----------------+-----------------+-------------------+-------------------+
*
* With the above information, and assuming that the {Treasury} has allocated
* 1000 FLOOR tokens to be additionally distributed in this snapshot, we would
* have the following allocations going to the strategies.
*
* +----------------+-----------------+-------------------+-------------------+
* | Strategy | Votes Total | Vote Percent | veFloor Rewards |
* +----------------+-----------------+-------------------+-------------------+
* | 0 (veFloor) | 35 | 17.5% | 175 |
* | 1 | 40 | 20% | 200 |
* | 2 | 70 | 35% | 350 |
* | 3 | 55 | 27.5% | 275 |
* | 4 | 0 | 0% | 0 |
* +----------------+-----------------+-------------------+-------------------+
*
* This would distribute the strategies allocated rewards against the staked
* percentage in the strategy. Any Treasury holdings that would be given in rewards
* are just deposited into the {Treasury} as FLOOR, bypassing the {RewardsLedger}.
*/
function snapshot(uint tokens) external returns (address[] memory collections, uint[] memory amounts);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* Handles epoch management for all other contracts.
*/
interface IEpochManager {
/// Emitted when an epoch is ended
event EpochEnded(uint epoch, uint timestamp);
/// Emitted when a new collection war is scheduled
event CollectionAdditionWarScheduled(uint epoch, uint index);
/// Emitted when required contracts are updated
event EpochManagerContractsUpdated(address newCollectionWars, address voteMarket);
/**
* The current epoch that is running across the codebase.
*
* @return The current epoch
*/
function currentEpoch() external view returns (uint);
/**
* Stores a mapping of an epoch to a collection addition war index.
*
* @param _epoch Epoch to check
*
* @return Index of the collection addition war. Will return 0 if none found
*/
function collectionEpochs(uint _epoch) external view returns (uint);
/**
* Will return if the current epoch is a collection addition vote.
*
* @return If the current epoch is a collection addition
*/
function isCollectionAdditionEpoch() external view returns (bool);
/**
* Will return if the specified epoch is a collection addition vote.
*
* @param epoch The epoch to check
*
* @return If the specified epoch is a collection addition
*/
function isCollectionAdditionEpoch(uint epoch) external view returns (bool);
/**
* Allows an epoch to be scheduled to be a collection addition vote. An index will
* be specified to show which collection addition will be used. The index will not
* be a zero value.
*
* @param epoch The epoch that the Collection Addition will take place in
* @param index The Collection Addition array index
*/
function scheduleCollectionAdditionEpoch(uint epoch, uint index) external;
/**
* Triggers an epoch to end.
*
* @dev More information about this function can be found in the actual contract
*/
function endEpoch() external;
/**
* Provides an estimated timestamp of when an epoch started, and also the earliest
* that an epoch in the future could start.
*
* @param _epoch The epoch to find the estimated timestamp of
*
* @return The estimated timestamp of when the specified epoch started
*/
function epochIterationTimestamp(uint _epoch) external returns (uint);
/**
* The length of an epoch in seconds.
*
* @return The length of the epoch in seconds
*/
function EPOCH_LENGTH() external returns (uint);
/**
* Sets contracts that the epoch manager relies on. This doesn't have to include
* all of the contracts that are {EpochManaged}, but only needs to set ones that the
* {EpochManager} needs to interact with.
*/
function setContracts(address _newCollectionWars, address _voteMarket) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (proxy/Clones.sol)
pragma solidity ^0.8.0;
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*
* _Available since v3.4._
*/
library Clones {
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address implementation) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(0, 0x09, 0x37)
}
require(instance != address(0), "ERC1167: create failed");
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple time will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(0, 0x09, 0x37, salt)
}
require(instance != address(0), "ERC1167: create2 failed");
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := keccak256(add(ptr, 0x43), 0x55)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Context} from '@openzeppelin/contracts/utils/Context.sol';
import {CannotSetNullAddress} from '@floor/utils/Errors.sol';
import {IAuthorityControl} from '@floor-interfaces/authorities/AuthorityControl.sol';
import {IAuthorityRegistry} from '@floor-interfaces/authorities/AuthorityRegistry.sol';
/// If the account does not have the required role for the call.
/// @param caller The address making the call
/// @param role The role that is required for the call
error AccountDoesNotHaveRole(address caller, bytes32 role);
/// If the account does not have the required admin role for the call.
/// @param caller The address making the call
error AccountDoesNotHaveAdminRole(address caller);
/**
* This contract is heavily based on the standardised OpenZeppelin `AccessControl` library.
* This allows for the creation of role based access levels that can be assigned to 1-n
* addresses.
*
* Contracts will be able to implement the AuthorityControl to provide access to the `onlyRole` modifier or the
* `hasRole` function. This will ensure that the `msg.sender` is allowed to perform an action.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed in the external API and be
* unique. The best way to achieve this is by using `public constant` hash digests:
*
* ```
* bytes32 public constant MY_ROLE = keccak256("TreasuryManager");
* ```
*/
contract AuthorityControl is Context, IAuthorityControl {
/// CollectionManager - Can approve token addresses to be allowed to be used in strategies
bytes32 public constant COLLECTION_MANAGER = keccak256('CollectionManager');
/// EpochTrigger - Can run epoch trigger contract specific logic
bytes32 public constant EPOCH_TRIGGER = keccak256('EpochTrigger');
/// FloorManager - Can mint and manage Floor and VeFloor tokens
bytes32 public constant FLOOR_MANAGER = keccak256('FloorManager');
/// Governor - A likely DAO owned vote address to allow for wide scale decisions to
/// be made and implemented.
bytes32 public constant GOVERNOR = keccak256('Governor');
/// Guardian - Wallet address that will allow for Governor based actions, except without
/// timeframe restrictions.
bytes32 public constant GUARDIAN = keccak256('Guardian');
/// TreasuryManager - Access to Treasury asset management
bytes32 public constant TREASURY_MANAGER = keccak256('TreasuryManager');
/// StrategyManager - Can create new strategies against approved strategies and collections
bytes32 public constant STRATEGY_MANAGER = keccak256('StrategyManager');
/// VoteManager - Can manage account votes
bytes32 public constant VOTE_MANAGER = keccak256('VoteManager');
/// Reference to the {AuthorityRegistry} contract that maintains role allocations
IAuthorityRegistry public immutable registry;
/**
* Modifier that checks that an account has a specific role. Reverts with a
* standardized message if user does not have specified role.
*
* @param role The keccak256 encoded role string
*/
modifier onlyRole(bytes32 role) {
if (!registry.hasRole(role, _msgSender())) {
revert AccountDoesNotHaveRole(_msgSender(), role);
}
_;
}
/**
* Modifier that checks that an account has a governor or guardian role.
* Reverts with a standardized message if sender does not have an admin role.
*/
modifier onlyAdminRole() {
if (!registry.hasAdminRole(_msgSender())) {
revert AccountDoesNotHaveAdminRole(_msgSender());
}
_;
}
/**
* The address that deploys the {AuthorityControl} becomes the default controller. This
* can only be overwritten by the existing.
*
* @param _registry The address of our deployed AuthorityRegistry contract
*/
constructor(address _registry) {
if (_registry == address(0)) revert CannotSetNullAddress();
registry = IAuthorityRegistry(_registry);
}
/**
* Returns `true` if `account` has been granted `role`.
*
* @param role The keccak256 encoded role string
* @param account Address to check ownership of role
*
* @return bool If the address has the specified user role
*/
function hasRole(bytes32 role, address account) public view override returns (bool) {
return registry.hasRole(role, account);
}
/**
* Returns `true` if `account` has been granted either GOVERNOR or GUARDIAN role.
*
* @param account Address to check ownership of role
*
* @return bool If the address has the GOVERNOR or GUARDIAN role
*/
function hasAdminRole(address account) public view returns (bool) {
return registry.hasAdminRole(account);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* Allows collection contracts to be approved and revoked by addresses holding the
* {CollectionManager} role. Only once approved can these collections be applied to
* new or existing strategies. They will only need to be stored as a mapping of address
* to boolean.
*/
interface ICollectionRegistry {
/// Emitted when a collection is successfully approved
event CollectionApproved(address contractAddr);
/// Emitted when a collection has been successfully revoked
event CollectionRevoked(address contractAddr);
/**
* Returns `true` if the contract address is an approved collection, otherwise
* returns `false`.
*/
function isApproved(address contractAddr) external view returns (bool);
/**
* Returns an array of all approved collections.
*/
function approvedCollections() external view returns (address[] memory);
/**
* Approves a collection contract to be used for strategies.
*/
function approveCollection(address contractAddr) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* Strategies will hold the logic for interacting with external platforms to stake
* and harvest reward yield. Each strategy will require its own strategy implementation
* to allow for different immutable variables to be defined during construct.
*
* This will follow a similar approach to how NFTX offer their eligibility module
* logic, with a lot of the power coming from inheritence.
*
* When constructed, we want to give the {Treasury} a max uint approval of the yield
* and underlying tokens.
*/
interface IBaseStrategy {
/// @dev When strategy receives a deposit
event Deposit(address token, uint amount, address caller);
/// @dev When strategy is harvested
event Harvest(address token, uint amount);
/// @dev When a staked user exits their position
event Withdraw(address token, uint amount, address recipient);
/**
* Allows the strategy to be initialised.
*/
function initialize(bytes32 name, uint strategyId, bytes calldata initData) external;
/**
* Name of the strategy.
*/
function name() external view returns (bytes32);
/**
* The numerical ID of the strategy that acts as an index for the {StrategyFactory}.
*/
function strategyId() external view returns (uint);
/**
* Total rewards generated by the strategy in all time. This is pure bragging rights.
*/
function lifetimeRewards(address token) external returns (uint amount_);
/**
* The amount of rewards claimed in the last claim call.
*/
function lastEpochRewards(address token) external returns (uint amount_);
/**
* Gets rewards that are available to harvest.
*/
function available() external returns (address[] memory, uint[] memory);
/**
* Extracts all rewards from third party and moves it to a recipient. This should
* only be called by a specific action.
*
* @dev This _should_ always be imposed to be the {Treasury} by the {StrategyFactory}.
*/
function harvest(address /* _recipient */ ) external;
/**
* Returns an array of tokens that the strategy supports.
*
* @return address[] The address of valid tokens
*/
function validTokens() external view returns (address[] memory);
/**
* Makes a call to a strategy to withdraw a percentage of the deposited holdings.
*
* @param recipient Strategy address to be updated
* @param percentage The 2 decimal accuracy of the percentage to withdraw (e.g. 100% = 10000)
*
* @return address[] Array of tokens withdrawn
* @return uint[] Amounts of respective tokens withdrawn
*/
function withdrawPercentage(address recipient, uint percentage) external returns (address[] memory, uint[] memory);
/**
* Pauses deposits from being made into the strategy. This should only be called by
* a guardian or governor.
*
* @param _p Boolean value for if the strategy should be paused
*/
function pause(bool _p) external;
/**
* Gets a read of new yield since the last call. This is what can be called when
* the epoch ends to determine the amount generated within the epoch.
*/
function snapshot() external returns (address[] memory, uint[] memory);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* Allows for strategies to be created, pairing them with a {Strategy} and an approved
* collection. The strategy creation script needs to be as highly optimised as possible
* to ensure that the gas costs are kept down.
*
* This factory will keep an index of created strategies and secondary information to ensure
* that external applications can display and maintain a list of available strategies.
*
* The contract can be paused to prevent the creation of new strategies.
*/
interface IStrategyFactory {
/// @dev Sent when a strategy is created successfully
event StrategyCreated(uint indexed strategyId, address strategyAddress, address assetAddress);
/// @dev Sent when a snapshot is taken
event StrategySnapshot(uint epoch, address[] tokens, uint[] amounts);
/// @dev Sent when the Treasury address is updated
event TreasuryUpdated(address treasury);
/**
* Our stored {Treasury} address.
*/
function treasury() external view returns (address);
/**
* Provides a list of all strategies created.
*
* @return Array of all strategies created by the {StrategyFactory}
*/
function strategies() external view returns (address[] memory);
/**
* Returns an array of all strategies that belong to a specific collection.
*/
function collectionStrategies(address _collection) external view returns (address[] memory);
/**
* Provides a strategy against the provided `strategyId` (index). If the index does not exist,
* then address(0) will be returned.
*
* @param _strategyId ID of the strategy to retrieve
*
* @return Address of the strategy
*/
function strategy(uint _strategyId) external view returns (address);
/**
* Creates a strategy with an approved collection.
*
* @dev The strategy is not created using Clones as there are complications when allocated
* roles and permissions.
*
* @param _name Human-readable name of the strategy
* @param _strategy The strategy implemented by the strategy
* @param _strategyInitData Bytes data required by the {Strategy} for initialization
* @param _collection The address of the collection attached to the strategy
*
* @return strategyId_ ID of the newly created strategy
* @return strategyAddr_ Address of the newly created strategy
*/
function deployStrategy(bytes32 _name, address _strategy, bytes calldata _strategyInitData, address _collection)
external
returns (uint strategyId_, address strategyAddr_);
/**
* Allows individual strategies to be paused, meaning that assets can no longer be deposited,
* although staked assets can always be withdrawn.
*
* @dev Events are fired within the strategy to allow listeners to update.
*
* @param _strategyId Strategy ID to be paused
* @param _paused If the strategy should be paused or unpaused
*/
function pause(uint _strategyId, bool _paused) external;
/**
* Reads the yield generated by a strategy since the last time that this function was called.
*
* @param _epoch The current epoch being snapshotted
*
* @return tokens Tokens that have been generated as yield
* @return amounts The amount of yield generated for the corresponding token
*/
function snapshot(uint _epoch) external returns (address[] memory tokens, uint[] memory amounts, uint totalAmount);
/**
* Harvest available reward yield from the strategy. This won't affect the amount
* depositted into the contract and should only harvest rewards directly into the
* {Treasury}.
*
* @param _strategyId Strategy ID to be harvested
*/
function harvest(uint _strategyId) external;
/**
* Makes a call to a strategy withdraw function by passing the strategy ID and
* `abi.encodeWithSelector` to build the bytes `_data` parameter. This will then
* pass the data on to the strategy function and inject the treasury recipient
* address within the call as the first function parameter.
*
* @dev It is required for the transaction to return a successful call, otherwise
* the transaction will be reverted. The error response will be standardised so
* debugging will require a trace, rather than just the end message.
*
* @param _strategyId Strategy ID to be withdrawn from
* @param _data Strategy withdraw function call, using `encodeWithSelector`
*/
function withdraw(uint _strategyId, bytes calldata _data) external;
/**
* Makes a call to a strategy to withdraw a percentage of the deposited holdings.
*
* @param _strategy Strategy address to be updated
* @param _percentage The 2 decimal accuracy of the percentage to withdraw (e.g. 100% = 10000)
*/
function withdrawPercentage(address _strategy, uint _percentage) external returns (address[] memory, uint[] memory);
/**
* Allows the {Treasury} contract address to be updated. All withdrawals will
* be requested to be sent to this address when the `withdraw` is called.
*
* @dev This address is dynamically injected into the subsequent strategy
* withdraw call.
*
* @param _treasury The new {Treasury} contract address
*/
function setTreasury(address _treasury) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* Allows strategy contracts to be approved and revoked by addresses holding the
* {TREASURY_MANAGER} role. Only once approved can these strategy implementations be deployed
* to new or existing strategies.
*/
interface IStrategyRegistry {
/// Emitted when a strategy is approved or unapproved
event ApprovedStrategyUpdated(address contractAddr, bool approved);
/**
* Checks if a strategy has previously been approved.
*
* @param contractAddr The strategy implementation address to be checked
*
* @return Returns `true` if the contract address is an approved strategy, otherwise
* returns `false`.
*/
function isApproved(address contractAddr) external view returns (bool);
/**
* Changes the approval state of a strategy implementation contract.
*
* The strategy address cannot be null, and if it is already the new state, then
* no changes will be made.
*
* The caller must have the `TREASURY_MANAGER` role.
*
* @param contractAddr Address of unapproved strategy to approve
* @param approved The new approval state for the implementation
*/
function approveStrategy(address contractAddr, bool approved) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IWETH} from '@floor-interfaces/tokens/WETH.sol';
library TreasuryEnums {
/// Different sweep types that can be specified.
enum SweepType {
COLLECTION_ADDITION,
SWEEP
}
/// Different approval types that can be specified.
enum ApprovalType {
NATIVE,
ERC20,
ERC721,
ERC1155
}
}
/**
* @dev The Treasury will hold all assets.
*/
interface ITreasury {
/// Stores data that allows the Treasury to action a sweep.
struct Sweep {
TreasuryEnums.SweepType sweepType;
address[] collections;
uint[] amounts;
bool completed;
string message;
}
/// The data structure format that will be mapped against to define a token
/// approval request.
struct ActionApproval {
TreasuryEnums.ApprovalType _type; // Token type
address assetContract; // Used by 20, 721 and 1155
address target; // Used by 20, 721 and 1155
uint amount; // Used by native and 20 tokens
}
/// @dev When native network token is withdrawn from the Treasury
event Deposit(uint amount);
/// @dev When an ERC20 is depositted into the Treasury
event DepositERC20(address token, uint amount);
/// @dev When an ERC721 is depositted into the Treasury
event DepositERC721(address token, uint tokenId);
/// @dev When an ERC1155 is depositted into the Treasury
event DepositERC1155(address token, uint tokenId, uint amount);
/// @dev When native network token is withdrawn from the Treasury
event Withdraw(uint amount, address recipient);
/// @dev When an ERC20 token is withdrawn from the Treasury
event WithdrawERC20(address token, uint amount, address recipient);
/// @dev When an ERC721 token is withdrawn from the Treasury
event WithdrawERC721(address token, uint tokenId, address recipient);
/// @dev When an ERC1155 is withdrawn from the Treasury
event WithdrawERC1155(address token, uint tokenId, uint amount, address recipient);
/// @dev When FLOOR is minted
event FloorMinted(uint amount);
/// @dev When a {Treasury} action is processed
event ActionProcessed(address action, bytes data);
/// @dev When a sweep is registered against an epoch
event SweepRegistered(uint sweepEpoch, TreasuryEnums.SweepType sweepType, address[] collections, uint[] amounts);
/// @dev When an action is assigned to a sweep epoch
event SweepAction(uint sweepEpoch);
/// @dev When an epoch is swept
event EpochSwept(uint epochIndex);
/// Emitted when the {MercenarySweeper} contract address is updated
event MercenarySweeperUpdated(address mercSweeper);
/// Emitted when the minimum sweep amount is updated
event MinSweepAmountUpdated(uint minSweepAmount);
/// Emitted when the {VeFloorStaking} contract is updated
event VeFloorStakingUpdated(address veFloorStaking);
/// Emitted when the {StrategyFactory} contract is updated
event StrategyFactoryUpdated(address strategyFactory);
/**
* Our stored WETH address for the {Treasury}
*/
function weth() external returns (IWETH);
/**
* Allow FLOOR token to be minted. This should be called from the deposit method
* internally, but a public method will allow a {TreasuryManager} to bypass this
* and create additional FLOOR tokens if needed.
*
* @dev We only want to do this on creation and for inflation. Have a think on how
* we can implement this!
*/
function mint(uint amount) external;
/**
* Allows an ERC20 token to be deposited and generates FLOOR tokens based on
* the current determined value of FLOOR and the token.
*/
function depositERC20(address token, uint amount) external;
/**
* Allows an ERC721 token to be deposited and generates FLOOR tokens based on
* the current determined value of FLOOR and the token.
*/
function depositERC721(address token, uint tokenId) external;
/**
* Allows an ERC1155 token(s) to be deposited and generates FLOOR tokens based on
* the current determined value of FLOOR and the token.
*/
function depositERC1155(address token, uint tokenId, uint amount) external;
/**
* Allows an approved user to withdraw native token.
*/
function withdraw(address recipient, uint amount) external;
/**
* Allows an approved user to withdraw and ERC20 token from the Treasury.
*/
function withdrawERC20(address recipient, address token, uint amount) external;
/**
* Allows an approved user to withdraw and ERC721 token from the Treasury.
*/
function withdrawERC721(address recipient, address token, uint tokenId) external;
/**
* Allows an approved user to withdraw an ERC1155 token(s) from the Treasury.
*/
function withdrawERC1155(address recipient, address token, uint tokenId, uint amount) external;
/**
* Actions a sweep to be used against a contract that implements {ISweeper}. This
* will fulfill the sweep and we then mark the sweep as completed.
*/
function sweepEpoch(uint epochIndex, address sweeper, bytes calldata data, uint mercSweep) external;
/**
* Allows the DAO to resweep an already swept "Sweep" struct, using a contract that
* implements {ISweeper}. This will fulfill the sweep again and keep the sweep marked
* as completed.
*/
function resweepEpoch(uint epochIndex, address sweeper, bytes calldata data, uint mercSweep) external;
/**
* When an epoch ends, we have the ability to register a sweep against the {Treasury}
* via an approved contract. This will store a DAO sweep that will need to be actioned
* using the `sweepEpoch` function.
*/
function registerSweep(uint epoch, address[] calldata collections, uint[] calldata amounts, TreasuryEnums.SweepType sweepType)
external;
/**
* The minimum sweep amount that can be implemented, or excluded, as desired by the DAO.
*/
function minSweepAmount() external returns (uint);
/**
* Allows the mercenary sweeper contract to be updated.
*/
function setMercenarySweeper(address _mercSweeper) external;
/**
* Allows us to set a new VeFloorStaking contract that is used when sweeping epochs.
*/
function setVeFloorStaking(address _veFloorStaking) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IAuthorityControl {
/// CollectionManager - Can approve token addresses to be allowed to be used in strategies
function COLLECTION_MANAGER() external returns (bytes32);
/// EpochTrigger - Can run epoch trigger contract specific logic
function EPOCH_TRIGGER() external returns (bytes32);
/// FloorManager - Can mint and manage Floor and VeFloor tokens
function FLOOR_MANAGER() external returns (bytes32);
/// Governor - A likely DAO owned vote address to allow for wide scale decisions to
/// be made and implemented.
function GOVERNOR() external returns (bytes32);
/// Guardian - Wallet address that will allow for Governor based actions, except without
/// timeframe restrictions.
function GUARDIAN() external returns (bytes32);
/// TreasuryManager - Access to Treasury asset management
function TREASURY_MANAGER() external returns (bytes32);
/// StrategyManager - Can create new strategies against approved strategies and collections
function STRATEGY_MANAGER() external returns (bytes32);
/// VoteManager - Can manage account votes
function VOTE_MANAGER() external returns (bytes32);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns `true` if `account` has been granted either the GOVERNOR or
* GUARDIAN `role`.
*/
function hasAdminRole(address account) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* This interface expands upon the OpenZeppelin `IAccessControl` interface:
* https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/access/IAccessControl.sol
*/
interface IAuthorityRegistry {
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns `true` if `account` has been granted either the GOVERNOR or
* GUARDIAN `role`.
*/
function hasAdminRole(address account) external view returns (bool);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
interface IWETH is IERC20 {
function allowance(address, address) external view returns (uint);
function balanceOf(address) external view returns (uint);
function approve(address, uint) external returns (bool);
function transfer(address, uint) external returns (bool);
function transferFrom(address, address, uint) external returns (bool);
function deposit() external payable;
function withdraw(uint) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}{
"remappings": [
"ds-test/=lib/forge-std/lib/ds-test/src/",
"erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
"forge-std/=lib/forge-std/src/",
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
"@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"@uniswap-v3/=lib/",
"@uniswap/v3-periphery/=lib/v3-periphery/",
"@chainlink/=lib/chainlink/",
"@murky/=lib/murky/src/",
"@solidity-math-utils/=lib/solidity-math-utils/project/contracts/",
"@solidity-trigonometry/=lib/solidity-trigonometry/src/",
"@1inch/=lib/",
"@charmfi/=lib/charmfi-contracts-0.8.0-support/",
"@sudoswap/=lib/lssvm/src/",
"@floor/=src/contracts/",
"@floor-interfaces/=src/interfaces/",
"@floor-scripts/=script/",
"@ERC721A/=lib/ERC721A/contracts/",
"foundry-random/=lib/foundry-random/src/",
"lssvm2/=lib/lssvm2/src/",
"@nftx-protocol-v3/=lib/nftx-protocol-v3/src/",
"@manifoldxyz/=lib/lssvm2/lib/",
"@mocks/=lib/nftx-protocol-v3/src/mocks/",
"@permit2/=lib/nftx-protocol-v3/lib/permit2/src/",
"@prb/math/=lib/lssvm2/lib/prb-math/src/",
"@prb/test/=lib/foundry-random/lib/prb-test/src/",
"@src/=lib/nftx-protocol-v3/src/",
"@test/=lib/nftx-protocol-v3/test/",
"@uni-core/=lib/nftx-protocol-v3/src/uniswap/v3-core/",
"@uni-periphery/=lib/nftx-protocol-v3/src/uniswap/v3-periphery/",
"@uniswap/lib/=lib/nftx-protocol-v3/lib/solidity-lib/",
"@uniswap/v2-core/=lib/nftx-protocol-v3/lib/v2-core/",
"@uniswap/v3-core/contracts/=lib/nftx-protocol-v3/src/uniswap/v3-core/",
"CramBit/=lib/foundry-random/lib/CramBit/",
"ERC721A/=lib/ERC721A/contracts/",
"base64-sol/=lib/nftx-protocol-v3/src/uniswap/v3-periphery/libraries/",
"chainlink/=lib/chainlink/",
"charmfi-contracts-0.8.0-support/=lib/charmfi-contracts-0.8.0-support/",
"clones-with-immutable-args/=lib/lssvm2/lib/clones-with-immutable-args/src/",
"crambit/=lib/foundry-random/lib/CramBit/src/",
"create2-helpers/=lib/lssvm2/lib/royalty-registry-solidity/lib/create2-helpers/",
"create3-factory/=lib/lssvm2/lib/create3-factory/",
"foundry-huff/=lib/lssvm2/lib/foundry-huff/src/",
"foundry-random/=lib/foundry-random/src/",
"huffmate/=lib/lssvm2/lib/huffmate/src/",
"libraries-solidity/=lib/lssvm2/lib/libraries-solidity/contracts/",
"lssvm/=lib/lssvm/src/",
"lssvm2/=lib/lssvm2/src/",
"manifoldxyz/=lib/lssvm2/lib/royalty-registry-solidity/contracts/",
"murky/=lib/murky/src/",
"nftx-protocol-v3/=lib/nftx-protocol-v3/src/",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"prb-math/=lib/solidity-trigonometry/lib/prb-math/contracts/",
"prb-test/=lib/foundry-random/lib/prb-test/src/",
"royalty-registry-solidity.git/=lib/lssvm/lib/royalty-registry-solidity.git/contracts/",
"royalty-registry-solidity/=lib/lssvm2/lib/royalty-registry-solidity/",
"solidity-bytes-utils/=lib/foundry-random/lib/solidity-bytes-utils/contracts/",
"solidity-math-utils/=lib/solidity-math-utils/",
"solidity-stringutils/=lib/lssvm2/lib/foundry-huff/lib/solidity-stringutils/",
"solidity-trigonometry/=lib/solidity-trigonometry/src/",
"solidity-utils/=lib/solidity-utils/contracts/",
"solmate/=lib/lssvm2/lib/solmate/src/",
"src/=lib/foundry-random/src/",
"stringutils/=lib/lssvm2/lib/foundry-huff/lib/solidity-stringutils/",
"v3-core/=lib/v3-core/contracts/",
"v3-periphery/=lib/v3-periphery/contracts/",
"weird-erc20/=lib/lssvm/lib/solmate/lib/weird-erc20/src/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "none"
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "london",
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_sweepWars","type":"address"},{"internalType":"address","name":"_strategyFactory","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"CannotSetNullAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_worstCollection","type":"address"},{"indexed":false,"internalType":"address[]","name":"_strategies","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"_percentage","type":"uint256"}],"name":"CollectionTokensLiquidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"epochManager","type":"address"}],"name":"EpochManagerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"THRESHOLD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"endEpoch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"epochManager","outputs":[{"internalType":"contract IEpochManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_epochManager","type":"address"}],"name":"setEpochManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"strategyFactory","outputs":[{"internalType":"contract StrategyFactory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sweepWars","outputs":[{"internalType":"contract ISweepWars","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
60c060405234801561001057600080fd5b50604051610a7d380380610a7d83398101604081905261002f916100f6565b6100383361008a565b6001600160a01b038216158061005557506001600160a01b038116155b156100735760405163d713c59760e01b815260040160405180910390fd5b6001600160a01b039182166080521660a052610129565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80516001600160a01b03811681146100f157600080fd5b919050565b6000806040838503121561010957600080fd5b610112836100da565b9150610120602084016100da565b90509250929050565b60805160a05161091b6101626000396000818161012601526103f8015260008181609d015281816101e80152610280015261091b6000f3fe608060405234801561001057600080fd5b50600436106100935760003560e01c80638da5cb5b116100665780638da5cb5b146101105780639ef3571014610121578063d4d59edb14610148578063e2d2bfe31461015b578063f2fde38b1461016e57600080fd5b80632a2c6b86146100985780636164e45d146100dc578063715018a6146100f1578063785ffb37146100f9575b600080fd5b6100bf7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b6100ef6100ea366004610671565b610181565b005b6100ef6104b1565b6101026103e881565b6040519081526020016100d3565b6000546001600160a01b03166100bf565b6100bf7f000000000000000000000000000000000000000000000000000000000000000081565b6100ef61015636600461069f565b6104c5565b6001546100bf906001600160a01b031681565b6100ef61017c36600461069f565b6104d6565b6001546001600160a01b031633146101e05760405162461bcd60e51b815260206004820152601a60248201527f4f6e6c792045706f63684d616e616765722063616e2063616c6c00000000000060448201526064015b60405180910390fd5b6000806000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166341495d1c6040518163ffffffff1660e01b8152600401600060405180830381865afa158015610244573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261026c91908101906106e9565b805190915060005b8181101561038b5760007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d8bff5a58584815181106102bf576102bf6107ae565b60200260200101516040518263ffffffff1660e01b81526004016102f291906001600160a01b0391909116815260200190565b602060405180830381865afa15801561030f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061033391906107c4565b90508581121561035d57809550838281518110610352576103526107ae565b602002602001015196505b60008112156103745761036f816107f3565b610376565b805b610380908661080f565b945050600101610274565b508260000361039c57505050505050565b6000836103ab86612710610837565b6103b5919061086d565b6103c190600019610837565b90506103e88110156103d65750505050505050565b60405163630a6fc160e01b81526001600160a01b0387811660048301526000917f00000000000000000000000000000000000000000000000000000000000000009091169063630a6fc190602401600060405180830381865afa158015610441573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261046991908101906106e9565b90507f6ea7eec715d8ed2035e1da6761dc0452a89785257a9bb31fe0e1c631e3c1fc2f87828460405161049e939291906108a9565b60405180910390a1505050505050505b50565b6104b961054c565b6104c360006105a6565b565b6104cd61054c565b6104ae816105f6565b6104de61054c565b6001600160a01b0381166105435760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016101d7565b6104ae816105a6565b6000546001600160a01b031633146104c35760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016101d7565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811661061d5760405163d713c59760e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f4b9d66d8e64dff6d9d2728f9a38da9739d3cfdbe676fde05564aae22f85084659060200160405180910390a150565b60006020828403121561068357600080fd5b5035919050565b6001600160a01b03811681146104ae57600080fd5b6000602082840312156106b157600080fd5b81356106bc8161068a565b9392505050565b634e487b7160e01b600052604160045260246000fd5b80516106e48161068a565b919050565b600060208083850312156106fc57600080fd5b825167ffffffffffffffff8082111561071457600080fd5b818501915085601f83011261072857600080fd5b81518181111561073a5761073a6106c3565b8060051b604051601f19603f8301168101818110858211171561075f5761075f6106c3565b60405291825284820192508381018501918883111561077d57600080fd5b938501935b828510156107a257610793856106d9565b84529385019392850192610782565b98975050505050505050565b634e487b7160e01b600052603260045260246000fd5b6000602082840312156107d657600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b6000600160ff1b8201610808576108086107dd565b5060000390565b808201828112600083128015821682158216171561082f5761082f6107dd565b505092915050565b80820260008212600160ff1b84141615610853576108536107dd565b8181058314821517610867576108676107dd565b92915050565b60008261088a57634e487b7160e01b600052601260045260246000fd5b600160ff1b8214600019841416156108a4576108a46107dd565b500590565b6001600160a01b038481168252606060208084018290528551918401829052600092868201929091906080860190855b818110156108f75785518516835294830194918301916001016108d9565b50508094505050505082604083015294935050505056fea164736f6c6343000811000a00000000000000000000000009c9381417e0ecff536ec33375f1d5b2efa97d78000000000000000000000000df2e023ea56d752d0b5be79f65557987976676cc
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106100935760003560e01c80638da5cb5b116100665780638da5cb5b146101105780639ef3571014610121578063d4d59edb14610148578063e2d2bfe31461015b578063f2fde38b1461016e57600080fd5b80632a2c6b86146100985780636164e45d146100dc578063715018a6146100f1578063785ffb37146100f9575b600080fd5b6100bf7f00000000000000000000000009c9381417e0ecff536ec33375f1d5b2efa97d7881565b6040516001600160a01b0390911681526020015b60405180910390f35b6100ef6100ea366004610671565b610181565b005b6100ef6104b1565b6101026103e881565b6040519081526020016100d3565b6000546001600160a01b03166100bf565b6100bf7f000000000000000000000000df2e023ea56d752d0b5be79f65557987976676cc81565b6100ef61015636600461069f565b6104c5565b6001546100bf906001600160a01b031681565b6100ef61017c36600461069f565b6104d6565b6001546001600160a01b031633146101e05760405162461bcd60e51b815260206004820152601a60248201527f4f6e6c792045706f63684d616e616765722063616e2063616c6c00000000000060448201526064015b60405180910390fd5b6000806000807f00000000000000000000000009c9381417e0ecff536ec33375f1d5b2efa97d786001600160a01b03166341495d1c6040518163ffffffff1660e01b8152600401600060405180830381865afa158015610244573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261026c91908101906106e9565b805190915060005b8181101561038b5760007f00000000000000000000000009c9381417e0ecff536ec33375f1d5b2efa97d786001600160a01b031663d8bff5a58584815181106102bf576102bf6107ae565b60200260200101516040518263ffffffff1660e01b81526004016102f291906001600160a01b0391909116815260200190565b602060405180830381865afa15801561030f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061033391906107c4565b90508581121561035d57809550838281518110610352576103526107ae565b602002602001015196505b60008112156103745761036f816107f3565b610376565b805b610380908661080f565b945050600101610274565b508260000361039c57505050505050565b6000836103ab86612710610837565b6103b5919061086d565b6103c190600019610837565b90506103e88110156103d65750505050505050565b60405163630a6fc160e01b81526001600160a01b0387811660048301526000917f000000000000000000000000df2e023ea56d752d0b5be79f65557987976676cc9091169063630a6fc190602401600060405180830381865afa158015610441573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261046991908101906106e9565b90507f6ea7eec715d8ed2035e1da6761dc0452a89785257a9bb31fe0e1c631e3c1fc2f87828460405161049e939291906108a9565b60405180910390a1505050505050505b50565b6104b961054c565b6104c360006105a6565b565b6104cd61054c565b6104ae816105f6565b6104de61054c565b6001600160a01b0381166105435760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016101d7565b6104ae816105a6565b6000546001600160a01b031633146104c35760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016101d7565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811661061d5760405163d713c59760e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f4b9d66d8e64dff6d9d2728f9a38da9739d3cfdbe676fde05564aae22f85084659060200160405180910390a150565b60006020828403121561068357600080fd5b5035919050565b6001600160a01b03811681146104ae57600080fd5b6000602082840312156106b157600080fd5b81356106bc8161068a565b9392505050565b634e487b7160e01b600052604160045260246000fd5b80516106e48161068a565b919050565b600060208083850312156106fc57600080fd5b825167ffffffffffffffff8082111561071457600080fd5b818501915085601f83011261072857600080fd5b81518181111561073a5761073a6106c3565b8060051b604051601f19603f8301168101818110858211171561075f5761075f6106c3565b60405291825284820192508381018501918883111561077d57600080fd5b938501935b828510156107a257610793856106d9565b84529385019392850192610782565b98975050505050505050565b634e487b7160e01b600052603260045260246000fd5b6000602082840312156107d657600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b6000600160ff1b8201610808576108086107dd565b5060000390565b808201828112600083128015821682158216171561082f5761082f6107dd565b505092915050565b80820260008212600160ff1b84141615610853576108536107dd565b8181058314821517610867576108676107dd565b92915050565b60008261088a57634e487b7160e01b600052601260045260246000fd5b600160ff1b8214600019841416156108a4576108a46107dd565b500590565b6001600160a01b038481168252606060208084018290528551918401829052600092868201929091906080860190855b818110156108f75785518516835294830194918301916001016108d9565b50508094505050505082604083015294935050505056fea164736f6c6343000811000a
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
00000000000000000000000009c9381417e0ecff536ec33375f1d5b2efa97d78000000000000000000000000df2e023ea56d752d0b5be79f65557987976676cc
-----Decoded View---------------
Arg [0] : _sweepWars (address): 0x09c9381417E0ECff536EC33375f1d5b2EFa97D78
Arg [1] : _strategyFactory (address): 0xdf2e023Ea56d752D0B5bE79f65557987976676CC
-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 00000000000000000000000009c9381417e0ecff536ec33375f1d5b2efa97d78
Arg [1] : 000000000000000000000000df2e023ea56d752d0b5be79f65557987976676cc
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 33 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
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.