60 lines
47 KiB
JSON
60 lines
47 KiB
JSON
{
|
||
"language": "Solidity",
|
||
"sources": {
|
||
"@openzeppelin/contracts/access/Ownable.sol": {
|
||
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n"
|
||
},
|
||
"@openzeppelin/contracts/interfaces/draft-IERC6093.sol": {
|
||
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)\npragma solidity ^0.8.20;\n\n/**\n * @dev Standard ERC20 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.\n */\ninterface IERC20Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC20InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC20InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC20InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC20InvalidSpender(address spender);\n}\n\n/**\n * @dev Standard ERC721 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.\n */\ninterface IERC721Errors {\n /**\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.\n * Used in balance queries.\n * @param owner Address of the current owner of a token.\n */\n error ERC721InvalidOwner(address owner);\n\n /**\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\n * @param tokenId Identifier number of a token.\n */\n error ERC721NonexistentToken(uint256 tokenId);\n\n /**\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param tokenId Identifier number of a token.\n * @param owner Address of the current owner of a token.\n */\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC721InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC721InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param tokenId Identifier number of a token.\n */\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC721InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC721InvalidOperator(address operator);\n}\n\n/**\n * @dev Standard ERC1155 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.\n */\ninterface IERC1155Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n * @param tokenId Identifier number of a token.\n */\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC1155InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC1155InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param owner Address of the current owner of a token.\n */\n error ERC1155MissingApprovalForAll(address operator, address owner);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC1155InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC1155InvalidOperator(address operator);\n\n /**\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\n * Used in batch transfers.\n * @param idsLength Length of the array of token identifiers\n * @param valuesLength Length of the array of token amounts\n */\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\n}\n"
|
||
},
|
||
"@openzeppelin/contracts/token/ERC20/ERC20.sol": {
|
||
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC20Metadata} from \"./extensions/IERC20Metadata.sol\";\nimport {Context} from \"../../utils/Context.sol\";\nimport {IERC20Errors} from \"../../interfaces/draft-IERC6093.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n */\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\n mapping(address account => uint256) private _balances;\n\n mapping(address account => mapping(address spender => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `value`.\n */\n function transfer(address to, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `value`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `value`.\n */\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, value);\n _transfer(from, to, value);\n return true;\n }\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _transfer(address from, address to, uint256 value) internal {\n if (from == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n if (to == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(from, to, value);\n }\n\n /**\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\n * this function.\n *\n * Emits a {Transfer} event.\n */\n function _update(address from, address to, uint256 value) internal virtual {\n if (from == address(0)) {\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\n _totalSupply += value;\n } else {\n uint256 fromBalance = _balances[from];\n if (fromBalance < value) {\n revert ERC20InsufficientBalance(from, fromBalance, value);\n }\n unchecked {\n // Overflow not possible: value <= fromBalance <= totalSupply.\n _balances[from] = fromBalance - value;\n }\n }\n\n if (to == address(0)) {\n unchecked {\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\n _totalSupply -= value;\n }\n } else {\n unchecked {\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\n _balances[to] += value;\n }\n }\n\n emit Transfer(from, to, value);\n }\n\n /**\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\n * Relies on the `_update` mechanism\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _mint(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(address(0), account, value);\n }\n\n /**\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\n * Relies on the `_update` mechanism.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead\n */\n function _burn(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n _update(account, address(0), value);\n }\n\n /**\n * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n *\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\n */\n function _approve(address owner, address spender, uint256 value) internal {\n _approve(owner, spender, value, true);\n }\n\n /**\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\n *\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\n * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any\n * `Approval` event during `transferFrom` operations.\n *\n * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to\n * true using the following override:\n * ```\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\n * super._approve(owner, spender, value, true);\n * }\n * ```\n *\n * Requirements are the same as {_approve}.\n */\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\n if (owner == address(0)) {\n revert ERC20InvalidApprover(address(0));\n }\n if (spender == address(0)) {\n revert ERC20InvalidSpender(address(0));\n }\n _allowances[owner][spender] = value;\n if (emitEvent) {\n emit Approval(owner, spender, value);\n }\n }\n\n /**\n * @dev Updates `owner` s allowance for `spender` based on spent `value`.\n *\n * Does not update the allowance value in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Does not emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance != type(uint256).max) {\n if (currentAllowance < value) {\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\n }\n unchecked {\n _approve(owner, spender, currentAllowance - value, false);\n }\n }\n }\n}\n"
|
||
},
|
||
"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
|
||
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n"
|
||
},
|
||
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
|
||
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the value of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the value of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\n * allowance mechanism. `value` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n"
|
||
},
|
||
"@openzeppelin/contracts/utils/Context.sol": {
|
||
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n"
|
||
},
|
||
"contracts/01_Staking/OracleToken.sol": {
|
||
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract ORA is ERC20, Ownable {\n //////////////////\n /// Constants ////\n //////////////////\n\n // 0.5 ETH = 100 ORA => 1 ETH = 200 ORA (18 decimals)\n uint256 public constant ORA_PER_ETH = 200;\n\n //////////////////////\n /// State Variables //\n //////////////////////\n\n ////////////////\n /// Events /////\n ////////////////\n\n event OraPurchased(address indexed buyer, uint256 ethIn, uint256 oraOut);\n event EthWithdrawn(address indexed to, uint256 amount);\n\n /////////////////\n /// Errors //////\n /////////////////\n\n error EthTransferFailed();\n\n constructor() ERC20(\"Oracle Token\", \"ORA\") Ownable(msg.sender) {\n // Mint initial supply to the contract deployer\n _mint(msg.sender, 1000000000000 ether);\n }\n\n function mint(address to, uint256 amount) public onlyOwner {\n _mint(to, amount);\n }\n\n function burn(uint256 amount) public {\n _burn(msg.sender, amount);\n }\n\n /**\n * @notice Buy ORA at a fixed rate by sending ETH. Mints directly to the buyer.\n */\n receive() external payable {\n _buy(msg.sender);\n }\n\n function buy() external payable {\n _buy(msg.sender);\n }\n\n function quoteOra(uint256 ethAmountWei) public pure returns (uint256) {\n return ethAmountWei * ORA_PER_ETH;\n }\n\n function _buy(address buyer) internal {\n uint256 oraOut = quoteOra(msg.value);\n _mint(buyer, oraOut);\n emit OraPurchased(buyer, msg.value, oraOut);\n }\n}\n"
|
||
},
|
||
"contracts/01_Staking/StakingOracle.sol": {
|
||
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\nimport { ORA } from \"./OracleToken.sol\";\nimport { StatisticsUtils } from \"../utils/StatisticsUtils.sol\";\n\ncontract StakingOracle {\n using StatisticsUtils for uint256[];\n\n /////////////////\n /// Errors //////\n /////////////////\n\n error NodeNotRegistered();\n error InsufficientStake();\n error NodeAlreadyRegistered();\n error NoRewardsAvailable();\n error OnlyPastBucketsAllowed();\n error NodeAlreadySlashed();\n error AlreadyReportedInCurrentBucket();\n error NotDeviated();\n error WaitingPeriodNotOver();\n error InvalidPrice();\n error IndexOutOfBounds();\n error NodeNotAtGivenIndex();\n error TransferFailed();\n error MedianNotRecorded();\n error BucketMedianAlreadyRecorded();\n error NodeDidNotReport();\n\n //////////////////////\n /// State Variables //\n //////////////////////\n\n ORA public oracleToken;\n\n struct OracleNode {\n uint256 stakedAmount;\n uint256 lastReportedBucket;\n uint256 reportCount;\n uint256 claimedReportCount;\n uint256 firstBucket; // block when node registered\n bool active;\n }\n\n struct BlockBucket {\n mapping(address => bool) slashedOffenses;\n address[] reporters;\n uint256[] prices;\n uint256 medianPrice;\n }\n\n mapping(address => OracleNode) public nodes;\n mapping(uint256 => BlockBucket) public blockBuckets; // one bucket per 24 blocks\n address[] public nodeAddresses;\n\n uint256 public constant MINIMUM_STAKE = 100 ether;\n uint256 public constant BUCKET_WINDOW = 24; // 24 blocks\n uint256 public constant SLASHER_REWARD_PERCENTAGE = 10;\n uint256 public constant REWARD_PER_REPORT = 1 ether; // ORA Token reward per report\n uint256 public constant INACTIVITY_PENALTY = 1 ether;\n uint256 public constant MISREPORT_PENALTY = 100 ether;\n uint256 public constant MAX_DEVIATION_BPS = 1000; // 10% default threshold\n uint256 public constant WAITING_PERIOD = 2; // 2 buckets after last report before exit allowed\n\n ////////////////\n /// Events /////\n ////////////////\n\n event NodeRegistered(address indexed node, uint256 stakedAmount);\n event PriceReported(address indexed node, uint256 price, uint256 bucketNumber);\n event BucketMedianRecorded(uint256 indexed bucketNumber, uint256 medianPrice);\n event NodeSlashed(address indexed node, uint256 amount);\n event NodeRewarded(address indexed node, uint256 amount);\n event StakeAdded(address indexed node, uint256 amount);\n event NodeExited(address indexed node, uint256 amount);\n\n ///////////////////\n /// Modifiers /////\n ///////////////////\n\n /**\n * @notice Modifier to restrict function access to registered oracle nodes\n * @dev Checks if the sender has a registered node in the mapping\n */\n modifier onlyNode() {\n if (nodes[msg.sender].active == false) revert NodeNotRegistered();\n _;\n }\n\n ///////////////////\n /// Constructor ///\n ///////////////////\n\n constructor(address oraTokenAddress) {\n oracleToken = ORA(payable(oraTokenAddress));\n }\n\n ///////////////////\n /// Functions /////\n ///////////////////\n\n /**\n * @notice Registers a new oracle node with initial ORA token stake\n * @dev Creates a new OracleNode struct and adds the sender to the nodeAddresses array.\n * Requires minimum stake amount and prevents duplicate registrations.\n */\n function registerNode(uint256 amount) public {\n if (amount < MINIMUM_STAKE) revert InsufficientStake();\n if (nodes[msg.sender].active) revert NodeAlreadyRegistered();\n\n bool success = oracleToken.transferFrom(msg.sender, address(this), amount);\n if (!success) revert TransferFailed();\n\n nodes[msg.sender] = OracleNode({\n stakedAmount: amount,\n lastReportedBucket: 0,\n\treportCount: 0,\n\tclaimedReportCount: 0,\n\tfirstBucket: getCurrentBucketNumber(),\n\tactive: true\n });\n\n nodeAddresses.push(msg.sender);\n emit NodeRegistered(msg.sender, amount);\n }\n\n /**\n * @notice Updates the price reported by an oracle node (only registered nodes)\n * @dev Updates the node's lastReportedBucket and price in that bucket. Requires sufficient stake.\n * Enforces that previous report's bucket must have its median recorded before allowing new report.\n * This creates a chain of finalized buckets, ensuring all past reports are accountable.\n * @param price The new price value to report\n */\n function reportPrice(uint256 price) public onlyNode {\n if (price == 0) revert InvalidPrice();\n if (getEffectiveStake(msg.sender) < MINIMUM_STAKE) revert InsufficientStake();\n\n uint256 currentBucket = getCurrentBucketNumber();\n OracleNode storage n = nodes[msg.sender];\n if (n.lastReportedBucket == currentBucket) revert AlreadyReportedInCurrentBucket();\n\n BlockBucket storage bucket = blockBuckets[currentBucket];\n bucket.reporters.push(msg.sender);\n bucket.prices.push(price);\n\n n.lastReportedBucket = currentBucket;\n n.reportCount++;\n emit PriceReported(msg.sender, price, currentBucket);\n }\n\n /**\n * @notice Allows active and inactive nodes to claim accumulated ORA token rewards\n * @dev Calculates rewards based on time elapsed since last claim.\n */\n function claimReward() public {\n OracleNode storage node = nodes[msg.sender];\n uint256 delta = node.reportCount - node.claimedReportCount;\n if (delta == 0) revert NoRewardsAvailable();\n\n node.claimedReportCount = node.reportCount;\n oracleToken.mint(msg.sender, delta * REWARD_PER_REPORT);\n emit NodeRewarded(msg.sender, delta * REWARD_PER_REPORT);\n }\n\n /**\n * @notice Allows a registered node to increase its ORA token stake\n */\n function addStake(uint256 amount) public onlyNode {\n if (amount == 0) revert InsufficientStake();\n\n bool success = oracleToken.transferFrom(msg.sender, address(this), amount);\n if (!success) revert TransferFailed();\n\n nodes[msg.sender].stakedAmount += amount;\n emit StakeAdded(msg.sender, amount);\n }\n\n /**\n * @notice Records the median price for a bucket once sufficient reports are available\n * @dev Anyone who uses the oracle's price feed can call this function to record the median price for a bucket.\n * @param bucketNumber The bucket number to finalize\n */\n function recordBucketMedian(uint256 bucketNumber) public {\n uint256 currentBucket = getCurrentBucketNumber();\n if (bucketNumber == currentBucket) revert OnlyPastBucketsAllowed();\n\n BlockBucket storage bucket = blockBuckets[bucketNumber];\n if (bucket.medianPrice != 0) revert BucketMedianAlreadyRecorded();\n\n uint256[] memory prices = bucket.prices;\n prices.sort();\n uint256 medianPrice = prices.getMedian();\n bucket.medianPrice = medianPrice;\n\n emit BucketMedianRecorded(bucketNumber, medianPrice);\n }\n\n /**\n * @notice Slashes a node for giving a price that is deviated too far from the average\n * @param nodeToSlash The address of the node to slash\n * @param bucketNumber The bucket number to slash the node from\n * @param reportIndex The index of node in the prices and reporters arrays\n * @param nodeAddressesIndex The index of the node to slash in the nodeAddresses array\n */\n function slashNode(\n address nodeToSlash,\n uint256 bucketNumber,\n uint256 reportIndex,\n uint256 nodeAddressesIndex\n ) public {\n if (bucketNumber == getCurrentBucketNumber()) revert OnlyPastBucketsAllowed();\n if (nodeAddressesIndex >= nodeAddresses.length) revert IndexOutOfBounds();\n\n BlockBucket storage bucket = blockBuckets[bucketNumber];\n address reporter = bucket.reporters[reportIndex];\n if (bucket.slashedOffenses[reporter]) revert NodeAlreadySlashed();\n if (bucket.medianPrice == 0) revert MedianNotRecorded();\n if (reportIndex >= bucket.prices.length) revert IndexOutOfBounds();\n if (reporter != nodeToSlash) revert NodeNotAtGivenIndex();\n\n uint256 reportedPrice = bucket.prices[reportIndex];\n if (reportedPrice == 0) revert NodeDidNotReport();\n bool isOutlier = _checkPriceDeviated(reportedPrice, bucket.medianPrice); \n if (!isOutlier) revert NotDeviated();\n\n bucket.slashedOffenses[reporter] = true;\n OracleNode storage node = nodes[nodeToSlash];\n uint256 actualPenalty = MISREPORT_PENALTY > node.stakedAmount ? node.stakedAmount : MISREPORT_PENALTY;\n node.stakedAmount -= actualPenalty;\n\n if (node.stakedAmount == 0) {\n _removeNode(nodeToSlash, nodeAddressesIndex);\n\temit NodeExited(nodeToSlash, 0);\n }\n\n uint256 reward = (actualPenalty * SLASHER_REWARD_PERCENTAGE) / 100;\n\n bool rewardSent = oracleToken.transfer(msg.sender, reward);\n if (!rewardSent) revert TransferFailed();\n\n emit NodeSlashed(nodeToSlash, actualPenalty);\n }\n\n /**\n * @notice Allows a registered node to exit the system and withdraw their stake\n * @dev Removes the node from the system and sends the stake to the node.\n * Requires that the the initial waiting period has passed to ensure the\n * node has been slashed if it reported a bad price before allowing it to exit.\n * @param index The index of the node to remove in nodeAddresses\n */\n function exitNode(uint256 index) public onlyNode {\n if (index >= nodeAddresses.length) revert IndexOutOfBounds();\n if (nodeAddresses[index] != msg.sender) revert NodeNotAtGivenIndex();\n\n OracleNode storage node = nodes[msg.sender];\n if (!node.active) revert NodeNotRegistered();\n if (node.lastReportedBucket + WAITING_PERIOD > getCurrentBucketNumber()) revert WaitingPeriodNotOver();\n\n uint256 effectiveStake = getEffectiveStake(msg.sender);\n _removeNode(msg.sender, index);\n\n node.stakedAmount = 0;\n node.active = false;\n bool success = oracleToken.transfer(msg.sender, effectiveStake);\n if (!success) revert TransferFailed();\n\n emit NodeExited(msg.sender, effectiveStake);\n }\n\n ////////////////////////\n /// View Functions /////\n ////////////////////////\n\n /**\n * @notice Returns the current bucket number\n * @dev Returns the current bucket number based on the block number\n * @return The current bucket number\n */\n function getCurrentBucketNumber() public view returns (uint256) {\n return (block.number / BUCKET_WINDOW) + 1;\n }\n\n /**\n * @notice Returns the list of registered oracle node addresses\n * @return Array of registered oracle node addresses\n */\n function getNodeAddresses() public view returns (address[] memory) {\n return nodeAddresses;\n }\n\n /**\n * @notice Returns the stored median price from the most recently completed bucket\n * @dev Requires that the median for the bucket be recorded via recordBucketMedian\n * @return The median price for the last finalized bucket\n */\n function getLatestPrice() public view returns (uint256) {\n uint256 bucketNumber = getCurrentBucketNumber() - 1;\n BlockBucket storage bucket = blockBuckets[bucketNumber];\n if (bucket.medianPrice == 0) revert MedianNotRecorded();\n\n return bucket.medianPrice;\n }\n\n /**\n * @notice Returns the stored median price from a specified bucket\n * @param bucketNumber The bucket number to read the median price from\n * @return The median price stored for the bucket\n */\n function getPastPrice(uint256 bucketNumber) public view returns (uint256) {\n BlockBucket storage bucket = blockBuckets[bucketNumber];\n if (bucket.medianPrice == 0) revert MedianNotRecorded();\n\n return bucket.medianPrice;\n }\n\n /**\n * @notice Returns the price and slashed status of a node at a given bucket\n * @param nodeAddress The address of the node to get the data for\n * @param bucketNumber The bucket number to get the data from\n * @return price The price of the node at the specified bucket\n * @return slashed The slashed status of the node at the specified bucket\n */\n function getSlashedStatus(\n address nodeAddress,\n uint256 bucketNumber\n ) public view returns (uint256 price, bool slashed) {\n BlockBucket storage bucket = blockBuckets[bucketNumber];\n for (uint256 i = 0; i < bucket.reporters.length; i++) {\n if (bucket.reporters[i] == nodeAddress) {\n price = bucket.prices[i];\n\t slashed = bucket.slashedOffenses[nodeAddress];\n\t}\n }\n }\n\n /**\n * @notice Returns the effective stake accounting for inactivity penalties via missed buckets\n * @dev Effective stake = stakedAmount - (missedBuckets * INACTIVITY_PENALTY), floored at 0\n */\n function getEffectiveStake(address nodeAddress) public view returns (uint256) {\n OracleNode memory n = nodes[nodeAddress]; // get node detail\n if (!n.active) return 0; // inactive node\n\n uint256 currentBucket = getCurrentBucketNumber();\n if (currentBucket == n.firstBucket) return n.stakedAmount; // basically the node just registered itself\n\n uint256 expectedReports = currentBucket - n.firstBucket; // get the amount of \"buckets\" since registration\n uint256 actualReportsCompleted = n.reportCount;\n if (n.lastReportedBucket == currentBucket && actualReportsCompleted > 0) {\n actualReportsCompleted -= 1; // we remove the report from current bucket\n }\n if (actualReportsCompleted >= expectedReports) return n.stakedAmount; // no penalty\n uint256 missed = expectedReports - actualReportsCompleted;\n uint256 penalty = missed * INACTIVITY_PENALTY;\n if (penalty > n.stakedAmount) return 0; // the amount of penalty is more than the staked amount\n return n.stakedAmount - penalty;\n }\n\n /**\n * @notice Returns the addresses of nodes in a bucket whose reported price deviates beyond the threshold\n * @param bucketNumber The bucket number to get the outliers from\n * @return Array of node addresses considered outliers\n */\n function getOutlierNodes(uint256 bucketNumber) public view returns (address[] memory) {\n BlockBucket storage bucket = blockBuckets[bucketNumber];\n if (bucket.medianPrice == 0) revert MedianNotRecorded();\n\n address[] memory outliers = new address[](bucket.reporters.length);\n uint256 outlierCount = 0;\n for (uint256 i = 0; i < bucket.reporters.length; i++) {\n\taddress reporter = bucket.reporters[i];\n if (bucket.slashedOffenses[reporter]) continue;\n\n uint256 reportedPrice = bucket.prices[i];\n\tif (reportedPrice == 0) continue;\n\n\tbool isOutlier = _checkPriceDeviated(reportedPrice, bucket.medianPrice);\n\tif (isOutlier) {\n outliers[outlierCount] = reporter;\n\t outlierCount++;\n\t}\n }\n\n address[] memory fixedOutliers = new address[](outlierCount);\n for (uint256 i = 0; i < outlierCount; i++) {\n fixedOutliers[i] = outliers[i];\n }\n return fixedOutliers;\n }\n\n //////////////////////////\n /// Internal Functions ///\n //////////////////////////\n\n /**\n * @notice Removes a node from the nodeAddresses array\n * @param nodeAddress The address of the node to remove\n * @param index The index of the node to remove\n */\n function _removeNode(address nodeAddress, uint256 index) internal {\n if (index >= nodeAddresses.length) revert IndexOutOfBounds();\n\n address storedNodeAddress = nodeAddresses[index];\n if (storedNodeAddress != nodeAddress) revert NodeNotAtGivenIndex(); \n\n if (index != nodeAddresses.length - 1) {\n nodeAddresses[index] = nodeAddresses[nodeAddresses.length - 1];\n }\n nodeAddresses.pop();\n nodes[nodeAddress].active = false;\n }\n\n /**\n * @notice Checks if the price deviation is greater than the threshold\n * @param reportedPrice The price reported by the node\n * @param medianPrice The average price of the bucket\n * @return True if the price deviation is greater than the threshold, false otherwise\n */\n function _checkPriceDeviated(uint256 reportedPrice, uint256 medianPrice) internal pure returns (bool) {\n uint256 deviation = reportedPrice > medianPrice ? reportedPrice - medianPrice : medianPrice - reportedPrice;\n uint256 deviationBps = (deviation * 10_000) / medianPrice;\n return deviationBps > MAX_DEVIATION_BPS;\n }\n}\n"
|
||
},
|
||
"contracts/utils/StatisticsUtils.sol": {
|
||
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\nlibrary StatisticsUtils {\n /////////////////\n /// Errors //////\n /////////////////\n\n error EmptyArray();\n\n ///////////////////\n /// Functions /////\n ///////////////////\n\n /**\n * @notice Sorts an array of uint256 values in ascending order using selection sort\n * @dev Uses selection sort algorithm which is not gas-efficient but acceptable for small arrays.\n * This implementation mimics the early MakerDAO Medianizer exactly.\n * Modifies the input array in-place.\n * @param arr The array of uint256 values to sort in ascending order\n */\n function sort(uint256[] memory arr) internal pure {\n uint256 n = arr.length;\n for (uint256 i = 0; i < n; i++) {\n uint256 minIndex = i;\n for (uint256 j = i + 1; j < n; j++) {\n if (arr[j] < arr[minIndex]) {\n minIndex = j;\n }\n }\n if (minIndex != i) {\n (arr[i], arr[minIndex]) = (arr[minIndex], arr[i]);\n }\n }\n }\n\n /**\n * @notice Calculates the median value from a sorted array of uint256 values\n * @dev For arrays with even length, returns the average of the two middle elements.\n * For arrays with odd length, returns the middle element.\n * Assumes the input array is already sorted in ascending order.\n * @param arr The sorted array of uint256 values to calculate median from\n * @return The median value as a uint256\n */\n function getMedian(uint256[] memory arr) internal pure returns (uint256) {\n uint256 length = arr.length;\n if (length == 0) revert EmptyArray();\n if (length % 2 == 0) {\n return (arr[length / 2 - 1] + arr[length / 2]) / 2;\n } else {\n return arr[length / 2];\n }\n }\n}\n"
|
||
}
|
||
},
|
||
"settings": {
|
||
"optimizer": {
|
||
"enabled": true,
|
||
"runs": 200
|
||
},
|
||
"evmVersion": "paris",
|
||
"outputSelection": {
|
||
"*": {
|
||
"*": [
|
||
"abi",
|
||
"evm.bytecode",
|
||
"evm.deployedBytecode",
|
||
"evm.methodIdentifiers",
|
||
"metadata",
|
||
"devdoc",
|
||
"userdoc",
|
||
"storageLayout",
|
||
"evm.gasEstimates"
|
||
],
|
||
"": [
|
||
"ast"
|
||
]
|
||
}
|
||
},
|
||
"metadata": {
|
||
"useLiteralContent": true
|
||
}
|
||
}
|
||
} |