Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
- Contract name:
- GrailMarket
- Optimization enabled
- true
- Compiler version
- v0.8.20+commit.a1b79de6
- Optimization runs
- 44444444
- EVM Version
- paris
- Verified at
- 2024-12-14T16:04:21.012581Z
Constructor Arguments
0x000000000000000000000000bcb5fd84cc71cedc15b4651f18d912f816b61d330000000000000000000000002880ab155794e7179c9ee2e38200202908c17b430000000000000000000000000000000000000000000000000000000000000000
Arg [0] (address) : 0xbcb5fd84cc71cedc15b4651f18d912f816b61d33
Arg [1] (address) : 0x2880ab155794e7179c9ee2e38200202908c17b43
Arg [2] (address) : 0x0000000000000000000000000000000000000000
src/GrailMarket.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; import {IGrailMarket} from "./interfaces/IGrailMarket.sol"; import {Currency} from "./types/Currency.sol"; import {MarketId} from "./types/MarketId.sol"; import {Market} from "./libraries/Market.sol"; import {BaseMarket} from "./base/BaseMarket.sol"; import {BaseMarketConfig} from "./base/BaseMarketConfig.sol"; contract GrailMarket is IGrailMarket, BaseMarket, BaseMarketConfig { constructor(address _owner, address _pyth, Currency _currency) BaseMarketConfig(_owner, _pyth, _currency) {} /// @inheritdoc IGrailMarket function resolve(MarketId id, uint256 roundId, bytes calldata priceUpdate) external payable override { Market.MarketInfo storage market = markets[id]; Market.Round storage round = market.rounds[roundId]; // ensure round status can be setlled if (round.status != Market.RoundStatus.LIVE) revert InvalidRoundStatus(); // ensure the timing is right if (round.closingTime > block.timestamp) revert ActionTooEarly(); (int64 closingPrice,) = _fetchAssetPrice(market.oracleId, priceUpdate, round.closingTime, round.closingTime + MAX_SECONDS_OFFSET); // lock the following n + 1 round _lockMarketRound(market, id, roundId + 1, closingPrice); // start another n + 2 round _startNextRound(market, id); // only collect protocol fee when position exists on both sides bool isRefunding; uint256 winningSide; if (round.bearShares > 0 && round.bullShares > 0) { // calculate protocol fee uint128 protocolFee = (round.totalShares * config.protocolFeeBps) / BASIS_POINT; // calculate incentive fee uint128 incentiveFee = (protocolFee * config.incentiveFeeBps) / BASIS_POINT; uint128 netProtocolFee = protocolFee - incentiveFee; round.rewardPool = round.totalShares - protocolFee; // account for fee unchecked { protocolFeesAccrued += netProtocolFee; } // if closing price greater than locked price, market was bullish, the bulls wins if (closingPrice > round.priceMark) { round.winningShares = round.bullShares; winningSide = 1; // if closing price less than locked price, market was bearish, the bears wins } else if (closingPrice < round.priceMark) { round.winningShares = round.bearShares; } round.status = Market.RoundStatus.RESOLVED; round.closingPrice = closingPrice; // transfer the resolver fee _tryCredit(msg.sender, incentiveFee); } else { // we should probably refund as we are not interested in the protocol winning isRefunding = true; round.status = Market.RoundStatus.REFUNDING; round.closingPrice = closingPrice; } emit Resolve(id, roundId, round.rewardPool, round.winningShares, closingPrice, winningSide, isRefunding); } /// @inheritdoc IGrailMarket function setResolverFee(uint16 fee) external override onlyOwner { // ensure fee is moderate if (fee > MAX_PROTOCOL_FEE) revert FeeTooLarge(fee); config.incentiveFeeBps = fee; emit SetProtocolFee(fee); } }
lib/solady/src/auth/Ownable.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Simple single owner authorization mixin. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol) /// /// @dev Note: /// This implementation does NOT auto-initialize the owner to `msg.sender`. /// You MUST call the `_initializeOwner` in the constructor / initializer. /// /// While the ownable portion follows /// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility, /// the nomenclature for the 2-step ownership handover may be unique to this codebase. abstract contract Ownable { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The caller is not authorized to call the function. error Unauthorized(); /// @dev The `newOwner` cannot be the zero address. error NewOwnerIsZeroAddress(); /// @dev The `pendingOwner` does not have a valid handover request. error NoHandoverRequest(); /// @dev Cannot double-initialize. error AlreadyInitialized(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EVENTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The ownership is transferred from `oldOwner` to `newOwner`. /// This event is intentionally kept the same as OpenZeppelin's Ownable to be /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173), /// despite it not being as lightweight as a single argument event. event OwnershipTransferred(address indexed oldOwner, address indexed newOwner); /// @dev An ownership handover to `pendingOwner` has been requested. event OwnershipHandoverRequested(address indexed pendingOwner); /// @dev The ownership handover to `pendingOwner` has been canceled. event OwnershipHandoverCanceled(address indexed pendingOwner); /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`. uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE = 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0; /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`. uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE = 0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d; /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`. uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE = 0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The owner slot is given by: /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`. /// It is intentionally chosen to be a high value /// to avoid collision with lower slots. /// The choice of manual storage layout is to enable compatibility /// with both regular and upgradeable contracts. bytes32 internal constant _OWNER_SLOT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927; /// The ownership handover slot of `newOwner` is given by: /// ``` /// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED)) /// let handoverSlot := keccak256(0x00, 0x20) /// ``` /// It stores the expiry timestamp of the two-step ownership handover. uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Override to return true to make `_initializeOwner` prevent double-initialization. function _guardInitializeOwner() internal pure virtual returns (bool guard) {} /// @dev Initializes the owner directly without authorization guard. /// This function must be called upon initialization, /// regardless of whether the contract is upgradeable or not. /// This is to enable generalization to both regular and upgradeable contracts, /// and to save gas in case the initial owner is not the caller. /// For performance reasons, this function will not check if there /// is an existing owner. function _initializeOwner(address newOwner) internal virtual { if (_guardInitializeOwner()) { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT if sload(ownerSlot) { mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`. revert(0x1c, 0x04) } // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Store the new value. sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) } } else { /// @solidity memory-safe-assembly assembly { // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Store the new value. sstore(_OWNER_SLOT, newOwner) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) } } } /// @dev Sets the owner directly without authorization guard. function _setOwner(address newOwner) internal virtual { if (_guardInitializeOwner()) { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) // Store the new value. sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) } } else { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) // Store the new value. sstore(ownerSlot, newOwner) } } } /// @dev Throws if the sender is not the owner. function _checkOwner() internal view virtual { /// @solidity memory-safe-assembly assembly { // If the caller is not the stored owner, revert. if iszero(eq(caller(), sload(_OWNER_SLOT))) { mstore(0x00, 0x82b42900) // `Unauthorized()`. revert(0x1c, 0x04) } } } /// @dev Returns how long a two-step ownership handover is valid for in seconds. /// Override to return a different value if needed. /// Made internal to conserve bytecode. Wrap it in a public function if needed. function _ownershipHandoverValidFor() internal view virtual returns (uint64) { return 48 * 3600; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PUBLIC UPDATE FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Allows the owner to transfer the ownership to `newOwner`. function transferOwnership(address newOwner) public payable virtual onlyOwner { /// @solidity memory-safe-assembly assembly { if iszero(shl(96, newOwner)) { mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`. revert(0x1c, 0x04) } } _setOwner(newOwner); } /// @dev Allows the owner to renounce their ownership. function renounceOwnership() public payable virtual onlyOwner { _setOwner(address(0)); } /// @dev Request a two-step ownership handover to the caller. /// The request will automatically expire in 48 hours (172800 seconds) by default. function requestOwnershipHandover() public payable virtual { unchecked { uint256 expires = block.timestamp + _ownershipHandoverValidFor(); /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to `expires`. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, caller()) sstore(keccak256(0x0c, 0x20), expires) // Emit the {OwnershipHandoverRequested} event. log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller()) } } } /// @dev Cancels the two-step ownership handover to the caller, if any. function cancelOwnershipHandover() public payable virtual { /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to 0. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, caller()) sstore(keccak256(0x0c, 0x20), 0) // Emit the {OwnershipHandoverCanceled} event. log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller()) } } /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`. /// Reverts if there is no existing ownership handover requested by `pendingOwner`. function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner { /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to 0. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, pendingOwner) let handoverSlot := keccak256(0x0c, 0x20) // If the handover does not exist, or has expired. if gt(timestamp(), sload(handoverSlot)) { mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`. revert(0x1c, 0x04) } // Set the handover slot to 0. sstore(handoverSlot, 0) } _setOwner(pendingOwner); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PUBLIC READ FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the owner of the contract. function owner() public view virtual returns (address result) { /// @solidity memory-safe-assembly assembly { result := sload(_OWNER_SLOT) } } /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`. function ownershipHandoverExpiresAt(address pendingOwner) public view virtual returns (uint256 result) { /// @solidity memory-safe-assembly assembly { // Compute the handover slot. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, pendingOwner) // Load the handover slot. result := sload(keccak256(0x0c, 0x20)) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* MODIFIERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Marks a function as only callable by the owner. modifier onlyOwner() virtual { _checkOwner(); _; } }
node_modules/@pythnetwork/pyth-sdk-solidity/IPyth.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; import "./PythStructs.sol"; import "./IPythEvents.sol"; /// @title Consume prices from the Pyth Network (https://pyth.network/). /// @dev Please refer to the guidance at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for how to consume prices safely. /// @author Pyth Data Association interface IPyth is IPythEvents { /// @notice Returns the price of a price feed without any sanity checks. /// @dev This function returns the most recent price update in this contract without any recency checks. /// This function is unsafe as the returned price update may be arbitrarily far in the past. /// /// Users of this function should check the `publishTime` in the price to ensure that the returned price is /// sufficiently recent for their application. If you are considering using this function, it may be /// safer / easier to use `getPriceNoOlderThan`. /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. function getPriceUnsafe( bytes32 id ) external view returns (PythStructs.Price memory price); /// @notice Returns the price that is no older than `age` seconds of the current time. /// @dev This function is a sanity-checked version of `getPriceUnsafe` which is useful in /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently /// recently. /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. function getPriceNoOlderThan( bytes32 id, uint age ) external view returns (PythStructs.Price memory price); /// @notice Returns the exponentially-weighted moving average price of a price feed without any sanity checks. /// @dev This function returns the same price as `getEmaPrice` in the case where the price is available. /// However, if the price is not recent this function returns the latest available price. /// /// The returned price can be from arbitrarily far in the past; this function makes no guarantees that /// the returned price is recent or useful for any particular application. /// /// Users of this function should check the `publishTime` in the price to ensure that the returned price is /// sufficiently recent for their application. If you are considering using this function, it may be /// safer / easier to use either `getEmaPrice` or `getEmaPriceNoOlderThan`. /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. function getEmaPriceUnsafe( bytes32 id ) external view returns (PythStructs.Price memory price); /// @notice Returns the exponentially-weighted moving average price that is no older than `age` seconds /// of the current time. /// @dev This function is a sanity-checked version of `getEmaPriceUnsafe` which is useful in /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently /// recently. /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. function getEmaPriceNoOlderThan( bytes32 id, uint age ) external view returns (PythStructs.Price memory price); /// @notice Update price feeds with given update messages. /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling /// `getUpdateFee` with the length of the `updateData` array. /// Prices will be updated if they are more recent than the current stored prices. /// The call will succeed even if the update is not the most recent. /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid. /// @param updateData Array of price update data. function updatePriceFeeds(bytes[] calldata updateData) external payable; /// @notice Wrapper around updatePriceFeeds that rejects fast if a price update is not necessary. A price update is /// necessary if the current on-chain publishTime is older than the given publishTime. It relies solely on the /// given `publishTimes` for the price feeds and does not read the actual price update publish time within `updateData`. /// /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling /// `getUpdateFee` with the length of the `updateData` array. /// /// `priceIds` and `publishTimes` are two arrays with the same size that correspond to senders known publishTime /// of each priceId when calling this method. If all of price feeds within `priceIds` have updated and have /// a newer or equal publish time than the given publish time, it will reject the transaction to save gas. /// Otherwise, it calls updatePriceFeeds method to update the prices. /// /// @dev Reverts if update is not needed or the transferred fee is not sufficient or the updateData is invalid. /// @param updateData Array of price update data. /// @param priceIds Array of price ids. /// @param publishTimes Array of publishTimes. `publishTimes[i]` corresponds to known `publishTime` of `priceIds[i]` function updatePriceFeedsIfNecessary( bytes[] calldata updateData, bytes32[] calldata priceIds, uint64[] calldata publishTimes ) external payable; /// @notice Returns the required fee to update an array of price updates. /// @param updateData Array of price update data. /// @return feeAmount The required fee in Wei. function getUpdateFee( bytes[] calldata updateData ) external view returns (uint feeAmount); /// @notice Parse `updateData` and return price feeds of the given `priceIds` if they are all published /// within `minPublishTime` and `maxPublishTime`. /// /// You can use this method if you want to use a Pyth price at a fixed time and not the most recent price; /// otherwise, please consider using `updatePriceFeeds`. This method may store the price updates on-chain, if they /// are more recent than the current stored prices. /// /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling /// `getUpdateFee` with the length of the `updateData` array. /// /// /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is /// no update for any of the given `priceIds` within the given time range. /// @param updateData Array of price update data. /// @param priceIds Array of price ids. /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). function parsePriceFeedUpdates( bytes[] calldata updateData, bytes32[] calldata priceIds, uint64 minPublishTime, uint64 maxPublishTime ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds); /// @notice Similar to `parsePriceFeedUpdates` but ensures the updates returned are /// the first updates published in minPublishTime. That is, if there are multiple updates for a given timestamp, /// this method will return the first update. This method may store the price updates on-chain, if they /// are more recent than the current stored prices. /// /// /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is /// no update for any of the given `priceIds` within the given time range and uniqueness condition. /// @param updateData Array of price update data. /// @param priceIds Array of price ids. /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). function parsePriceFeedUpdatesUnique( bytes[] calldata updateData, bytes32[] calldata priceIds, uint64 minPublishTime, uint64 maxPublishTime ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds); }
node_modules/@pythnetwork/pyth-sdk-solidity/IPythEvents.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; /// @title IPythEvents contains the events that Pyth contract emits. /// @dev This interface can be used for listening to the updates for off-chain and testing purposes. interface IPythEvents { /// @dev Emitted when the price feed with `id` has received a fresh update. /// @param id The Pyth Price Feed ID. /// @param publishTime Publish time of the given price update. /// @param price Price of the given price update. /// @param conf Confidence interval of the given price update. event PriceFeedUpdate( bytes32 indexed id, uint64 publishTime, int64 price, uint64 conf ); }
node_modules/@pythnetwork/pyth-sdk-solidity/PythStructs.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; contract PythStructs { // A price with a degree of uncertainty, represented as a price +- a confidence interval. // // The confidence interval roughly corresponds to the standard error of a normal distribution. // Both the price and confidence are stored in a fixed-point numeric representation, // `x * (10^expo)`, where `expo` is the exponent. // // Please refer to the documentation at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for how // to how this price safely. struct Price { // Price int64 price; // Confidence interval around the price uint64 conf; // Price exponent int32 expo; // Unix timestamp describing when the price was published uint publishTime; } // PriceFeed represents a current aggregate price from pyth publisher feeds. struct PriceFeed { // The price ID. bytes32 id; // Latest available price Price price; // Latest available exponentially-weighted moving average price Price emaPrice; } }
src/base/BaseMarket.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; import {IBaseMarket} from "../interfaces/IBaseMarket.sol"; import {ICommonEvents} from "../interfaces/ICommonEvents.sol"; import {MarketId} from "../types/MarketId.sol"; import {Currency} from "../types/Currency.sol"; import {Option, OptionLibrary} from "../libraries/Option.sol"; import {Position} from "../libraries/Position.sol"; import {Market} from "../libraries/Market.sol"; import {Ownable} from "solady/auth/Ownable.sol"; abstract contract BaseMarket is IBaseMarket, ICommonEvents, Ownable { using OptionLibrary for Option; Currency public immutable currency; // @dev keeps track of total value locked to aid token recovery uint256 public tvl; /// @dev keeps track of each markets data mapping(MarketId id => Market.MarketInfo) public markets; constructor(Currency _currency) { currency = _currency; } /// @inheritdoc IBaseMarket function bullish(MarketId id, uint256 roundId, uint256 stake) external payable override { bytes32 positionId = _takePosition(id, roundId, msg.sender, uint128(stake), Option.wrap(1)); // try debiting the user _tryDebit(msg.sender, stake, msg.value); emit Bullish(id, roundId, msg.sender, positionId, stake); } /// @inheritdoc IBaseMarket function bearish(MarketId id, uint256 roundId, uint256 stake) external payable override { bytes32 positionId = _takePosition(id, roundId, msg.sender, uint128(stake), Option.wrap(0)); // try debiting the user _tryDebit(msg.sender, stake, msg.value); emit Bearish(id, roundId, msg.sender, positionId, stake); } /// @inheritdoc IBaseMarket function settle(MarketId id, uint256 roundId) external override { uint256 reward = _settle(id, roundId, msg.sender); // credit the user _tryCredit(msg.sender, reward); } /// @inheritdoc IBaseMarket function settleFor(MarketId id, uint256 roundId, address account) external override { uint256 reward = _settle(id, roundId, account); // credit the user _tryCredit(account, reward); } /// @inheritdoc IBaseMarket function settleBatch(MarketId id, uint256[] calldata roundIds) external override { uint256 len = roundIds.length; uint256 reward; for (uint256 i = 0; i < len;) { reward = _settle(id, roundIds[i], msg.sender); unchecked { ++i; } } // credit the user _tryCredit(msg.sender, reward); } /// @inheritdoc IBaseMarket function claimRefund(MarketId id, uint256 roundId) external override { Market.MarketInfo storage market = markets[id]; Market.Round storage round = market.rounds[roundId]; bytes32 positionId = Position.toId(id, roundId, msg.sender); Position.State storage position = market.positions[positionId]; // ensure the round is refunding if (round.status != Market.RoundStatus.REFUNDING) revert InvalidRoundStatus(); // ensure the user has not claimed before if (position.settled) revert RewardClaimed(); position.settled = true; // credit user stake _tryCredit(msg.sender, position.stake); emit ClaimRefund(id, roundId, msg.sender, positionId, position.stake); } /// @inheritdoc IBaseMarket function recoverToken(Currency token, address recipient, uint256 amount) external override onlyOwner { token.transfer(recipient, amount); /// @notice ensure recovery will not have negative impact on the total value locked uint256 balanceAfter = token.balanceOfSelf(); if (token == currency && balanceAfter < tvl) revert InsufficientBalance(); emit RecoverToken(token, recipient, amount); } /// @inheritdoc IBaseMarket function getRoundInfo(MarketId id, uint256 roundId, address account) external view override returns (Market.Round memory r, Position.State memory p) { Market.MarketInfo storage market = markets[id]; r = market.rounds[roundId]; bytes32 positionId = Position.toId(id, roundId, account); p = market.positions[positionId]; } /// @inheritdoc IBaseMarket function getPositionInfo(MarketId id, uint256 roundId, address account) external view returns (Position.State memory r) { Market.MarketInfo storage market = markets[id]; bytes32 positionId = Position.toId(id, roundId, account); r = market.positions[positionId]; } /** * ============================== Private/Internal functions ================================= */ function _takePosition(MarketId id, uint256 roundId, address account, uint128 stake, Option option) private returns (bytes32 positionId) { Market.MarketInfo storage market = markets[id]; Market.Round storage round = market.rounds[roundId]; // ensure market is initialized if (market.oracleId == bytes32(0)) revert MarketDoesNotExist(); // ensure round entry is open if (round.status != Market.RoundStatus.OPEN) revert EntryNotAllowed(); // ensure stake is not zero if (stake == 0) revert CannotStakeZeroAmount(); positionId = Position.toId(id, roundId, account); Position.State storage position = market.positions[positionId]; // ensure user has no previous position if (position.stake > 0) revert PositionAlreadyExist(); // if all checks well, open position // probably not going to overflow, even if Elon Musk stakes all his wealth, LOL! unchecked { round.totalShares += stake; if (option.isBullish()) { round.bullShares += stake; } else { round.bearShares += stake; } } position.stake = stake; position.option = option; } function _settle(MarketId id, uint256 roundId, address account) private returns (uint256 reward) { Market.MarketInfo storage market = markets[id]; Market.Round storage round = market.rounds[roundId]; bytes32 positionId = Position.toId(id, roundId, account); Position.State storage position = market.positions[positionId]; // ensure round has been settled if (round.status != Market.RoundStatus.RESOLVED) revert InvalidRoundStatus(); // ensure user has a valid position if (position.stake == 0) revert PositionNotFound(); // ensure user has not claimed reward already if (position.settled) revert RewardClaimed(); // tag claimed to avoid reentrancy position.settled = true; bool isRewardable; if (position.option.isBullish()) { isRewardable = round.closingPrice > round.priceMark; } else { isRewardable = round.closingPrice < round.priceMark; } if (isRewardable) { reward = ((position.stake * round.rewardPool) / round.winningShares); } else { revert SorryNoReward(); } emit Settle(id, roundId, account, positionId, reward); } /** * @dev allow transfer or crediting user balance * * @param account the address of user debit from * @param amount the amount to debit */ function _tryCredit(address account, uint256 amount) internal { unchecked { tvl -= amount; } currency.transfer(account, amount); } /** * @dev allow pulling or debiting user balance * * @param account the address of user debit from * @param amount the amount to debit * @param value the original message value */ function _tryDebit(address account, uint256 amount, uint256 value) internal { unchecked { tvl += amount; } if (currency.isNative()) { if (amount != value) revert InsufficientFee(); } else { currency.safeTransferFrom(account, address(this), amount); } } }
src/base/BaseMarketConfig.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; import {IBaseMarketConfig} from "../interfaces/IBaseMarketConfig.sol"; import {IPyth} from "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; import {Currency} from "../types/Currency.sol"; import {MarketId} from "../types/MarketId.sol"; import {Market} from "../libraries/Market.sol"; import {PythStructs} from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; import {BaseMarket} from "./BaseMarket.sol"; abstract contract BaseMarketConfig is IBaseMarketConfig, BaseMarket { uint16 public constant MAX_PROTOCOL_FEE = 1000; // 10% uint16 public constant BASIS_POINT = 10_000; uint8 public constant MAX_SECONDS_OFFSET = 10; uint256 public constant CAN_CANCEL_AFTER = 15 minutes; IPyth public immutable pyth; uint256 public protocolFeesAccrued; Market.Config public config; constructor(address _owner, address _pyth, Currency _currency) BaseMarket(_currency) { _initializeOwner(_owner); pyth = IPyth(_pyth); config.duration = uint48(5 minutes); config.incentiveFeeBps = 500; // 5% config.protocolFeeBps = 500; // 5% } /// @inheritdoc IBaseMarketConfig function createMarket(MarketId id, bytes calldata priceUpdate) external payable override { Market.MarketInfo storage market = markets[id]; bytes32 oracleId = MarketId.unwrap(id); // ensure market does not exist already if (market.oracleId != bytes32(0)) revert MarketAlreadyExist(); uint64 minPublishTime = uint64(block.timestamp - 5 minutes); // no use for the price, just to ascertain a priceFeed exists on-chain (int64 price,) = _fetchAssetPrice(oracleId, priceUpdate, minPublishTime, uint64(block.timestamp)); market.oracleId = MarketId.unwrap(id); emit CreateMarket(id, msg.sender); // start and lock the genesis round _startNextRound(market, id); // lock the genesis round _lockMarketRound(market, id, 1, price); // start n + 2 round _startNextRound(market, id); } /// @inheritdoc IBaseMarketConfig function collectProtocolFee(address recipient, uint256 amount) external override onlyOwner { uint256 amountCollected; amountCollected = (amount == 0) ? protocolFeesAccrued : amount; protocolFeesAccrued -= amountCollected; // credit the recipient _tryCredit(recipient, amountCollected); emit CollectFee(currency, msg.sender, recipient, amountCollected); } /// @inheritdoc IBaseMarketConfig function setProtocolFee(uint16 fee) external override onlyOwner { // ensure fee is moderate if (fee > MAX_PROTOCOL_FEE) revert FeeTooLarge(fee); config.protocolFeeBps = fee; emit SetProtocolFee(fee); } /// @inheritdoc IBaseMarketConfig function setMarketDuration(uint32 duration) external override onlyOwner { config.duration = duration; emit SetMarketDuration(duration); } /// @inheritdoc IBaseMarketConfig function setMinStakeAmount(uint256 minStakeAmount) external override onlyOwner { config.minStakeAmount = uint176(minStakeAmount); emit SetMinStakeAmount(minStakeAmount); } /// @inheritdoc IBaseMarketConfig function cancelRound(MarketId id, uint256 roundId, bytes calldata priceUpdate) external payable override { Market.MarketInfo storage market = markets[id]; Market.Round storage round = market.rounds[roundId]; // ensure the round status allows cancellation if (round.status != Market.RoundStatus.LIVE) revert InvalidRoundStatus(); // ensure users can only cancel when round cannot be locked/settled if (round.closingTime + CAN_CANCEL_AFTER > block.timestamp) revert ActionTooEarly(); round.status = Market.RoundStatus.REFUNDING; // fetch asset price (int64 price,) = _fetchAssetPrice(market.oracleId, priceUpdate, uint64(block.timestamp - 1 minutes), uint64(block.timestamp)); /// @dev we should also maintain the loop by // locking n + 1 round _lockMarketRound(market, id, roundId + 1, price); // start n + 2 round _startNextRound(market, id); emit CancelRound(id, roundId); } /** * ============================ Internal functions ================================== */ function _fetchAssetPrice( bytes32 priceFeedId, bytes calldata priceUpdateData, uint64 minPublishTime, uint64 maxPublishTime ) internal returns (int64 latestPrice, uint256 publishTime) { bytes32[] memory priceFeedIds = new bytes32[](1); priceFeedIds[0] = priceFeedId; bytes[] memory updateData = new bytes[](1); updateData[0] = priceUpdateData; uint256 updateFee = pyth.getUpdateFee(updateData); if (msg.value < updateFee) revert InsufficientFee(); PythStructs.PriceFeed[] memory priceFeeds = pyth.parsePriceFeedUpdates{value: updateFee}(updateData, priceFeedIds, minPublishTime, maxPublishTime); PythStructs.PriceFeed memory priceFeed = priceFeeds[0]; PythStructs.Price memory price = priceFeed.price; latestPrice = price.price; publishTime = price.publishTime; } function _startNextRound(Market.MarketInfo storage market, MarketId id) internal { uint256 roundId = ++market.roundId; uint64 openingTime = uint64(block.timestamp); uint64 closingTime = uint64(openingTime + (config.duration * 2)); Market.Round storage round = market.rounds[roundId]; round.status = Market.RoundStatus.OPEN; round.openingTime = openingTime; round.closingTime = closingTime; emit NewRound(id, roundId, openingTime, closingTime); } function _lockMarketRound(Market.MarketInfo storage market, MarketId id, uint256 roundId, int64 price) internal { Market.Round storage round = market.rounds[roundId]; uint64 closingTime = uint64(block.timestamp + config.duration); round.status = Market.RoundStatus.LIVE; round.closingTime = closingTime; round.priceMark = price; emit SetRoundPriceMark(id, roundId, price, closingTime); } }
src/interfaces/IBaseMarket.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; import {Currency} from "../types/Currency.sol"; import {MarketId} from "../types/MarketId.sol"; import {Market} from "../libraries/Market.sol"; import {Position} from "../libraries/Position.sol"; interface IBaseMarket { /// @notice Revert when trying to spend more than your available balance error InsufficientBalance(); /// @notice Revert when caller provides insufficient fee for Pyth calls error InsufficientFee(); /// @notice Revert when it is too early to perform an action error ActionTooEarly(); /// @notice Revert if market already exist error MarketAlreadyExist(); /// @notice Revert if round status does not meet action requirement error InvalidRoundStatus(); /// @notice Revert if reward has been claimed error RewardClaimed(); /// @notice Revert if no reward to claim error SorryNoReward(); /// @notice Revert when trying to interact with non-existing market error MarketDoesNotExist(); /// @notice Revert when a round entry is no longer allowed error EntryNotAllowed(); /// @notice Revert when trying to open a position double position error PositionAlreadyExist(); /// @notice Revert if the position has no stake error PositionNotFound(); /// @notice Revert when stake is zero error CannotStakeZeroAmount(); /// @notice Revert when trying to recover accepted token in cross-chain pools error CannotRecoverCurrency(); /** * @notice Called to place a bullish bet on a market * * @param id the identifier of market to open position in * @param roundId the unique id of the active round * @param stake the amount staked as wager for position */ function bullish(MarketId id, uint256 roundId, uint256 stake) external payable; /** * @notice Called to place a bearish bet on a market * * @param id the identifier of market to open position in * @param roundId the unique id of the active round * @param stake the amount staked as wager for position */ function bearish(MarketId id, uint256 roundId, uint256 stake) external payable; /** * @dev Allows owner to settle a position, more like claiming rewards accrued in `roundId` * * @param id market identifier * @param roundId round identifier */ function settle(MarketId id, uint256 roundId) external; /** * @dev Allows anyone to settle a position, more like claiming rewards accrued in `roundId` * @notice Anyone can claim onbehalf of someone else, opening door for automating claiming to improve UX * * @param id market identifier * @param roundId round identifier * @param account the position owner */ function settleFor(MarketId id, uint256 roundId, address account) external; /** * @dev Allows anyone to settle positions, more like claiming rewards accrued in `roundIds` * @notice Only the position owner can claim batch reward * * @param id market identifier * @param roundIds array of round identifiers */ function settleBatch(MarketId id, uint256[] calldata roundIds) external; /** * @dev Allows anyone to claim refund of cancelled round * @notice Can be called by onyone on behalf of someone else * * @param id market identifier * @param roundId round identifier */ function claimRefund(MarketId id, uint256 roundId) external; /** * @notice useful for recovering native/local tokens sent to the contract by mistake * * @param currency address of token to withdraw * @param recipient address of token receiver * @param amount amount of token to withdraw */ function recoverToken(Currency currency, address recipient, uint256 amount) external; /** * @dev Returns market round info * * @param id market identifier * @param roundId round identifier * @param account address to fetch position for * */ function getRoundInfo(MarketId id, uint256 roundId, address account) external view returns (Market.Round memory r, Position.State memory p); /** * @dev Returns market round info * * @param id market identifier * @param roundId round identifier * @return market market info * */ function getPositionInfo(MarketId id, uint256 roundId, address account) external view returns (Position.State memory); }
src/interfaces/IBaseMarketConfig.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; import {Currency} from "../types/Currency.sol"; import {MarketId} from "../types/MarketId.sol"; interface IBaseMarketConfig { /// @notice Revert when protocol fee exceeds maximum allowed error FeeTooLarge(uint16 fee); /** * @dev Emitted when the protocol manager collects accrued fees */ event CollectFee(Currency indexed currency, address indexed collector, address indexed recipient, uint256 amount); /** * @dev Emitted when a new protocol fee is set for market */ event SetProtocolFee(uint16 newFee); /** * @dev Emitted when a new resolver fee is set */ event SetResolverFee(uint16 newFee); /** * @dev Emitted when the market duration is updated */ event SetMarketDuration(uint32 duration); /** * @notice Emitted when the minimum stake for a market is updated */ event SetMinStakeAmount(uint256 minStakeAmount); /** * @dev Allows anyone to initialize a new market if it's not yet existing * * @param id Pyth oracle price feed identifier * @param priceUpdate pyth price update data */ function createMarket(MarketId id, bytes calldata priceUpdate) external payable; /** * @dev Allows protocol managers to collect accrued fee * * @param recipient address to forward fund to * @param amount amount */ function collectProtocolFee(address recipient, uint256 amount) external; /** * @dev Allows protocol managers to set fee for market * * @param fee the fee in BPS */ function setProtocolFee(uint16 fee) external; /** * @dev Allow managers to adjust how long a round lasts * * @param duration the new duration in seconds */ function setMarketDuration(uint32 duration) external; /** * @dev Allows protocol managers to set minimum stake amount * for market * * @param minStakeAmount the minimum allowed stake */ function setMinStakeAmount(uint256 minStakeAmount) external; /** * @dev Allows anyone to cancel round to allow users claim refund of stakes * @notice Calling this function will cancel round with `roundId`, locks round * with id roundId + 1 and equally starts a new round with id of roundId + 2 * * @param id market identifier * @param roundId id of round to cancel * @param priceUpdate pyth price update data */ function cancelRound(MarketId id, uint256 roundId, bytes calldata priceUpdate) external payable; }
src/interfaces/ICommonEvents.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; import {Currency} from "../types/Currency.sol"; import {MarketId} from "../types/MarketId.sol"; interface ICommonEvents { /** * @dev Emitted when a new prediction round is started */ event NewRound(MarketId indexed id, uint256 indexed roundId, uint256 openingTime, uint256 closingTime); /** * @dev Emitted when a round's price mark is locked */ event SetRoundPriceMark(MarketId indexed id, uint256 indexed roundId, int256 priceMark, uint256 closingTime); /** * @dev Emitted when a round is resolved */ event Resolve( MarketId indexed id, uint256 indexed roundId, uint256 rewardPool, uint256 totalWinningStake, int256 closingPrice, uint256 winningSide, bool isRefunding ); /** * @dev Emitted when a round is cancelled for any reason */ event CancelRound(MarketId indexed id, uint256 indexed roundId); /** * @dev Emitted whenever a new market is created successfully */ event CreateMarket(MarketId indexed id, address creator); /** * @dev Emiited when a user opens a bearish position on a market */ event Bearish( MarketId indexed id, uint256 indexed roundId, address indexed account, bytes32 positionId, uint256 stake ); /** * @dev Emitted when a user opens a bullish position on a market */ event Bullish( MarketId indexed id, uint256 indexed roundId, address indexed account, bytes32 positionId, uint256 stake ); /** * @dev Emitted when a user settles a position */ event Settle( MarketId indexed id, uint256 indexed roundId, address indexed account, bytes32 positionId, uint256 reward ); /** * @dev Emitted when a user claims refund refund from a cancelled round */ event ClaimRefund( MarketId indexed id, uint256 indexed roundId, address indexed account, bytes32 positionId, uint256 stake ); /// @notice Emitted when native/local tokens are recovered from the contract event RecoverToken(Currency indexed currency, address indexed recipient, uint256 amount); }
src/interfaces/IGrailMarket.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; import {MarketId} from "../types/MarketId.sol"; interface IGrailMarket { /** * @dev Allows anyone to resolve a prediction round * * @param id market identifier * @param roundId id of round to finalize * @param priceUpdate pyth price update data */ function resolve(MarketId id, uint256 roundId, bytes calldata priceUpdate) external payable; /** * @dev Allows protocol managers to set incentive fee for market round resolvers * * @notice As a permissionless decentralized protocol, we have resorted to incentivize * market resolvers. Anyone who successfully resolves a round, is entitled * to earn a rebate percentage of accrued protocol fee in that round. * * @param fee the fee in BPS */ function setResolverFee(uint16 fee) external; }
src/interfaces/external/IERC20.sol
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; /** * @title IERC20 * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the value of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the value of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves a `value` amount of tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 value) 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 a `value` amount of tokens 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 value) external returns (bool); /** * @dev Moves a `value` amount of tokens from `from` to `to` using the * allowance mechanism. `value` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 value) external returns (bool); }
src/libraries/Market.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; import {MarketId} from "../types/MarketId.sol"; import {Position} from "./Position.sol"; library Market { /// @dev possible state of a round enum RoundStatus { NOT_OPEN, OPEN, LIVE, RESOLVED, REFUNDING } /** * @notice A structure defining a market uniquely */ struct Config { /// the minimum amount that can be staked uint176 minStakeAmount; /// the duration of each market round in seconds uint48 duration; /// the incentive fee rewarded by this market for settling a round uint16 incentiveFeeBps; /// the protocol fee charged on this market uint16 protocolFeeBps; } /// @dev structure for market prediction round struct Round { /// unix timestamp of when market round opens uint64 openingTime; /// unix timestamp of when market round is due for finaliztion uint64 closingTime; /// asset closing price as obtained from the oracle int64 closingPrice; /// round locked price int64 priceMark; /// sum of all stakes in this round uint128 totalShares; /// sum of all bullish positions stakes uint128 bullShares; /// sum of all bearish positions stakes uint128 bearShares; /// total amount available for disbursment as reward uint128 rewardPool; /// total winning stakes uint128 winningShares; /// the status describing the round state RoundStatus status; } struct MarketInfo { /// keeps track of market last round id uint256 roundId; /// Pyth Network oracle price feed identifier bytes32 oracleId; /// keeps track of users positions in this market mapping(bytes32 positionId => Position.State) positions; /// keeps track of market rounds mapping(uint256 roundId => Round) rounds; } }
src/libraries/Option.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; type Option is uint8; library OptionLibrary { using OptionLibrary for Option; Option public constant BEARISH = Option.wrap(0); Option public constant BULLISH = Option.wrap(1); function isBearish(Option option) internal pure returns (bool) { return Option.unwrap(option) == Option.unwrap(BEARISH); } function isBullish(Option option) internal pure returns (bool) { return Option.unwrap(option) == Option.unwrap(BULLISH); } }
src/libraries/Position.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; import {MarketId} from "../types/MarketId.sol"; import {Option, OptionLibrary} from "./Option.sol"; library Position { using OptionLibrary for Option; /** * @dev State represents a structure for users current * prediction */ struct State { // The amount staked as wager for position uint240 stake; // The user's choice of either `Bearish` or `Bullish` Option option; // True if the option has been exercised or position reward claimed bool settled; } /** * @dev Returns true if a users position won otherwise false * * @param position The user position * @param closingPrice the market closing price * @param markPrice the prediction round mark price * @return rewardable true if position expired in the money */ function isRewardable(State memory position, int64 closingPrice, int64 markPrice) internal pure returns (bool rewardable) { if (position.option.isBullish()) { rewardable = closingPrice > markPrice; } else { rewardable = closingPrice < markPrice; } } /** * @dev Abi encoded `marketId`, `sequenceId` and `owner` * * @param id unique market identifier * @param sequenceId incremented sequence Id * @param owner address of Postion taker * * @return positionId Unique id for the users position in round */ function toId(MarketId id, uint256 sequenceId, address owner) internal pure returns (bytes32 positionId) { positionId = keccak256(abi.encodePacked(id, sequenceId, owner)); } }
src/types/Currency.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; import {IERC20} from "../interfaces/external/IERC20.sol"; type Currency is address; using {equals as ==} for Currency global; using CurrencyLibrary for Currency global; function equals(Currency currency, Currency other) pure returns (bool) { return Currency.unwrap(currency) == Currency.unwrap(other); } /** * @title CurrencyLibrary * @dev This library allows for transferring and holding native tokens and ERC20 tokens */ library CurrencyLibrary { /** * @notice Thrown when native transfer fails */ error NativeTransferFailed(); /// @notice Thrown when an ERC20 transfer fails error ERC20TransferFailed(); /** * @notice Thrown when an ERC20 TrasnaferFrom fails */ error ERC20TransferFromFailed(); /// @notice A constant to represent the native currency Currency public constant ADDRESS_ZERO = Currency.wrap(address(0)); function nativeTransfer(address to, uint256 amount) internal { bool success; assembly ("memory-safe") { // Transfer the ETH and revert if it fails. success := call(gas(), to, amount, 0, 0, 0, 0) } // revert with NativeTransferFailed if (!success) revert NativeTransferFailed(); } function tokenTransfer(Currency currency, address to, uint256 amount) internal { bool success; assembly ("memory-safe") { // Get a pointer to some free memory. let fmp := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(fmp, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) mstore(add(fmp, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(fmp, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), currency, 0, fmp, 68, 0, 32) ) // Now clean the memory we used mstore(fmp, 0) // 4 byte `selector` and 28 bytes of `to` were stored here mstore(add(fmp, 0x20), 0) // 4 bytes of `to` and 28 bytes of `amount` were stored here mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here } // revert with ERC20TransferFailed if (!success) revert ERC20TransferFailed(); } function transfer(Currency currency, address to, uint256 amount) internal { // altered from https://github.com/transmissions11/solmate/blob/44a9963d4c78111f77caa0e65d677b8b46d6f2e6/src/utils/SafeTransferLib.sol // modified custom error selectors if (currency.isNative()) { nativeTransfer(to, amount); } else { tokenTransfer(currency, to, amount); } } function safeTransferFrom(Currency currency, address from, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x60, amount) // Store the `amount` argument. mstore(0x40, to) // Store the `to` argument. mstore(0x2c, shl(96, from)) // Store the `from` argument. mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`. success := and( // The arguments of `and` are evaluated from right to left. or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing. call(gas(), currency, 0, 0x1c, 0x64, 0x00, 0x20) ) mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, m) // Restore the free memory pointer. } // revert with ERC20TransferFromFailed if (!success) revert ERC20TransferFromFailed(); } function balanceOfSelf(Currency currency) internal view returns (uint256) { if (currency.isNative()) { return address(this).balance; } else { return IERC20(Currency.unwrap(currency)).balanceOf(address(this)); } } function isNative(Currency currency) internal pure returns (bool) { return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO); } }
src/types/MarketId.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; type MarketId is bytes32; function equals(MarketId id, MarketId otherId) pure returns (bool) { return MarketId.unwrap(id) == MarketId.unwrap(otherId); } function notEquals(MarketId id, MarketId otherId) pure returns (bool) { return MarketId.unwrap(id) == MarketId.unwrap(otherId); } using { equals as ==, notEquals as != } for MarketId global;
Compiler Settings
{"viaIR":true,"remappings":["forge-std/=lib/forge-std/src/","solady/=lib/solady/src/","@pythnetwork/=node_modules/@pythnetwork/","hardhat/=node_modules/hardhat/","wormhole-solidity-sdk/=lib/wormhole-solidity-sdk/src/","ds-test/=lib/wormhole-solidity-sdk/lib/forge-std/lib/ds-test/src/"],"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers","metadata"]}},"optimizer":{"runs":44444444,"enabled":true},"metadata":{"useLiteralContent":false,"bytecodeHash":"ipfs","appendCBOR":true},"libraries":{},"evmVersion":"paris"}
Contract ABI
[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"address","name":"_owner","internalType":"address"},{"type":"address","name":"_pyth","internalType":"address"},{"type":"address","name":"_currency","internalType":"Currency"}]},{"type":"error","name":"ActionTooEarly","inputs":[]},{"type":"error","name":"AlreadyInitialized","inputs":[]},{"type":"error","name":"CannotRecoverCurrency","inputs":[]},{"type":"error","name":"CannotStakeZeroAmount","inputs":[]},{"type":"error","name":"ERC20TransferFailed","inputs":[]},{"type":"error","name":"ERC20TransferFromFailed","inputs":[]},{"type":"error","name":"EntryNotAllowed","inputs":[]},{"type":"error","name":"FeeTooLarge","inputs":[{"type":"uint16","name":"fee","internalType":"uint16"}]},{"type":"error","name":"InsufficientBalance","inputs":[]},{"type":"error","name":"InsufficientFee","inputs":[]},{"type":"error","name":"InvalidRoundStatus","inputs":[]},{"type":"error","name":"MarketAlreadyExist","inputs":[]},{"type":"error","name":"MarketDoesNotExist","inputs":[]},{"type":"error","name":"NativeTransferFailed","inputs":[]},{"type":"error","name":"NewOwnerIsZeroAddress","inputs":[]},{"type":"error","name":"NoHandoverRequest","inputs":[]},{"type":"error","name":"PositionAlreadyExist","inputs":[]},{"type":"error","name":"PositionNotFound","inputs":[]},{"type":"error","name":"RewardClaimed","inputs":[]},{"type":"error","name":"SorryNoReward","inputs":[]},{"type":"error","name":"Unauthorized","inputs":[]},{"type":"event","name":"Bearish","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId","indexed":true},{"type":"uint256","name":"roundId","internalType":"uint256","indexed":true},{"type":"address","name":"account","internalType":"address","indexed":true},{"type":"bytes32","name":"positionId","internalType":"bytes32","indexed":false},{"type":"uint256","name":"stake","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Bullish","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId","indexed":true},{"type":"uint256","name":"roundId","internalType":"uint256","indexed":true},{"type":"address","name":"account","internalType":"address","indexed":true},{"type":"bytes32","name":"positionId","internalType":"bytes32","indexed":false},{"type":"uint256","name":"stake","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"CancelRound","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId","indexed":true},{"type":"uint256","name":"roundId","internalType":"uint256","indexed":true}],"anonymous":false},{"type":"event","name":"ClaimRefund","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId","indexed":true},{"type":"uint256","name":"roundId","internalType":"uint256","indexed":true},{"type":"address","name":"account","internalType":"address","indexed":true},{"type":"bytes32","name":"positionId","internalType":"bytes32","indexed":false},{"type":"uint256","name":"stake","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"CollectFee","inputs":[{"type":"address","name":"currency","internalType":"Currency","indexed":true},{"type":"address","name":"collector","internalType":"address","indexed":true},{"type":"address","name":"recipient","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"CreateMarket","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId","indexed":true},{"type":"address","name":"creator","internalType":"address","indexed":false}],"anonymous":false},{"type":"event","name":"NewRound","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId","indexed":true},{"type":"uint256","name":"roundId","internalType":"uint256","indexed":true},{"type":"uint256","name":"openingTime","internalType":"uint256","indexed":false},{"type":"uint256","name":"closingTime","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"OwnershipHandoverCanceled","inputs":[{"type":"address","name":"pendingOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"OwnershipHandoverRequested","inputs":[{"type":"address","name":"pendingOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"type":"address","name":"oldOwner","internalType":"address","indexed":true},{"type":"address","name":"newOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"RecoverToken","inputs":[{"type":"address","name":"currency","internalType":"Currency","indexed":true},{"type":"address","name":"recipient","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Resolve","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId","indexed":true},{"type":"uint256","name":"roundId","internalType":"uint256","indexed":true},{"type":"uint256","name":"rewardPool","internalType":"uint256","indexed":false},{"type":"uint256","name":"totalWinningStake","internalType":"uint256","indexed":false},{"type":"int256","name":"closingPrice","internalType":"int256","indexed":false},{"type":"uint256","name":"winningSide","internalType":"uint256","indexed":false},{"type":"bool","name":"isRefunding","internalType":"bool","indexed":false}],"anonymous":false},{"type":"event","name":"SetMarketDuration","inputs":[{"type":"uint32","name":"duration","internalType":"uint32","indexed":false}],"anonymous":false},{"type":"event","name":"SetMinStakeAmount","inputs":[{"type":"uint256","name":"minStakeAmount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"SetProtocolFee","inputs":[{"type":"uint16","name":"newFee","internalType":"uint16","indexed":false}],"anonymous":false},{"type":"event","name":"SetResolverFee","inputs":[{"type":"uint16","name":"newFee","internalType":"uint16","indexed":false}],"anonymous":false},{"type":"event","name":"SetRoundPriceMark","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId","indexed":true},{"type":"uint256","name":"roundId","internalType":"uint256","indexed":true},{"type":"int256","name":"priceMark","internalType":"int256","indexed":false},{"type":"uint256","name":"closingTime","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Settle","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId","indexed":true},{"type":"uint256","name":"roundId","internalType":"uint256","indexed":true},{"type":"address","name":"account","internalType":"address","indexed":true},{"type":"bytes32","name":"positionId","internalType":"bytes32","indexed":false},{"type":"uint256","name":"reward","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"uint16","name":"","internalType":"uint16"}],"name":"BASIS_POINT","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"CAN_CANCEL_AFTER","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint16","name":"","internalType":"uint16"}],"name":"MAX_PROTOCOL_FEE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"uint8"}],"name":"MAX_SECONDS_OFFSET","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[],"name":"bearish","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"},{"type":"uint256","name":"roundId","internalType":"uint256"},{"type":"uint256","name":"stake","internalType":"uint256"}]},{"type":"function","stateMutability":"payable","outputs":[],"name":"bullish","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"},{"type":"uint256","name":"roundId","internalType":"uint256"},{"type":"uint256","name":"stake","internalType":"uint256"}]},{"type":"function","stateMutability":"payable","outputs":[],"name":"cancelOwnershipHandover","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[],"name":"cancelRound","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"},{"type":"uint256","name":"roundId","internalType":"uint256"},{"type":"bytes","name":"priceUpdate","internalType":"bytes"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"claimRefund","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"},{"type":"uint256","name":"roundId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"collectProtocolFee","inputs":[{"type":"address","name":"recipient","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"payable","outputs":[],"name":"completeOwnershipHandover","inputs":[{"type":"address","name":"pendingOwner","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint176","name":"minStakeAmount","internalType":"uint176"},{"type":"uint48","name":"duration","internalType":"uint48"},{"type":"uint16","name":"incentiveFeeBps","internalType":"uint16"},{"type":"uint16","name":"protocolFeeBps","internalType":"uint16"}],"name":"config","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[],"name":"createMarket","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"},{"type":"bytes","name":"priceUpdate","internalType":"bytes"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"Currency"}],"name":"currency","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple","name":"r","internalType":"struct Position.State","components":[{"type":"uint240","name":"stake","internalType":"uint240"},{"type":"uint8","name":"option","internalType":"Option"},{"type":"bool","name":"settled","internalType":"bool"}]}],"name":"getPositionInfo","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"},{"type":"uint256","name":"roundId","internalType":"uint256"},{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple","name":"r","internalType":"struct Market.Round","components":[{"type":"uint64","name":"openingTime","internalType":"uint64"},{"type":"uint64","name":"closingTime","internalType":"uint64"},{"type":"int64","name":"closingPrice","internalType":"int64"},{"type":"int64","name":"priceMark","internalType":"int64"},{"type":"uint128","name":"totalShares","internalType":"uint128"},{"type":"uint128","name":"bullShares","internalType":"uint128"},{"type":"uint128","name":"bearShares","internalType":"uint128"},{"type":"uint128","name":"rewardPool","internalType":"uint128"},{"type":"uint128","name":"winningShares","internalType":"uint128"},{"type":"uint8","name":"status","internalType":"enum Market.RoundStatus"}]},{"type":"tuple","name":"p","internalType":"struct Position.State","components":[{"type":"uint240","name":"stake","internalType":"uint240"},{"type":"uint8","name":"option","internalType":"Option"},{"type":"bool","name":"settled","internalType":"bool"}]}],"name":"getRoundInfo","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"},{"type":"uint256","name":"roundId","internalType":"uint256"},{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"roundId","internalType":"uint256"},{"type":"bytes32","name":"oracleId","internalType":"bytes32"}],"name":"markets","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"result","internalType":"address"}],"name":"owner","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"result","internalType":"uint256"}],"name":"ownershipHandoverExpiresAt","inputs":[{"type":"address","name":"pendingOwner","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"protocolFeesAccrued","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract IPyth"}],"name":"pyth","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"recoverToken","inputs":[{"type":"address","name":"token","internalType":"Currency"},{"type":"address","name":"recipient","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"payable","outputs":[],"name":"renounceOwnership","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[],"name":"requestOwnershipHandover","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[],"name":"resolve","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"},{"type":"uint256","name":"roundId","internalType":"uint256"},{"type":"bytes","name":"priceUpdate","internalType":"bytes"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setMarketDuration","inputs":[{"type":"uint32","name":"duration","internalType":"uint32"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setMinStakeAmount","inputs":[{"type":"uint256","name":"minStakeAmount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setProtocolFee","inputs":[{"type":"uint16","name":"fee","internalType":"uint16"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setResolverFee","inputs":[{"type":"uint16","name":"fee","internalType":"uint16"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"settle","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"},{"type":"uint256","name":"roundId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"settleBatch","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"},{"type":"uint256[]","name":"roundIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"settleFor","inputs":[{"type":"bytes32","name":"id","internalType":"MarketId"},{"type":"uint256","name":"roundId","internalType":"uint256"},{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"payable","outputs":[],"name":"transferOwnership","inputs":[{"type":"address","name":"newOwner","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"tvl","inputs":[]}]
Contract Creation Code
0x60c0346200012957601f6200324138819003918201601f19168301916001600160401b038311848410176200012e5780849260609460405283398101031262000129576200004d8162000144565b906200006a6040620000626020840162000144565b920162000144565b6080526001600160a01b03918216638b78c6d81981905560007f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a31660a052600380546001600160b01b0316687d007d00000000004b60b21b1790556040516130e790816200015a8239608051818181610ca401528181610ec8015281816111560152818161143d0152818161198d01528181611c0401528181611d4501528181611eec015261265d015260a05181818161056d015261296a0152f35b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b0382168203620001295756fe60808060405260048036101561001457600080fd5b60003560e01c91826310aa4e8f14611f105750816312b437db14611ec357816320758f6414611df85781632569296214611d8e578163347ec17d14611cb857816335b00f1d14611c7e5781633cff986014611b9a57816342dcf2a214611acb57816354d1f13d14611a655781635a15b573146118a85781635d5910c41461166d578163715018a6146115ee5781637564912b1461159d57816375f2448d146114de57816379502c551461146357816379f48d4c146114175781638da5cb5b146113a65781638da9fa271461136b578163972571ae146111e6578163a7229fd9146110d5578163ada5f6421461109a578163b621e75a1461105e578163b8ca3b8314611023578163e4467f3514610f28578163e5328e0614610eec578163e5a6b10f14610e7d578163e79d83ab14610a36578163eb4af0451461098f578163ef9162861461070a578163f04e283e14610643578163f2fde38b14610591578163f98d06f014610522578163fad71835146101ed575063fee81cf41461019757600080fd5b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576101ce6120ce565b63389a75e1600c52600052602080600c2054604051908152f35b600080fd5b346101e8576101fb366120f1565b600061012060405161020c81612305565b8281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152015261024a61237f565b50826000526001602052604060002091806000526003830160205260406000209360036040519561027a87612305565b805467ffffffffffffffff8116885267ffffffffffffffff8160401c1660208901528060801c60070b604089015260c01d60070b606088015260018101546fffffffffffffffffffffffffffffffff8116608089015260801c60a088015260028101546fffffffffffffffffffffffffffffffff811660c089015260801c60e088015201546fffffffffffffffffffffffffffffffff8116610100870152600560ff8260801c1610156104f45791600294939160ff6103429460801c16610120880152612ec6565b600052016020526040600020916040519261035c846122ba565b547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116845260ff8160f01c16602085015260f81c151560408401526101206040519267ffffffffffffffff815116845267ffffffffffffffff6020820151166020850152604081015160070b6040850152606081015160070b60608501526fffffffffffffffffffffffffffffffff60808201511660808501526fffffffffffffffffffffffffffffffff60a08201511660a08501526fffffffffffffffffffffffffffffffff60c08201511660c08501526fffffffffffffffffffffffffffffffff60e08201511660e08501526fffffffffffffffffffffffffffffffff6101008201511661010085015201519060058210156104c6575061012082015281517dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16610140820152602082015160ff1661016082015260409091015115156101808201526101a090f35bf35b6021907f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b6021877f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576105c36120ce565b6105cb6121fd565b8060601b156106365773ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a355005b50637448fbae600052601cfd5b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576106756120ce565b61067d6121fd565b63389a75e1600c52806000526020600c2091825442116106fe5750600073ffffffffffffffffffffffffffffffffffffffff9255167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a355005b636f5e8818600052601cfd5b61071336612096565b6fffffffffffffffffffffffffffffffff92919293848216836000526001602052604060002095856000526003870160205260406000206001880154156109665760ff600382015460801c1660058110156109385760010361090f5782156108e6576002610782338989612ec6565b9889600052016020526040600020937dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8554166108be57507e01000000000000000000000000000000000000000000000000000000000000929160016108589201908382547fffffffffffffffffffffffffffffffff000000000000000000000000000000008383818416011691161780845560801c01166fffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffff0000000000000000000000000000000083549260801b169116179055565b7fff000000000000000000000000000000000000000000000000000000000000008354161717905561088b348233612652565b60405193845260208401527f76be093b9f8b5c898a6038f68c84b4dc903bc2fbefdc455a35a86fbe0875acc860403394a4005b6040517f68cf1349000000000000000000000000000000000000000000000000000000008152fd5b836040517f3c4a9486000000000000000000000000000000000000000000000000000000008152fd5b836040517f0ea6e1e7000000000000000000000000000000000000000000000000000000008152fd5b6021857f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b836040517fb0cfa447000000000000000000000000000000000000000000000000000000008152fd5b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760207f6e4bcc82cb4b918df6b911c8092c49f027b7d409733e2c97bb0a4b5a367e19db91356109eb6121fd565b75ffffffffffffffffffffffffffffffffffffffffffff81167fffffffffffffffffffff000000000000000000000000000000000000000000006003541617600355604051908152a1005b610a3f366121a8565b83600095929395526001918260205260406000208660005260038101602052604060002092600384019560ff875460801c16600581101561093857600203610e545767ffffffffffffffff9182865460401c1691428311610e2b5787850154600a8401948511610dfd5790610ab694939291612895565b509184880190818911610dcf575091610b018783610afc8460a09997847fac40c95b92f792b83170dfdb011f9e519b63f5990b694950d73c48ca504dd52f9c9a98612d96565b612c4d565b600092600060028401906fffffffffffffffffffffffffffffffff9283835416151580610dc0575b15610d385780860184815416928560035494610bbd610b7c610b5c84610b6961ffff8b8361271095869260f01c8a612271565b16049b60e01c168b612271565b160497610b768982612298565b93612298565b88546fffffffffffffffffffffffffffffffff1660809190911b7fffffffffffffffffffffffffffffffff0000000000000000000000000000000016178855565b1660025401600255875460c01d60070b8760070b818113600014610cf15750505091610c98868693610cc8955460801c7fffffffffffffffffffffffffffffffff000000000000000000000000000000008d5416178c55985b7003000000000000000000000000000000007fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff8d5416178c55907fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff82549160801b77ffffffffffffffff00000000000000000000000000000000169116179055565b168060005403600055337f0000000000000000000000000000000000000000000000000000000000000000612f26565b5460801c945416604051948552602085015260070b6040840152606083015215156080820152a3005b879450610cc8959350988891610c98939a1215610c1657848754167fffffffffffffffffffffffffffffffff000000000000000000000000000000008d5416178c55610c16565b87547fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff1670040000000000000000000000000000000017885585547fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff1677ffffffffffffffff00000000000000000000000000000000608087901b1617909555939450610cc8565b508086015460801c1515610b29565b6011907f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b6011877f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b856040517fde8f5316000000000000000000000000000000000000000000000000000000008152fd5b836040517fc38434ab000000000000000000000000000000000000000000000000000000008152fd5b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576020600054604051908152f35b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857803561ffff8116918282036101e857610f6e6121fd565b6103e88311610ff3577f5aa89d2018da401c466211564e2994895ca1dc68df9d55db7482e437dadc0811602084847dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffff0000000000000000000000000000000000000000000000000000000000006003549260f01b16911617600355604051908152a1005b60249083604051917f35a30751000000000000000000000000000000000000000000000000000000008352820152fd5b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760206040516103e88152f35b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576020600254604051908152f35b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760206040516127108152f35b346101e85760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85761110c6120ce565b9060243573ffffffffffffffffffffffffffffffffffffffff91828216938483036101e85761114760443580946111416121fd565b83612f26565b836111518261300e565b9116937f000000000000000000000000000000000000000000000000000000000000000016841490816111da575b506111b2575060207f16a1412f01b73c390eb2548427101644aa86c1443c272f73df00fb74c48fe49991604051908152a3005b6040517ff4d678b8000000000000000000000000000000000000000000000000000000008152fd5b9050600054118561117f565b6111ef366121a8565b83600095929395526001602052604060002092856000526003840160205260406000209160038301805460ff8160801c1660058110156104f4576002036113425767ffffffffffffffff80955460401c166103848101809111610dfd574210610e2b577fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff70040000000000000000000000000000000091161790556001850154917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc4420193428511611314576112cd94428216949116929190612895565b50906001850190818611610dcf575091610afc84926112ed948484612d96565b7f4be96238c3feeb1d8156e058500c5d1f6d147bb1c234e2019998bfbabe2b4a5c600080a3005b6011867f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b856040517fc38434ab000000000000000000000000000000000000000000000000000000008152fd5b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760206040516103848152f35b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760207fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739275473ffffffffffffffffffffffffffffffffffffffff60405191168152f35b346101e85761146161143261142b36612146565b339161239e565b8060005403600055337f0000000000000000000000000000000000000000000000000000000000000000612f26565b005b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760806003546040519075ffffffffffffffffffffffffffffffffffffffffffff8116825265ffffffffffff8160b01c16602083015261ffff8160e01c16604083015260f01c6060820152f35b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8573563ffffffff8116908181036101e8577f50e5c1c885ea2510512f889012b87e95d7696281a6c2985c6c7d45fda6d792d59160209161154a6121fd565b7fffffffff000000000000ffffffffffffffffffffffffffffffffffffffffffff79ffffffff000000000000000000000000000000000000000000006003549260b01b16911617600355604051908152a1005b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8573560005260016020526040806000206001815491015482519182526020820152f35b60007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576116206121fd565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a355005b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85780356024359167ffffffffffffffff928381116101e8576116bb903690830161217a565b9390918360005260016020526040600020926001840195865461187f577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed4420192428411610dcf575061146196848761174381967fccd5d394b6c44121d18f7dc9af1472e400cd79e3318c578aca4eeacb5d9df7a4966040968560019b421693169185612895565b509255847fb695ede53322ecf2e50fbc58c9cd70baa3835819be7d0c523a69efcab3c44bf560208551338152a261177a8588612c4d565b856000526003870160205282600020906117a165ffffffffffff60035460b01c1642612235565b1690600381017002000000000000000000000000000000007fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff8254161790556118268282907fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff6fffffffffffffffff000000000000000083549260401b169116179055565b805477ffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffff0000000000000000000000000000000000000000000000008560c01b16911617905582519160070b82526020820152a3612c4d565b826040517fde3c363f000000000000000000000000000000000000000000000000000000008152fd5b346101e8576118b636612146565b908060005260016020526040600020928260005260038401602052604060002060026118e3338686612ec6565b95866000520160205260ff6003604060002092015460801c166005811015611a37578203611a0e578054918260f81c6119e657507f01000000000000000000000000000000000000000000000000000000000000007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83161781556119b17dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8093168060005403600055337f0000000000000000000000000000000000000000000000000000000000000000612f26565b541660405193845260208401527ffb75c09f4826c289f841e35b10c86b0e385d3f01ff49cfc12d5626d24645f64a60403394a4005b6040517f4f9e331b000000000000000000000000000000000000000000000000000000008152fd5b506040517fc38434ab000000000000000000000000000000000000000000000000000000008152fd5b6021837f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b60007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85763389a75e1600c523360005260006020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92600080a2005b346101e8576002611aff611ade366120f1565b90611aea93929361237f565b50836000526001602052604060002093612ec6565b600052016020526060604060002060405190611b1a826122ba565b547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116825260ff8160f01c16602083015260f81c151560408201526104c46040518092604080917dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff815116845260ff602082015116602085015201511515910152565b346101e85760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857611bd16120ce565b90602435611bdd6121fd565b80611c785750600254905b60025490828203918211610dcf575060025580600054036000557f000000000000000000000000000000000000000000000000000000000000000090611c2f818484612f26565b6040519081527f4faa6fbacd77a523e80ea39326e5d792fb633131f897c119ec498d60f4bd68f9602073ffffffffffffffffffffffffffffffffffffffff8095169433941692a4005b90611be8565b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576020604051600a8152f35b346101e85760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857602480359167ffffffffffffffff908035908285116101e857366023860112156101e8578401359182116101e857600590368484841b870101116101e8579291906000936000935b838510611d6957611461868060005403600055337f0000000000000000000000000000000000000000000000000000000000000000612f26565b9091929394506001611d83338388871b8a0101358561239e565b950193929190611d2f565b60007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85763389a75e1600c52336000526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d600080a2005b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857803561ffff8116918282036101e857611e3e6121fd565b6103e88311610ff3577f5aa89d2018da401c466211564e2994895ca1dc68df9d55db7482e437dadc0811602084847fffff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff7dffff000000000000000000000000000000000000000000000000000000006003549260e01b16911617600355604051908152a1005b346101e857611461611ee1611ed7366120f1565b929190839161239e565b9081600054036000557f0000000000000000000000000000000000000000000000000000000000000000612f26565b90611f1a36612096565b90926fffffffffffffffffffffffffffffffff9485831690846000526001602052604060002096866000526003880160205260406000209160018901541561206f575060ff600383015460801c1660058110156109385760010361090f5782156108e6576002611f8b338989612ec6565b9889600052016020526040600020937dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8554166108be5750600260018301928354937fffffffffffffffffffffffffffffffff000000000000000000000000000000009485858881841601169116179055019182549184818416011691161790557fff0000000000000000000000000000000000000000000000000000000000000082541617905561203c348233612652565b60405193845260208401527facad006dfb05c480e68abb20c66f13c27f5971dd27fb0e3fa9e4f0f6d1a3abee60403394a4005b807fb0cfa44700000000000000000000000000000000000000000000000000000000869252fd5b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60609101126101e857600435906024359060443590565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036101e857565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60609101126101e857600435906024359060443573ffffffffffffffffffffffffffffffffffffffff811681036101e85790565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60409101126101e8576004359060243590565b9181601f840112156101e85782359167ffffffffffffffff83116101e857602083818601950101116101e857565b60607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8201126101e85760043591602435916044359067ffffffffffffffff82116101e8576121f99160040161217a565b9091565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7487392754330361222757565b6382b429006000526004601cfd5b9190820180921161224257565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9190916fffffffffffffffffffffffffffffffff8080941691160291821691820361224257565b6fffffffffffffffffffffffffffffffff918216908216039190821161224257565b6060810190811067ffffffffffffffff8211176122d657604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610140810190811067ffffffffffffffff8211176122d657604052565b6040810190811067ffffffffffffffff8211176122d657604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176122d657604052565b6040519061238c826122ba565b60006040838281528260208201520152565b909291926000918083526001602052604080842093838152600385016020528181209060026123ce898787612ec6565b9687835201602052828120916003810160ff815460801c166005811015612625576003036125fc578354917dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff948584169384156125d3578060f81c6125aa577effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0100000000000000000000000000000000000000000000000000000000000000179081905560f01c60ff166001036125945780548060c01d60070b9060801c60070b135b1561256b576002015460801c84818402169283040361253e57546fffffffffffffffffffffffffffffffff16918215612511575083519687520416602085018190529573ffffffffffffffffffffffffffffffffffffffff16937f31295a2f764b13b07abb49b6fd4fe8a7ca220b09c72570d04cd947871c999d1a9190a4565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526012600452fd5b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b600486517f4f97cab6000000000000000000000000000000000000000000000000000000008152fd5b80548060c01d60070b9060801c60070b12612491565b600488517f4f9e331b000000000000000000000000000000000000000000000000000000008152fd5b600488517f6ec9be11000000000000000000000000000000000000000000000000000000008152fd5b600485517fc38434ab000000000000000000000000000000000000000000000000000000008152fd5b6024847f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b6000805483018155907f00000000000000000000000000000000000000000000000000000000000000009073ffffffffffffffffffffffffffffffffffffffff82166126cc57505050036126a257565b60046040517f025dbdd4000000000000000000000000000000000000000000000000000000008152fd5b82939450601c8360209493606493604051986060523060405260601b602c526f23b872dd000000000000000000000000600c525af13d156001835114171690606052816040521561271a5750565b807fa512d51e0000000000000000000000000000000000000000000000000000000060049252fd5b80511561274f5760200190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9080825180825260208092019180808360051b86010195019360009384915b8483106127ae575050505050505090565b90919293949596847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08084840301855289518051908185528a5b82811061281457505080840183018a9052601f011690910181019781019695949360010192019061279d565b8181018501518682018601528994016127e8565b91908260809103126101e85760405167ffffffffffffffff9260808201848111838210176122d657604052819381518060070b81036101e8578352602082015190811681036101e857602083015260408101518060030b81036101e8576040830152606090810151910152565b92939190916040938451906128a982612322565b60019283835260209788840192893685376128c385612742565b528751966128d088612322565b8588528960005b818110612c3d57505067ffffffffffffffff968781116122d6578951916129258c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f850116018461233e565b81835236828201116101e857816000928d92838601378301015261294888612742565b5261295287612742565b5073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016948851947fd47eed450000000000000000000000000000000000000000000000000000000086528a60048701528a86806129c7602482018d61277e565b03818a5afa958615612c3257600096612c00575b50853410612bd757918792612a2899969594928c8c519b8c997f4716e9c5000000000000000000000000000000000000000000000000000000008b52608060048c015260848b019061277e565b8981037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0160248b015292518084529201959160008e5b828210612bbd5750505050908316604487015250166064840152600093908390039183915af1918215612bb257600092612ab1575b5050612aa09150612742565b510151906060825160070b92015190565b90913d8082843e612ac2818461233e565b8201918581840312612b7d57805190848211612bae57019180601f84011215612b7d578251938411612b8157845194612b00878660051b018761233e565b848652868601928761012080970286010194838611612b7d578801935b858510612b365750505050505050612aa0903880612a94565b8685850312612b7d578887918451612b4d816122ba565b87518152612b5d87848a01612828565b83820152612b6e8760a08a01612828565b86820152815201940193612b1d565b5080fd5b6024827f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b8280fd5b83513d6000823e3d90fd5b845189528d9a50978801978c97509093019282018e612a5f565b60048a517f025dbdd4000000000000000000000000000000000000000000000000000000008152fd5b90958b82813d8311612c2b575b612c17818361233e565b81010312612c2857505194386129db565b80fd5b503d612c0d565b8a513d6000823e3d90fd5b606082828c010152018a906128d7565b9081547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114612242576001019182815567ffffffffffffffff9081421660035460af1c6601fffffffffffe65fffffffffffe82169116810361224257810192808411612242576000868152600393840160205260409081902093840180547fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff1670010000000000000000000000000000000017905583547fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff84167fffffffffffffffffffffffffffffffff000000000000000000000000000000009091161785821b6fffffffffffffffff000000000000000016179093557f30745dda6a7b321538e5fc06390354a4f93646c86e3d8d29dad0b0beeff58098938351928352166020820152a3565b7fccd5d394b6c44121d18f7dc9af1472e400cd79e3318c578aca4eeacb5d9df7a4919293600360409286600052016020528160002067ffffffffffffffff612deb65ffffffffffff60035460b01c1642612235565b1690600381017002000000000000000000000000000000007fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff825416179055612e708282907fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff6fffffffffffffffff000000000000000083549260401b169116179055565b805477ffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffff0000000000000000000000000000000000000000000000008560c01b16911617905582519160070b82526020820152a3565b917fffffffffffffffffffffffffffffffffffffffff000000000000000000000000906040519260208401948552604084015260601b166060820152605481526080810181811067ffffffffffffffff8211176122d65760405251902090565b73ffffffffffffffffffffffffffffffffffffffff90808216612f7f575050600080809381935af115612f5557565b60046040517ff4b3b1bc000000000000000000000000000000000000000000000000000000008152fd5b6020906040929483519586957fa9059cbb000000000000000000000000000000000000000000000000000000008752166004860152602485015260446000948580935af13d15601f3d11600185511416171692828152826020820152015215612fe457565b60046040517ff27f64e4000000000000000000000000000000000000000000000000000000008152fd5b73ffffffffffffffffffffffffffffffffffffffff168061302e57504790565b6020602491604051928380927f70a082310000000000000000000000000000000000000000000000000000000082523060048301525afa9081156130a557600091613077575090565b906020823d821161309d575b816130906020938361233e565b81010312612c2857505190565b3d9150613083565b6040513d6000823e3d90fdfea2646970667358221220c8b84f57112d19e1a7c44a61808e583205ee5d1dc30194a4ae7e43c7ff74620d64736f6c63430008140033000000000000000000000000bcb5fd84cc71cedc15b4651f18d912f816b61d330000000000000000000000002880ab155794e7179c9ee2e38200202908c17b430000000000000000000000000000000000000000000000000000000000000000
Deployed ByteCode
0x60808060405260048036101561001457600080fd5b60003560e01c91826310aa4e8f14611f105750816312b437db14611ec357816320758f6414611df85781632569296214611d8e578163347ec17d14611cb857816335b00f1d14611c7e5781633cff986014611b9a57816342dcf2a214611acb57816354d1f13d14611a655781635a15b573146118a85781635d5910c41461166d578163715018a6146115ee5781637564912b1461159d57816375f2448d146114de57816379502c551461146357816379f48d4c146114175781638da5cb5b146113a65781638da9fa271461136b578163972571ae146111e6578163a7229fd9146110d5578163ada5f6421461109a578163b621e75a1461105e578163b8ca3b8314611023578163e4467f3514610f28578163e5328e0614610eec578163e5a6b10f14610e7d578163e79d83ab14610a36578163eb4af0451461098f578163ef9162861461070a578163f04e283e14610643578163f2fde38b14610591578163f98d06f014610522578163fad71835146101ed575063fee81cf41461019757600080fd5b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576101ce6120ce565b63389a75e1600c52600052602080600c2054604051908152f35b600080fd5b346101e8576101fb366120f1565b600061012060405161020c81612305565b8281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152015261024a61237f565b50826000526001602052604060002091806000526003830160205260406000209360036040519561027a87612305565b805467ffffffffffffffff8116885267ffffffffffffffff8160401c1660208901528060801c60070b604089015260c01d60070b606088015260018101546fffffffffffffffffffffffffffffffff8116608089015260801c60a088015260028101546fffffffffffffffffffffffffffffffff811660c089015260801c60e088015201546fffffffffffffffffffffffffffffffff8116610100870152600560ff8260801c1610156104f45791600294939160ff6103429460801c16610120880152612ec6565b600052016020526040600020916040519261035c846122ba565b547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116845260ff8160f01c16602085015260f81c151560408401526101206040519267ffffffffffffffff815116845267ffffffffffffffff6020820151166020850152604081015160070b6040850152606081015160070b60608501526fffffffffffffffffffffffffffffffff60808201511660808501526fffffffffffffffffffffffffffffffff60a08201511660a08501526fffffffffffffffffffffffffffffffff60c08201511660c08501526fffffffffffffffffffffffffffffffff60e08201511660e08501526fffffffffffffffffffffffffffffffff6101008201511661010085015201519060058210156104c6575061012082015281517dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16610140820152602082015160ff1661016082015260409091015115156101808201526101a090f35bf35b6021907f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b6021877f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000002880ab155794e7179c9ee2e38200202908c17b43168152f35b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576105c36120ce565b6105cb6121fd565b8060601b156106365773ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a355005b50637448fbae600052601cfd5b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576106756120ce565b61067d6121fd565b63389a75e1600c52806000526020600c2091825442116106fe5750600073ffffffffffffffffffffffffffffffffffffffff9255167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a355005b636f5e8818600052601cfd5b61071336612096565b6fffffffffffffffffffffffffffffffff92919293848216836000526001602052604060002095856000526003870160205260406000206001880154156109665760ff600382015460801c1660058110156109385760010361090f5782156108e6576002610782338989612ec6565b9889600052016020526040600020937dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8554166108be57507e01000000000000000000000000000000000000000000000000000000000000929160016108589201908382547fffffffffffffffffffffffffffffffff000000000000000000000000000000008383818416011691161780845560801c01166fffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffff0000000000000000000000000000000083549260801b169116179055565b7fff000000000000000000000000000000000000000000000000000000000000008354161717905561088b348233612652565b60405193845260208401527f76be093b9f8b5c898a6038f68c84b4dc903bc2fbefdc455a35a86fbe0875acc860403394a4005b6040517f68cf1349000000000000000000000000000000000000000000000000000000008152fd5b836040517f3c4a9486000000000000000000000000000000000000000000000000000000008152fd5b836040517f0ea6e1e7000000000000000000000000000000000000000000000000000000008152fd5b6021857f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b836040517fb0cfa447000000000000000000000000000000000000000000000000000000008152fd5b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760207f6e4bcc82cb4b918df6b911c8092c49f027b7d409733e2c97bb0a4b5a367e19db91356109eb6121fd565b75ffffffffffffffffffffffffffffffffffffffffffff81167fffffffffffffffffffff000000000000000000000000000000000000000000006003541617600355604051908152a1005b610a3f366121a8565b83600095929395526001918260205260406000208660005260038101602052604060002092600384019560ff875460801c16600581101561093857600203610e545767ffffffffffffffff9182865460401c1691428311610e2b5787850154600a8401948511610dfd5790610ab694939291612895565b509184880190818911610dcf575091610b018783610afc8460a09997847fac40c95b92f792b83170dfdb011f9e519b63f5990b694950d73c48ca504dd52f9c9a98612d96565b612c4d565b600092600060028401906fffffffffffffffffffffffffffffffff9283835416151580610dc0575b15610d385780860184815416928560035494610bbd610b7c610b5c84610b6961ffff8b8361271095869260f01c8a612271565b16049b60e01c168b612271565b160497610b768982612298565b93612298565b88546fffffffffffffffffffffffffffffffff1660809190911b7fffffffffffffffffffffffffffffffff0000000000000000000000000000000016178855565b1660025401600255875460c01d60070b8760070b818113600014610cf15750505091610c98868693610cc8955460801c7fffffffffffffffffffffffffffffffff000000000000000000000000000000008d5416178c55985b7003000000000000000000000000000000007fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff8d5416178c55907fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff82549160801b77ffffffffffffffff00000000000000000000000000000000169116179055565b168060005403600055337f0000000000000000000000000000000000000000000000000000000000000000612f26565b5460801c945416604051948552602085015260070b6040840152606083015215156080820152a3005b879450610cc8959350988891610c98939a1215610c1657848754167fffffffffffffffffffffffffffffffff000000000000000000000000000000008d5416178c55610c16565b87547fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff1670040000000000000000000000000000000017885585547fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff1677ffffffffffffffff00000000000000000000000000000000608087901b1617909555939450610cc8565b508086015460801c1515610b29565b6011907f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b6011877f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b856040517fde8f5316000000000000000000000000000000000000000000000000000000008152fd5b836040517fc38434ab000000000000000000000000000000000000000000000000000000008152fd5b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576020600054604051908152f35b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857803561ffff8116918282036101e857610f6e6121fd565b6103e88311610ff3577f5aa89d2018da401c466211564e2994895ca1dc68df9d55db7482e437dadc0811602084847dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffff0000000000000000000000000000000000000000000000000000000000006003549260f01b16911617600355604051908152a1005b60249083604051917f35a30751000000000000000000000000000000000000000000000000000000008352820152fd5b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760206040516103e88152f35b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576020600254604051908152f35b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760206040516127108152f35b346101e85760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85761110c6120ce565b9060243573ffffffffffffffffffffffffffffffffffffffff91828216938483036101e85761114760443580946111416121fd565b83612f26565b836111518261300e565b9116937f000000000000000000000000000000000000000000000000000000000000000016841490816111da575b506111b2575060207f16a1412f01b73c390eb2548427101644aa86c1443c272f73df00fb74c48fe49991604051908152a3005b6040517ff4d678b8000000000000000000000000000000000000000000000000000000008152fd5b9050600054118561117f565b6111ef366121a8565b83600095929395526001602052604060002092856000526003840160205260406000209160038301805460ff8160801c1660058110156104f4576002036113425767ffffffffffffffff80955460401c166103848101809111610dfd574210610e2b577fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff70040000000000000000000000000000000091161790556001850154917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc4420193428511611314576112cd94428216949116929190612895565b50906001850190818611610dcf575091610afc84926112ed948484612d96565b7f4be96238c3feeb1d8156e058500c5d1f6d147bb1c234e2019998bfbabe2b4a5c600080a3005b6011867f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b856040517fc38434ab000000000000000000000000000000000000000000000000000000008152fd5b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760206040516103848152f35b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760207fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739275473ffffffffffffffffffffffffffffffffffffffff60405191168152f35b346101e85761146161143261142b36612146565b339161239e565b8060005403600055337f0000000000000000000000000000000000000000000000000000000000000000612f26565b005b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85760806003546040519075ffffffffffffffffffffffffffffffffffffffffffff8116825265ffffffffffff8160b01c16602083015261ffff8160e01c16604083015260f01c6060820152f35b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8573563ffffffff8116908181036101e8577f50e5c1c885ea2510512f889012b87e95d7696281a6c2985c6c7d45fda6d792d59160209161154a6121fd565b7fffffffff000000000000ffffffffffffffffffffffffffffffffffffffffffff79ffffffff000000000000000000000000000000000000000000006003549260b01b16911617600355604051908152a1005b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8573560005260016020526040806000206001815491015482519182526020820152f35b60007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576116206121fd565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a355005b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85780356024359167ffffffffffffffff928381116101e8576116bb903690830161217a565b9390918360005260016020526040600020926001840195865461187f577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed4420192428411610dcf575061146196848761174381967fccd5d394b6c44121d18f7dc9af1472e400cd79e3318c578aca4eeacb5d9df7a4966040968560019b421693169185612895565b509255847fb695ede53322ecf2e50fbc58c9cd70baa3835819be7d0c523a69efcab3c44bf560208551338152a261177a8588612c4d565b856000526003870160205282600020906117a165ffffffffffff60035460b01c1642612235565b1690600381017002000000000000000000000000000000007fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff8254161790556118268282907fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff6fffffffffffffffff000000000000000083549260401b169116179055565b805477ffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffff0000000000000000000000000000000000000000000000008560c01b16911617905582519160070b82526020820152a3612c4d565b826040517fde3c363f000000000000000000000000000000000000000000000000000000008152fd5b346101e8576118b636612146565b908060005260016020526040600020928260005260038401602052604060002060026118e3338686612ec6565b95866000520160205260ff6003604060002092015460801c166005811015611a37578203611a0e578054918260f81c6119e657507f01000000000000000000000000000000000000000000000000000000000000007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83161781556119b17dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8093168060005403600055337f0000000000000000000000000000000000000000000000000000000000000000612f26565b541660405193845260208401527ffb75c09f4826c289f841e35b10c86b0e385d3f01ff49cfc12d5626d24645f64a60403394a4005b6040517f4f9e331b000000000000000000000000000000000000000000000000000000008152fd5b506040517fc38434ab000000000000000000000000000000000000000000000000000000008152fd5b6021837f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b60007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85763389a75e1600c523360005260006020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92600080a2005b346101e8576002611aff611ade366120f1565b90611aea93929361237f565b50836000526001602052604060002093612ec6565b600052016020526060604060002060405190611b1a826122ba565b547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116825260ff8160f01c16602083015260f81c151560408201526104c46040518092604080917dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff815116845260ff602082015116602085015201511515910152565b346101e85760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857611bd16120ce565b90602435611bdd6121fd565b80611c785750600254905b60025490828203918211610dcf575060025580600054036000557f000000000000000000000000000000000000000000000000000000000000000090611c2f818484612f26565b6040519081527f4faa6fbacd77a523e80ea39326e5d792fb633131f897c119ec498d60f4bd68f9602073ffffffffffffffffffffffffffffffffffffffff8095169433941692a4005b90611be8565b346101e85760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e8576020604051600a8152f35b346101e85760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857602480359167ffffffffffffffff908035908285116101e857366023860112156101e8578401359182116101e857600590368484841b870101116101e8579291906000936000935b838510611d6957611461868060005403600055337f0000000000000000000000000000000000000000000000000000000000000000612f26565b9091929394506001611d83338388871b8a0101358561239e565b950193929190611d2f565b60007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e85763389a75e1600c52336000526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d600080a2005b346101e85760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e857803561ffff8116918282036101e857611e3e6121fd565b6103e88311610ff3577f5aa89d2018da401c466211564e2994895ca1dc68df9d55db7482e437dadc0811602084847fffff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff7dffff000000000000000000000000000000000000000000000000000000006003549260e01b16911617600355604051908152a1005b346101e857611461611ee1611ed7366120f1565b929190839161239e565b9081600054036000557f0000000000000000000000000000000000000000000000000000000000000000612f26565b90611f1a36612096565b90926fffffffffffffffffffffffffffffffff9485831690846000526001602052604060002096866000526003880160205260406000209160018901541561206f575060ff600383015460801c1660058110156109385760010361090f5782156108e6576002611f8b338989612ec6565b9889600052016020526040600020937dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8554166108be5750600260018301928354937fffffffffffffffffffffffffffffffff000000000000000000000000000000009485858881841601169116179055019182549184818416011691161790557fff0000000000000000000000000000000000000000000000000000000000000082541617905561203c348233612652565b60405193845260208401527facad006dfb05c480e68abb20c66f13c27f5971dd27fb0e3fa9e4f0f6d1a3abee60403394a4005b807fb0cfa44700000000000000000000000000000000000000000000000000000000869252fd5b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60609101126101e857600435906024359060443590565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036101e857565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60609101126101e857600435906024359060443573ffffffffffffffffffffffffffffffffffffffff811681036101e85790565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60409101126101e8576004359060243590565b9181601f840112156101e85782359167ffffffffffffffff83116101e857602083818601950101116101e857565b60607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8201126101e85760043591602435916044359067ffffffffffffffff82116101e8576121f99160040161217a565b9091565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7487392754330361222757565b6382b429006000526004601cfd5b9190820180921161224257565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9190916fffffffffffffffffffffffffffffffff8080941691160291821691820361224257565b6fffffffffffffffffffffffffffffffff918216908216039190821161224257565b6060810190811067ffffffffffffffff8211176122d657604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610140810190811067ffffffffffffffff8211176122d657604052565b6040810190811067ffffffffffffffff8211176122d657604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176122d657604052565b6040519061238c826122ba565b60006040838281528260208201520152565b909291926000918083526001602052604080842093838152600385016020528181209060026123ce898787612ec6565b9687835201602052828120916003810160ff815460801c166005811015612625576003036125fc578354917dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff948584169384156125d3578060f81c6125aa577effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0100000000000000000000000000000000000000000000000000000000000000179081905560f01c60ff166001036125945780548060c01d60070b9060801c60070b135b1561256b576002015460801c84818402169283040361253e57546fffffffffffffffffffffffffffffffff16918215612511575083519687520416602085018190529573ffffffffffffffffffffffffffffffffffffffff16937f31295a2f764b13b07abb49b6fd4fe8a7ca220b09c72570d04cd947871c999d1a9190a4565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526012600452fd5b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b600486517f4f97cab6000000000000000000000000000000000000000000000000000000008152fd5b80548060c01d60070b9060801c60070b12612491565b600488517f4f9e331b000000000000000000000000000000000000000000000000000000008152fd5b600488517f6ec9be11000000000000000000000000000000000000000000000000000000008152fd5b600485517fc38434ab000000000000000000000000000000000000000000000000000000008152fd5b6024847f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b6000805483018155907f00000000000000000000000000000000000000000000000000000000000000009073ffffffffffffffffffffffffffffffffffffffff82166126cc57505050036126a257565b60046040517f025dbdd4000000000000000000000000000000000000000000000000000000008152fd5b82939450601c8360209493606493604051986060523060405260601b602c526f23b872dd000000000000000000000000600c525af13d156001835114171690606052816040521561271a5750565b807fa512d51e0000000000000000000000000000000000000000000000000000000060049252fd5b80511561274f5760200190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9080825180825260208092019180808360051b86010195019360009384915b8483106127ae575050505050505090565b90919293949596847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08084840301855289518051908185528a5b82811061281457505080840183018a9052601f011690910181019781019695949360010192019061279d565b8181018501518682018601528994016127e8565b91908260809103126101e85760405167ffffffffffffffff9260808201848111838210176122d657604052819381518060070b81036101e8578352602082015190811681036101e857602083015260408101518060030b81036101e8576040830152606090810151910152565b92939190916040938451906128a982612322565b60019283835260209788840192893685376128c385612742565b528751966128d088612322565b8588528960005b818110612c3d57505067ffffffffffffffff968781116122d6578951916129258c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f850116018461233e565b81835236828201116101e857816000928d92838601378301015261294888612742565b5261295287612742565b5073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000002880ab155794e7179c9ee2e38200202908c17b4316948851947fd47eed450000000000000000000000000000000000000000000000000000000086528a60048701528a86806129c7602482018d61277e565b03818a5afa958615612c3257600096612c00575b50853410612bd757918792612a2899969594928c8c519b8c997f4716e9c5000000000000000000000000000000000000000000000000000000008b52608060048c015260848b019061277e565b8981037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0160248b015292518084529201959160008e5b828210612bbd5750505050908316604487015250166064840152600093908390039183915af1918215612bb257600092612ab1575b5050612aa09150612742565b510151906060825160070b92015190565b90913d8082843e612ac2818461233e565b8201918581840312612b7d57805190848211612bae57019180601f84011215612b7d578251938411612b8157845194612b00878660051b018761233e565b848652868601928761012080970286010194838611612b7d578801935b858510612b365750505050505050612aa0903880612a94565b8685850312612b7d578887918451612b4d816122ba565b87518152612b5d87848a01612828565b83820152612b6e8760a08a01612828565b86820152815201940193612b1d565b5080fd5b6024827f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b8280fd5b83513d6000823e3d90fd5b845189528d9a50978801978c97509093019282018e612a5f565b60048a517f025dbdd4000000000000000000000000000000000000000000000000000000008152fd5b90958b82813d8311612c2b575b612c17818361233e565b81010312612c2857505194386129db565b80fd5b503d612c0d565b8a513d6000823e3d90fd5b606082828c010152018a906128d7565b9081547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114612242576001019182815567ffffffffffffffff9081421660035460af1c6601fffffffffffe65fffffffffffe82169116810361224257810192808411612242576000868152600393840160205260409081902093840180547fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff1670010000000000000000000000000000000017905583547fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff84167fffffffffffffffffffffffffffffffff000000000000000000000000000000009091161785821b6fffffffffffffffff000000000000000016179093557f30745dda6a7b321538e5fc06390354a4f93646c86e3d8d29dad0b0beeff58098938351928352166020820152a3565b7fccd5d394b6c44121d18f7dc9af1472e400cd79e3318c578aca4eeacb5d9df7a4919293600360409286600052016020528160002067ffffffffffffffff612deb65ffffffffffff60035460b01c1642612235565b1690600381017002000000000000000000000000000000007fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff825416179055612e708282907fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff6fffffffffffffffff000000000000000083549260401b169116179055565b805477ffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffff0000000000000000000000000000000000000000000000008560c01b16911617905582519160070b82526020820152a3565b917fffffffffffffffffffffffffffffffffffffffff000000000000000000000000906040519260208401948552604084015260601b166060820152605481526080810181811067ffffffffffffffff8211176122d65760405251902090565b73ffffffffffffffffffffffffffffffffffffffff90808216612f7f575050600080809381935af115612f5557565b60046040517ff4b3b1bc000000000000000000000000000000000000000000000000000000008152fd5b6020906040929483519586957fa9059cbb000000000000000000000000000000000000000000000000000000008752166004860152602485015260446000948580935af13d15601f3d11600185511416171692828152826020820152015215612fe457565b60046040517ff27f64e4000000000000000000000000000000000000000000000000000000008152fd5b73ffffffffffffffffffffffffffffffffffffffff168061302e57504790565b6020602491604051928380927f70a082310000000000000000000000000000000000000000000000000000000082523060048301525afa9081156130a557600091613077575090565b906020823d821161309d575b816130906020938361233e565b81010312612c2857505190565b3d9150613083565b6040513d6000823e3d90fdfea2646970667358221220c8b84f57112d19e1a7c44a61808e583205ee5d1dc30194a4ae7e43c7ff74620d64736f6c63430008140033