Feature Tip: Add private address tag to any address under My Name Tag !
Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
Latest 6 from a total of 6 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Create Expiring ... | 10799829 | 1476 days ago | IN | 0 ETH | 1.29302208 | ||||
Create Expiring ... | 10723813 | 1488 days ago | IN | 0 ETH | 0.769647 | ||||
Create Expiring ... | 10703935 | 1491 days ago | IN | 0 ETH | 0.73885968 | ||||
Create Expiring ... | 10510912 | 1521 days ago | IN | 0 ETH | 0.27707346 | ||||
Create Expiring ... | 10510420 | 1521 days ago | IN | 0 ETH | 0.52953158 | ||||
0x60806040 | 10484773 | 1525 days ago | IN | 0 ETH | 0.0663627 |
Latest 5 internal transactions
Advanced mode:
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
10799829 | 1476 days ago | Contract Creation | 0 ETH | |||
10723813 | 1488 days ago | Contract Creation | 0 ETH | |||
10703935 | 1491 days ago | Contract Creation | 0 ETH | |||
10510912 | 1521 days ago | Contract Creation | 0 ETH | |||
10510420 | 1521 days ago | Contract Creation | 0 ETH |
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
ExpiringMultiPartyCreator
Compiler Version
v0.6.6+commit.6c089d02
Contract Source Code (Solidity)
/** *Submitted for verification at Etherscan.io on 2020-07-18 */ // File: contracts/oracle/interfaces/FinderInterface.sol pragma solidity ^0.6.0; /** * @title Provides addresses of the live contracts implementing certain interfaces. * @dev Examples are the Oracle or Store interfaces. */ interface FinderInterface { /** * @notice Updates the address of the contract that implements `interfaceName`. * @param interfaceName bytes32 encoding of the interface name that is either changed or registered. * @param implementationAddress address of the deployed contract that implements the interface. */ function changeImplementationAddress(bytes32 interfaceName, address implementationAddress) external; /** * @notice Gets the address of the contract that implements the given `interfaceName`. * @param interfaceName queried interface. * @return implementationAddress address of the deployed contract that implements the interface. */ function getImplementationAddress(bytes32 interfaceName) external view returns (address); } // File: @openzeppelin/contracts/GSN/Context.sol pragma solidity ^0.6.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 GSN 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. */ contract Context { // Empty internal constructor, to prevent people from mistakenly deploying // an instance of this contract, which should be used via inheritance. constructor () internal { } function _msgSender() internal view virtual returns (address payable) { return msg.sender; } function _msgData() internal view virtual returns (bytes memory) { this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 return msg.data; } } // File: @openzeppelin/contracts/access/Ownable.sol pragma solidity ^0.6.0; /** * @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. */ 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 () internal { address msgSender = _msgSender(); _owner = msgSender; emit OwnershipTransferred(address(0), msgSender); } /** * @dev Returns the address of the current owner. */ function owner() public view returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { 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 { emit OwnershipTransferred(_owner, address(0)); _owner = 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"); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } } // File: contracts/common/implementation/Lockable.sol pragma solidity ^0.6.0; /** * @title A contract that provides modifiers to prevent reentrancy to state-changing and view-only methods. This contract * is inspired by https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol * and https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol. */ contract Lockable { bool private _notEntered; constructor() internal { // Storing an initial non-zero value makes deployment a bit more // expensive, but in exchange the refund on every call to nonReentrant // will be lower in amount. Since refunds are capped to a percetange of // the total transaction's gas, it is best to keep them low in cases // like this one, to increase the likelihood of the full refund coming // into effect. _notEntered = true; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and make it call a * `private` function that does the actual work. */ modifier nonReentrant() { _preEntranceCheck(); _preEntranceSet(); _; _postEntranceReset(); } /** * @dev Designed to prevent a view-only method from being re-entered during a call to a `nonReentrant()` state-changing method. */ modifier nonReentrantView() { _preEntranceCheck(); _; } // Internal methods are used to avoid copying the require statement's bytecode to every `nonReentrant()` method. // On entry into a function, `_preEntranceCheck()` should always be called to check if the function is being re-entered. // Then, if the function modifies state, it should call `_postEntranceSet()`, perform its logic, and then call `_postEntranceReset()`. // View-only methods can simply call `_preEntranceCheck()` to make sure that it is not being re-entered. function _preEntranceCheck() internal view { // On the first call to nonReentrant, _notEntered will be true require(_notEntered, "ReentrancyGuard: reentrant call"); } function _preEntranceSet() internal { // Any calls to nonReentrant after this point will fail _notEntered = false; } function _postEntranceReset() internal { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _notEntered = true; } } // File: contracts/common/implementation/AddressWhitelist.sol pragma solidity ^0.6.0; /** * @title A contract to track a whitelist of addresses. */ contract AddressWhitelist is Ownable, Lockable { enum Status { None, In, Out } mapping(address => Status) public whitelist; address[] public whitelistIndices; event AddedToWhitelist(address indexed addedAddress); event RemovedFromWhitelist(address indexed removedAddress); /** * @notice Adds an address to the whitelist. * @param newElement the new address to add. */ function addToWhitelist(address newElement) external nonReentrant() onlyOwner { // Ignore if address is already included if (whitelist[newElement] == Status.In) { return; } // Only append new addresses to the array, never a duplicate if (whitelist[newElement] == Status.None) { whitelistIndices.push(newElement); } whitelist[newElement] = Status.In; emit AddedToWhitelist(newElement); } /** * @notice Removes an address from the whitelist. * @param elementToRemove the existing address to remove. */ function removeFromWhitelist(address elementToRemove) external nonReentrant() onlyOwner { if (whitelist[elementToRemove] != Status.Out) { whitelist[elementToRemove] = Status.Out; emit RemovedFromWhitelist(elementToRemove); } } /** * @notice Checks whether an address is on the whitelist. * @param elementToCheck the address to check. * @return True if `elementToCheck` is on the whitelist, or False. */ function isOnWhitelist(address elementToCheck) external view nonReentrantView() returns (bool) { return whitelist[elementToCheck] == Status.In; } /** * @notice Gets all addresses that are currently included in the whitelist. * @dev Note: This method skips over, but still iterates through addresses. It is possible for this call to run out * of gas if a large number of addresses have been removed. To reduce the likelihood of this unlikely scenario, we * can modify the implementation so that when addresses are removed, the last addresses in the array is moved to * the empty index. * @return activeWhitelist the list of addresses on the whitelist. */ function getWhitelist() external view nonReentrantView() returns (address[] memory activeWhitelist) { // Determine size of whitelist first uint256 activeCount = 0; for (uint256 i = 0; i < whitelistIndices.length; i++) { if (whitelist[whitelistIndices[i]] == Status.In) { activeCount++; } } // Populate whitelist activeWhitelist = new address[](activeCount); activeCount = 0; for (uint256 i = 0; i < whitelistIndices.length; i++) { address addr = whitelistIndices[i]; if (whitelist[addr] == Status.In) { activeWhitelist[activeCount] = addr; activeCount++; } } } } // File: contracts/common/implementation/MultiRole.sol pragma solidity ^0.6.0; library Exclusive { struct RoleMembership { address member; } function isMember(RoleMembership storage roleMembership, address memberToCheck) internal view returns (bool) { return roleMembership.member == memberToCheck; } function resetMember(RoleMembership storage roleMembership, address newMember) internal { require(newMember != address(0x0), "Cannot set an exclusive role to 0x0"); roleMembership.member = newMember; } function getMember(RoleMembership storage roleMembership) internal view returns (address) { return roleMembership.member; } function init(RoleMembership storage roleMembership, address initialMember) internal { resetMember(roleMembership, initialMember); } } library Shared { struct RoleMembership { mapping(address => bool) members; } function isMember(RoleMembership storage roleMembership, address memberToCheck) internal view returns (bool) { return roleMembership.members[memberToCheck]; } function addMember(RoleMembership storage roleMembership, address memberToAdd) internal { require(memberToAdd != address(0x0), "Cannot add 0x0 to a shared role"); roleMembership.members[memberToAdd] = true; } function removeMember(RoleMembership storage roleMembership, address memberToRemove) internal { roleMembership.members[memberToRemove] = false; } function init(RoleMembership storage roleMembership, address[] memory initialMembers) internal { for (uint256 i = 0; i < initialMembers.length; i++) { addMember(roleMembership, initialMembers[i]); } } } /** * @title Base class to manage permissions for the derived class. */ abstract contract MultiRole { using Exclusive for Exclusive.RoleMembership; using Shared for Shared.RoleMembership; enum RoleType { Invalid, Exclusive, Shared } struct Role { uint256 managingRole; RoleType roleType; Exclusive.RoleMembership exclusiveRoleMembership; Shared.RoleMembership sharedRoleMembership; } mapping(uint256 => Role) private roles; event ResetExclusiveMember(uint256 indexed roleId, address indexed newMember, address indexed manager); event AddedSharedMember(uint256 indexed roleId, address indexed newMember, address indexed manager); event RemovedSharedMember(uint256 indexed roleId, address indexed oldMember, address indexed manager); /** * @notice Reverts unless the caller is a member of the specified roleId. */ modifier onlyRoleHolder(uint256 roleId) { require(holdsRole(roleId, msg.sender), "Sender does not hold required role"); _; } /** * @notice Reverts unless the caller is a member of the manager role for the specified roleId. */ modifier onlyRoleManager(uint256 roleId) { require(holdsRole(roles[roleId].managingRole, msg.sender), "Can only be called by a role manager"); _; } /** * @notice Reverts unless the roleId represents an initialized, exclusive roleId. */ modifier onlyExclusive(uint256 roleId) { require(roles[roleId].roleType == RoleType.Exclusive, "Must be called on an initialized Exclusive role"); _; } /** * @notice Reverts unless the roleId represents an initialized, shared roleId. */ modifier onlyShared(uint256 roleId) { require(roles[roleId].roleType == RoleType.Shared, "Must be called on an initialized Shared role"); _; } /** * @notice Whether `memberToCheck` is a member of roleId. * @dev Reverts if roleId does not correspond to an initialized role. * @param roleId the Role to check. * @param memberToCheck the address to check. * @return True if `memberToCheck` is a member of `roleId`. */ function holdsRole(uint256 roleId, address memberToCheck) public view returns (bool) { Role storage role = roles[roleId]; if (role.roleType == RoleType.Exclusive) { return role.exclusiveRoleMembership.isMember(memberToCheck); } else if (role.roleType == RoleType.Shared) { return role.sharedRoleMembership.isMember(memberToCheck); } revert("Invalid roleId"); } /** * @notice Changes the exclusive role holder of `roleId` to `newMember`. * @dev Reverts if the caller is not a member of the managing role for `roleId` or if `roleId` is not an * initialized, ExclusiveRole. * @param roleId the ExclusiveRole membership to modify. * @param newMember the new ExclusiveRole member. */ function resetMember(uint256 roleId, address newMember) public onlyExclusive(roleId) onlyRoleManager(roleId) { roles[roleId].exclusiveRoleMembership.resetMember(newMember); emit ResetExclusiveMember(roleId, newMember, msg.sender); } /** * @notice Gets the current holder of the exclusive role, `roleId`. * @dev Reverts if `roleId` does not represent an initialized, exclusive role. * @param roleId the ExclusiveRole membership to check. * @return the address of the current ExclusiveRole member. */ function getMember(uint256 roleId) public view onlyExclusive(roleId) returns (address) { return roles[roleId].exclusiveRoleMembership.getMember(); } /** * @notice Adds `newMember` to the shared role, `roleId`. * @dev Reverts if `roleId` does not represent an initialized, SharedRole or if the caller is not a member of the * managing role for `roleId`. * @param roleId the SharedRole membership to modify. * @param newMember the new SharedRole member. */ function addMember(uint256 roleId, address newMember) public onlyShared(roleId) onlyRoleManager(roleId) { roles[roleId].sharedRoleMembership.addMember(newMember); emit AddedSharedMember(roleId, newMember, msg.sender); } /** * @notice Removes `memberToRemove` from the shared role, `roleId`. * @dev Reverts if `roleId` does not represent an initialized, SharedRole or if the caller is not a member of the * managing role for `roleId`. * @param roleId the SharedRole membership to modify. * @param memberToRemove the current SharedRole member to remove. */ function removeMember(uint256 roleId, address memberToRemove) public onlyShared(roleId) onlyRoleManager(roleId) { roles[roleId].sharedRoleMembership.removeMember(memberToRemove); emit RemovedSharedMember(roleId, memberToRemove, msg.sender); } /** * @notice Removes caller from the role, `roleId`. * @dev Reverts if the caller is not a member of the role for `roleId` or if `roleId` is not an * initialized, SharedRole. * @param roleId the SharedRole membership to modify. */ function renounceMembership(uint256 roleId) public onlyShared(roleId) onlyRoleHolder(roleId) { roles[roleId].sharedRoleMembership.removeMember(msg.sender); emit RemovedSharedMember(roleId, msg.sender, msg.sender); } /** * @notice Reverts if `roleId` is not initialized. */ modifier onlyValidRole(uint256 roleId) { require(roles[roleId].roleType != RoleType.Invalid, "Attempted to use an invalid roleId"); _; } /** * @notice Reverts if `roleId` is initialized. */ modifier onlyInvalidRole(uint256 roleId) { require(roles[roleId].roleType == RoleType.Invalid, "Cannot use a pre-existing role"); _; } /** * @notice Internal method to initialize a shared role, `roleId`, which will be managed by `managingRoleId`. * `initialMembers` will be immediately added to the role. * @dev Should be called by derived contracts, usually at construction time. Will revert if the role is already * initialized. */ function _createSharedRole( uint256 roleId, uint256 managingRoleId, address[] memory initialMembers ) internal onlyInvalidRole(roleId) { Role storage role = roles[roleId]; role.roleType = RoleType.Shared; role.managingRole = managingRoleId; role.sharedRoleMembership.init(initialMembers); require( roles[managingRoleId].roleType != RoleType.Invalid, "Attempted to use an invalid role to manage a shared role" ); } /** * @notice Internal method to initialize an exclusive role, `roleId`, which will be managed by `managingRoleId`. * `initialMember` will be immediately added to the role. * @dev Should be called by derived contracts, usually at construction time. Will revert if the role is already * initialized. */ function _createExclusiveRole( uint256 roleId, uint256 managingRoleId, address initialMember ) internal onlyInvalidRole(roleId) { Role storage role = roles[roleId]; role.roleType = RoleType.Exclusive; role.managingRole = managingRoleId; role.exclusiveRoleMembership.init(initialMember); require( roles[managingRoleId].roleType != RoleType.Invalid, "Attempted to use an invalid role to manage an exclusive role" ); } } // File: contracts/oracle/interfaces/RegistryInterface.sol pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; /** * @title Interface for a registry of contracts and contract creators. */ interface RegistryInterface { /** * @notice Registers a new contract. * @dev Only authorized contract creators can call this method. * @param parties an array of addresses who become parties in the contract. * @param contractAddress defines the address of the deployed contract. */ function registerContract(address[] calldata parties, address contractAddress) external; /** * @notice Returns whether the contract has been registered with the registry. * @dev If it is registered, it is an authorized participant in the UMA system. * @param contractAddress address of the contract. * @return bool indicates whether the contract is registered. */ function isContractRegistered(address contractAddress) external view returns (bool); /** * @notice Returns a list of all contracts that are associated with a particular party. * @param party address of the party. * @return an array of the contracts the party is registered to. */ function getRegisteredContracts(address party) external view returns (address[] memory); /** * @notice Returns all registered contracts. * @return all registered contract addresses within the system. */ function getAllRegisteredContracts() external view returns (address[] memory); /** * @notice Adds a party to the calling contract. * @dev msg.sender must be the contract to which the party member is added. * @param party address to be added to the contract. */ function addPartyToContract(address party) external; /** * @notice Removes a party member to the calling contract. * @dev msg.sender must be the contract to which the party member is added. * @param party address to be removed from the contract. */ function removePartyFromContract(address party) external; /** * @notice checks if an address is a party in a contract. * @param party party to check. * @param contractAddress address to check against the party. * @return bool indicating if the address is a party of the contract. */ function isPartyMemberOfContract(address party, address contractAddress) external view returns (bool); } // File: @openzeppelin/contracts/math/SafeMath.sol pragma solidity ^0.6.0; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } /** * @dev Returns the integer division of two unsigned integers. Reverts with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMath: modulo by zero"); } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts with custom message when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } } // File: contracts/oracle/implementation/Registry.sol pragma solidity ^0.6.0; /** * @title Registry for financial contracts and approved financial contract creators. * @dev Maintains a whitelist of financial contract creators that are allowed * to register new financial contracts and stores party members of a financial contract. */ contract Registry is RegistryInterface, MultiRole { using SafeMath for uint256; /**************************************** * INTERNAL VARIABLES AND STORAGE * ****************************************/ enum Roles { Owner, // The owner manages the set of ContractCreators. ContractCreator // Can register financial contracts. } // This enum is required because a `WasValid` state is required // to ensure that financial contracts cannot be re-registered. enum Validity { Invalid, Valid } // Local information about a contract. struct FinancialContract { Validity valid; uint128 index; } struct Party { address[] contracts; // Each financial contract address is stored in this array. // The address of each financial contract is mapped to its index for constant time look up and deletion. mapping(address => uint256) contractIndex; } // Array of all contracts that are approved to use the UMA Oracle. address[] public registeredContracts; // Map of financial contract contracts to the associated FinancialContract struct. mapping(address => FinancialContract) public contractMap; // Map each party member to their their associated Party struct. mapping(address => Party) private partyMap; /**************************************** * EVENTS * ****************************************/ event NewContractRegistered(address indexed contractAddress, address indexed creator, address[] parties); event PartyAdded(address indexed contractAddress, address indexed party); event PartyRemoved(address indexed contractAddress, address indexed party); /** * @notice Construct the Registry contract. */ constructor() public { _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender); // Start with no contract creators registered. _createSharedRole(uint256(Roles.ContractCreator), uint256(Roles.Owner), new address[](0)); } /**************************************** * REGISTRATION FUNCTIONS * ****************************************/ /** * @notice Registers a new financial contract. * @dev Only authorized contract creators can call this method. * @param parties array of addresses who become parties in the contract. * @param contractAddress address of the contract against which the parties are registered. */ function registerContract(address[] calldata parties, address contractAddress) external override onlyRoleHolder(uint256(Roles.ContractCreator)) { FinancialContract storage financialContract = contractMap[contractAddress]; require(contractMap[contractAddress].valid == Validity.Invalid, "Can only register once"); // Store contract address as a registered contract. registeredContracts.push(contractAddress); // No length check necessary because we should never hit (2^127 - 1) contracts. financialContract.index = uint128(registeredContracts.length.sub(1)); // For all parties in the array add them to the contract's parties. financialContract.valid = Validity.Valid; for (uint256 i = 0; i < parties.length; i = i.add(1)) { _addPartyToContract(parties[i], contractAddress); } emit NewContractRegistered(contractAddress, msg.sender, parties); } /** * @notice Adds a party member to the calling contract. * @dev msg.sender will be used to determine the contract that this party is added to. * @param party new party for the calling contract. */ function addPartyToContract(address party) external override { address contractAddress = msg.sender; require(contractMap[contractAddress].valid == Validity.Valid, "Can only add to valid contract"); _addPartyToContract(party, contractAddress); } /** * @notice Removes a party member from the calling contract. * @dev msg.sender will be used to determine the contract that this party is removed from. * @param partyAddress address to be removed from the calling contract. */ function removePartyFromContract(address partyAddress) external override { address contractAddress = msg.sender; Party storage party = partyMap[partyAddress]; uint256 numberOfContracts = party.contracts.length; require(numberOfContracts != 0, "Party has no contracts"); require(contractMap[contractAddress].valid == Validity.Valid, "Remove only from valid contract"); require(isPartyMemberOfContract(partyAddress, contractAddress), "Can only remove existing party"); // Index of the current location of the contract to remove. uint256 deleteIndex = party.contractIndex[contractAddress]; // Store the last contract's address to update the lookup map. address lastContractAddress = party.contracts[numberOfContracts - 1]; // Swap the contract to be removed with the last contract. party.contracts[deleteIndex] = lastContractAddress; // Update the lookup index with the new location. party.contractIndex[lastContractAddress] = deleteIndex; // Pop the last contract from the array and update the lookup map. party.contracts.pop(); delete party.contractIndex[contractAddress]; emit PartyRemoved(contractAddress, partyAddress); } /**************************************** * REGISTRY STATE GETTERS * ****************************************/ /** * @notice Returns whether the contract has been registered with the registry. * @dev If it is registered, it is an authorized participant in the UMA system. * @param contractAddress address of the financial contract. * @return bool indicates whether the contract is registered. */ function isContractRegistered(address contractAddress) external override view returns (bool) { return contractMap[contractAddress].valid == Validity.Valid; } /** * @notice Returns a list of all contracts that are associated with a particular party. * @param party address of the party. * @return an array of the contracts the party is registered to. */ function getRegisteredContracts(address party) external override view returns (address[] memory) { return partyMap[party].contracts; } /** * @notice Returns all registered contracts. * @return all registered contract addresses within the system. */ function getAllRegisteredContracts() external override view returns (address[] memory) { return registeredContracts; } /** * @notice checks if an address is a party of a contract. * @param party party to check. * @param contractAddress address to check against the party. * @return bool indicating if the address is a party of the contract. */ function isPartyMemberOfContract(address party, address contractAddress) public override view returns (bool) { uint256 index = partyMap[party].contractIndex[contractAddress]; return partyMap[party].contracts.length > index && partyMap[party].contracts[index] == contractAddress; } /**************************************** * INTERNAL FUNCTIONS * ****************************************/ function _addPartyToContract(address party, address contractAddress) internal { require(!isPartyMemberOfContract(party, contractAddress), "Can only register a party once"); uint256 contractIndex = partyMap[party].contracts.length; partyMap[party].contracts.push(contractAddress); partyMap[party].contractIndex[contractAddress] = contractIndex; emit PartyAdded(contractAddress, party); } } // File: contracts/oracle/implementation/Constants.sol pragma solidity ^0.6.0; /** * @title Stores common interface names used throughout the DVM by registration in the Finder. */ library OracleInterfaces { bytes32 public constant Oracle = "Oracle"; bytes32 public constant IdentifierWhitelist = "IdentifierWhitelist"; bytes32 public constant Store = "Store"; bytes32 public constant FinancialContractsAdmin = "FinancialContractsAdmin"; bytes32 public constant Registry = "Registry"; bytes32 public constant CollateralWhitelist = "CollateralWhitelist"; } // File: contracts/oracle/implementation/ContractCreator.sol pragma solidity ^0.6.0; /** * @title Base contract for all financial contract creators */ abstract contract ContractCreator { address internal finderAddress; constructor(address _finderAddress) public { finderAddress = _finderAddress; } function _requireWhitelistedCollateral(address collateralAddress) internal view { FinderInterface finder = FinderInterface(finderAddress); AddressWhitelist collateralWhitelist = AddressWhitelist( finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist) ); require(collateralWhitelist.isOnWhitelist(collateralAddress), "Collateral not whitelisted"); } function _registerContract(address[] memory parties, address contractToRegister) internal { FinderInterface finder = FinderInterface(finderAddress); Registry registry = Registry(finder.getImplementationAddress(OracleInterfaces.Registry)); registry.registerContract(parties, contractToRegister); } } // File: contracts/common/implementation/Timer.sol pragma solidity ^0.6.0; /** * @title Universal store of current contract time for testing environments. */ contract Timer { uint256 private currentTime; constructor() public { currentTime = now; // solhint-disable-line not-rely-on-time } /** * @notice Sets the current time. * @dev Will revert if not running in test mode. * @param time timestamp to set `currentTime` to. */ function setCurrentTime(uint256 time) external { currentTime = time; } /** * @notice Gets the current time. Will return the last time set in `setCurrentTime` if running in test mode. * Otherwise, it will return the block timestamp. * @return uint256 for the current Testable timestamp. */ function getCurrentTime() public view returns (uint256) { return currentTime; } } // File: contracts/common/implementation/Testable.sol pragma solidity ^0.6.0; /** * @title Base class that provides time overrides, but only if being run in test mode. */ abstract contract Testable { // If the contract is being run on the test network, then `timerAddress` will be the 0x0 address. // Note: this variable should be set on construction and never modified. address public timerAddress; /** * @notice Constructs the Testable contract. Called by child contracts. * @param _timerAddress Contract that stores the current time in a testing environment. * Must be set to 0x0 for production environments that use live time. */ constructor(address _timerAddress) internal { timerAddress = _timerAddress; } /** * @notice Reverts if not running in test mode. */ modifier onlyIfTest { require(timerAddress != address(0x0)); _; } /** * @notice Sets the current time. * @dev Will revert if not running in test mode. * @param time timestamp to set current Testable time to. */ function setCurrentTime(uint256 time) external onlyIfTest { Timer(timerAddress).setCurrentTime(time); } /** * @notice Gets the current time. Will return the last time set in `setCurrentTime` if running in test mode. * Otherwise, it will return the block timestamp. * @return uint for the current Testable timestamp. */ function getCurrentTime() public view returns (uint256) { if (timerAddress != address(0x0)) { return Timer(timerAddress).getCurrentTime(); } else { return now; // solhint-disable-line not-rely-on-time } } } // File: @openzeppelin/contracts/token/ERC20/IERC20.sol pragma solidity ^0.6.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @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 `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, 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 `sender` to `recipient` 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 sender, address recipient, uint256 amount) external returns (bool); /** * @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); } // File: @openzeppelin/contracts/utils/Address.sol pragma solidity ^0.6.2; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== */ function isContract(address account) internal view returns (bool) { // According to EIP-1052, 0x0 is the value returned for not-yet created accounts // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned // for accounts without code, i.e. `keccak256('')` bytes32 codehash; bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; // solhint-disable-next-line no-inline-assembly assembly { codehash := extcodehash(account) } return (codehash != accountHash && codehash != 0x0); } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (bool success, ) = recipient.call{ value: amount }(""); require(success, "Address: unable to send value, recipient may have reverted"); } } // File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol pragma solidity ^0.6.0; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using SafeMath for uint256; using Address for address; function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' // solhint-disable-next-line max-line-length require((value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).add(value); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. // A Solidity high level call has three parts: // 1. The target address is checked to verify it contains contract code // 2. The call itself is made, and success asserted // 3. The return value is decoded, which in turn checks the size of the returned data. // solhint-disable-next-line max-line-length require(address(token).isContract(), "SafeERC20: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = address(token).call(data); require(success, "SafeERC20: low-level call failed"); if (returndata.length > 0) { // Return data is optional // solhint-disable-next-line max-line-length require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } } } // File: contracts/common/implementation/FixedPoint.sol pragma solidity ^0.6.0; /** * @title Library for fixed point arithmetic on uints */ library FixedPoint { using SafeMath for uint256; // Supports 18 decimals. E.g., 1e18 represents "1", 5e17 represents "0.5". // Can represent a value up to (2^256 - 1)/10^18 = ~10^59. 10^59 will be stored internally as uint256 10^77. uint256 private constant FP_SCALING_FACTOR = 10**18; struct Unsigned { uint256 rawValue; } /** * @notice Constructs an `Unsigned` from an unscaled uint, e.g., `b=5` gets stored internally as `5**18`. * @param a uint to convert into a FixedPoint. * @return the converted FixedPoint. */ function fromUnscaledUint(uint256 a) internal pure returns (Unsigned memory) { return Unsigned(a.mul(FP_SCALING_FACTOR)); } /** * @notice Whether `a` is equal to `b`. * @param a a FixedPoint. * @param b a uint256. * @return True if equal, or False. */ function isEqual(Unsigned memory a, uint256 b) internal pure returns (bool) { return a.rawValue == fromUnscaledUint(b).rawValue; } /** * @notice Whether `a` is equal to `b`. * @param a a FixedPoint. * @param b a FixedPoint. * @return True if equal, or False. */ function isEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) { return a.rawValue == b.rawValue; } /** * @notice Whether `a` is greater than `b`. * @param a a FixedPoint. * @param b a FixedPoint. * @return True if `a > b`, or False. */ function isGreaterThan(Unsigned memory a, Unsigned memory b) internal pure returns (bool) { return a.rawValue > b.rawValue; } /** * @notice Whether `a` is greater than `b`. * @param a a FixedPoint. * @param b a uint256. * @return True if `a > b`, or False. */ function isGreaterThan(Unsigned memory a, uint256 b) internal pure returns (bool) { return a.rawValue > fromUnscaledUint(b).rawValue; } /** * @notice Whether `a` is greater than `b`. * @param a a uint256. * @param b a FixedPoint. * @return True if `a > b`, or False. */ function isGreaterThan(uint256 a, Unsigned memory b) internal pure returns (bool) { return fromUnscaledUint(a).rawValue > b.rawValue; } /** * @notice Whether `a` is greater than or equal to `b`. * @param a a FixedPoint. * @param b a FixedPoint. * @return True if `a >= b`, or False. */ function isGreaterThanOrEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) { return a.rawValue >= b.rawValue; } /** * @notice Whether `a` is greater than or equal to `b`. * @param a a FixedPoint. * @param b a uint256. * @return True if `a >= b`, or False. */ function isGreaterThanOrEqual(Unsigned memory a, uint256 b) internal pure returns (bool) { return a.rawValue >= fromUnscaledUint(b).rawValue; } /** * @notice Whether `a` is greater than or equal to `b`. * @param a a uint256. * @param b a FixedPoint. * @return True if `a >= b`, or False. */ function isGreaterThanOrEqual(uint256 a, Unsigned memory b) internal pure returns (bool) { return fromUnscaledUint(a).rawValue >= b.rawValue; } /** * @notice Whether `a` is less than `b`. * @param a a FixedPoint. * @param b a FixedPoint. * @return True if `a < b`, or False. */ function isLessThan(Unsigned memory a, Unsigned memory b) internal pure returns (bool) { return a.rawValue < b.rawValue; } /** * @notice Whether `a` is less than `b`. * @param a a FixedPoint. * @param b a uint256. * @return True if `a < b`, or False. */ function isLessThan(Unsigned memory a, uint256 b) internal pure returns (bool) { return a.rawValue < fromUnscaledUint(b).rawValue; } /** * @notice Whether `a` is less than `b`. * @param a a uint256. * @param b a FixedPoint. * @return True if `a < b`, or False. */ function isLessThan(uint256 a, Unsigned memory b) internal pure returns (bool) { return fromUnscaledUint(a).rawValue < b.rawValue; } /** * @notice Whether `a` is less than or equal to `b`. * @param a a FixedPoint. * @param b a FixedPoint. * @return True if `a <= b`, or False. */ function isLessThanOrEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) { return a.rawValue <= b.rawValue; } /** * @notice Whether `a` is less than or equal to `b`. * @param a a FixedPoint. * @param b a uint256. * @return True if `a <= b`, or False. */ function isLessThanOrEqual(Unsigned memory a, uint256 b) internal pure returns (bool) { return a.rawValue <= fromUnscaledUint(b).rawValue; } /** * @notice Whether `a` is less than or equal to `b`. * @param a a uint256. * @param b a FixedPoint. * @return True if `a <= b`, or False. */ function isLessThanOrEqual(uint256 a, Unsigned memory b) internal pure returns (bool) { return fromUnscaledUint(a).rawValue <= b.rawValue; } /** * @notice The minimum of `a` and `b`. * @param a a FixedPoint. * @param b a FixedPoint. * @return the minimum of `a` and `b`. */ function min(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { return a.rawValue < b.rawValue ? a : b; } /** * @notice The maximum of `a` and `b`. * @param a a FixedPoint. * @param b a FixedPoint. * @return the maximum of `a` and `b`. */ function max(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { return a.rawValue > b.rawValue ? a : b; } /** * @notice Adds two `Unsigned`s, reverting on overflow. * @param a a FixedPoint. * @param b a FixedPoint. * @return the sum of `a` and `b`. */ function add(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { return Unsigned(a.rawValue.add(b.rawValue)); } /** * @notice Adds an `Unsigned` to an unscaled uint, reverting on overflow. * @param a a FixedPoint. * @param b a uint256. * @return the sum of `a` and `b`. */ function add(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { return add(a, fromUnscaledUint(b)); } /** * @notice Subtracts two `Unsigned`s, reverting on overflow. * @param a a FixedPoint. * @param b a FixedPoint. * @return the difference of `a` and `b`. */ function sub(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { return Unsigned(a.rawValue.sub(b.rawValue)); } /** * @notice Subtracts an unscaled uint256 from an `Unsigned`, reverting on overflow. * @param a a FixedPoint. * @param b a uint256. * @return the difference of `a` and `b`. */ function sub(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { return sub(a, fromUnscaledUint(b)); } /** * @notice Subtracts an `Unsigned` from an unscaled uint256, reverting on overflow. * @param a a uint256. * @param b a FixedPoint. * @return the difference of `a` and `b`. */ function sub(uint256 a, Unsigned memory b) internal pure returns (Unsigned memory) { return sub(fromUnscaledUint(a), b); } /** * @notice Multiplies two `Unsigned`s, reverting on overflow. * @dev This will "floor" the product. * @param a a FixedPoint. * @param b a FixedPoint. * @return the product of `a` and `b`. */ function mul(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { // There are two caveats with this computation: // 1. Max output for the represented number is ~10^41, otherwise an intermediate value overflows. 10^41 is // stored internally as a uint256 ~10^59. // 2. Results that can't be represented exactly are truncated not rounded. E.g., 1.4 * 2e-18 = 2.8e-18, which // would round to 3, but this computation produces the result 2. // No need to use SafeMath because FP_SCALING_FACTOR != 0. return Unsigned(a.rawValue.mul(b.rawValue) / FP_SCALING_FACTOR); } /** * @notice Multiplies an `Unsigned` and an unscaled uint256, reverting on overflow. * @dev This will "floor" the product. * @param a a FixedPoint. * @param b a uint256. * @return the product of `a` and `b`. */ function mul(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { return Unsigned(a.rawValue.mul(b)); } /** * @notice Multiplies two `Unsigned`s and "ceil's" the product, reverting on overflow. * @param a a FixedPoint. * @param b a FixedPoint. * @return the product of `a` and `b`. */ function mulCeil(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { uint256 mulRaw = a.rawValue.mul(b.rawValue); uint256 mulFloor = mulRaw / FP_SCALING_FACTOR; uint256 mod = mulRaw.mod(FP_SCALING_FACTOR); if (mod != 0) { return Unsigned(mulFloor.add(1)); } else { return Unsigned(mulFloor); } } /** * @notice Multiplies an `Unsigned` and an unscaled uint256 and "ceil's" the product, reverting on overflow. * @param a a FixedPoint. * @param b a FixedPoint. * @return the product of `a` and `b`. */ function mulCeil(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { // Since b is an int, there is no risk of truncation and we can just mul it normally return Unsigned(a.rawValue.mul(b)); } /** * @notice Divides one `Unsigned` by an `Unsigned`, reverting on overflow or division by 0. * @dev This will "floor" the quotient. * @param a a FixedPoint numerator. * @param b a FixedPoint denominator. * @return the quotient of `a` divided by `b`. */ function div(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { // There are two caveats with this computation: // 1. Max value for the number dividend `a` represents is ~10^41, otherwise an intermediate value overflows. // 10^41 is stored internally as a uint256 10^59. // 2. Results that can't be represented exactly are truncated not rounded. E.g., 2 / 3 = 0.6 repeating, which // would round to 0.666666666666666667, but this computation produces the result 0.666666666666666666. return Unsigned(a.rawValue.mul(FP_SCALING_FACTOR).div(b.rawValue)); } /** * @notice Divides one `Unsigned` by an unscaled uint256, reverting on overflow or division by 0. * @dev This will "floor" the quotient. * @param a a FixedPoint numerator. * @param b a uint256 denominator. * @return the quotient of `a` divided by `b`. */ function div(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { return Unsigned(a.rawValue.div(b)); } /** * @notice Divides one unscaled uint256 by an `Unsigned`, reverting on overflow or division by 0. * @dev This will "floor" the quotient. * @param a a uint256 numerator. * @param b a FixedPoint denominator. * @return the quotient of `a` divided by `b`. */ function div(uint256 a, Unsigned memory b) internal pure returns (Unsigned memory) { return div(fromUnscaledUint(a), b); } /** * @notice Divides one `Unsigned` by an `Unsigned` and "ceil's" the quotient, reverting on overflow or division by 0. * @param a a FixedPoint numerator. * @param b a FixedPoint denominator. * @return the quotient of `a` divided by `b`. */ function divCeil(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) { uint256 aScaled = a.rawValue.mul(FP_SCALING_FACTOR); uint256 divFloor = aScaled.div(b.rawValue); uint256 mod = aScaled.mod(b.rawValue); if (mod != 0) { return Unsigned(divFloor.add(1)); } else { return Unsigned(divFloor); } } /** * @notice Divides one `Unsigned` by an unscaled uint256 and "ceil's" the quotient, reverting on overflow or division by 0. * @param a a FixedPoint numerator. * @param b a uint256 denominator. * @return the quotient of `a` divided by `b`. */ function divCeil(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) { // Because it is possible that a quotient gets truncated, we can't just call "Unsigned(a.rawValue.div(b))" // similarly to mulCeil with a uint256 as the second parameter. Therefore we need to convert b into an Unsigned. // This creates the possibility of overflow if b is very large. return divCeil(a, fromUnscaledUint(b)); } /** * @notice Raises an `Unsigned` to the power of an unscaled uint256, reverting on overflow. E.g., `b=2` squares `a`. * @dev This will "floor" the result. * @param a a FixedPoint numerator. * @param b a uint256 denominator. * @return output is `a` to the power of `b`. */ function pow(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory output) { output = fromUnscaledUint(1); for (uint256 i = 0; i < b; i = i.add(1)) { output = mul(output, a); } } } // File: contracts/common/interfaces/ExpandedIERC20.sol pragma solidity ^0.6.0; /** * @title ERC20 interface that includes burn and mint methods. */ abstract contract ExpandedIERC20 is IERC20 { /** * @notice Burns a specific amount of the caller's tokens. * @dev Only burns the caller's tokens, so it is safe to leave this method permissionless. */ function burn(uint256 value) external virtual; /** * @notice Mints tokens and adds them to the balance of the `to` address. * @dev This method should be permissioned to only allow designated parties to mint tokens. */ function mint(address to, uint256 value) external virtual returns (bool); } // File: contracts/oracle/interfaces/OracleInterface.sol pragma solidity ^0.6.0; /** * @title Financial contract facing Oracle interface. * @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface. */ interface OracleInterface { /** * @notice Enqueues a request (if a request isn't already present) for the given `identifier`, `time` pair. * @dev Time must be in the past and the identifier must be supported. * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. * @param time unix timestamp for the price request. */ function requestPrice(bytes32 identifier, uint256 time) external; /** * @notice Whether the price for `identifier` and `time` is available. * @dev Time must be in the past and the identifier must be supported. * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. * @param time unix timestamp for the price request. * @return bool if the DVM has resolved to a price for the given identifier and timestamp. */ function hasPrice(bytes32 identifier, uint256 time) external view returns (bool); /** * @notice Gets the price for `identifier` and `time` if it has already been requested and resolved. * @dev If the price is not available, the method reverts. * @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested. * @param time unix timestamp for the price request. * @return int256 representing the resolved price for the given identifier and timestamp. */ function getPrice(bytes32 identifier, uint256 time) external view returns (int256); } // File: contracts/oracle/interfaces/IdentifierWhitelistInterface.sol pragma solidity ^0.6.0; /** * @title Interface for whitelists of supported identifiers that the oracle can provide prices for. */ interface IdentifierWhitelistInterface { /** * @notice Adds the provided identifier as a supported identifier. * @dev Price requests using this identifier will succeed after this call. * @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD. */ function addSupportedIdentifier(bytes32 identifier) external; /** * @notice Removes the identifier from the whitelist. * @dev Price requests using this identifier will no longer succeed after this call. * @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD. */ function removeSupportedIdentifier(bytes32 identifier) external; /** * @notice Checks whether an identifier is on the whitelist. * @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD. * @return bool if the identifier is supported (or not). */ function isIdentifierSupported(bytes32 identifier) external view returns (bool); } // File: contracts/oracle/interfaces/AdministrateeInterface.sol pragma solidity ^0.6.0; /** * @title Interface that all financial contracts expose to the admin. */ interface AdministrateeInterface { /** * @notice Initiates the shutdown process, in case of an emergency. */ function emergencyShutdown() external; /** * @notice A core contract method called independently or as a part of other financial contract transactions. * @dev It pays fees and moves money between margin accounts to make sure they reflect the NAV of the contract. */ function remargin() external; } // File: @openzeppelin/contracts/token/ERC20/ERC20.sol pragma solidity ^0.6.0; /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * For a generic mechanism see {ERC20MinterPauser}. * * TIP: For a detailed writeup see our guide * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * We have followed general OpenZeppelin guidelines: functions revert instead * of returning `false` on failure. This behavior is nonetheless conventional * and does not conflict with the expectations of ERC20 applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. * * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ contract ERC20 is Context, IERC20 { using SafeMath for uint256; using Address for address; mapping (address => uint256) private _balances; mapping (address => mapping (address => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; uint8 private _decimals; /** * @dev Sets the values for {name} and {symbol}, initializes {decimals} with * a default value of 18. * * To select a different value for {decimals}, use {_setupDecimals}. * * All three of these values are immutable: they can only be set once during * construction. */ constructor (string memory name, string memory symbol) public { _name = name; _symbol = symbol; _decimals = 18; } /** * @dev Returns the name of the token. */ function name() public view returns (string memory) { return _name; } /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() public view returns (string memory) { return _symbol; } /** * @dev Returns the number of decimals used to get its user representation. * For example, if `decimals` equals `2`, a balance of `505` tokens should * be displayed to a user as `5,05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is * called. * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ function decimals() public view returns (uint8) { return _decimals; } /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view override returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view override returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `recipient` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ function transfer(address recipient, uint256 amount) public virtual override returns (bool) { _transfer(_msgSender(), recipient, amount); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual override returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 amount) public virtual override returns (bool) { _approve(_msgSender(), spender, amount); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}; * * Requirements: * - `sender` and `recipient` cannot be the zero address. * - `sender` must have a balance of at least `amount`. * - the caller must have allowance for ``sender``'s tokens of at least * `amount`. */ function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { _transfer(sender, recipient, amount); _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); return true; } /** * @dev Atomically increases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. */ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); return true; } /** * @dev Atomically decreases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least * `subtractedValue`. */ function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); return true; } /** * @dev Moves tokens `amount` from `sender` to `recipient`. * * This is internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * Requirements: * * - `sender` cannot be the zero address. * - `recipient` cannot be the zero address. * - `sender` must have a balance of at least `amount`. */ function _transfer(address sender, address recipient, uint256 amount) internal virtual { require(sender != address(0), "ERC20: transfer from the zero address"); require(recipient != address(0), "ERC20: transfer to the zero address"); _beforeTokenTransfer(sender, recipient, amount); _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); _balances[recipient] = _balances[recipient].add(amount); emit Transfer(sender, recipient, amount); } /** @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements * * - `to` cannot be the zero address. */ function _mint(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: mint to the zero address"); _beforeTokenTransfer(address(0), account, amount); _totalSupply = _totalSupply.add(amount); _balances[account] = _balances[account].add(amount); emit Transfer(address(0), account, amount); } /** * @dev Destroys `amount` tokens from `account`, reducing the * total supply. * * Emits a {Transfer} event with `to` set to the zero address. * * Requirements * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. */ function _burn(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: burn from the zero address"); _beforeTokenTransfer(account, address(0), amount); _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); _totalSupply = _totalSupply.sub(amount); emit Transfer(account, address(0), amount); } /** * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens. * * This is internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve(address owner, address spender, uint256 amount) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } /** * @dev Sets {decimals} to a value other than the default one of 18. * * WARNING: This function should only be called from the constructor. Most * applications that interact with token contracts will not expect * {decimals} to ever change, and may work incorrectly if it does. */ function _setupDecimals(uint8 decimals_) internal { _decimals = decimals_; } /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * will be to transferred to `to`. * - when `from` is zero, `amount` tokens will be minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } } // File: contracts/common/implementation/ExpandedERC20.sol pragma solidity ^0.6.0; /** * @title An ERC20 with permissioned burning and minting. The contract deployer will initially * be the owner who is capable of adding new roles. */ contract ExpandedERC20 is ExpandedIERC20, ERC20, MultiRole { enum Roles { // Can set the minter and burner. Owner, // Addresses that can mint new tokens. Minter, // Addresses that can burn tokens that address owns. Burner } /** * @notice Constructs the ExpandedERC20. * @param _tokenName The name which describes the new token. * @param _tokenSymbol The ticker abbreviation of the name. Ideally < 5 chars. * @param _tokenDecimals The number of decimals to define token precision. */ constructor( string memory _tokenName, string memory _tokenSymbol, uint8 _tokenDecimals ) public ERC20(_tokenName, _tokenSymbol) { _setupDecimals(_tokenDecimals); _createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender); _createSharedRole(uint256(Roles.Minter), uint256(Roles.Owner), new address[](0)); _createSharedRole(uint256(Roles.Burner), uint256(Roles.Owner), new address[](0)); } /** * @dev Mints `value` tokens to `recipient`, returning true on success. * @param recipient address to mint to. * @param value amount of tokens to mint. * @return True if the mint succeeded, or False. */ function mint(address recipient, uint256 value) external override onlyRoleHolder(uint256(Roles.Minter)) returns (bool) { _mint(recipient, value); return true; } /** * @dev Burns `value` tokens owned by `msg.sender`. * @param value amount of tokens to burn. */ function burn(uint256 value) external override onlyRoleHolder(uint256(Roles.Burner)) { _burn(msg.sender, value); } } // File: contracts/financial-templates/common/SyntheticToken.sol pragma solidity ^0.6.0; /** * @title Burnable and mintable ERC20. * @dev The contract deployer will initially be the only minter, burner and owner capable of adding new roles. */ contract SyntheticToken is ExpandedERC20, Lockable { /** * @notice Constructs the SyntheticToken. * @param tokenName The name which describes the new token. * @param tokenSymbol The ticker abbreviation of the name. Ideally < 5 chars. * @param tokenDecimals The number of decimals to define token precision. */ constructor( string memory tokenName, string memory tokenSymbol, uint8 tokenDecimals ) public ExpandedERC20(tokenName, tokenSymbol, tokenDecimals) nonReentrant() {} /** * @notice Add Minter role to account. * @dev The caller must have the Owner role. * @param account The address to which the Minter role is added. */ function addMinter(address account) external nonReentrant() { addMember(uint256(Roles.Minter), account); } /** * @notice Remove Minter role from account. * @dev The caller must have the Owner role. * @param account The address from which the Minter role is removed. */ function removeMinter(address account) external nonReentrant() { removeMember(uint256(Roles.Minter), account); } /** * @notice Add Burner role to account. * @dev The caller must have the Owner role. * @param account The address to which the Burner role is added. */ function addBurner(address account) external nonReentrant() { addMember(uint256(Roles.Burner), account); } /** * @notice Removes Burner role from account. * @dev The caller must have the Owner role. * @param account The address from which the Burner role is removed. */ function removeBurner(address account) external nonReentrant() { removeMember(uint256(Roles.Burner), account); } /** * @notice Reset Owner role to account. * @dev The caller must have the Owner role. * @param account The new holder of the Owner role. */ function resetOwner(address account) external nonReentrant() { resetMember(uint256(Roles.Owner), account); } /** * @notice Checks if a given account holds the Minter role. * @param account The address which is checked for the Minter role. * @return bool True if the provided account is a Minter. */ function isMinter(address account) public view nonReentrantView() returns (bool) { return holdsRole(uint256(Roles.Minter), account); } /** * @notice Checks if a given account holds the Burner role. * @param account The address which is checked for the Burner role. * @return bool True if the provided account is a Burner. */ function isBurner(address account) public view nonReentrantView() returns (bool) { return holdsRole(uint256(Roles.Burner), account); } } // File: contracts/financial-templates/common/TokenFactory.sol pragma solidity ^0.6.0; /** * @title Factory for creating new mintable and burnable tokens. */ contract TokenFactory is Lockable { /** * @notice Create a new token and return it to the caller. * @dev The caller will become the only minter and burner and the new owner capable of assigning the roles. * @param tokenName used to describe the new token. * @param tokenSymbol short ticker abbreviation of the name. Ideally < 5 chars. * @param tokenDecimals used to define the precision used in the token's numerical representation. * @return newToken an instance of the newly created token interface. */ function createToken( string calldata tokenName, string calldata tokenSymbol, uint8 tokenDecimals ) external nonReentrant() returns (ExpandedIERC20 newToken) { SyntheticToken mintableToken = new SyntheticToken(tokenName, tokenSymbol, tokenDecimals); mintableToken.addMinter(msg.sender); mintableToken.addBurner(msg.sender); mintableToken.resetOwner(msg.sender); newToken = ExpandedIERC20(address(mintableToken)); } } // File: contracts/oracle/interfaces/StoreInterface.sol pragma solidity ^0.6.0; /** * @title Interface that allows financial contracts to pay oracle fees for their use of the system. */ interface StoreInterface { /** * @notice Pays Oracle fees in ETH to the store. * @dev To be used by contracts whose margin currency is ETH. */ function payOracleFees() external payable; /** * @notice Pays oracle fees in the margin currency, erc20Address, to the store. * @dev To be used if the margin currency is an ERC20 token rather than ETH. * @param erc20Address address of the ERC20 token used to pay the fee. * @param amount number of tokens to transfer. An approval for at least this amount must exist. */ function payOracleFeesErc20(address erc20Address, FixedPoint.Unsigned calldata amount) external; /** * @notice Computes the regular oracle fees that a contract should pay for a period. * @param startTime defines the beginning time from which the fee is paid. * @param endTime end time until which the fee is paid. * @param pfc "profit from corruption", or the maximum amount of margin currency that a * token sponsor could extract from the contract through corrupting the price feed in their favor. * @return regularFee amount owed for the duration from start to end time for the given pfc. * @return latePenalty for paying the fee after the deadline. */ function computeRegularFee( uint256 startTime, uint256 endTime, FixedPoint.Unsigned calldata pfc ) external view returns (FixedPoint.Unsigned memory regularFee, FixedPoint.Unsigned memory latePenalty); /** * @notice Computes the final oracle fees that a contract should pay at settlement. * @param currency token used to pay the final fee. * @return finalFee amount due. */ function computeFinalFee(address currency) external view returns (FixedPoint.Unsigned memory); } // File: contracts/financial-templates/common/FeePayer.sol pragma solidity ^0.6.0; /** * @title FeePayer contract. * @notice Provides fee payment functionality for the ExpiringMultiParty contract. * contract is abstract as each derived contract that inherits `FeePayer` must implement `pfc()`. */ abstract contract FeePayer is Testable, Lockable { using SafeMath for uint256; using FixedPoint for FixedPoint.Unsigned; using SafeERC20 for IERC20; /**************************************** * FEE PAYER DATA STRUCTURES * ****************************************/ // The collateral currency used to back the positions in this contract. IERC20 public collateralCurrency; // Finder contract used to look up addresses for UMA system contracts. FinderInterface public finder; // Tracks the last block time when the fees were paid. uint256 private lastPaymentTime; // Tracks the cumulative fees that have been paid by the contract for use by derived contracts. // The multiplier starts at 1, and is updated by computing cumulativeFeeMultiplier * (1 - effectiveFee). // Put another way, the cumulativeFeeMultiplier is (1 - effectiveFee1) * (1 - effectiveFee2) ... // For example: // The cumulativeFeeMultiplier should start at 1. // If a 1% fee is charged, the multiplier should update to .99. // If another 1% fee is charged, the multiplier should be 0.99^2 (0.9801). FixedPoint.Unsigned public cumulativeFeeMultiplier; /**************************************** * EVENTS * ****************************************/ event RegularFeesPaid(uint256 indexed regularFee, uint256 indexed lateFee); event FinalFeesPaid(uint256 indexed amount); /**************************************** * MODIFIERS * ****************************************/ // modifier that calls payRegularFees(). modifier fees { payRegularFees(); _; } /** * @notice Constructs the FeePayer contract. Called by child contracts. * @param _collateralAddress ERC20 token that is used as the underlying collateral for the synthetic. * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. * @param _timerAddress Contract that stores the current time in a testing environment. * Must be set to 0x0 for production environments that use live time. */ constructor( address _collateralAddress, address _finderAddress, address _timerAddress ) public Testable(_timerAddress) nonReentrant() { collateralCurrency = IERC20(_collateralAddress); finder = FinderInterface(_finderAddress); lastPaymentTime = getCurrentTime(); cumulativeFeeMultiplier = FixedPoint.fromUnscaledUint(1); } /**************************************** * FEE PAYMENT FUNCTIONS * ****************************************/ /** * @notice Pays UMA DVM regular fees (as a % of the collateral pool) to the Store contract. * @dev These must be paid periodically for the life of the contract. If the contract has not paid its regular fee * in a week or more then a late penalty is applied which is sent to the caller. If the amount of * fees owed are greater than the pfc, then this will pay as much as possible from the available collateral. * An event is only fired if the fees charged are greater than 0. * @return totalPaid Amount of collateral that the contract paid (sum of the amount paid to the Store and caller). * This returns 0 and exit early if there is no pfc, fees were already paid during the current block, or the fee rate is 0. */ function payRegularFees() public nonReentrant() returns (FixedPoint.Unsigned memory totalPaid) { StoreInterface store = _getStore(); uint256 time = getCurrentTime(); FixedPoint.Unsigned memory collateralPool = _pfc(); // Exit early if there is no collateral from which to pay fees. if (collateralPool.isEqual(0)) { return totalPaid; } // Exit early if fees were already paid during this block. if (lastPaymentTime == time) { return totalPaid; } (FixedPoint.Unsigned memory regularFee, FixedPoint.Unsigned memory latePenalty) = store.computeRegularFee( lastPaymentTime, time, collateralPool ); lastPaymentTime = time; totalPaid = regularFee.add(latePenalty); if (totalPaid.isEqual(0)) { return totalPaid; } // If the effective fees paid as a % of the pfc is > 100%, then we need to reduce it and make the contract pay // as much of the fee that it can (up to 100% of its pfc). We'll reduce the late penalty first and then the // regular fee, which has the effect of paying the store first, followed by the caller if there is any fee remaining. if (totalPaid.isGreaterThan(collateralPool)) { FixedPoint.Unsigned memory deficit = totalPaid.sub(collateralPool); FixedPoint.Unsigned memory latePenaltyReduction = FixedPoint.min(latePenalty, deficit); latePenalty = latePenalty.sub(latePenaltyReduction); deficit = deficit.sub(latePenaltyReduction); regularFee = regularFee.sub(FixedPoint.min(regularFee, deficit)); totalPaid = collateralPool; } emit RegularFeesPaid(regularFee.rawValue, latePenalty.rawValue); _adjustCumulativeFeeMultiplier(totalPaid, collateralPool); if (regularFee.isGreaterThan(0)) { collateralCurrency.safeIncreaseAllowance(address(store), regularFee.rawValue); store.payOracleFeesErc20(address(collateralCurrency), regularFee); } if (latePenalty.isGreaterThan(0)) { collateralCurrency.safeTransfer(msg.sender, latePenalty.rawValue); } return totalPaid; } /** * @notice Gets the current profit from corruption for this contract in terms of the collateral currency. * @dev This is equivalent to the collateral pool available from which to pay fees. Therefore, derived contracts are * expected to implement this so that pay-fee methods can correctly compute the owed fees as a % of PfC. * @return pfc value for equal to the current profit from corrution denominated in collateral currency. */ function pfc() public view nonReentrantView() returns (FixedPoint.Unsigned memory) { return _pfc(); } /**************************************** * INTERNAL FUNCTIONS * ****************************************/ // Pays UMA Oracle final fees of `amount` in `collateralCurrency` to the Store contract. Final fee is a flat fee // charged for each price request. If payer is the contract, adjusts internal bookkeeping variables. If payer is not // the contract, pulls in `amount` of collateral currency. function _payFinalFees(address payer, FixedPoint.Unsigned memory amount) internal { if (amount.isEqual(0)) { return; } if (payer != address(this)) { // If the payer is not the contract pull the collateral from the payer. collateralCurrency.safeTransferFrom(payer, address(this), amount.rawValue); } else { // If the payer is the contract, adjust the cumulativeFeeMultiplier to compensate. FixedPoint.Unsigned memory collateralPool = _pfc(); // The final fee must be < available collateral or the fee will be larger than 100%. require(collateralPool.isGreaterThan(amount), "Final fee is more than PfC"); _adjustCumulativeFeeMultiplier(amount, collateralPool); } emit FinalFeesPaid(amount.rawValue); StoreInterface store = _getStore(); collateralCurrency.safeIncreaseAllowance(address(store), amount.rawValue); store.payOracleFeesErc20(address(collateralCurrency), amount); } function _pfc() internal virtual view returns (FixedPoint.Unsigned memory); function _getStore() internal view returns (StoreInterface) { return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store)); } function _computeFinalFees() internal view returns (FixedPoint.Unsigned memory finalFees) { StoreInterface store = _getStore(); return store.computeFinalFee(address(collateralCurrency)); } // Returns the user's collateral minus any fees that have been subtracted since it was originally // deposited into the contract. Note: if the contract has paid fees since it was deployed, the raw // value should be larger than the returned value. function _getFeeAdjustedCollateral(FixedPoint.Unsigned memory rawCollateral) internal view returns (FixedPoint.Unsigned memory collateral) { return rawCollateral.mul(cumulativeFeeMultiplier); } // Converts a user-readable collateral value into a raw value that accounts for already-assessed fees. If any fees // have been taken from this contract in the past, then the raw value will be larger than the user-readable value. function _convertToRawCollateral(FixedPoint.Unsigned memory collateral) internal view returns (FixedPoint.Unsigned memory rawCollateral) { return collateral.div(cumulativeFeeMultiplier); } // Decrease rawCollateral by a fee-adjusted collateralToRemove amount. Fee adjustment scales up collateralToRemove // by dividing it by cumulativeFeeMultiplier. There is potential for this quotient to be floored, therefore // rawCollateral is decreased by less than expected. Because this method is usually called in conjunction with an // actual removal of collateral from this contract, return the fee-adjusted amount that the rawCollateral is // decreased by so that the caller can minimize error between collateral removed and rawCollateral debited. function _removeCollateral(FixedPoint.Unsigned storage rawCollateral, FixedPoint.Unsigned memory collateralToRemove) internal returns (FixedPoint.Unsigned memory removedCollateral) { FixedPoint.Unsigned memory initialBalance = _getFeeAdjustedCollateral(rawCollateral); FixedPoint.Unsigned memory adjustedCollateral = _convertToRawCollateral(collateralToRemove); rawCollateral.rawValue = rawCollateral.sub(adjustedCollateral).rawValue; removedCollateral = initialBalance.sub(_getFeeAdjustedCollateral(rawCollateral)); } // Increase rawCollateral by a fee-adjusted collateralToAdd amount. Fee adjustment scales up collateralToAdd // by dividing it by cumulativeFeeMultiplier. There is potential for this quotient to be floored, therefore // rawCollateral is increased by less than expected. Because this method is usually called in conjunction with an // actual addition of collateral to this contract, return the fee-adjusted amount that the rawCollateral is // increased by so that the caller can minimize error between collateral added and rawCollateral credited. // NOTE: This return value exists only for the sake of symmetry with _removeCollateral. We don't actually use it // because we are OK if more collateral is stored in the contract than is represented by rawTotalPositionCollateral. function _addCollateral(FixedPoint.Unsigned storage rawCollateral, FixedPoint.Unsigned memory collateralToAdd) internal returns (FixedPoint.Unsigned memory addedCollateral) { FixedPoint.Unsigned memory initialBalance = _getFeeAdjustedCollateral(rawCollateral); FixedPoint.Unsigned memory adjustedCollateral = _convertToRawCollateral(collateralToAdd); rawCollateral.rawValue = rawCollateral.add(adjustedCollateral).rawValue; addedCollateral = _getFeeAdjustedCollateral(rawCollateral).sub(initialBalance); } // Scale the cumulativeFeeMultiplier by the ratio of fees paid to the current available collateral. function _adjustCumulativeFeeMultiplier(FixedPoint.Unsigned memory amount, FixedPoint.Unsigned memory currentPfc) internal { FixedPoint.Unsigned memory effectiveFee = amount.divCeil(currentPfc); cumulativeFeeMultiplier = cumulativeFeeMultiplier.mul(FixedPoint.fromUnscaledUint(1).sub(effectiveFee)); } } // File: contracts/financial-templates/expiring-multiparty/PricelessPositionManager.sol pragma solidity ^0.6.0; /** * @title Financial contract with priceless position management. * @notice Handles positions for multiple sponsors in an optimistic (i.e., priceless) way without relying * on a price feed. On construction, deploys a new ERC20, managed by this contract, that is the synthetic token. */ contract PricelessPositionManager is FeePayer, AdministrateeInterface { using SafeMath for uint256; using FixedPoint for FixedPoint.Unsigned; using SafeERC20 for IERC20; using SafeERC20 for ExpandedIERC20; /**************************************** * PRICELESS POSITION DATA STRUCTURES * ****************************************/ // Stores the state of the PricelessPositionManager. Set on expiration, emergency shutdown, or settlement. enum ContractState { Open, ExpiredPriceRequested, ExpiredPriceReceived } ContractState public contractState; // Represents a single sponsor's position. All collateral is held by this contract. // This struct acts as bookkeeping for how much of that collateral is allocated to each sponsor. struct PositionData { FixedPoint.Unsigned tokensOutstanding; // Tracks pending withdrawal requests. A withdrawal request is pending if `withdrawalRequestPassTimestamp != 0`. uint256 withdrawalRequestPassTimestamp; FixedPoint.Unsigned withdrawalRequestAmount; // Raw collateral value. This value should never be accessed directly -- always use _getFeeAdjustedCollateral(). // To add or remove collateral, use _addCollateral() and _removeCollateral(). FixedPoint.Unsigned rawCollateral; // Tracks pending transfer position requests. A transfer position request is pending if `transferPositionRequestPassTimestamp != 0`. uint256 transferPositionRequestPassTimestamp; } // Maps sponsor addresses to their positions. Each sponsor can have only one position. mapping(address => PositionData) public positions; // Keep track of the total collateral and tokens across all positions to enable calculating the // global collateralization ratio without iterating over all positions. FixedPoint.Unsigned public totalTokensOutstanding; // Similar to the rawCollateral in PositionData, this value should not be used directly. // _getFeeAdjustedCollateral(), _addCollateral() and _removeCollateral() must be used to access and adjust. FixedPoint.Unsigned public rawTotalPositionCollateral; // Synthetic token created by this contract. ExpandedIERC20 public tokenCurrency; // Unique identifier for DVM price feed ticker. bytes32 public priceIdentifier; // Time that this contract expires. Should not change post-construction unless an emergency shutdown occurs. uint256 public expirationTimestamp; // Time that has to elapse for a withdrawal request to be considered passed, if no liquidations occur. uint256 public withdrawalLiveness; // Minimum number of tokens in a sponsor's position. FixedPoint.Unsigned public minSponsorTokens; // The expiry price pulled from the DVM. FixedPoint.Unsigned public expiryPrice; /**************************************** * EVENTS * ****************************************/ event RequestTransferPosition(address indexed oldSponsor); event RequestTransferPositionExecuted(address indexed oldSponsor, address indexed newSponsor); event RequestTransferPositionCanceled(address indexed oldSponsor); event Deposit(address indexed sponsor, uint256 indexed collateralAmount); event Withdrawal(address indexed sponsor, uint256 indexed collateralAmount); event RequestWithdrawal(address indexed sponsor, uint256 indexed collateralAmount); event RequestWithdrawalExecuted(address indexed sponsor, uint256 indexed collateralAmount); event RequestWithdrawalCanceled(address indexed sponsor, uint256 indexed collateralAmount); event PositionCreated(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount); event NewSponsor(address indexed sponsor); event EndedSponsorPosition(address indexed sponsor); event Redeem(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount); event ContractExpired(address indexed caller); event SettleExpiredPosition( address indexed caller, uint256 indexed collateralReturned, uint256 indexed tokensBurned ); event EmergencyShutdown(address indexed caller, uint256 originalExpirationTimestamp, uint256 shutdownTimestamp); /**************************************** * MODIFIERS * ****************************************/ modifier onlyPreExpiration() { _onlyPreExpiration(); _; } modifier onlyPostExpiration() { _onlyPostExpiration(); _; } modifier onlyCollateralizedPosition(address sponsor) { _onlyCollateralizedPosition(sponsor); _; } // Check that the current state of the pricelessPositionManager is Open. // This prevents multiple calls to `expire` and `EmergencyShutdown` post expiration. modifier onlyOpenState() { _onlyOpenState(); _; } modifier noPendingWithdrawal(address sponsor) { _positionHasNoPendingWithdrawal(sponsor); _; } /** * @notice Construct the PricelessPositionManager * @param _expirationTimestamp unix timestamp of when the contract will expire. * @param _withdrawalLiveness liveness delay, in seconds, for pending withdrawals. * @param _collateralAddress ERC20 token used as collateral for all positions. * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. * @param _priceIdentifier registered in the DVM for the synthetic. * @param _syntheticName name for the token contract that will be deployed. * @param _syntheticSymbol symbol for the token contract that will be deployed. * @param _tokenFactoryAddress deployed UMA token factory to create the synthetic token. * @param _minSponsorTokens minimum amount of collateral that must exist at any time in a position. * @param _timerAddress Contract that stores the current time in a testing environment. * Must be set to 0x0 for production environments that use live time. */ constructor( uint256 _expirationTimestamp, uint256 _withdrawalLiveness, address _collateralAddress, address _finderAddress, bytes32 _priceIdentifier, string memory _syntheticName, string memory _syntheticSymbol, address _tokenFactoryAddress, FixedPoint.Unsigned memory _minSponsorTokens, address _timerAddress ) public FeePayer(_collateralAddress, _finderAddress, _timerAddress) nonReentrant() { require(_expirationTimestamp > getCurrentTime(), "Invalid expiration in future"); require(_getIdentifierWhitelist().isIdentifierSupported(_priceIdentifier), "Unsupported price identifier"); expirationTimestamp = _expirationTimestamp; withdrawalLiveness = _withdrawalLiveness; TokenFactory tf = TokenFactory(_tokenFactoryAddress); tokenCurrency = tf.createToken(_syntheticName, _syntheticSymbol, 18); minSponsorTokens = _minSponsorTokens; priceIdentifier = _priceIdentifier; } /**************************************** * POSITION FUNCTIONS * ****************************************/ /** * @notice Requests to transfer ownership of the caller's current position to a new sponsor address. * Once the request liveness is passed, the sponsor can execute the transfer and specify the new sponsor. * @dev The liveness length is the same as the withdrawal liveness. */ function requestTransferPosition() public onlyPreExpiration() nonReentrant() { PositionData storage positionData = _getPositionData(msg.sender); require(positionData.transferPositionRequestPassTimestamp == 0, "Pending transfer"); // Make sure the proposed expiration of this request is not post-expiry. uint256 requestPassTime = getCurrentTime().add(withdrawalLiveness); require(requestPassTime < expirationTimestamp, "Request expires post-expiry"); // Update the position object for the user. positionData.transferPositionRequestPassTimestamp = requestPassTime; emit RequestTransferPosition(msg.sender); } /** * @notice After a passed transfer position request (i.e., by a call to `requestTransferPosition` and waiting * `withdrawalLiveness`), transfers ownership of the caller's current position to `newSponsorAddress`. * @dev Transferring positions can only occur if the recipient does not already have a position. * @param newSponsorAddress is the address to which the position will be transferred. */ function transferPositionPassedRequest(address newSponsorAddress) public onlyPreExpiration() noPendingWithdrawal(msg.sender) nonReentrant() { require( _getFeeAdjustedCollateral(positions[newSponsorAddress].rawCollateral).isEqual( FixedPoint.fromUnscaledUint(0) ), "Sponsor already has position" ); PositionData storage positionData = _getPositionData(msg.sender); require( positionData.transferPositionRequestPassTimestamp != 0 && positionData.transferPositionRequestPassTimestamp <= getCurrentTime(), "Invalid transfer request" ); // Reset transfer request. positionData.transferPositionRequestPassTimestamp = 0; positions[newSponsorAddress] = positionData; delete positions[msg.sender]; emit RequestTransferPositionExecuted(msg.sender, newSponsorAddress); emit NewSponsor(newSponsorAddress); emit EndedSponsorPosition(msg.sender); } /** * @notice Cancels a pending transfer position request. */ function cancelTransferPosition() external onlyPreExpiration() nonReentrant() { PositionData storage positionData = _getPositionData(msg.sender); require(positionData.transferPositionRequestPassTimestamp != 0, "No pending transfer"); emit RequestTransferPositionCanceled(msg.sender); // Reset withdrawal request. positionData.transferPositionRequestPassTimestamp = 0; } /** * @notice Transfers `collateralAmount` of `collateralCurrency` into the specified sponsor's position. * @dev Increases the collateralization level of a position after creation. This contract must be approved to spend * at least `collateralAmount` of `collateralCurrency`. * @param sponsor the sponsor to credit the deposit to. * @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position. */ function depositTo(address sponsor, FixedPoint.Unsigned memory collateralAmount) public onlyPreExpiration() noPendingWithdrawal(sponsor) fees() nonReentrant() { require(collateralAmount.isGreaterThan(0), "Invalid collateral amount"); PositionData storage positionData = _getPositionData(sponsor); // Increase the position and global collateral balance by collateral amount. _incrementCollateralBalances(positionData, collateralAmount); emit Deposit(sponsor, collateralAmount.rawValue); // Move collateral currency from sender to contract. collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue); } /** * @notice Transfers `collateralAmount` of `collateralCurrency` into the caller's position. * @dev Increases the collateralization level of a position after creation. This contract must be approved to spend * at least `collateralAmount` of `collateralCurrency`. * @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position. */ function deposit(FixedPoint.Unsigned memory collateralAmount) public { // This is just a thin wrapper over depositTo that specified the sender as the sponsor. depositTo(msg.sender, collateralAmount); } /** * @notice Transfers `collateralAmount` of `collateralCurrency` from the sponsor's position to the sponsor. * @dev Reverts if the withdrawal puts this position's collateralization ratio below the global collateralization * ratio. In that case, use `requestWithdrawal`. Might not withdraw the full requested amount to account for precision loss. * @param collateralAmount is the amount of collateral to withdraw. * @return amountWithdrawn The actual amount of collateral withdrawn. */ function withdraw(FixedPoint.Unsigned memory collateralAmount) public onlyPreExpiration() noPendingWithdrawal(msg.sender) fees() nonReentrant() returns (FixedPoint.Unsigned memory amountWithdrawn) { PositionData storage positionData = _getPositionData(msg.sender); require(collateralAmount.isGreaterThan(0), "Invalid collateral amount"); // Decrement the sponsor's collateral and global collateral amounts. Check the GCR between decrement to ensure // position remains above the GCR within the witdrawl. If this is not the case the caller must submit a request. amountWithdrawn = _decrementCollateralBalancesCheckGCR(positionData, collateralAmount); emit Withdrawal(msg.sender, amountWithdrawn.rawValue); // Move collateral currency from contract to sender. // Note: that we move the amount of collateral that is decreased from rawCollateral (inclusive of fees) // instead of the user requested amount. This eliminates precision loss that could occur // where the user withdraws more collateral than rawCollateral is decremented by. collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); } /** * @notice Starts a withdrawal request that, if passed, allows the sponsor to withdraw` from their position. * @dev The request will be pending for `withdrawalLiveness`, during which the position can be liquidated. * @param collateralAmount the amount of collateral requested to withdraw */ function requestWithdrawal(FixedPoint.Unsigned memory collateralAmount) public onlyPreExpiration() noPendingWithdrawal(msg.sender) nonReentrant() { PositionData storage positionData = _getPositionData(msg.sender); require( collateralAmount.isGreaterThan(0) && collateralAmount.isLessThanOrEqual(_getFeeAdjustedCollateral(positionData.rawCollateral)), "Invalid collateral amount" ); // Make sure the proposed expiration of this request is not post-expiry. uint256 requestPassTime = getCurrentTime().add(withdrawalLiveness); require(requestPassTime < expirationTimestamp, "Request expires post-expiry"); // Update the position object for the user. positionData.withdrawalRequestPassTimestamp = requestPassTime; positionData.withdrawalRequestAmount = collateralAmount; emit RequestWithdrawal(msg.sender, collateralAmount.rawValue); } /** * @notice After a passed withdrawal request (i.e., by a call to `requestWithdrawal` and waiting * `withdrawalLiveness`), withdraws `positionData.withdrawalRequestAmount` of collateral currency. * @dev Might not withdraw the full requested amount in order to account for precision loss or if the full requested * amount exceeds the collateral in the position (due to paying fees). * @return amountWithdrawn The actual amount of collateral withdrawn. */ function withdrawPassedRequest() external onlyPreExpiration() fees() nonReentrant() returns (FixedPoint.Unsigned memory amountWithdrawn) { PositionData storage positionData = _getPositionData(msg.sender); require( positionData.withdrawalRequestPassTimestamp != 0 && positionData.withdrawalRequestPassTimestamp <= getCurrentTime(), "Invalid withdraw request" ); // If withdrawal request amount is > position collateral, then withdraw the full collateral amount. // This situation is possible due to fees charged since the withdrawal was originally requested. FixedPoint.Unsigned memory amountToWithdraw = positionData.withdrawalRequestAmount; if (positionData.withdrawalRequestAmount.isGreaterThan(_getFeeAdjustedCollateral(positionData.rawCollateral))) { amountToWithdraw = _getFeeAdjustedCollateral(positionData.rawCollateral); } // Decrement the sponsor's collateral and global collateral amounts. amountWithdrawn = _decrementCollateralBalances(positionData, amountToWithdraw); // Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0. _resetWithdrawalRequest(positionData); // Transfer approved withdrawal amount from the contract to the caller. collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); emit RequestWithdrawalExecuted(msg.sender, amountWithdrawn.rawValue); } /** * @notice Cancels a pending withdrawal request. */ function cancelWithdrawal() external onlyPreExpiration() nonReentrant() { PositionData storage positionData = _getPositionData(msg.sender); require(positionData.withdrawalRequestPassTimestamp != 0, "No pending withdrawal"); emit RequestWithdrawalCanceled(msg.sender, positionData.withdrawalRequestAmount.rawValue); // Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0. _resetWithdrawalRequest(positionData); } /** * @notice Creates tokens by creating a new position or by augmenting an existing position. Pulls `collateralAmount` into the sponsor's position and mints `numTokens` of `tokenCurrency`. * @dev Reverts if minting these tokens would put the position's collateralization ratio below the * global collateralization ratio. This contract must be approved to spend at least `collateralAmount` of * `collateralCurrency`. * @param collateralAmount is the number of collateral tokens to collateralize the position with * @param numTokens is the number of tokens to mint from the position. */ function create(FixedPoint.Unsigned memory collateralAmount, FixedPoint.Unsigned memory numTokens) public onlyPreExpiration() fees() nonReentrant() { require(_checkCollateralization(collateralAmount, numTokens), "CR below GCR"); PositionData storage positionData = positions[msg.sender]; require(positionData.withdrawalRequestPassTimestamp == 0, "Pending withdrawal"); if (positionData.tokensOutstanding.isEqual(0)) { require(numTokens.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position"); emit NewSponsor(msg.sender); } // Increase the position and global collateral balance by collateral amount. _incrementCollateralBalances(positionData, collateralAmount); // Add the number of tokens created to the position's outstanding tokens. positionData.tokensOutstanding = positionData.tokensOutstanding.add(numTokens); totalTokensOutstanding = totalTokensOutstanding.add(numTokens); emit PositionCreated(msg.sender, collateralAmount.rawValue, numTokens.rawValue); // Transfer tokens into the contract from caller and mint corresponding synthetic tokens to the caller's address. collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue); require(tokenCurrency.mint(msg.sender, numTokens.rawValue), "Minting synthetic tokens failed"); } /** * @notice Burns `numTokens` of `tokenCurrency` and sends back the proportional amount of `collateralCurrency`. * @dev Can only be called by a token sponsor. Might not redeem the full proportional amount of collateral * in order to account for precision loss. This contract must be approved to spend at least `numTokens` of * `tokenCurrency`. * @param numTokens is the number of tokens to be burnt for a commensurate amount of collateral. * @return amountWithdrawn The actual amount of collateral withdrawn. */ function redeem(FixedPoint.Unsigned memory numTokens) public onlyPreExpiration() noPendingWithdrawal(msg.sender) fees() nonReentrant() returns (FixedPoint.Unsigned memory amountWithdrawn) { PositionData storage positionData = _getPositionData(msg.sender); require(!numTokens.isGreaterThan(positionData.tokensOutstanding), "Invalid token amount"); FixedPoint.Unsigned memory fractionRedeemed = numTokens.div(positionData.tokensOutstanding); FixedPoint.Unsigned memory collateralRedeemed = fractionRedeemed.mul( _getFeeAdjustedCollateral(positionData.rawCollateral) ); // If redemption returns all tokens the sponsor has then we can delete their position. Else, downsize. if (positionData.tokensOutstanding.isEqual(numTokens)) { amountWithdrawn = _deleteSponsorPosition(msg.sender); } else { // Decrement the sponsor's collateral and global collateral amounts. amountWithdrawn = _decrementCollateralBalances(positionData, collateralRedeemed); // Decrease the sponsors position tokens size. Ensure it is above the min sponsor size. FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(numTokens); require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position"); positionData.tokensOutstanding = newTokenCount; // Update the totalTokensOutstanding after redemption. totalTokensOutstanding = totalTokensOutstanding.sub(numTokens); } emit Redeem(msg.sender, amountWithdrawn.rawValue, numTokens.rawValue); // Transfer collateral from contract to caller and burn callers synthetic tokens. collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); tokenCurrency.safeTransferFrom(msg.sender, address(this), numTokens.rawValue); tokenCurrency.burn(numTokens.rawValue); } /** * @notice After a contract has passed expiry all token holders can redeem their tokens for underlying at the * prevailing price defined by the DVM from the `expire` function. * @dev This burns all tokens from the caller of `tokenCurrency` and sends back the proportional amount of * `collateralCurrency`. Might not redeem the full proportional amount of collateral in order to account for * precision loss. This contract must be approved to spend `tokenCurrency` at least up to the caller's full balance. * @return amountWithdrawn The actual amount of collateral withdrawn. */ function settleExpired() external onlyPostExpiration() fees() nonReentrant() returns (FixedPoint.Unsigned memory amountWithdrawn) { // If the contract state is open and onlyPostExpiration passed then `expire()` has not yet been called. require(contractState != ContractState.Open, "Unexpired position"); // Get the current settlement price and store it. If it is not resolved will revert. if (contractState != ContractState.ExpiredPriceReceived) { expiryPrice = _getOraclePrice(expirationTimestamp); contractState = ContractState.ExpiredPriceReceived; } // Get caller's tokens balance and calculate amount of underlying entitled to them. FixedPoint.Unsigned memory tokensToRedeem = FixedPoint.Unsigned(tokenCurrency.balanceOf(msg.sender)); FixedPoint.Unsigned memory totalRedeemableCollateral = tokensToRedeem.mul(expiryPrice); // If the caller is a sponsor with outstanding collateral they are also entitled to their excess collateral after their debt. PositionData storage positionData = positions[msg.sender]; if (_getFeeAdjustedCollateral(positionData.rawCollateral).isGreaterThan(0)) { // Calculate the underlying entitled to a token sponsor. This is collateral - debt in underlying. FixedPoint.Unsigned memory tokenDebtValueInCollateral = positionData.tokensOutstanding.mul(expiryPrice); FixedPoint.Unsigned memory positionCollateral = _getFeeAdjustedCollateral(positionData.rawCollateral); // If the debt is greater than the remaining collateral, they cannot redeem anything. FixedPoint.Unsigned memory positionRedeemableCollateral = tokenDebtValueInCollateral.isLessThan( positionCollateral ) ? positionCollateral.sub(tokenDebtValueInCollateral) : FixedPoint.Unsigned(0); // Add the number of redeemable tokens for the sponsor to their total redeemable collateral. totalRedeemableCollateral = totalRedeemableCollateral.add(positionRedeemableCollateral); // Reset the position state as all the value has been removed after settlement. delete positions[msg.sender]; emit EndedSponsorPosition(msg.sender); } // Take the min of the remaining collateral and the collateral "owed". If the contract is undercapitalized, // the caller will get as much collateral as the contract can pay out. FixedPoint.Unsigned memory payout = FixedPoint.min( _getFeeAdjustedCollateral(rawTotalPositionCollateral), totalRedeemableCollateral ); // Decrement total contract collateral and outstanding debt. amountWithdrawn = _removeCollateral(rawTotalPositionCollateral, payout); totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRedeem); emit SettleExpiredPosition(msg.sender, amountWithdrawn.rawValue, tokensToRedeem.rawValue); // Transfer tokens & collateral and burn the redeemed tokens. collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensToRedeem.rawValue); tokenCurrency.burn(tokensToRedeem.rawValue); } /**************************************** * GLOBAL STATE FUNCTIONS * ****************************************/ /** * @notice Locks contract state in expired and requests oracle price. * @dev this function can only be called once the contract is expired and can't be re-called. */ function expire() external onlyPostExpiration() onlyOpenState() fees() nonReentrant() { contractState = ContractState.ExpiredPriceRequested; // The final fee for this request is paid out of the contract rather than by the caller. _payFinalFees(address(this), _computeFinalFees()); _requestOraclePrice(expirationTimestamp); emit ContractExpired(msg.sender); } /** * @notice Premature contract settlement under emergency circumstances. * @dev Only the governor can call this function as they are permissioned within the `FinancialContractAdmin`. * Upon emergency shutdown, the contract settlement time is set to the shutdown time. This enables withdrawal * to occur via the standard `settleExpired` function. Contract state is set to `ExpiredPriceRequested` * which prevents re-entry into this function or the `expire` function. No fees are paid when calling * `emergencyShutdown` as the governor who would call the function would also receive the fees. */ function emergencyShutdown() external override onlyPreExpiration() onlyOpenState() nonReentrant() { require(msg.sender == _getFinancialContractsAdminAddress(), "Caller not Governor"); contractState = ContractState.ExpiredPriceRequested; // Expiratory time now becomes the current time (emergency shutdown time). // Price requested at this time stamp. `settleExpired` can now withdraw at this timestamp. uint256 oldExpirationTimestamp = expirationTimestamp; expirationTimestamp = getCurrentTime(); _requestOraclePrice(expirationTimestamp); emit EmergencyShutdown(msg.sender, oldExpirationTimestamp, expirationTimestamp); } /** * @notice Theoretically supposed to pay fees and move money between margin accounts to make sure they * reflect the NAV of the contract. However, this functionality doesn't apply to this contract. * @dev This is supposed to be implemented by any contract that inherits `AdministrateeInterface` and callable * only by the Governor contract. This method is therefore minimally implemented in this contract and does nothing. */ function remargin() external override onlyPreExpiration() nonReentrant() { return; } /** * @notice Accessor method for a sponsor's collateral. * @dev This is necessary because the struct returned by the positions() method shows * rawCollateral, which isn't a user-readable value. * @param sponsor address whose collateral amount is retrieved. * @return collateralAmount amount of collateral within a sponsors position. */ function getCollateral(address sponsor) external view nonReentrantView() returns (FixedPoint.Unsigned memory collateralAmount) { // Note: do a direct access to avoid the validity check. return _getFeeAdjustedCollateral(positions[sponsor].rawCollateral); } /** * @notice Accessor method for the total collateral stored within the PricelessPositionManager. * @return totalCollateral amount of all collateral within the Expiring Multi Party Contract. */ function totalPositionCollateral() external view nonReentrantView() returns (FixedPoint.Unsigned memory totalCollateral) { return _getFeeAdjustedCollateral(rawTotalPositionCollateral); } /**************************************** * INTERNAL FUNCTIONS * ****************************************/ // Reduces a sponsor's position and global counters by the specified parameters. Handles deleting the entire // position if the entire position is being removed. Does not make any external transfers. function _reduceSponsorPosition( address sponsor, FixedPoint.Unsigned memory tokensToRemove, FixedPoint.Unsigned memory collateralToRemove, FixedPoint.Unsigned memory withdrawalAmountToRemove ) internal { PositionData storage positionData = _getPositionData(sponsor); // If the entire position is being removed, delete it instead. if ( tokensToRemove.isEqual(positionData.tokensOutstanding) && _getFeeAdjustedCollateral(positionData.rawCollateral).isEqual(collateralToRemove) ) { _deleteSponsorPosition(sponsor); return; } // Decrement the sponsor's collateral and global collateral amounts. _decrementCollateralBalances(positionData, collateralToRemove); // Ensure that the sponsor will meet the min position size after the reduction. FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(tokensToRemove); require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position"); positionData.tokensOutstanding = newTokenCount; // Decrement the position's withdrawal amount. positionData.withdrawalRequestAmount = positionData.withdrawalRequestAmount.sub(withdrawalAmountToRemove); // Decrement the total outstanding tokens in the overall contract. totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRemove); } // Deletes a sponsor's position and updates global counters. Does not make any external transfers. function _deleteSponsorPosition(address sponsor) internal returns (FixedPoint.Unsigned memory) { PositionData storage positionToLiquidate = _getPositionData(sponsor); FixedPoint.Unsigned memory startingGlobalCollateral = _getFeeAdjustedCollateral(rawTotalPositionCollateral); // Remove the collateral and outstanding from the overall total position. FixedPoint.Unsigned memory remainingRawCollateral = positionToLiquidate.rawCollateral; rawTotalPositionCollateral = rawTotalPositionCollateral.sub(remainingRawCollateral); totalTokensOutstanding = totalTokensOutstanding.sub(positionToLiquidate.tokensOutstanding); // Reset the sponsors position to have zero outstanding and collateral. delete positions[sponsor]; emit EndedSponsorPosition(sponsor); // Return fee-adjusted amount of collateral deleted from position. return startingGlobalCollateral.sub(_getFeeAdjustedCollateral(rawTotalPositionCollateral)); } function _pfc() internal virtual override view returns (FixedPoint.Unsigned memory) { return _getFeeAdjustedCollateral(rawTotalPositionCollateral); } function _getPositionData(address sponsor) internal view onlyCollateralizedPosition(sponsor) returns (PositionData storage) { return positions[sponsor]; } function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) { return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist)); } function _getOracle() internal view returns (OracleInterface) { return OracleInterface(finder.getImplementationAddress(OracleInterfaces.Oracle)); } function _getFinancialContractsAdminAddress() internal view returns (address) { return finder.getImplementationAddress(OracleInterfaces.FinancialContractsAdmin); } // Requests a price for `priceIdentifier` at `requestedTime` from the Oracle. function _requestOraclePrice(uint256 requestedTime) internal { OracleInterface oracle = _getOracle(); oracle.requestPrice(priceIdentifier, requestedTime); } // Fetches a resolved Oracle price from the Oracle. Reverts if the Oracle hasn't resolved for this request. function _getOraclePrice(uint256 requestedTime) internal view returns (FixedPoint.Unsigned memory) { // Create an instance of the oracle and get the price. If the price is not resolved revert. OracleInterface oracle = _getOracle(); require(oracle.hasPrice(priceIdentifier, requestedTime), "Unresolved oracle price"); int256 oraclePrice = oracle.getPrice(priceIdentifier, requestedTime); // For now we don't want to deal with negative prices in positions. if (oraclePrice < 0) { oraclePrice = 0; } return FixedPoint.Unsigned(uint256(oraclePrice)); } // Reset withdrawal request by setting the withdrawal request and withdrawal timestamp to 0. function _resetWithdrawalRequest(PositionData storage positionData) internal { positionData.withdrawalRequestAmount = FixedPoint.fromUnscaledUint(0); positionData.withdrawalRequestPassTimestamp = 0; } // Ensure individual and global consistency when increasing collateral balances. Returns the change to the position. function _incrementCollateralBalances( PositionData storage positionData, FixedPoint.Unsigned memory collateralAmount ) internal returns (FixedPoint.Unsigned memory) { _addCollateral(positionData.rawCollateral, collateralAmount); return _addCollateral(rawTotalPositionCollateral, collateralAmount); } // Ensure individual and global consistency when decrementing collateral balances. Returns the change to the // position. We elect to return the amount that the global collateral is decreased by, rather than the individual // position's collateral, because we need to maintain the invariant that the global collateral is always // <= the collateral owned by the contract to avoid reverts on withdrawals. The amount returned = amount withdrawn. function _decrementCollateralBalances( PositionData storage positionData, FixedPoint.Unsigned memory collateralAmount ) internal returns (FixedPoint.Unsigned memory) { _removeCollateral(positionData.rawCollateral, collateralAmount); return _removeCollateral(rawTotalPositionCollateral, collateralAmount); } // Ensure individual and global consistency when decrementing collateral balances. Returns the change to the position. // This function is similar to the _decrementCollateralBalances function except this function checks position GCR // between the decrements. This ensures that collateral removal will not leave the position undercollateralized. function _decrementCollateralBalancesCheckGCR( PositionData storage positionData, FixedPoint.Unsigned memory collateralAmount ) internal returns (FixedPoint.Unsigned memory) { _removeCollateral(positionData.rawCollateral, collateralAmount); require(_checkPositionCollateralization(positionData), "CR below GCR"); return _removeCollateral(rawTotalPositionCollateral, collateralAmount); } // These internal functions are supposed to act identically to modifiers, but re-used modifiers // unnecessarily increase contract bytecode size. // source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6 function _onlyOpenState() internal view { require(contractState == ContractState.Open, "Contract state is not OPEN"); } function _onlyPreExpiration() internal view { require(getCurrentTime() < expirationTimestamp, "Only callable pre-expiry"); } function _onlyPostExpiration() internal view { require(getCurrentTime() >= expirationTimestamp, "Only callable post-expiry"); } function _onlyCollateralizedPosition(address sponsor) internal view { require( _getFeeAdjustedCollateral(positions[sponsor].rawCollateral).isGreaterThan(0), "Position has no collateral" ); } // Note: This checks whether an already existing position has a pending withdrawal. This cannot be used on the // `create` method because it is possible that `create` is called on a new position (i.e. one without any collateral // or tokens outstanding) which would fail the `onlyCollateralizedPosition` modifier on `_getPositionData`. function _positionHasNoPendingWithdrawal(address sponsor) internal view { require(_getPositionData(sponsor).withdrawalRequestPassTimestamp == 0, "Pending withdrawal"); } /**************************************** * PRIVATE FUNCTIONS * ****************************************/ function _checkPositionCollateralization(PositionData storage positionData) private view returns (bool) { return _checkCollateralization( _getFeeAdjustedCollateral(positionData.rawCollateral), positionData.tokensOutstanding ); } // Checks whether the provided `collateral` and `numTokens` have a collateralization ratio above the global // collateralization ratio. function _checkCollateralization(FixedPoint.Unsigned memory collateral, FixedPoint.Unsigned memory numTokens) private view returns (bool) { FixedPoint.Unsigned memory global = _getCollateralizationRatio( _getFeeAdjustedCollateral(rawTotalPositionCollateral), totalTokensOutstanding ); FixedPoint.Unsigned memory thisChange = _getCollateralizationRatio(collateral, numTokens); return !global.isGreaterThan(thisChange); } function _getCollateralizationRatio(FixedPoint.Unsigned memory collateral, FixedPoint.Unsigned memory numTokens) private pure returns (FixedPoint.Unsigned memory ratio) { if (!numTokens.isGreaterThan(0)) { return FixedPoint.fromUnscaledUint(0); } else { return collateral.div(numTokens); } } } // File: contracts/financial-templates/expiring-multiparty/Liquidatable.sol pragma solidity ^0.6.0; /** * @title Liquidatable * @notice Adds logic to a position-managing contract that enables callers to liquidate an undercollateralized position. * @dev The liquidation has a liveness period before expiring successfully, during which someone can "dispute" the * liquidation, which sends a price request to the relevant Oracle to settle the final collateralization ratio based on * a DVM price. The contract enforces dispute rewards in order to incentivize disputers to correctly dispute false * liquidations and compensate position sponsors who had their position incorrectly liquidated. Importantly, a * prospective disputer must deposit a dispute bond that they can lose in the case of an unsuccessful dispute. */ contract Liquidatable is PricelessPositionManager { using FixedPoint for FixedPoint.Unsigned; using SafeMath for uint256; using SafeERC20 for IERC20; /**************************************** * LIQUIDATION DATA STRUCTURES * ****************************************/ // Because of the check in withdrawable(), the order of these enum values should not change. enum Status { Uninitialized, PreDispute, PendingDispute, DisputeSucceeded, DisputeFailed } struct LiquidationData { // Following variables set upon creation of liquidation: address sponsor; // Address of the liquidated position's sponsor address liquidator; // Address who created this liquidation Status state; // Liquidated (and expired or not), Pending a Dispute, or Dispute has resolved uint256 liquidationTime; // Time when liquidation is initiated, needed to get price from Oracle // Following variables determined by the position that is being liquidated: FixedPoint.Unsigned tokensOutstanding; // Synthetic tokens required to be burned by liquidator to initiate dispute FixedPoint.Unsigned lockedCollateral; // Collateral locked by contract and released upon expiry or post-dispute // Amount of collateral being liquidated, which could be different from // lockedCollateral if there were pending withdrawals at the time of liquidation FixedPoint.Unsigned liquidatedCollateral; // Unit value (starts at 1) that is used to track the fees per unit of collateral over the course of the liquidation. FixedPoint.Unsigned rawUnitCollateral; // Following variable set upon initiation of a dispute: address disputer; // Person who is disputing a liquidation // Following variable set upon a resolution of a dispute: FixedPoint.Unsigned settlementPrice; // Final price as determined by an Oracle following a dispute FixedPoint.Unsigned finalFee; } // Define the contract's constructor parameters as a struct to enable more variables to be specified. // This is required to enable more params, over and above Solidity's limits. struct ConstructorParams { // Params for PricelessPositionManager only. uint256 expirationTimestamp; uint256 withdrawalLiveness; address collateralAddress; address finderAddress; address tokenFactoryAddress; address timerAddress; bytes32 priceFeedIdentifier; string syntheticName; string syntheticSymbol; FixedPoint.Unsigned minSponsorTokens; // Params specifically for Liquidatable. uint256 liquidationLiveness; FixedPoint.Unsigned collateralRequirement; FixedPoint.Unsigned disputeBondPct; FixedPoint.Unsigned sponsorDisputeRewardPct; FixedPoint.Unsigned disputerDisputeRewardPct; } // Liquidations are unique by ID per sponsor mapping(address => LiquidationData[]) public liquidations; // Total collateral in liquidation. FixedPoint.Unsigned public rawLiquidationCollateral; // Immutable contract parameters: // Amount of time for pending liquidation before expiry. uint256 public liquidationLiveness; // Required collateral:TRV ratio for a position to be considered sufficiently collateralized. FixedPoint.Unsigned public collateralRequirement; // Percent of a Liquidation/Position's lockedCollateral to be deposited by a potential disputer // Represented as a multiplier, for example 1.5e18 = "150%" and 0.05e18 = "5%" FixedPoint.Unsigned public disputeBondPct; // Percent of oraclePrice paid to sponsor in the Disputed state (i.e. following a successful dispute) // Represented as a multiplier, see above. FixedPoint.Unsigned public sponsorDisputeRewardPct; // Percent of oraclePrice paid to disputer in the Disputed state (i.e. following a successful dispute) // Represented as a multiplier, see above. FixedPoint.Unsigned public disputerDisputeRewardPct; /**************************************** * EVENTS * ****************************************/ event LiquidationCreated( address indexed sponsor, address indexed liquidator, uint256 indexed liquidationId, uint256 tokensOutstanding, uint256 lockedCollateral, uint256 liquidatedCollateral, uint256 liquidationTime ); event LiquidationDisputed( address indexed sponsor, address indexed liquidator, address indexed disputer, uint256 liquidationId, uint256 disputeBondAmount ); event DisputeSettled( address indexed caller, address indexed sponsor, address indexed liquidator, address disputer, uint256 liquidationId, bool disputeSucceeded ); event LiquidationWithdrawn( address indexed caller, uint256 withdrawalAmount, Status indexed liquidationStatus, uint256 settlementPrice ); /**************************************** * MODIFIERS * ****************************************/ modifier disputable(uint256 liquidationId, address sponsor) { _disputable(liquidationId, sponsor); _; } modifier withdrawable(uint256 liquidationId, address sponsor) { _withdrawable(liquidationId, sponsor); _; } /** * @notice Constructs the liquidatable contract. * @param params struct to define input parameters for construction of Liquidatable. Some params * are fed directly into the PricelessPositionManager's constructor within the inheritance tree. */ constructor(ConstructorParams memory params) public PricelessPositionManager( params.expirationTimestamp, params.withdrawalLiveness, params.collateralAddress, params.finderAddress, params.priceFeedIdentifier, params.syntheticName, params.syntheticSymbol, params.tokenFactoryAddress, params.minSponsorTokens, params.timerAddress ) nonReentrant() { require(params.collateralRequirement.isGreaterThan(1), "CR is more than 100%"); require( params.sponsorDisputeRewardPct.add(params.disputerDisputeRewardPct).isLessThan(1), "Rewards are more than 100%" ); // Set liquidatable specific variables. liquidationLiveness = params.liquidationLiveness; collateralRequirement = params.collateralRequirement; disputeBondPct = params.disputeBondPct; sponsorDisputeRewardPct = params.sponsorDisputeRewardPct; disputerDisputeRewardPct = params.disputerDisputeRewardPct; } /**************************************** * LIQUIDATION FUNCTIONS * ****************************************/ /** * @notice Liquidates the sponsor's position if the caller has enough * synthetic tokens to retire the position's outstanding tokens. * @dev This method generates an ID that will uniquely identify liquidation for the sponsor. This contract must be * approved to spend at least `tokensLiquidated` of `tokenCurrency` and at least `finalFeeBond` of `collateralCurrency`. * @param sponsor address of the sponsor to liquidate. * @param minCollateralPerToken abort the liquidation if the position's collateral per token is below this value. * @param maxCollateralPerToken abort the liquidation if the position's collateral per token exceeds this value. * @param maxTokensToLiquidate max number of tokens to liquidate. * @param deadline abort the liquidation if the transaction is mined after this timestamp. * @return liquidationId ID of the newly created liquidation. * @return tokensLiquidated amount of synthetic tokens removed and liquidated from the `sponsor`'s position. * @return finalFeeBond amount of collateral to be posted by liquidator and returned if not disputed successfully. */ function createLiquidation( address sponsor, FixedPoint.Unsigned calldata minCollateralPerToken, FixedPoint.Unsigned calldata maxCollateralPerToken, FixedPoint.Unsigned calldata maxTokensToLiquidate, uint256 deadline ) external fees() onlyPreExpiration() nonReentrant() returns ( uint256 liquidationId, FixedPoint.Unsigned memory tokensLiquidated, FixedPoint.Unsigned memory finalFeeBond ) { // Check that this transaction was mined pre-deadline. require(getCurrentTime() <= deadline, "Mined after deadline"); // Retrieve Position data for sponsor PositionData storage positionToLiquidate = _getPositionData(sponsor); tokensLiquidated = FixedPoint.min(maxTokensToLiquidate, positionToLiquidate.tokensOutstanding); // Starting values for the Position being liquidated. If withdrawal request amount is > position's collateral, // then set this to 0, otherwise set it to (startCollateral - withdrawal request amount). FixedPoint.Unsigned memory startCollateral = _getFeeAdjustedCollateral(positionToLiquidate.rawCollateral); FixedPoint.Unsigned memory startCollateralNetOfWithdrawal = FixedPoint.fromUnscaledUint(0); if (positionToLiquidate.withdrawalRequestAmount.isLessThanOrEqual(startCollateral)) { startCollateralNetOfWithdrawal = startCollateral.sub(positionToLiquidate.withdrawalRequestAmount); } // Scoping to get rid of a stack too deep error. { FixedPoint.Unsigned memory startTokens = positionToLiquidate.tokensOutstanding; // The Position's collateralization ratio must be between [minCollateralPerToken, maxCollateralPerToken]. // maxCollateralPerToken >= startCollateralNetOfWithdrawal / startTokens. require( maxCollateralPerToken.mul(startTokens).isGreaterThanOrEqual(startCollateralNetOfWithdrawal), "CR is more than max liq. price" ); // minCollateralPerToken >= startCollateralNetOfWithdrawal / startTokens. require( minCollateralPerToken.mul(startTokens).isLessThanOrEqual(startCollateralNetOfWithdrawal), "CR is less than min liq. price" ); } // Compute final fee at time of liquidation. finalFeeBond = _computeFinalFees(); // These will be populated within the scope below. FixedPoint.Unsigned memory lockedCollateral; FixedPoint.Unsigned memory liquidatedCollateral; // Scoping to get rid of a stack too deep error. { FixedPoint.Unsigned memory ratio = tokensLiquidated.div(positionToLiquidate.tokensOutstanding); // The actual amount of collateral that gets moved to the liquidation. lockedCollateral = startCollateral.mul(ratio); // For purposes of disputes, it's actually this liquidatedCollateral value that's used. This value is net of // withdrawal requests. liquidatedCollateral = startCollateralNetOfWithdrawal.mul(ratio); // Part of the withdrawal request is also removed. Ideally: // liquidatedCollateral + withdrawalAmountToRemove = lockedCollateral. FixedPoint.Unsigned memory withdrawalAmountToRemove = positionToLiquidate.withdrawalRequestAmount.mul( ratio ); _reduceSponsorPosition(sponsor, tokensLiquidated, lockedCollateral, withdrawalAmountToRemove); } // Add to the global liquidation collateral count. _addCollateral(rawLiquidationCollateral, lockedCollateral.add(finalFeeBond)); // Construct liquidation object. // Note: All dispute-related values are zeroed out until a dispute occurs. liquidationId is the index of the new // LiquidationData that is pushed into the array, which is equal to the current length of the array pre-push. liquidationId = liquidations[sponsor].length; liquidations[sponsor].push( LiquidationData({ sponsor: sponsor, liquidator: msg.sender, state: Status.PreDispute, liquidationTime: getCurrentTime(), tokensOutstanding: tokensLiquidated, lockedCollateral: lockedCollateral, liquidatedCollateral: liquidatedCollateral, rawUnitCollateral: _convertToRawCollateral(FixedPoint.fromUnscaledUint(1)), disputer: address(0), settlementPrice: FixedPoint.fromUnscaledUint(0), finalFee: finalFeeBond }) ); emit LiquidationCreated( sponsor, msg.sender, liquidationId, tokensLiquidated.rawValue, lockedCollateral.rawValue, liquidatedCollateral.rawValue, getCurrentTime() ); // Destroy tokens tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensLiquidated.rawValue); tokenCurrency.burn(tokensLiquidated.rawValue); // Pull final fee from liquidator. collateralCurrency.safeTransferFrom(msg.sender, address(this), finalFeeBond.rawValue); } /** * @notice Disputes a liquidation, if the caller has enough collateral to post a dispute bond * and pay a fixed final fee charged on each price request. * @dev Can only dispute a liquidation before the liquidation expires and if there are no other pending disputes. * This contract must be approved to spend at least the dispute bond amount of `collateralCurrency`. This dispute * bond amount is calculated from `disputeBondPct` times the collateral in the liquidation. * @param liquidationId of the disputed liquidation. * @param sponsor the address of the sponsor whose liquidation is being disputed. * @return totalPaid amount of collateral charged to disputer (i.e. final fee bond + dispute bond). */ function dispute(uint256 liquidationId, address sponsor) external disputable(liquidationId, sponsor) fees() nonReentrant() returns (FixedPoint.Unsigned memory totalPaid) { LiquidationData storage disputedLiquidation = _getLiquidationData(sponsor, liquidationId); // Multiply by the unit collateral so the dispute bond is a percentage of the locked collateral after fees. FixedPoint.Unsigned memory disputeBondAmount = disputedLiquidation.lockedCollateral.mul(disputeBondPct).mul( _getFeeAdjustedCollateral(disputedLiquidation.rawUnitCollateral) ); _addCollateral(rawLiquidationCollateral, disputeBondAmount); // Request a price from DVM. Liquidation is pending dispute until DVM returns a price. disputedLiquidation.state = Status.PendingDispute; disputedLiquidation.disputer = msg.sender; // Enqueue a request with the DVM. _requestOraclePrice(disputedLiquidation.liquidationTime); emit LiquidationDisputed( sponsor, disputedLiquidation.liquidator, msg.sender, liquidationId, disputeBondAmount.rawValue ); totalPaid = disputeBondAmount.add(disputedLiquidation.finalFee); // Pay the final fee for requesting price from the DVM. _payFinalFees(msg.sender, disputedLiquidation.finalFee); // Transfer the dispute bond amount from the caller to this contract. collateralCurrency.safeTransferFrom(msg.sender, address(this), disputeBondAmount.rawValue); } /** * @notice After a dispute has settled or after a non-disputed liquidation has expired, * the sponsor, liquidator, and/or disputer can call this method to receive payments. * @dev If the dispute SUCCEEDED: the sponsor, liquidator, and disputer are eligible for payment. * If the dispute FAILED: only the liquidator can receive payment. * Once all collateral is withdrawn, delete the liquidation data. * @param liquidationId uniquely identifies the sponsor's liquidation. * @param sponsor address of the sponsor associated with the liquidation. * @return amountWithdrawn the total amount of underlying returned from the liquidation. */ function withdrawLiquidation(uint256 liquidationId, address sponsor) public withdrawable(liquidationId, sponsor) fees() nonReentrant() returns (FixedPoint.Unsigned memory amountWithdrawn) { LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); require( (msg.sender == liquidation.disputer) || (msg.sender == liquidation.liquidator) || (msg.sender == liquidation.sponsor), "Caller cannot withdraw rewards" ); // Settles the liquidation if necessary. This call will revert if the price has not resolved yet. _settle(liquidationId, sponsor); // Calculate rewards as a function of the TRV. // Note: all payouts are scaled by the unit collateral value so all payouts are charged the fees pro rata. FixedPoint.Unsigned memory feeAttenuation = _getFeeAdjustedCollateral(liquidation.rawUnitCollateral); FixedPoint.Unsigned memory tokenRedemptionValue = liquidation .tokensOutstanding .mul(liquidation.settlementPrice) .mul(feeAttenuation); FixedPoint.Unsigned memory collateral = liquidation.lockedCollateral.mul(feeAttenuation); FixedPoint.Unsigned memory disputerDisputeReward = disputerDisputeRewardPct.mul(tokenRedemptionValue); FixedPoint.Unsigned memory sponsorDisputeReward = sponsorDisputeRewardPct.mul(tokenRedemptionValue); FixedPoint.Unsigned memory disputeBondAmount = collateral.mul(disputeBondPct); FixedPoint.Unsigned memory finalFee = liquidation.finalFee.mul(feeAttenuation); // There are three main outcome states: either the dispute succeeded, failed or was not updated. // Based on the state, different parties of a liquidation can withdraw different amounts. // Once a caller has been paid their address deleted from the struct. // This prevents them from being paid multiple from times the same liquidation. FixedPoint.Unsigned memory withdrawalAmount = FixedPoint.fromUnscaledUint(0); if (liquidation.state == Status.DisputeSucceeded) { // If the dispute is successful then all three users can withdraw from the contract. if (msg.sender == liquidation.disputer) { // Pay DISPUTER: disputer reward + dispute bond + returned final fee FixedPoint.Unsigned memory payToDisputer = disputerDisputeReward.add(disputeBondAmount).add(finalFee); withdrawalAmount = withdrawalAmount.add(payToDisputer); delete liquidation.disputer; } if (msg.sender == liquidation.sponsor) { // Pay SPONSOR: remaining collateral (collateral - TRV) + sponsor reward FixedPoint.Unsigned memory remainingCollateral = collateral.sub(tokenRedemptionValue); FixedPoint.Unsigned memory payToSponsor = sponsorDisputeReward.add(remainingCollateral); withdrawalAmount = withdrawalAmount.add(payToSponsor); delete liquidation.sponsor; } if (msg.sender == liquidation.liquidator) { // Pay LIQUIDATOR: TRV - dispute reward - sponsor reward // If TRV > Collateral, then subtract rewards from collateral // NOTE: This should never be below zero since we prevent (sponsorDisputePct+disputerDisputePct) >= 0 in // the constructor when these params are set. FixedPoint.Unsigned memory payToLiquidator = tokenRedemptionValue.sub(sponsorDisputeReward).sub( disputerDisputeReward ); withdrawalAmount = withdrawalAmount.add(payToLiquidator); delete liquidation.liquidator; } // Free up space once all collateral is withdrawn by removing the liquidation object from the array. if ( liquidation.disputer == address(0) && liquidation.sponsor == address(0) && liquidation.liquidator == address(0) ) { delete liquidations[sponsor][liquidationId]; } // In the case of a failed dispute only the liquidator can withdraw. } else if (liquidation.state == Status.DisputeFailed && msg.sender == liquidation.liquidator) { // Pay LIQUIDATOR: collateral + dispute bond + returned final fee withdrawalAmount = collateral.add(disputeBondAmount).add(finalFee); delete liquidations[sponsor][liquidationId]; // If the state is pre-dispute but time has passed liveness then there was no dispute. We represent this // state as a dispute failed and the liquidator can withdraw. } else if (liquidation.state == Status.PreDispute && msg.sender == liquidation.liquidator) { // Pay LIQUIDATOR: collateral + returned final fee withdrawalAmount = collateral.add(finalFee); delete liquidations[sponsor][liquidationId]; } require(withdrawalAmount.isGreaterThan(0), "Invalid withdrawal amount"); // Decrease the total collateral held in liquidatable by the amount withdrawn. amountWithdrawn = _removeCollateral(rawLiquidationCollateral, withdrawalAmount); emit LiquidationWithdrawn( msg.sender, amountWithdrawn.rawValue, liquidation.state, liquidation.settlementPrice.rawValue ); // Transfer amount withdrawn from this contract to the caller. collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue); return amountWithdrawn; } /** * @notice Gets all liquidation information for a given sponsor address. * @param sponsor address of the position sponsor. * @return liquidationData array of all liquidation information for the given sponsor address. */ function getLiquidations(address sponsor) external view nonReentrantView() returns (LiquidationData[] memory liquidationData) { return liquidations[sponsor]; } /**************************************** * INTERNAL FUNCTIONS * ****************************************/ // This settles a liquidation if it is in the PendingDispute state. If not, it will immediately return. // If the liquidation is in the PendingDispute state, but a price is not available, this will revert. function _settle(uint256 liquidationId, address sponsor) internal { LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); // Settlement only happens when state == PendingDispute and will only happen once per liquidation. // If this liquidation is not ready to be settled, this method should return immediately. if (liquidation.state != Status.PendingDispute) { return; } // Get the returned price from the oracle. If this has not yet resolved will revert. liquidation.settlementPrice = _getOraclePrice(liquidation.liquidationTime); // Find the value of the tokens in the underlying collateral. FixedPoint.Unsigned memory tokenRedemptionValue = liquidation.tokensOutstanding.mul( liquidation.settlementPrice ); // The required collateral is the value of the tokens in underlying * required collateral ratio. FixedPoint.Unsigned memory requiredCollateral = tokenRedemptionValue.mul(collateralRequirement); // If the position has more than the required collateral it is solvent and the dispute is valid(liquidation is invalid) // Note that this check uses the liquidatedCollateral not the lockedCollateral as this considers withdrawals. bool disputeSucceeded = liquidation.liquidatedCollateral.isGreaterThanOrEqual(requiredCollateral); liquidation.state = disputeSucceeded ? Status.DisputeSucceeded : Status.DisputeFailed; emit DisputeSettled( msg.sender, sponsor, liquidation.liquidator, liquidation.disputer, liquidationId, disputeSucceeded ); } function _pfc() internal override view returns (FixedPoint.Unsigned memory) { return super._pfc().add(_getFeeAdjustedCollateral(rawLiquidationCollateral)); } function _getLiquidationData(address sponsor, uint256 liquidationId) internal view returns (LiquidationData storage liquidation) { LiquidationData[] storage liquidationArray = liquidations[sponsor]; // Revert if the caller is attempting to access an invalid liquidation // (one that has never been created or one has never been initialized). require( liquidationId < liquidationArray.length && liquidationArray[liquidationId].state != Status.Uninitialized, "Invalid liquidation ID" ); return liquidationArray[liquidationId]; } function _getLiquidationExpiry(LiquidationData storage liquidation) internal view returns (uint256) { return liquidation.liquidationTime.add(liquidationLiveness); } // These internal functions are supposed to act identically to modifiers, but re-used modifiers // unnecessarily increase contract bytecode size. // source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6 function _disputable(uint256 liquidationId, address sponsor) internal view { LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); require( (getCurrentTime() < _getLiquidationExpiry(liquidation)) && (liquidation.state == Status.PreDispute), "Liquidation not disputable" ); } function _withdrawable(uint256 liquidationId, address sponsor) internal view { LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId); Status state = liquidation.state; // Must be disputed or the liquidation has passed expiry. require( (state > Status.PreDispute) || ((_getLiquidationExpiry(liquidation) <= getCurrentTime()) && (state == Status.PreDispute)), "Liquidation not withdrawable" ); } } // File: contracts/financial-templates/expiring-multiparty/ExpiringMultiParty.sol pragma solidity ^0.6.0; /** * @title Expiring Multi Party. * @notice Convenient wrapper for Liquidatable. */ contract ExpiringMultiParty is Liquidatable { /** * @notice Constructs the ExpiringMultiParty contract. * @param params struct to define input parameters for construction of Liquidatable. Some params * are fed directly into the PricelessPositionManager's constructor within the inheritance tree. */ constructor(ConstructorParams memory params) public Liquidatable(params) // Note: since there is no logic here, there is no need to add a re-entrancy guard. { } } // File: contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyLib.sol pragma solidity ^0.6.0; /** * @title Provides convenient Expiring Multi Party contract utilities. * @dev Using this library to deploy EMP's allows calling contracts to avoid importing the full EMP bytecode. */ library ExpiringMultiPartyLib { /** * @notice Returns address of new EMP deployed with given `params` configuration. * @dev Caller will need to register new EMP with the Registry to begin requesting prices. Caller is also * responsible for enforcing constraints on `params`. * @param params is a `ConstructorParams` object from ExpiringMultiParty. * @return address of the deployed ExpiringMultiParty contract */ function deploy(ExpiringMultiParty.ConstructorParams memory params) public returns (address) { ExpiringMultiParty derivative = new ExpiringMultiParty(params); return address(derivative); } } // File: contracts/financial-templates/expiring-multiparty/ExpiringMultiPartyCreator.sol pragma solidity ^0.6.0; /** * @title Expiring Multi Party Contract creator. * @notice Factory contract to create and register new instances of expiring multiparty contracts. * Responsible for constraining the parameters used to construct a new EMP. This creator contains a number of constraints * that are applied to newly created expiring multi party contract. These constraints can evolve over time and are * initially constrained to conservative values in this first iteration. Technically there is nothing in the * ExpiringMultiParty contract requiring these constraints. However, because `createExpiringMultiParty()` is intended * to be the only way to create valid financial contracts that are registered with the DVM (via _registerContract), we can enforce deployment configurations here. */ contract ExpiringMultiPartyCreator is ContractCreator, Testable, Lockable { using FixedPoint for FixedPoint.Unsigned; /**************************************** * EMP CREATOR DATA STRUCTURES * ****************************************/ struct Params { uint256 expirationTimestamp; address collateralAddress; bytes32 priceFeedIdentifier; string syntheticName; string syntheticSymbol; FixedPoint.Unsigned collateralRequirement; FixedPoint.Unsigned disputeBondPct; FixedPoint.Unsigned sponsorDisputeRewardPct; FixedPoint.Unsigned disputerDisputeRewardPct; FixedPoint.Unsigned minSponsorTokens; uint256 withdrawalLiveness; uint256 liquidationLiveness; } // - Address of TokenFactory to pass into newly constructed ExpiringMultiParty contracts address public tokenFactoryAddress; event CreatedExpiringMultiParty(address indexed expiringMultiPartyAddress, address indexed deployerAddress); /** * @notice Constructs the ExpiringMultiPartyCreator contract. * @param _finderAddress UMA protocol Finder used to discover other protocol contracts. * @param _tokenFactoryAddress ERC20 token factory used to deploy synthetic token instances. * @param _timerAddress Contract that stores the current time in a testing environment. */ constructor( address _finderAddress, address _tokenFactoryAddress, address _timerAddress ) public ContractCreator(_finderAddress) Testable(_timerAddress) nonReentrant() { tokenFactoryAddress = _tokenFactoryAddress; } /** * @notice Creates an instance of expiring multi party and registers it within the registry. * @param params is a `ConstructorParams` object from ExpiringMultiParty. * @return address of the deployed ExpiringMultiParty contract. */ function createExpiringMultiParty(Params memory params) public nonReentrant() returns (address) { address derivative = ExpiringMultiPartyLib.deploy(_convertParams(params)); _registerContract(new address[](0), address(derivative)); emit CreatedExpiringMultiParty(address(derivative), msg.sender); return address(derivative); } /**************************************** * PRIVATE FUNCTIONS * ****************************************/ // Converts createExpiringMultiParty params to ExpiringMultiParty constructor params. function _convertParams(Params memory params) private view returns (ExpiringMultiParty.ConstructorParams memory constructorParams) { // Known from creator deployment. constructorParams.finderAddress = finderAddress; constructorParams.tokenFactoryAddress = tokenFactoryAddress; constructorParams.timerAddress = timerAddress; // Enforce configuration constraints. require(bytes(params.syntheticName).length != 0, "Missing synthetic name"); require(bytes(params.syntheticSymbol).length != 0, "Missing synthetic symbol"); require(params.withdrawalLiveness != 0, "Withdrawal liveness cannot be 0"); require(params.liquidationLiveness != 0, "Liquidation liveness cannot be 0"); _requireWhitelistedCollateral(params.collateralAddress); // Input from function call. constructorParams.expirationTimestamp = params.expirationTimestamp; constructorParams.collateralAddress = params.collateralAddress; constructorParams.priceFeedIdentifier = params.priceFeedIdentifier; constructorParams.syntheticName = params.syntheticName; constructorParams.syntheticSymbol = params.syntheticSymbol; constructorParams.collateralRequirement = params.collateralRequirement; constructorParams.disputeBondPct = params.disputeBondPct; constructorParams.sponsorDisputeRewardPct = params.sponsorDisputeRewardPct; constructorParams.disputerDisputeRewardPct = params.disputerDisputeRewardPct; constructorParams.minSponsorTokens = params.minSponsorTokens; constructorParams.withdrawalLiveness = params.withdrawalLiveness; constructorParams.liquidationLiveness = params.liquidationLiveness; } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"address","name":"_finderAddress","type":"address"},{"internalType":"address","name":"_tokenFactoryAddress","type":"address"},{"internalType":"address","name":"_timerAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"expiringMultiPartyAddress","type":"address"},{"indexed":true,"internalType":"address","name":"deployerAddress","type":"address"}],"name":"CreatedExpiringMultiParty","type":"event"},{"inputs":[{"components":[{"internalType":"uint256","name":"expirationTimestamp","type":"uint256"},{"internalType":"address","name":"collateralAddress","type":"address"},{"internalType":"bytes32","name":"priceFeedIdentifier","type":"bytes32"},{"internalType":"string","name":"syntheticName","type":"string"},{"internalType":"string","name":"syntheticSymbol","type":"string"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"collateralRequirement","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"disputeBondPct","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"sponsorDisputeRewardPct","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"disputerDisputeRewardPct","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"minSponsorTokens","type":"tuple"},{"internalType":"uint256","name":"withdrawalLiveness","type":"uint256"},{"internalType":"uint256","name":"liquidationLiveness","type":"uint256"}],"internalType":"struct ExpiringMultiPartyCreator.Params","name":"params","type":"tuple"}],"name":"createExpiringMultiParty","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCurrentTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"time","type":"uint256"}],"name":"setCurrentTime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"timerAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenFactoryAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
60806040523480156200001157600080fd5b5060405162000f8638038062000f86833981016040819052620000349162000121565b600080546001600160a01b038086166001600160a01b0319928316179092556001805460ff60a01b1993851692169190911791909116600160a01b179055620000856001600160e01b03620000c616565b620000986001600160e01b03620000fd16565b600280546001600160a01b0319166001600160a01b038416179055620000bd6200010c565b505050620001c4565b600154600160a01b900460ff16620000fb5760405162461bcd60e51b8152600401620000f29062000174565b60405180910390fd5b565b6001805460ff60a01b19169055565b6001805460ff60a01b1916600160a01b179055565b60008060006060848603121562000136578283fd5b83516200014381620001ab565b60208501519093506200015681620001ab565b60408501519092506200016981620001ab565b809150509250925092565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b6001600160a01b0381168114620001c157600080fd5b50565b610db280620001d46000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630368965b1461005c5780631c39c38d1461008557806322f8e5661461008d57806329cb924d146100a2578063be19f0a7146100b7575b600080fd5b61006f61006a36600461089a565b6100bf565b60405161007c9190610a55565b60405180910390f35b61006f6101c3565b6100a061009b3660046109c8565b6101d2565b005b6100aa61024c565b60405161007c9190610ac6565b61006f6102f3565b60006100c9610302565b6100d1610336565b600073cb08678e4381be3e264e6a0037e3297ca56a583e63bdc3f2446100f685610345565b6040518263ffffffff1660e01b81526004016101129190610c10565b60206040518083038186803b15801561012a57600080fd5b505af415801561013e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101629190610857565b60408051600081526020810190915290915061017e90826104a4565b60405133906001600160a01b038316907ff360b00b309dfe6565667df6b06eab15d0e0958d5e82d89a399ae7dd417b4b0990600090a390506101be61059c565b919050565b6001546001600160a01b031681565b6001546001600160a01b03166101e757600080fd5b60015460405163117c72b360e11b81526001600160a01b03909116906322f8e56690610217908490600401610ac6565b600060405180830381600087803b15801561023157600080fd5b505af1158015610245573d6000803e3d6000fd5b5050505050565b6001546000906001600160a01b0316156102ed57600160009054906101000a90046001600160a01b03166001600160a01b03166329cb924d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156102ae57600080fd5b505afa1580156102c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102e691906109e0565b90506102f0565b50425b90565b6002546001600160a01b031681565b600154600160a01b900460ff166103345760405162461bcd60e51b815260040161032b90610bd9565b60405180910390fd5b565b6001805460ff60a01b19169055565b61034d6106eb565b6000546001600160a01b039081166060808401919091526002548216608084015260015490911660a0830152820151516103995760405162461bcd60e51b815260040161032b90610ba9565b6080820151516103bb5760405162461bcd60e51b815260040161032b90610b06565b6101408201516103dd5760405162461bcd60e51b815260040161032b90610b72565b6101608201516103ff5760405162461bcd60e51b815260040161032b90610b3d565b61040c82602001516105b1565b815181526020808301516001600160a01b031660408084019190915283015160c080840191909152606084015160e08085019190915260808501516101008086019190915260a08601516101608087019190915292860151610180860152908501516101a08501528401516101c084015261012080850151908401526101408085015192840192909252909201519181019190915290565b600080546040516302abf57960e61b81526001600160a01b039091169190829063aafd5e40906104e39067526567697374727960c01b90600401610ac6565b60206040518083038186803b1580156104fb57600080fd5b505afa15801561050f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105339190610857565b60405163066c8c2560e41b81529091506001600160a01b038216906366c8c250906105649087908790600401610a69565b600060405180830381600087803b15801561057e57600080fd5b505af1158015610592573d6000803e3d6000fd5b5050505050505050565b6001805460ff60a01b1916600160a01b179055565b600080546040516302abf57960e61b81526001600160a01b039091169190829063aafd5e40906105fb907210dbdb1b185d195c985b15da1a5d195b1a5cdd606a1b90600401610ac6565b60206040518083038186803b15801561061357600080fd5b505afa158015610627573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061064b9190610857565b604051631d1d5b3960e11b81529091506001600160a01b03821690633a3ab6729061067a908690600401610a55565b60206040518083038186803b15801561069257600080fd5b505afa1580156106a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106ca919061087a565b6106e65760405162461bcd60e51b815260040161032b90610acf565b505050565b604051806101e00160405280600081526020016000815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000801916815260200160608152602001606081526020016107656107a5565b8152602001600081526020016107796107a5565b81526020016107866107a5565b81526020016107936107a5565b81526020016107a06107a5565b905290565b6040518060200160405280600081525090565b80356107c381610d64565b92915050565b600082601f8301126107d9578081fd5b813567ffffffffffffffff8111156107ef578182fd5b610802601f8201601f1916602001610d3d565b915080825283602082850101111561081957600080fd5b8060208401602084013760009082016020015292915050565b600060208284031215610843578081fd5b61084d6020610d3d565b9135825250919050565b600060208284031215610868578081fd5b815161087381610d64565b9392505050565b60006020828403121561088b578081fd5b81518015158114610873578182fd5b6000602082840312156108ab578081fd5b813567ffffffffffffffff808211156108c2578283fd5b6101809184018086038313156108d6578384fd5b6108df83610d3d565b813581526108f087602084016107b8565b6020820152604082013560408201526060820135935082841115610912578485fd5b61091e878584016107c9565b60608201526080820135935082841115610936578485fd5b610942878584016107c9565b60808201526109548760a08401610832565b60a08201526109668760c08401610832565b60c08201526109788760e08401610832565b60e0820152610100935061098e87858401610832565b8482015261012093506109a387858401610832565b9381019390935261014081810135908401526101609081013590830152509392505050565b6000602082840312156109d9578081fd5b5035919050565b6000602082840312156109f1578081fd5b5051919050565b6001600160a01b03169052565b60008151808452815b81811015610a2a57602081850181015186830182015201610a0e565b81811115610a3b5782602083870101525b50601f01601f19169290920160200192915050565b519052565b6001600160a01b0391909116815260200190565b604080825283519082018190526000906020906060840190828701845b82811015610aab5781516001600160a01b031684529284019290840190600101610a86565b5050506001600160a01b039490941692019190915250919050565b90815260200190565b6020808252601a908201527f436f6c6c61746572616c206e6f742077686974656c6973746564000000000000604082015260600190565b60208082526018908201527f4d697373696e672073796e7468657469632073796d626f6c0000000000000000604082015260600190565b6020808252818101527f4c69717569646174696f6e206c6976656e6573732063616e6e6f742062652030604082015260600190565b6020808252601f908201527f5769746864726177616c206c6976656e6573732063616e6e6f74206265203000604082015260600190565b6020808252601690820152754d697373696e672073796e746865746963206e616d6560501b604082015260600190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b60006020825282516020830152602083015160408301526040830151610c3960608401826109f8565b506060830151610c4c60808401826109f8565b506080830151610c5f60a08401826109f8565b5060a0830151610c7260c08401826109f8565b5060c083015160e083015260e08301516101e06101008181860152610c9b610200860184610a05565b8187015193506101209150601f198682030182870152610cbb8185610a05565b91870151935061014091610cd187840186610a50565b9187015161016087810191909152870151935061018091610cf483880186610a50565b9187015193506101a091610d0a87840186610a50565b9187015193506101c091610d2087840186610a50565b828801519450610d3284880186610a50565b979650505050505050565b60405181810167ffffffffffffffff81118282101715610d5c57600080fd5b604052919050565b6001600160a01b0381168114610d7957600080fd5b5056fea26469706673582212202ff3d03f6edc8c4d59cc62d204b3e5e22f2e0a43d8da84ae95e8addd912b69e164736f6c6343000606003300000000000000000000000040f941e48a552bf496b154af6bf55725f18d77c30000000000000000000000007c96d6235cfaaccac5d80fce74e6032b25dd1f030000000000000000000000000000000000000000000000000000000000000000
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106100575760003560e01c80630368965b1461005c5780631c39c38d1461008557806322f8e5661461008d57806329cb924d146100a2578063be19f0a7146100b7575b600080fd5b61006f61006a36600461089a565b6100bf565b60405161007c9190610a55565b60405180910390f35b61006f6101c3565b6100a061009b3660046109c8565b6101d2565b005b6100aa61024c565b60405161007c9190610ac6565b61006f6102f3565b60006100c9610302565b6100d1610336565b600073cb08678e4381be3e264e6a0037e3297ca56a583e63bdc3f2446100f685610345565b6040518263ffffffff1660e01b81526004016101129190610c10565b60206040518083038186803b15801561012a57600080fd5b505af415801561013e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101629190610857565b60408051600081526020810190915290915061017e90826104a4565b60405133906001600160a01b038316907ff360b00b309dfe6565667df6b06eab15d0e0958d5e82d89a399ae7dd417b4b0990600090a390506101be61059c565b919050565b6001546001600160a01b031681565b6001546001600160a01b03166101e757600080fd5b60015460405163117c72b360e11b81526001600160a01b03909116906322f8e56690610217908490600401610ac6565b600060405180830381600087803b15801561023157600080fd5b505af1158015610245573d6000803e3d6000fd5b5050505050565b6001546000906001600160a01b0316156102ed57600160009054906101000a90046001600160a01b03166001600160a01b03166329cb924d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156102ae57600080fd5b505afa1580156102c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102e691906109e0565b90506102f0565b50425b90565b6002546001600160a01b031681565b600154600160a01b900460ff166103345760405162461bcd60e51b815260040161032b90610bd9565b60405180910390fd5b565b6001805460ff60a01b19169055565b61034d6106eb565b6000546001600160a01b039081166060808401919091526002548216608084015260015490911660a0830152820151516103995760405162461bcd60e51b815260040161032b90610ba9565b6080820151516103bb5760405162461bcd60e51b815260040161032b90610b06565b6101408201516103dd5760405162461bcd60e51b815260040161032b90610b72565b6101608201516103ff5760405162461bcd60e51b815260040161032b90610b3d565b61040c82602001516105b1565b815181526020808301516001600160a01b031660408084019190915283015160c080840191909152606084015160e08085019190915260808501516101008086019190915260a08601516101608087019190915292860151610180860152908501516101a08501528401516101c084015261012080850151908401526101408085015192840192909252909201519181019190915290565b600080546040516302abf57960e61b81526001600160a01b039091169190829063aafd5e40906104e39067526567697374727960c01b90600401610ac6565b60206040518083038186803b1580156104fb57600080fd5b505afa15801561050f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105339190610857565b60405163066c8c2560e41b81529091506001600160a01b038216906366c8c250906105649087908790600401610a69565b600060405180830381600087803b15801561057e57600080fd5b505af1158015610592573d6000803e3d6000fd5b5050505050505050565b6001805460ff60a01b1916600160a01b179055565b600080546040516302abf57960e61b81526001600160a01b039091169190829063aafd5e40906105fb907210dbdb1b185d195c985b15da1a5d195b1a5cdd606a1b90600401610ac6565b60206040518083038186803b15801561061357600080fd5b505afa158015610627573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061064b9190610857565b604051631d1d5b3960e11b81529091506001600160a01b03821690633a3ab6729061067a908690600401610a55565b60206040518083038186803b15801561069257600080fd5b505afa1580156106a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106ca919061087a565b6106e65760405162461bcd60e51b815260040161032b90610acf565b505050565b604051806101e00160405280600081526020016000815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000801916815260200160608152602001606081526020016107656107a5565b8152602001600081526020016107796107a5565b81526020016107866107a5565b81526020016107936107a5565b81526020016107a06107a5565b905290565b6040518060200160405280600081525090565b80356107c381610d64565b92915050565b600082601f8301126107d9578081fd5b813567ffffffffffffffff8111156107ef578182fd5b610802601f8201601f1916602001610d3d565b915080825283602082850101111561081957600080fd5b8060208401602084013760009082016020015292915050565b600060208284031215610843578081fd5b61084d6020610d3d565b9135825250919050565b600060208284031215610868578081fd5b815161087381610d64565b9392505050565b60006020828403121561088b578081fd5b81518015158114610873578182fd5b6000602082840312156108ab578081fd5b813567ffffffffffffffff808211156108c2578283fd5b6101809184018086038313156108d6578384fd5b6108df83610d3d565b813581526108f087602084016107b8565b6020820152604082013560408201526060820135935082841115610912578485fd5b61091e878584016107c9565b60608201526080820135935082841115610936578485fd5b610942878584016107c9565b60808201526109548760a08401610832565b60a08201526109668760c08401610832565b60c08201526109788760e08401610832565b60e0820152610100935061098e87858401610832565b8482015261012093506109a387858401610832565b9381019390935261014081810135908401526101609081013590830152509392505050565b6000602082840312156109d9578081fd5b5035919050565b6000602082840312156109f1578081fd5b5051919050565b6001600160a01b03169052565b60008151808452815b81811015610a2a57602081850181015186830182015201610a0e565b81811115610a3b5782602083870101525b50601f01601f19169290920160200192915050565b519052565b6001600160a01b0391909116815260200190565b604080825283519082018190526000906020906060840190828701845b82811015610aab5781516001600160a01b031684529284019290840190600101610a86565b5050506001600160a01b039490941692019190915250919050565b90815260200190565b6020808252601a908201527f436f6c6c61746572616c206e6f742077686974656c6973746564000000000000604082015260600190565b60208082526018908201527f4d697373696e672073796e7468657469632073796d626f6c0000000000000000604082015260600190565b6020808252818101527f4c69717569646174696f6e206c6976656e6573732063616e6e6f742062652030604082015260600190565b6020808252601f908201527f5769746864726177616c206c6976656e6573732063616e6e6f74206265203000604082015260600190565b6020808252601690820152754d697373696e672073796e746865746963206e616d6560501b604082015260600190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b60006020825282516020830152602083015160408301526040830151610c3960608401826109f8565b506060830151610c4c60808401826109f8565b506080830151610c5f60a08401826109f8565b5060a0830151610c7260c08401826109f8565b5060c083015160e083015260e08301516101e06101008181860152610c9b610200860184610a05565b8187015193506101209150601f198682030182870152610cbb8185610a05565b91870151935061014091610cd187840186610a50565b9187015161016087810191909152870151935061018091610cf483880186610a50565b9187015193506101a091610d0a87840186610a50565b9187015193506101c091610d2087840186610a50565b828801519450610d3284880186610a50565b979650505050505050565b60405181810167ffffffffffffffff81118282101715610d5c57600080fd5b604052919050565b6001600160a01b0381168114610d7957600080fd5b5056fea26469706673582212202ff3d03f6edc8c4d59cc62d204b3e5e22f2e0a43d8da84ae95e8addd912b69e164736f6c63430006060033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
00000000000000000000000040f941e48a552bf496b154af6bf55725f18d77c30000000000000000000000007c96d6235cfaaccac5d80fce74e6032b25dd1f030000000000000000000000000000000000000000000000000000000000000000
-----Decoded View---------------
Arg [0] : _finderAddress (address): 0x40f941E48A552bF496B154Af6bf55725f18D77c3
Arg [1] : _tokenFactoryAddress (address): 0x7c96d6235CfaaCcAc5d80fCe74E6032B25dd1F03
Arg [2] : _timerAddress (address): 0x0000000000000000000000000000000000000000
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 00000000000000000000000040f941e48a552bf496b154af6bf55725f18d77c3
Arg [1] : 0000000000000000000000007c96d6235cfaaccac5d80fce74e6032b25dd1f03
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000000
Deployed Bytecode Sourcemap
173953:4383:0:-:0;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;173953:4383:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12:1:-1;9;2:12;175915:372:0;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;39531:27;;;:::i;40270:117::-;;;;;;;;;:::i;:::-;;40639:262;;;:::i;:::-;;;;;;;;174853:34;;;:::i;175915:372::-;176002:7;5873:19;:17;:19::i;:::-;5903:17;:15;:17::i;:::-;176022:18:::1;176043:21;:28;176072:22;176087:6;176072:14;:22::i;:::-;176043:52;;;;;;;;;;;;;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24::::0;17:12:::1;2:2;176043:52:0;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::1;77:16;74:1;67:27;5:2;176043:52:0;;;;;;;101:4:-1;97:9;90:4;84;80:15;76:31;69:5;65:43;126:6;120:4;113:20;0:138;176043:52:0;;;;;;;;;176126:16;::::0;;176140:1:::1;176126:16:::0;;::::1;::::0;::::1;::::0;;;176022:73;;-1:-1:-1;176108:56:0::1;::::0;176022:73;176108:17:::1;:56::i;:::-;176182:58;::::0;176229:10:::1;::::0;-1:-1:-1;;;;;176182:58:0;::::1;::::0;::::1;::::0;;;::::1;176268:10:::0;-1:-1:-1;5943:20:0;:18;:20::i;:::-;175915:372;;;:::o;39531:27::-;;;-1:-1:-1;;;;;39531:27:0;;:::o;40270:117::-;40039:12;;-1:-1:-1;;;;;40039:12:0;40031:37;;12:1:-1;9;2:12;40031:37:0;40345:12:::1;::::0;40339:40:::1;::::0;-1:-1:-1;;;40339:40:0;;-1:-1:-1;;;;;40345:12:0;;::::1;::::0;40339:34:::1;::::0;:40:::1;::::0;40374:4;;40339:40:::1;;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24::::0;17:12:::1;2:2;40339:40:0;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::1;77:16;74:1;67:27;5:2;40339:40:0;;;;40270:117:::0;:::o;40639:262::-;40710:12;;40686:7;;-1:-1:-1;;;;;40710:12:0;:28;40706:188;;40768:12;;;;;;;;;-1:-1:-1;;;;;40768:12:0;-1:-1:-1;;;;;40762:34:0;;:36;;;;;;;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;40762:36:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;40762:36:0;;;;;;;101:4:-1;97:9;90:4;84;80:15;76:31;69:5;65:43;126:6;120:4;113:20;0:138;40762:36:0;;;;;;;;;40755:43;;;;40706:188;-1:-1:-1;40838:3:0;40706:188;40639:262;:::o;174853:34::-;;;-1:-1:-1;;;;;174853:34:0;;:::o;6710:189::-;6844:11;;-1:-1:-1;;;6844:11:0;;;;6836:55;;;;-1:-1:-1;;;6836:55:0;;;;;;;;;;;;;;;;;6710:189::o;6907:139::-;7019:11;:19;;-1:-1:-1;;;;7019:19:0;;;6907:139::o;176530:1803::-;176625:61;;:::i;:::-;176781:13;;-1:-1:-1;;;;;176781:13:0;;;176747:31;;;;:47;;;;176845:19;;;;176805:37;;;:59;176781:13;176908:12;;;;176875:30;;;:45;176994:20;;;176988:34;176980:74;;;;-1:-1:-1;;;176980:74:0;;;;;;;;;177079:22;;;;177073:36;177065:78;;;;-1:-1:-1;;;177065:78:0;;;;;;;;;177162:25;;;;177154:74;;;;-1:-1:-1;;;177154:74:0;;;;;;;;;177247:26;;;;177239:76;;;;-1:-1:-1;;;177239:76:0;;;;;;;;;177326:55;177356:6;:24;;;177326:29;:55::i;:::-;177472:26;;177432:66;;177547:24;;;;;-1:-1:-1;;;;;177509:62:0;:35;;;;:62;;;;177622:26;;;177582:37;;;;:66;;;;177693:20;;;;177659:31;;;;:54;;;;177760:22;;;;177724:33;;;;:58;;;;177835:28;;;;177793:39;;;;:70;;;;177909:21;;;;177874:32;;;:56;177985:30;;;;177941:41;;;:74;178071:31;;;178026:42;;;:76;178150:23;;;;;178113:34;;;:60;178223:25;;;;;178184:36;;;:64;;;;178299:26;;;;178259:37;;;:66;;;;177432:17;176530:1803::o;37855:328::-;37956:22;37997:13;;38051:58;;-1:-1:-1;;;38051:58:0;;-1:-1:-1;;;;;37997:13:0;;;;37956:22;37997:13;;38051:31;;:58;;-1:-1:-1;;;38083:25:0;38051:58;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;38051:58:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;38051:58:0;;;;;;;101:4:-1;97:9;90:4;84;80:15;76:31;69:5;65:43;126:6;120:4;113:20;0:138;38051:58:0;;;;;;;;;38121:54;;-1:-1:-1;;;38121:54:0;;38022:88;;-1:-1:-1;;;;;;38121:25:0;;;;;:54;;38147:7;;38156:18;;38121:54;;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;38121:54:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;38121:54:0;;;;37855:328;;;;:::o;7054:210::-;7252:4;7238:18;;-1:-1:-1;;;;7238:18:0;-1:-1:-1;;;7238:18:0;;;7054:210::o;37430:417::-;37521:22;37562:13;;37657:69;;-1:-1:-1;;;37657:69:0;;-1:-1:-1;;;;;37562:13:0;;;;37521:22;37562:13;;37657:31;;:69;;-1:-1:-1;;;37689:36:0;37657:69;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;37657:69:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;37657:69:0;;;;;;;101:4:-1;97:9;90:4;84;80:15;76:31;69:5;65:43;126:6;120:4;113:20;0:138;37657:69:0;;;;;;;;;37756:52;;-1:-1:-1;;;37756:52:0;;37587:150;;-1:-1:-1;;;;;;37756:33:0;;;;;:52;;37790:17;;37756:52;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;37756:52:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;37756:52:0;;;;;;;101:4:-1;97:9;90:4;84;80:15;76:31;69:5;65:43;126:6;120:4;113:20;0:138;37756:52:0;;;;;;;;;37748:91;;;;-1:-1:-1;;;37748:91:0;;;;;;;;;37430:417;;;:::o;173953:4383::-;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;173953:4383:0;;;;;;-1:-1:-1;;;;;173953:4383:0;;;;;;-1:-1:-1;;;;;173953:4383:0;;;;;;-1:-1:-1;;;;;173953:4383:0;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;:::o;5:130:-1:-;72:20;;97:33;72:20;97:33;;;57:78;;;;;556:442;;658:3;651:4;643:6;639:17;635:27;625:2;;-1:-1;;666:12;625:2;713:6;700:20;17901:18;17893:6;17890:30;17887:2;;;-1:-1;;17923:12;17887:2;735:65;17996:9;17977:17;;-1:-1;;17973:33;18064:4;18054:15;735:65;;;726:74;;820:6;813:5;806:21;924:3;18064:4;915:6;848;906:16;;903:25;900:2;;;941:1;;931:12;900:2;19719:6;18064:4;848:6;844:17;18064:4;882:5;878:16;19696:30;19775:1;19757:16;;;18064:4;19757:16;19750:27;882:5;618:380;-1:-1;;618:380;3510:328;;3625:4;3613:9;3608:3;3604:19;3600:30;3597:2;;;-1:-1;;3633:12;3597:2;3661:20;3625:4;3661:20;;;3912;;3742:75;;-1:-1;3652:29;3591:247;-1:-1;3591:247;4123:263;;4238:2;4226:9;4217:7;4213:23;4209:32;4206:2;;;-1:-1;;4244:12;4206:2;226:6;220:13;238:33;265:5;238:33;;;4296:74;4200:186;-1:-1;;;4200:186;4393:257;;4505:2;4493:9;4484:7;4480:23;4476:32;4473:2;;;-1:-1;;4511:12;4473:2;364:6;358:13;20378:5;19325:13;19318:21;20356:5;20353:32;20343:2;;-1:-1;;20389:12;4657:375;;4785:2;4773:9;4764:7;4760:23;4756:32;4753:2;;;-1:-1;;4791:12;4753:2;4849:17;4836:31;4887:18;;4879:6;4876:30;4873:2;;;-1:-1;;4909:12;4873:2;1165:6;;4984:22;;1144:19;;;1140:32;-1:-1;1137:2;;;-1:-1;;1175:12;1137:2;1203:22;1165:6;1203:22;;;1343;3912:20;1304:16;1297:75;1479:49;1524:3;4785:2;1504:9;1500:22;1479:49;;;4785:2;1465:5;1461:16;1454:75;1605:2;1663:9;1659:22;485:20;1605:2;1624:5;1620:16;1613:75;1786:2;1775:9;1771:18;1758:32;1744:46;;4887:18;1802:6;1799:30;1796:2;;;-1:-1;;1832:12;1796:2;1877:59;1932:3;1923:6;1912:9;1908:22;1877:59;;;1786:2;1863:5;1859:16;1852:85;2037:3;2026:9;2022:19;2009:33;1995:47;;4887:18;2054:6;2051:30;2048:2;;;-1:-1;;2084:12;2048:2;2129:59;2184:3;2175:6;2164:9;2160:22;2129:59;;;2037:3;2115:5;2111:16;2104:85;2301:75;2372:3;2267;2352:9;2348:22;2301:75;;;2267:3;2287:5;2283:16;2276:101;2482:75;2553:3;2448;2533:9;2529:22;2482:75;;;2448:3;2468:5;2464:16;2457:101;2672:75;2743:3;2638;2723:9;2719:22;2672:75;;;2638:3;2658:5;2654:16;2647:101;2829:3;;;2865:75;2936:3;2829;2916:9;2912:22;2865:75;;;2829:3;2849:5;2845:18;2838:103;3014:3;;;3050:75;3121:3;3014;3101:9;3097:22;3050:75;;;3030:18;;;3023:103;;;;3201:3;3258:22;;;3912:20;3217:18;;;3210:77;3363:3;3420:22;;;3912:20;3379:18;;;3372:77;-1:-1;3034:5;4747:285;-1:-1;;;4747:285;5039:241;;5143:2;5131:9;5122:7;5118:23;5114:32;5111:2;;;-1:-1;;5149:12;5111:2;-1:-1;3912:20;;5105:175;-1:-1;5105:175;5287:263;;5402:2;5390:9;5381:7;5377:23;5373:32;5370:2;;;-1:-1;;5408:12;5370:2;-1:-1;4060:13;;5364:186;-1:-1;5364:186;5739:103;-1:-1;;;;;19492:54;5800:37;;5794:48;7054:343;;7197:5;18347:12;18763:6;18758:3;18751:19;-1:-1;19864:101;19878:6;19875:1;19872:13;19864:101;;;18800:4;19945:11;;;;;19939:18;19926:11;;;;;19919:39;19893:10;19864:101;;;19980:6;19977:1;19974:13;19971:2;;;-1:-1;18800:4;20036:6;18795:3;20027:16;;20020:27;19971:2;-1:-1;17996:9;20136:14;-1:-1;;20132:28;7353:39;;;;18800:4;7353:39;;7144:253;-1:-1;;7144:253;12868:334;13081:23;6887:37;;12984:218;13447:213;-1:-1;;;;;19492:54;;;;5800:37;;13565:2;13550:18;;13536:124;13667:472;13863:2;13877:47;;;18347:12;;13848:18;;;18751:19;;;13667:472;;18800:4;;18791:14;;;;18201;;;13667:472;6526:260;6551:6;6548:1;6545:13;6526:260;;;6612:13;;-1:-1;;;;;19492:54;5800:37;;5711:14;;;;18606;;;;17901:18;6566:9;6526:260;;;-1:-1;;;;;;;;19492:54;;;;14110:18;;5800:37;;;;-1:-1;13930:116;13834:305;-1:-1;13834:305;14146:213;6887:37;;;14264:2;14249:18;;14235:124;14366:407;14557:2;14571:47;;;7629:2;14542:18;;;18751:19;7665:28;18791:14;;;7645:49;7713:12;;;14528:245;14780:407;14971:2;14985:47;;;7964:2;14956:18;;;18751:19;8000:26;18791:14;;;7980:47;8046:12;;;14942:245;15194:407;15385:2;15399:47;;;15370:18;;;18751:19;8333:34;18791:14;;;8313:55;8387:12;;;15356:245;15608:407;15799:2;15813:47;;;8638:2;15784:18;;;18751:19;8674:33;18791:14;;;8654:54;8727:12;;;15770:245;16022:407;16213:2;16227:47;;;8978:2;16198:18;;;18751:19;-1:-1;;;18791:14;;;8994:45;9058:12;;;16184:245;16436:407;16627:2;16641:47;;;9309:2;16612:18;;;18751:19;9345:33;18791:14;;;9325:54;9398:12;;;16598:245;16850:417;;17046:2;17067:17;17060:47;9777:16;9771:23;17046:2;17035:9;17031:18;6887:37;17046:2;9957:5;9953:16;9947:23;10032:14;17035:9;10032:14;6887:37;10032:14;10132:5;10128:16;10122:23;10151:71;10207:14;17035:9;10207:14;10193:12;10151:71;;;;10207:14;10303:5;10299:16;10293:23;10322:71;10378:14;17035:9;10378:14;10364:12;10322:71;;;;10378:14;10480:5;10476:16;10470:23;10499:71;10555:14;17035:9;10555:14;10541:12;10499:71;;;;10555:14;10650:5;10646:16;10640:23;10669:71;10725:14;17035:9;10725:14;10711:12;10669:71;;;;10725:14;10827:5;10823:16;10817:23;10902:14;17035:9;10902:14;6887:37;10902:14;10998:5;10994:16;10988:23;9688:6;11031:14;9688:6;11031:14;17035:9;11031:14;11024:38;11077:81;9679:16;17035:9;9679:16;11139:12;11077:81;;;11031:14;11247:5;11243:18;11237:25;11217:45;;11282:16;;;17996:9;;17035;11304:4;11300:14;;11282:16;17035:9;11282:16;11275:40;11330:81;11406:4;11392:12;11330:81;;;11497:18;;;11491:25;;-1:-1;11630:16;;11522:125;11630:16;;;11491:25;11522:125;;;11730:18;;;11724:25;11811:16;;;;6887:37;;;;11913:18;;11907:25;;-1:-1;12046:16;;11938:125;12046:16;;;11907:25;11938:125;;;12141:18;;;12135:25;;-1:-1;12274:16;;12166:125;12274:16;;;12135:25;12166:125;;;12378:18;;;12372:25;;-1:-1;12511:16;;12403:125;12511:16;;;12372:25;12403:125;;;12511:16;12620:5;12616:18;12610:25;12590:45;;12641:125;9688:6;17035:9;12749:16;12735:12;12641:125;;;17113:144;17017:250;-1:-1;;;;;;;17017:250;17494:256;17556:2;17550:9;17582:17;;;17657:18;17642:34;;17678:22;;;17639:62;17636:2;;;17714:1;;17704:12;17636:2;17556;17723:22;17534:216;;-1:-1;17534:216;20173:117;-1:-1;;;;;19492:54;;20232:35;;20222:2;;20281:1;;20271:12;20222:2;20216:74;
Swarm Source
ipfs://2ff3d03f6edc8c4d59cc62d204b3e5e22f2e0a43d8da84ae95e8addd912b69e1
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 27 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
[ Download: CSV Export ]
[ 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.