Overview
S Balance
S Value
$0.00More Info
Private Name Tags
ContractCreator
Latest 1 internal transaction
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
4353631 | 23 days ago | Contract Creation | 0 S |
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Source Code Verified (Exact Match)
Contract Name:
RecipeMarketHub
Compiler Version
v0.8.27+commit.40a35a09
Optimization Enabled:
No with 5000 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; import { RecipeMarketHubBase, RewardStyle, WeirollWallet } from "./base/RecipeMarketHubBase.sol"; import { ERC20 } from "../lib/solmate/src/tokens/ERC20.sol"; import { ERC4626 } from "../lib/solmate/src/tokens/ERC4626.sol"; import { ClonesWithImmutableArgs } from "../lib/clones-with-immutable-args/src/ClonesWithImmutableArgs.sol"; import { SafeTransferLib } from "../lib/solmate/src/utils/SafeTransferLib.sol"; import { FixedPointMathLib } from "../lib/solmate/src/utils/FixedPointMathLib.sol"; import { Points } from "./Points.sol"; import { PointsFactory } from "./PointsFactory.sol"; import { Owned } from "../lib/solmate/src/auth/Owned.sol"; /// @title RecipeMarketHub /// @author Jack Corddry, CopyPaste, Shivaansh Kapoor /// @notice RecipeMarketHub contract for Incentivizing AP/IPs to participate in "recipe" markets which perform arbitrary actions contract RecipeMarketHub is RecipeMarketHubBase { using ClonesWithImmutableArgs for address; using SafeTransferLib for ERC20; using FixedPointMathLib for uint256; /// @param _weirollWalletImplementation The address of the WeirollWallet implementation contract /// @param _protocolFee The percent deducted from the IP's incentive amount and claimable by protocolFeeClaimant /// @param _minimumFrontendFee The minimum frontend fee that a market can set /// @param _owner The address that will be set as the owner of the contract constructor( address _weirollWalletImplementation, uint256 _protocolFee, uint256 _minimumFrontendFee, address _owner, address _pointsFactory ) payable Owned(_owner) { WEIROLL_WALLET_IMPLEMENTATION = _weirollWalletImplementation; POINTS_FACTORY = _pointsFactory; protocolFee = _protocolFee; protocolFeeClaimant = _owner; minimumFrontendFee = _minimumFrontendFee; } /// @notice Create a new recipe market /// @param inputToken The token that will be deposited into the user's weiroll wallet for use in the recipe /// @param lockupTime The time in seconds that the user's weiroll wallet will be locked up for after deposit /// @param frontendFee The fee that the frontend will take from the user's weiroll wallet, 1e18 == 100% fee /// @param depositRecipe The weiroll script that will be executed after the inputToken is transferred to the wallet /// @param withdrawRecipe The weiroll script that may be executed after lockupTime has passed to unwind a user's position /// @custom:field rewardStyle Whether the rewards are paid at the beginning, locked until the end, or forfeitable until the end /// @return marketHash The hash of the newly created market function createMarket( address inputToken, uint256 lockupTime, uint256 frontendFee, Recipe calldata depositRecipe, Recipe calldata withdrawRecipe, RewardStyle rewardStyle ) external payable returns (bytes32 marketHash) { // Check that the input token is not the zero address if (inputToken == address(0)) { revert InvalidMarketInputToken(); } // Check that the frontend fee is at least the global minimum if (frontendFee < minimumFrontendFee) { revert FrontendFeeTooLow(); } // Check that the sum of fees isn't too high if ((frontendFee + protocolFee) > 1e18) { revert TotalFeeTooHigh(); } WeirollMarket memory market = WeirollMarket(numMarkets, ERC20(inputToken), lockupTime, frontendFee, depositRecipe, withdrawRecipe, rewardStyle); marketHash = getMarketHash(market); marketHashToWeirollMarket[marketHash] = market; emit MarketCreated(numMarkets, marketHash, inputToken, lockupTime, frontendFee, rewardStyle); numMarkets++; } /// @notice Create a new AP offer. Offer params will be emitted in an event while only the hash of the offer and offer quantity is stored onchain /// @dev AP offers are funded via approvals to ensure multiple offers can be placed off of a single input /// @dev Setting an expiry of 0 means the offer never expires /// @param targetMarketHash The hash of the weiroll market to create the AP offer for /// @param fundingVault The address of the vault where the input tokens will be withdrawn from, if set to 0, the AP will deposit the base asset directly /// @param quantity The total amount of input tokens to be deposited /// @param expiry The timestamp after which the offer is considered expired /// @param incentivesRequested The addresses of the incentives requested by the AP to satisfy the offer /// @param incentiveAmountsRequested The amount of each incentive requested by the AP to satisfy the offer /// @return offerHash The hash of the AP offer created function createAPOffer( bytes32 targetMarketHash, address fundingVault, uint256 quantity, uint256 expiry, address[] calldata incentivesRequested, uint256[] calldata incentiveAmountsRequested ) external payable returns (bytes32 offerHash) { // Retrieve the target market WeirollMarket storage targetMarket = marketHashToWeirollMarket[targetMarketHash]; // Check that the market exists if (address(targetMarket.inputToken) == address(0)) { revert MarketDoesNotExist(); } // Check offer isn't expired (expiries of 0 live forever) if (expiry != 0 && expiry < block.timestamp) { revert CannotPlaceExpiredOffer(); } // Check offer isn't empty if (quantity < MINIMUM_QUANTITY) { revert CannotPlaceZeroQuantityOffer(); } // Check incentive and amounts arrays are the same length if (incentivesRequested.length != incentiveAmountsRequested.length) { revert ArrayLengthMismatch(); } address lastIncentive; for (uint256 i; i < incentivesRequested.length; i++) { address incentive = incentivesRequested[i]; if (uint256(bytes32(bytes20(incentive))) <= uint256(bytes32(bytes20(lastIncentive)))) { revert OfferCannotContainDuplicates(); } lastIncentive = incentive; } // NOTE: The cool use of short-circuit means this call can't revert if fundingVault doesn't support asset() if (fundingVault != address(0) && targetMarket.inputToken != ERC4626(fundingVault).asset()) { revert MismatchedBaseAsset(); } // Map the offer hash to the offer quantity APOffer memory offer = APOffer(numAPOffers, targetMarketHash, msg.sender, fundingVault, quantity, expiry, incentivesRequested, incentiveAmountsRequested); offerHash = getAPOfferHash(offer); offerHashToRemainingQuantity[offerHash] = quantity; /// @dev APOffer events are stored in events and do not exist onchain outside of the offerHashToRemainingQuantity mapping emit APOfferCreated(numAPOffers, targetMarketHash, msg.sender, fundingVault, quantity, incentivesRequested, incentiveAmountsRequested, expiry); // Increment the number of AP offers created numAPOffers++; } /// @notice Create a new IP offer, transferring the IP's incentives to the RecipeMarketHub and putting all the offer params in contract storage /// @dev IP must approve all incentives to be spent by the RecipeMarketHub before calling this function /// @param targetMarketHash The hash of the weiroll market to create the IP offer for /// @param quantity The total amount of input tokens to be deposited /// @param expiry The timestamp after which the offer is considered expired /// @param incentivesOffered The addresses of the incentives offered by the IP /// @param incentiveAmountsPaid The amount of each incentives paid by the IP (including fees) /// @return offerHash The hash of the IP offer created function createIPOffer( bytes32 targetMarketHash, uint256 quantity, uint256 expiry, address[] calldata incentivesOffered, uint256[] calldata incentiveAmountsPaid ) external payable nonReentrant returns (bytes32 offerHash) { // Retrieve the target market WeirollMarket storage targetMarket = marketHashToWeirollMarket[targetMarketHash]; // Check that the market exists if (address(targetMarket.inputToken) == address(0)) { revert MarketDoesNotExist(); } // Check that the offer isn't expired if (expiry != 0 && expiry < block.timestamp) { revert CannotPlaceExpiredOffer(); } // Check that the incentives and amounts arrays are the same length if (incentivesOffered.length != incentiveAmountsPaid.length) { revert ArrayLengthMismatch(); } // Check offer isn't empty if (quantity < MINIMUM_QUANTITY) { revert CannotPlaceZeroQuantityOffer(); } // To keep track of incentives allocated to the AP and fees (per incentive) uint256[] memory incentiveAmountsOffered = new uint256[](incentivesOffered.length); uint256[] memory protocolFeesToBePaid = new uint256[](incentivesOffered.length); uint256[] memory frontendFeesToBePaid = new uint256[](incentivesOffered.length); // Transfer the IP's incentives to the RecipeMarketHub and set aside fees address lastIncentive; for (uint256 i = 0; i < incentivesOffered.length; ++i) { // Get the incentive offered address incentive = incentivesOffered[i]; // Check that the sorted incentive array has no duplicates if (uint256(bytes32(bytes20(incentive))) <= uint256(bytes32(bytes20(lastIncentive)))) { revert OfferCannotContainDuplicates(); } lastIncentive = incentive; // Total amount IP is paying in this incentive including fees uint256 amount = incentiveAmountsPaid[i]; // Get the frontend fee for the target weiroll market uint256 frontendFee = targetMarket.frontendFee; // Calculate incentive and fee breakdown uint256 incentiveAmount = amount.divWadDown(1e18 + protocolFee + frontendFee); uint256 protocolFeeAmount = incentiveAmount.mulWadDown(protocolFee); uint256 frontendFeeAmount = incentiveAmount.mulWadDown(frontendFee); // Use a scoping block to avoid stack to deep errors { // Track incentive amounts and fees (per incentive) incentiveAmountsOffered[i] = incentiveAmount; protocolFeesToBePaid[i] = protocolFeeAmount; frontendFeesToBePaid[i] = frontendFeeAmount; } // Check if incentive is a points program if (PointsFactory(POINTS_FACTORY).isPointsProgram(incentive)) { // If points incentive, make sure: // 1. The points factory used to create the program is the same as this RecipeMarketHubs PF // 2. IP placing the offer can award points // 3. Points factory has this RecipeMarketHub marked as a valid RO - can be assumed true if (POINTS_FACTORY != address(Points(incentive).pointsFactory()) || !Points(incentive).allowedIPs(msg.sender)) { revert InvalidPointsProgram(); } } else { // SafeTransferFrom does not check if a incentive address has any code, so we need to check it manually to prevent incentive deployment // frontrunning if (incentive.code.length == 0) revert TokenDoesNotExist(); // Transfer frontend fee + protocol fee + incentiveAmount of the incentive to RecipeMarketHub ERC20(incentive).safeTransferFrom(msg.sender, address(this), incentiveAmount + protocolFeeAmount + frontendFeeAmount); } } // Set the offer hash offerHash = getIPOfferHash(numIPOffers, targetMarketHash, msg.sender, expiry, quantity, incentivesOffered, incentiveAmountsOffered); // Create and store the offer IPOffer storage offer = offerHashToIPOffer[offerHash]; offer.offerID = numIPOffers; offer.targetMarketHash = targetMarketHash; offer.ip = msg.sender; offer.quantity = quantity; offer.remainingQuantity = quantity; offer.expiry = expiry; offer.incentivesOffered = incentivesOffered; // Set incentives and fees in the offer mapping for (uint256 i = 0; i < incentivesOffered.length; ++i) { address incentive = incentivesOffered[i]; offer.incentiveAmountsOffered[incentive] = incentiveAmountsOffered[i]; offer.incentiveToProtocolFeeAmount[incentive] = protocolFeesToBePaid[i]; offer.incentiveToFrontendFeeAmount[incentive] = frontendFeesToBePaid[i]; } // Emit IP offer creation event emit IPOfferCreated( numIPOffers, offerHash, targetMarketHash, msg.sender, quantity, incentivesOffered, incentiveAmountsOffered, protocolFeesToBePaid, frontendFeesToBePaid, expiry ); // Increment the number of IP offers created numIPOffers++; } /// @param incentiveToken The incentive token to claim fees for /// @param to The address to send fees claimed to function claimFees(address incentiveToken, address to) external payable { uint256 amount = feeClaimantToTokenToAmount[msg.sender][incentiveToken]; delete feeClaimantToTokenToAmount[msg.sender][incentiveToken]; ERC20(incentiveToken).safeTransfer(to, amount); emit FeesClaimed(msg.sender, incentiveToken, amount); } /// @notice Filling multiple IP offers /// @param ipOfferHashes The hashes of the IP offers to fill /// @param fillAmounts The amounts of input tokens to fill the corresponding offers with /// @param fundingVault The address of the vault where the input tokens will be withdrawn from (vault not used if set to address(0)) /// @param frontendFeeRecipient The address that will receive the frontend fee function fillIPOffers( bytes32[] calldata ipOfferHashes, uint256[] calldata fillAmounts, address fundingVault, address frontendFeeRecipient ) external payable nonReentrant offersNotPaused { if (ipOfferHashes.length != fillAmounts.length) { revert ArrayLengthMismatch(); } for (uint256 i = 0; i < ipOfferHashes.length; ++i) { _fillIPOffer(ipOfferHashes[i], fillAmounts[i], fundingVault, frontendFeeRecipient); } } /// @notice Fill an IP offer, transferring the IP's incentives to the AP, withdrawing the AP from their funding vault into a fresh weiroll wallet, and /// executing the weiroll recipe /// @param offerHash The hash of the IP offer to fill /// @param fillAmount The amount of input tokens to fill the offer with /// @param fundingVault The address of the vault where the input tokens will be withdrawn from (vault not used if set to address(0)) /// @param frontendFeeRecipient The address that will receive the frontend fee function _fillIPOffer(bytes32 offerHash, uint256 fillAmount, address fundingVault, address frontendFeeRecipient) internal { // Retreive the IPOffer and WeirollMarket structs IPOffer storage offer = offerHashToIPOffer[offerHash]; WeirollMarket storage market = marketHashToWeirollMarket[offer.targetMarketHash]; // Check that the offer isn't expired if (offer.expiry != 0 && block.timestamp > offer.expiry) { revert OfferExpired(); } // Check that the offer has enough remaining quantity if (offer.remainingQuantity < fillAmount && fillAmount != type(uint256).max) { revert NotEnoughRemainingQuantity(); } if (fillAmount == type(uint256).max) { fillAmount = offer.remainingQuantity; } // Check that the offer's base asset matches the market's base asset if (fundingVault != address(0) && market.inputToken != ERC4626(fundingVault).asset()) { revert MismatchedBaseAsset(); } // Check that the offer isn't empty if (fillAmount == 0) { revert CannotPlaceZeroQuantityOffer(); } // Update the offer's remaining quantity before interacting with external contracts offer.remainingQuantity -= fillAmount; WeirollWallet wallet; { // Use a scoping block to avoid stack too deep bool forfeitable = market.rewardStyle == RewardStyle.Forfeitable; uint256 unlockTime = block.timestamp + market.lockupTime; // Create weiroll wallet to lock assets for recipe execution(s) wallet = WeirollWallet( payable( WEIROLL_WALLET_IMPLEMENTATION.clone( abi.encodePacked(msg.sender, address(this), fillAmount, unlockTime, forfeitable, offer.targetMarketHash) ) ) ); } // Number of incentives offered by the IP uint256 numIncentives = offer.incentivesOffered.length; // Arrays to store incentives and fee amounts to be paid uint256[] memory incentiveAmountsPaid = new uint256[](numIncentives); uint256[] memory protocolFeesPaid = new uint256[](numIncentives); uint256[] memory frontendFeesPaid = new uint256[](numIncentives); // Perform incentive accounting on a per incentive basis for (uint256 i = 0; i < numIncentives; ++i) { // Calculate the percentage of the offer the AP is filling uint256 fillPercentage = fillAmount.divWadDown(offer.quantity); // Incentive address address incentive = offer.incentivesOffered[i]; // Calculate fees to take based on percentage of fill protocolFeesPaid[i] = offer.incentiveToProtocolFeeAmount[incentive].mulWadDown(fillPercentage); frontendFeesPaid[i] = offer.incentiveToFrontendFeeAmount[incentive].mulWadDown(fillPercentage); // Calculate incentives to give based on percentage of fill incentiveAmountsPaid[i] = offer.incentiveAmountsOffered[incentive].mulWadDown(fillPercentage); if (market.rewardStyle == RewardStyle.Upfront) { // Push incentives to AP and account fees on fill in an upfront market _pushIncentivesAndAccountFees( incentive, msg.sender, incentiveAmountsPaid[i], protocolFeesPaid[i], frontendFeesPaid[i], offer.ip, frontendFeeRecipient ); } } if (market.rewardStyle != RewardStyle.Upfront) { // If RewardStyle is either Forfeitable or Arrear // Create locked rewards params to account for payouts upon wallet unlocking LockedRewardParams storage params = weirollWalletToLockedIncentivesParams[address(wallet)]; params.incentives = offer.incentivesOffered; params.amounts = incentiveAmountsPaid; params.ip = offer.ip; params.frontendFeeRecipient = frontendFeeRecipient; params.wasIPOffer = true; params.offerHash = offerHash; } // Fund the weiroll wallet with the specified amount of the market's input token // Will use the funding vault if specified or will fund directly from the AP _fundWeirollWallet(fundingVault, msg.sender, market.inputToken, fillAmount, address(wallet)); // Execute deposit recipe wallet.executeWeiroll(market.depositRecipe.weirollCommands, market.depositRecipe.weirollState); emit IPOfferFilled(offerHash, msg.sender, fillAmount, address(wallet), incentiveAmountsPaid, protocolFeesPaid, frontendFeesPaid); } /// @dev Fill multiple AP offers /// @param offers The AP offers to fill /// @param fillAmounts The amount of input tokens to fill the corresponding offer with /// @param frontendFeeRecipient The address that will receive the frontend fee function fillAPOffers( APOffer[] calldata offers, uint256[] calldata fillAmounts, address frontendFeeRecipient ) external payable nonReentrant offersNotPaused { if (offers.length != fillAmounts.length) { revert ArrayLengthMismatch(); } for (uint256 i = 0; i < offers.length; ++i) { _fillAPOffer(offers[i], fillAmounts[i], frontendFeeRecipient); } } /// @dev Fill an AP offer /// @dev IP must approve all incentives to be spent (both fills + fees!) by the RecipeMarketHub before calling this function. /// @param offer The AP offer to fill /// @param fillAmount The amount of input tokens to fill the offer with /// @param frontendFeeRecipient The address that will receive the frontend fee function _fillAPOffer(APOffer calldata offer, uint256 fillAmount, address frontendFeeRecipient) internal { if (offer.expiry != 0 && block.timestamp > offer.expiry) { revert OfferExpired(); } bytes32 offerHash = getAPOfferHash(offer); uint256 remaining = offerHashToRemainingQuantity[offerHash]; if (fillAmount > remaining) { if (fillAmount != type(uint256).max) { revert NotEnoughRemainingQuantity(); } fillAmount = remaining; } if (fillAmount == 0) { revert CannotFillZeroQuantityOffer(); } // Adjust remaining offer quantity by amount filled offerHashToRemainingQuantity[offerHash] -= fillAmount; // Calculate percentage of AP oder that IP is filling (IP gets this percantage of the offer quantity in a Weiroll wallet specified by the market) uint256 fillPercentage = fillAmount.divWadDown(offer.quantity); if (fillPercentage < MIN_FILL_PERCENT && fillAmount != remaining) revert InsufficientFillPercent(); // Get Weiroll market WeirollMarket storage market = marketHashToWeirollMarket[offer.targetMarketHash]; WeirollWallet wallet; { // Create weiroll wallet to lock assets for recipe execution(s) uint256 unlockTime = block.timestamp + market.lockupTime; bool forfeitable = market.rewardStyle == RewardStyle.Forfeitable; wallet = WeirollWallet( payable( WEIROLL_WALLET_IMPLEMENTATION.clone(abi.encodePacked(offer.ap, address(this), fillAmount, unlockTime, forfeitable, offer.targetMarketHash)) ) ); } // Number of incentives requested by the AP uint256 numIncentives = offer.incentivesRequested.length; // Arrays to store incentives and fee amounts to be paid uint256[] memory incentiveAmountsPaid = new uint256[](numIncentives); uint256[] memory protocolFeesPaid = new uint256[](numIncentives); uint256[] memory frontendFeesPaid = new uint256[](numIncentives); // Fees at the time of fill uint256 protocolFeeAtFill = protocolFee; uint256 marketFrontendFee = market.frontendFee; for (uint256 i = 0; i < numIncentives; ++i) { // Incentive requested by AP address incentive = offer.incentivesRequested[i]; // This is the incentive amount allocated to the AP uint256 incentiveAmount = offer.incentiveAmountsRequested[i].mulWadDown(fillPercentage); // Check that the incentives allocated to the AP are non-zero if (incentiveAmount == 0) { revert NoIncentivesPaidOnFill(); } incentiveAmountsPaid[i] = incentiveAmount; // Calculate fees based on fill percentage. These fees will be taken on top of the AP's requested amount. protocolFeesPaid[i] = incentiveAmount.mulWadDown(protocolFeeAtFill); frontendFeesPaid[i] = incentiveAmount.mulWadDown(marketFrontendFee); // Pull incentives from IP and account fees _pullIncentivesOnAPFill(incentive, incentiveAmount, protocolFeesPaid[i], frontendFeesPaid[i], offer.ap, frontendFeeRecipient, market.rewardStyle); } if (market.rewardStyle != RewardStyle.Upfront) { // If RewardStyle is either Forfeitable or Arrear // Create locked rewards params to account for payouts upon wallet unlocking LockedRewardParams storage params = weirollWalletToLockedIncentivesParams[address(wallet)]; params.incentives = offer.incentivesRequested; params.amounts = incentiveAmountsPaid; params.ip = msg.sender; params.frontendFeeRecipient = frontendFeeRecipient; params.protocolFeeAtFill = protocolFeeAtFill; // Redundant: Make sure this is set to false in case of a forfeit delete params.wasIPOffer; } // Fund the weiroll wallet with the specified amount of the market's input token // Will use the funding vault if specified or will fund directly from the AP _fundWeirollWallet(offer.fundingVault, offer.ap, market.inputToken, fillAmount, address(wallet)); // Execute deposit recipe wallet.executeWeiroll(market.depositRecipe.weirollCommands, market.depositRecipe.weirollState); emit APOfferFilled(offer.offerID, msg.sender, fillAmount, address(wallet), incentiveAmountsPaid, protocolFeesPaid, frontendFeesPaid); } /// @notice Cancel an AP offer, setting the remaining quantity available to fill to 0 function cancelAPOffer(APOffer calldata offer) external payable { // Check that the cancelling party is the offer's owner if (offer.ap != msg.sender) revert NotOwner(); // Check that the offer isn't already filled, hasn't been cancelled already, or never existed bytes32 offerHash = getAPOfferHash(offer); if (offerHashToRemainingQuantity[offerHash] == 0) { revert NotEnoughRemainingQuantity(); } // Set remaining quantity to 0 - effectively cancelling the offer delete offerHashToRemainingQuantity[offerHash]; emit APOfferCancelled(offer.offerID); } /// @notice Cancel an IP offer, setting the remaining quantity available to fill to 0 and returning the IP's incentives function cancelIPOffer(bytes32 offerHash) external payable nonReentrant { IPOffer storage offer = offerHashToIPOffer[offerHash]; // Check that the cancelling party is the offer's owner if (offer.ip != msg.sender) revert NotOwner(); // Check that the offer isn't already filled, hasn't been cancelled already, or never existed if (offer.remainingQuantity == 0) revert NotEnoughRemainingQuantity(); RewardStyle marketRewardStyle = marketHashToWeirollMarket[offer.targetMarketHash].rewardStyle; // Check the percentage of the offer not filled to calculate incentives to return uint256 percentNotFilled = offer.remainingQuantity.divWadDown(offer.quantity); // Transfer the remaining incentives back to the IP for (uint256 i = 0; i < offer.incentivesOffered.length; ++i) { address incentive = offer.incentivesOffered[i]; if (!PointsFactory(POINTS_FACTORY).isPointsProgram(offer.incentivesOffered[i])) { // Calculate the incentives which are still available for refunding the IP uint256 incentivesRemaining = offer.incentiveAmountsOffered[incentive].mulWadDown(percentNotFilled); // Calculate the unused fee amounts to refunding to the IP uint256 unchargedFrontendFeeAmount = offer.incentiveToFrontendFeeAmount[incentive].mulWadDown(percentNotFilled); uint256 unchargedProtocolFeeAmount = offer.incentiveToProtocolFeeAmount[incentive].mulWadDown(percentNotFilled); // Transfer reimbursements to the IP ERC20(incentive).safeTransfer(offer.ip, (incentivesRemaining + unchargedFrontendFeeAmount + unchargedProtocolFeeAmount)); } /// Delete cancelled fields of dynamic arrays and mappings delete offer.incentivesOffered[i]; delete offer.incentiveAmountsOffered[incentive]; if (marketRewardStyle == RewardStyle.Upfront) { // Need these on forfeit and claim for forfeitable and arrear markets // Safe to delete for Upfront markets delete offer.incentiveToProtocolFeeAmount[incentive]; delete offer.incentiveToFrontendFeeAmount[incentive]; } } if (marketRewardStyle != RewardStyle.Upfront) { // Need quantity to take the fees on forfeit and claim - don't delete // Need expiry to check offer expiry status on forfeit - don't delete // Delete the rest of the fields to indicate the offer was cancelled on forfeit delete offerHashToIPOffer[offerHash].targetMarketHash; delete offerHashToIPOffer[offerHash].ip; delete offerHashToIPOffer[offerHash].remainingQuantity; } else { // Delete cancelled offer completely from mapping if the market's RewardStyle is Upfront delete offerHashToIPOffer[offerHash]; } emit IPOfferCancelled(offerHash); } /// @notice For wallets of Forfeitable markets, an AP can call this function to forgo their rewards and unlock their wallet function forfeit(address weirollWallet, bool executeWithdrawal) external payable isWeirollOwner(weirollWallet) nonReentrant { // Instantiate a weiroll wallet for the specified address WeirollWallet wallet = WeirollWallet(payable(weirollWallet)); // Check that the wallet is forfeitable if (!wallet.isForfeitable()) { revert WalletNotForfeitable(); } // Get locked reward params LockedRewardParams storage params = weirollWalletToLockedIncentivesParams[weirollWallet]; // Forfeit wallet wallet.forfeit(); // Setting this option to false allows the AP to be able to forfeit even when the withdrawal script is reverting if (executeWithdrawal) { // Execute the withdrawal script if flag set to true _executeWithdrawalScript(weirollWallet); } // Check if IP offer // If not, the forfeited amount won't be replenished to the offer if (params.wasIPOffer) { // Retrieve IP offer if it was one IPOffer storage offer = offerHashToIPOffer[params.offerHash]; // Get amount filled by AP uint256 filledAmount = wallet.amount(); // If IP address is 0, offer has been cancelled if (offer.ip == address(0) || (offer.expiry != 0 && offer.expiry < block.timestamp)) { // Cancelled or expired offer - return incentives that were originally held for the AP to the IP and take the fees uint256 fillPercentage = filledAmount.divWadDown(offer.quantity); // Get the ip from locked reward params address ip = params.ip; for (uint256 i = 0; i < params.incentives.length; ++i) { address incentive = params.incentives[i]; // Calculate protocol fee to take based on percentage of fill uint256 protocolFeeAmount = offer.incentiveToProtocolFeeAmount[incentive].mulWadDown(fillPercentage); // Take protocol fee _accountFee(protocolFeeClaimant, incentive, protocolFeeAmount, ip); if (!PointsFactory(POINTS_FACTORY).isPointsProgram(incentive)) { // Calculate frontend fee to refund to the IP on forfeit uint256 frontendFeeAmount = offer.incentiveToFrontendFeeAmount[incentive].mulWadDown(fillPercentage); // Refund incentive tokens and frontend fee to IP. Points don't need to be refunded. ERC20(incentive).safeTransfer(ip, params.amounts[i] + frontendFeeAmount); } // Delete forfeited incentives and corresponding amounts from locked reward params delete params.incentives[i]; delete params.amounts[i]; // Can't delete since there might be more forfeitable wallets still locked and we need to take fees on claim // delete offer.incentiveToProtocolFeeAmount[incentive]; // delete offer.incentiveToFrontendFeeAmount[incentive]; } // Can't delete since there might be more forfeitable wallets still locked // delete offerHashToIPOffer[params.offerHash]; } else { // If not cancelled, add the filledAmount back to remaining quantity // Correct incentive amounts are still in this contract offer.remainingQuantity += filledAmount; // Delete forfeited incentives and corresponding amounts from locked reward params for (uint256 i = 0; i < params.incentives.length; ++i) { delete params.incentives[i]; delete params.amounts[i]; } } } else { // Get the protocol fee at fill and market frontend fee uint256 protocolFeeAtFill = params.protocolFeeAtFill; uint256 marketFrontendFee = marketHashToWeirollMarket[wallet.marketHash()].frontendFee; // Get the ip from locked reward params address ip = params.ip; // If offer was an AP offer, return the incentives to the IP and take the fee for (uint256 i = 0; i < params.incentives.length; ++i) { address incentive = params.incentives[i]; uint256 amount = params.amounts[i]; // Calculate fees to take based on percentage of fill uint256 protocolFeeAmount = amount.mulWadDown(protocolFeeAtFill); // Take fees _accountFee(protocolFeeClaimant, incentive, protocolFeeAmount, ip); if (!PointsFactory(POINTS_FACTORY).isPointsProgram(incentive)) { // Calculate frontend fee to refund to the IP on forfeit uint256 frontendFeeAmount = amount.mulWadDown(marketFrontendFee); // Refund incentive tokens and frontend fee to IP. Points don't need to be refunded. ERC20(incentive).safeTransfer(ip, amount + frontendFeeAmount); } // Delete forfeited incentives and corresponding amounts from locked reward params delete params.incentives[i]; delete params.amounts[i]; } } // Zero out the mapping delete weirollWalletToLockedIncentivesParams[weirollWallet]; emit WeirollWalletForfeited(weirollWallet); } /// @notice Execute the withdrawal script in the weiroll wallet function executeWithdrawalScript(address weirollWallet) external payable isWeirollOwner(weirollWallet) weirollIsUnlocked(weirollWallet) nonReentrant { _executeWithdrawalScript(weirollWallet); } /// @param weirollWallet The wallet to claim for /// @param to The address to send the incentive to function claim(address weirollWallet, address to) external payable isWeirollOwner(weirollWallet) weirollIsUnlocked(weirollWallet) nonReentrant { // Get locked reward details to facilitate claim LockedRewardParams storage params = weirollWalletToLockedIncentivesParams[weirollWallet]; // Instantiate a weiroll wallet for the specified address WeirollWallet wallet = WeirollWallet(payable(weirollWallet)); // Get the frontend fee recipient and ip from locked reward params address frontendFeeRecipient = params.frontendFeeRecipient; address ip = params.ip; if (params.wasIPOffer) { // If it was an ipoffer, get the offer so we can retrieve the fee amounts and fill quantity IPOffer storage offer = offerHashToIPOffer[params.offerHash]; uint256 fillAmount = wallet.amount(); uint256 fillPercentage = fillAmount.divWadDown(offer.quantity); for (uint256 i = 0; i < params.incentives.length; ++i) { address incentive = params.incentives[i]; // Calculate fees to take based on percentage of fill uint256 protocolFeeAmount = offer.incentiveToProtocolFeeAmount[incentive].mulWadDown(fillPercentage); uint256 frontendFeeAmount = offer.incentiveToFrontendFeeAmount[incentive].mulWadDown(fillPercentage); // Reward incentives to AP upon claim and account fees _pushIncentivesAndAccountFees(incentive, to, params.amounts[i], protocolFeeAmount, frontendFeeAmount, ip, frontendFeeRecipient); emit WeirollWalletClaimedIncentive(weirollWallet, to, incentive); /// Delete fields of dynamic arrays and mappings delete params.incentives[i]; delete params.amounts[i]; } } else { // Get the protocol fee at fill and market frontend fee uint256 protocolFeeAtFill = params.protocolFeeAtFill; uint256 marketFrontendFee = marketHashToWeirollMarket[wallet.marketHash()].frontendFee; for (uint256 i = 0; i < params.incentives.length; ++i) { address incentive = params.incentives[i]; uint256 amount = params.amounts[i]; // Calculate fees to take based on percentage of fill uint256 protocolFeeAmount = amount.mulWadDown(protocolFeeAtFill); uint256 frontendFeeAmount = amount.mulWadDown(marketFrontendFee); // Reward incentives to AP upon claim and account fees _pushIncentivesAndAccountFees(incentive, to, amount, protocolFeeAmount, frontendFeeAmount, ip, frontendFeeRecipient); emit WeirollWalletClaimedIncentive(weirollWallet, to, incentive); /// Delete fields of dynamic arrays and mappings delete params.incentives[i]; delete params.amounts[i]; } } // Zero out the mapping delete weirollWalletToLockedIncentivesParams[weirollWallet]; } /// @param weirollWallet The wallet to claim for /// @param incentiveToken The incentiveToken to claim /// @param to The address to send the incentive to function claim( address weirollWallet, address incentiveToken, address to ) external payable isWeirollOwner(weirollWallet) weirollIsUnlocked(weirollWallet) nonReentrant { // Get locked reward details to facilitate claim LockedRewardParams storage params = weirollWalletToLockedIncentivesParams[weirollWallet]; // Instantiate a weiroll wallet for the specified address WeirollWallet wallet = WeirollWallet(payable(weirollWallet)); // Get the frontend fee recipient and ip from locked reward params address frontendFeeRecipient = params.frontendFeeRecipient; address ip = params.ip; if (params.wasIPOffer) { // If it was an ipoffer, get the offer so we can retrieve the fee amounts and fill quantity IPOffer storage offer = offerHashToIPOffer[params.offerHash]; // Calculate percentage of offer quantity this offer filled uint256 fillAmount = wallet.amount(); uint256 fillPercentage = fillAmount.divWadDown(offer.quantity); for (uint256 i = 0; i < params.incentives.length; ++i) { address incentive = params.incentives[i]; if (incentiveToken == incentive) { // Calculate fees to take based on percentage of fill uint256 protocolFeeAmount = offer.incentiveToProtocolFeeAmount[incentive].mulWadDown(fillPercentage); uint256 frontendFeeAmount = offer.incentiveToFrontendFeeAmount[incentive].mulWadDown(fillPercentage); // Reward incentives to AP upon claim and account fees _pushIncentivesAndAccountFees(incentive, to, params.amounts[i], protocolFeeAmount, frontendFeeAmount, ip, frontendFeeRecipient); emit WeirollWalletClaimedIncentive(weirollWallet, to, incentiveToken); /// Delete fields of dynamic arrays and mappings once claimed delete params.incentives[i]; delete params.amounts[i]; // Return upon claiming the incentive return; } } } else { // Get the market frontend fee uint256 marketFrontendFee = marketHashToWeirollMarket[wallet.marketHash()].frontendFee; for (uint256 i = 0; i < params.incentives.length; ++i) { address incentive = params.incentives[i]; if (incentiveToken == incentive) { uint256 amount = params.amounts[i]; // Calculate fees to take based on percentage of fill uint256 protocolFeeAmount = amount.mulWadDown(params.protocolFeeAtFill); uint256 frontendFeeAmount = amount.mulWadDown(marketFrontendFee); // Reward incentives to AP upon wallet unlock and account fees _pushIncentivesAndAccountFees(incentive, to, amount, protocolFeeAmount, frontendFeeAmount, ip, frontendFeeRecipient); emit WeirollWalletClaimedIncentive(weirollWallet, to, incentiveToken); /// Delete fields of dynamic arrays and mappings delete params.incentives[i]; delete params.amounts[i]; // Return upon claiming the incentive return; } } } // This block will never get hit since array size doesn't get updated on delete // if (params.incentives.length == 0) { // // Zero out the mapping if no more locked incentives to claim // delete weirollWalletToLockedIncentivesParams[weirollWallet]; // } } /// @param recipient The address to send fees to /// @param incentive The incentive address where fees are accrued in /// @param amount The amount of fees to award /// @param ip The incentive provider if awarding points function _accountFee(address recipient, address incentive, uint256 amount, address ip) internal { //check to see the incentive is actually a points campaign if (PointsFactory(POINTS_FACTORY).isPointsProgram(incentive)) { // Points cannot be claimed and are rather directly awarded Points(incentive).award(recipient, amount, ip); } else { feeClaimantToTokenToAmount[recipient][incentive] += amount; } } /// @param fundingVault The ERC4626 vault to fund the weiroll wallet with - if address(0) fund directly via AP /// @param ap The address of the AP to fund the weiroll wallet if no funding vault specified /// @param token The market input token to fund the weiroll wallet with /// @param amount The amount of market input token to fund the weiroll wallet with /// @param weirollWallet The weiroll wallet to fund with the specified amount of the market input token function _fundWeirollWallet(address fundingVault, address ap, ERC20 token, uint256 amount, address weirollWallet) internal { if (fundingVault == address(0)) { // If no fundingVault specified, fund the wallet directly from AP token.safeTransferFrom(ap, weirollWallet, amount); } else { // Withdraw the tokens from the funding vault into the wallet ERC4626(fundingVault).withdraw(amount, weirollWallet, ap); // Ensure that the Weiroll wallet received at least fillAmount of the inputToken from the AP provided vault if (token.balanceOf(weirollWallet) < amount) { revert WeirollWalletFundingFailed(); } } } /** * @notice Handles the transfer and accounting of fees and incentives. * @dev This function is called internally to account fees and push incentives. * @param incentive The address of the incentive. * @param to The address of incentive recipient. * @param incentiveAmount The amount of the incentive token to be transferred. * @param protocolFeeAmount The protocol fee amount taken at fill. * @param frontendFeeAmount The frontend fee amount taken for this market. * @param ip The address of the action provider. * @param frontendFeeRecipient The address that will receive the frontend fee. */ function _pushIncentivesAndAccountFees( address incentive, address to, uint256 incentiveAmount, uint256 protocolFeeAmount, uint256 frontendFeeAmount, address ip, address frontendFeeRecipient ) internal { // Take fees _accountFee(protocolFeeClaimant, incentive, protocolFeeAmount, ip); _accountFee(frontendFeeRecipient, incentive, frontendFeeAmount, ip); // Push incentives to AP if (PointsFactory(POINTS_FACTORY).isPointsProgram(incentive)) { Points(incentive).award(to, incentiveAmount, ip); } else { ERC20(incentive).safeTransfer(to, incentiveAmount); } } /** * @notice Handles the transfer and accounting of fees and incentives for an AP offer fill. * @dev This function is called internally by `fillAPOffer` to manage the incentives. * @param incentive The address of the incentive. * @param incentiveAmount The amount of the incentive to be transferred. * @param protocolFeeAmount The protocol fee amount taken at fill. * @param frontendFeeAmount The frontend fee amount taken for this market. * @param ap The address of the action provider. * @param frontendFeeRecipient The address that will receive the frontend fee. * @param rewardStyle The style of reward distribution (Upfront, Arrear, Forfeitable). */ function _pullIncentivesOnAPFill( address incentive, uint256 incentiveAmount, uint256 protocolFeeAmount, uint256 frontendFeeAmount, address ap, address frontendFeeRecipient, RewardStyle rewardStyle ) internal { // msg.sender will always be IP if (rewardStyle == RewardStyle.Upfront) { // Take fees immediately from IP upon filling AP offers _accountFee(protocolFeeClaimant, incentive, protocolFeeAmount, msg.sender); _accountFee(frontendFeeRecipient, incentive, frontendFeeAmount, msg.sender); // Give incentives to AP immediately in an Upfront market if (PointsFactory(POINTS_FACTORY).isPointsProgram(incentive)) { // Award points on fill Points(incentive).award(ap, incentiveAmount, msg.sender); } else { // SafeTransferFrom does not check if a incentive address has any code, so we need to check it manually to prevent incentive deployment // frontrunning if (incentive.code.length == 0) { revert TokenDoesNotExist(); } // Transfer protcol and frontend fees to RecipeMarketHub for the claimants to withdraw them on-demand ERC20(incentive).safeTransferFrom(msg.sender, address(this), protocolFeeAmount + frontendFeeAmount); // Transfer AP's incentives to them on fill if token ERC20(incentive).safeTransferFrom(msg.sender, ap, incentiveAmount); } } else { // RewardStyle is Forfeitable or Arrear // If incentives will be paid out later, only handle the incentive case. Points will be awarded on claim. if (PointsFactory(POINTS_FACTORY).isPointsProgram(incentive)) { // If points incentive, make sure: // 1. The points factory used to create the program is the same as this RecipeMarketHubs PF // 2. IP placing the offer can award points // 3. Points factory has this RecipeMarketHub marked as a valid RO - can be assumed true if (POINTS_FACTORY != address(Points(incentive).pointsFactory()) || !Points(incentive).allowedIPs(msg.sender)) { revert InvalidPointsProgram(); } } else { // SafeTransferFrom does not check if a incentive address has any code, so we need to check it manually to prevent incentive deployment // frontrunning if (incentive.code.length == 0) { revert TokenDoesNotExist(); } // If not a points program, transfer amount requested (based on fill percentage) to the RecipeMarketHub in addition to protocol and frontend // fees. ERC20(incentive).safeTransferFrom(msg.sender, address(this), incentiveAmount + protocolFeeAmount + frontendFeeAmount); } } } /// @notice executes the withdrawal script for the provided weiroll wallet function _executeWithdrawalScript(address weirollWallet) internal { // Instantiate the WeirollWallet from the wallet address WeirollWallet wallet = WeirollWallet(payable(weirollWallet)); // Get the market in offer to get the withdrawal recipe WeirollMarket storage market = marketHashToWeirollMarket[wallet.marketHash()]; // Execute the withdrawal recipe wallet.executeWeiroll(market.withdrawRecipe.weirollCommands, market.withdrawRecipe.weirollState); emit WeirollWalletExecutedWithdrawal(weirollWallet); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; import { ERC20 } from "../../lib/solmate/src/tokens/ERC20.sol"; import { WeirollWallet } from "../WeirollWallet.sol"; import { ReentrancyGuardTransient } from "../../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuardTransient.sol"; import { Owned } from "../../lib/solmate/src/auth/Owned.sol"; enum RewardStyle { Upfront, Arrear, Forfeitable } /// @title RecipeMarketHubBase /// @author Shivaansh Kapoor, Jack Corddry, CopyPaste /// @notice Base contract for the RecipeMarketHub abstract contract RecipeMarketHubBase is Owned, ReentrancyGuardTransient { /// @notice The address of the WeirollWallet implementation contract for use with ClonesWithImmutableArgs address public immutable WEIROLL_WALLET_IMPLEMENTATION; /// @notice The address of the PointsFactory contract address public immutable POINTS_FACTORY; /// @notice The minimum percent you can fill an AP offer with, to prevent griefing attacks uint256 public constant MIN_FILL_PERCENT = 0.1e18; // == 10% /// @dev The minimum quantity of tokens for an offer uint256 internal constant MINIMUM_QUANTITY = 1e6; /// @notice The number of AP offers that have been created uint256 public numAPOffers; /// @notice The number of IP offers that have been created uint256 public numIPOffers; /// @notice The number of unique weiroll markets added uint256 public numMarkets; /// @notice whether offer fills are paused bool offersPaused; /// @notice The percent deducted from the IP's incentive amount and claimable by protocolFeeClaimant uint256 public protocolFee; // 1e18 == 100% fee address public protocolFeeClaimant; /// @notice Markets can opt into a higher frontend fee to incentivize quick discovery but cannot go below this minimum uint256 public minimumFrontendFee; // 1e18 == 100% fee /// @notice Holds all WeirollMarket structs mapping(bytes32 => WeirollMarket) public marketHashToWeirollMarket; /// @notice Holds all IPOffer structs mapping(bytes32 => IPOffer) public offerHashToIPOffer; /// @notice Tracks the unfilled quantity of each AP offer mapping(bytes32 => uint256) public offerHashToRemainingQuantity; // Tracks the locked incentives associated with a weiroll wallet mapping(address => LockedRewardParams) public weirollWalletToLockedIncentivesParams; // Structure to store each fee claimant's accrued fees for a particular incentive token (claimant => incentive token => feesAccrued) mapping(address => mapping(address => uint256)) public feeClaimantToTokenToAmount; /// @custom:field weirollCommands The weiroll script that will be executed on an AP's weiroll wallet after receiving the inputToken /// @custom:field weirollState State of the weiroll VM, necessary for executing the weiroll script struct Recipe { bytes32[] weirollCommands; bytes[] weirollState; } /// @custom:field marketID The ID of the Weiroll Market /// @custom:field inputToken The token that will be deposited into the user's weiroll wallet for use in the recipe /// @custom:field lockupTime The time in seconds that the user's weiroll wallet will be locked up for after deposit /// @custom:field frontendFee The fee that the frontend will take from IP incentives, 1e18 == 100% fee /// @custom:field depositRecipe The weiroll recipe that will be executed after the inputToken is transferred to the wallet /// @custom:field withdrawRecipe The weiroll recipe that may be executed after lockupTime has passed to unwind a user's position struct WeirollMarket { uint256 marketID; ERC20 inputToken; uint256 lockupTime; uint256 frontendFee; Recipe depositRecipe; Recipe withdrawRecipe; RewardStyle rewardStyle; } /// @custom:field offerID Set to numAPOffers (zero-indexed) - ordered separately for AP and IP offers /// @custom:field targetMarketHash The hash of the weiroll market which the IP offer is for /// @custom:field ip The address of the incentive provider /// @custom:field expiry The timestamp after which the offer is considered expired /// @custom:field quantity The total quantity of the market's input token requested by the IP /// @custom:field incentivesOffered The incentives offered by the IP /// @custom:field incentiveAmountsOffered Mapping of incentive to the amount of the incentive allocated to APs /// @custom:field incentiveToProtocolFeeAmount Mapping of incentive to the amount of the incentive allocated to the protocol fee /// @custom:field incentiveToFrontendFeeAmount Mapping of incentive to the amount of the incentive allocated to frontend fee recipients struct IPOffer { uint256 offerID; bytes32 targetMarketHash; address ip; uint256 expiry; uint256 quantity; uint256 remainingQuantity; address[] incentivesOffered; mapping(address => uint256) incentiveAmountsOffered; // amounts to be allocated to APs (per incentive) mapping(address => uint256) incentiveToProtocolFeeAmount; // amounts to be allocated to protocolFeeClaimant (per incentive) mapping(address => uint256) incentiveToFrontendFeeAmount; // amounts to be allocated to frontend provider (per incentive) } /// @custom:field offerID Set to numAPOffers (zero-indexed) - ordered separately for AP and IP offers /// @custom:field targetMarketHash The hash of the weiroll market which the AP offer is for /// @custom:field ap The address of the action provider /// @custom:field fundingVault The address of the vault where the input tokens will be withdrawn from /// @custom:field expiry The timestamp after which the offer is considered expired /// @custom:field quantity The total quantity of the market's input token offered by the AP /// @custom:field incentivesRequested The incentives requested by the AP /// @custom:field incentiveAmountsRequested The desired incentives per input token struct APOffer { uint256 offerID; bytes32 targetMarketHash; address ap; address fundingVault; uint256 quantity; uint256 expiry; address[] incentivesRequested; uint256[] incentiveAmountsRequested; } /// @custom:field incentives Tokens offered as incentives /// @custom:field amounts The amount of incentives offered for each incentive /// @custom:field ip The incentives provider struct LockedRewardParams { address[] incentives; uint256[] amounts; address ip; address frontendFeeRecipient; bool wasIPOffer; bytes32 offerHash; // For IP offer identification uint256 protocolFeeAtFill; // Used to keep track of protocol fee charged on fill for AP offers. } /// @custom:field marketID The ID of the newly created market /// @custom:field marketHash The hash of the newly created market /// @custom:field inputToken The token that will be deposited into the user's weiroll wallet for use in the recipe /// @custom:field lockupTime The time in seconds that the user's weiroll wallet will be locked up for after deposit /// @custom:field frontendFee The fee paid to the frontend out of IP incentives /// @custom:field rewardStyle Whether the rewards are paid at the beginning, locked until the end, or forfeitable until the end event MarketCreated( uint256 indexed marketID, bytes32 indexed marketHash, address indexed inputToken, uint256 lockupTime, uint256 frontendFee, RewardStyle rewardStyle ); /// @param offerID Set to numAPOffers (zero-indexed) - ordered separately for AP and IP offers /// @param marketHash The hash of the weiroll market which the AP offer is for /// @param ap The address of the AP that created this offer. /// @param fundingVault The address of the vault where the input tokens will be withdrawn from /// @param quantity The total amount of input tokens to be deposited /// @param incentiveAddresses The requested rewards /// @param incentiveAmounts The requested rewards per input token /// @param expiry The timestamp after which the offer is considered expired event APOfferCreated( uint256 indexed offerID, bytes32 indexed marketHash, address indexed ap, address fundingVault, uint256 quantity, address[] incentiveAddresses, uint256[] incentiveAmounts, uint256 expiry ); /// @param offerID Set to numIPOffers (zero-indexed) - ordered separately for AP and IP offers /// @param offerHash Set to the hash of the offer (used to identify IP offers) /// @param marketHash The hash of the weiroll market which the IP offer is for /// @param ip The address of the IP that created this offer. /// @param quantity The total amount of input tokens to be deposited /// @param incentivesOffered The offered rewards /// @param incentiveAmounts The offered rewards per input token /// @param protocolFeeAmounts The offered rewards protocol fee per input token /// @param frontendFeeAmounts The offered rewards frontend fee per input token /// @param expiry The timestamp after which the offer is considered expired event IPOfferCreated( uint256 offerID, bytes32 indexed offerHash, bytes32 indexed marketHash, address indexed ip, uint256 quantity, address[] incentivesOffered, uint256[] incentiveAmounts, uint256[] protocolFeeAmounts, uint256[] frontendFeeAmounts, uint256 expiry ); /// @param offerHash Hash of the offer (used to identify IP offers) /// @param ap The address of the AP that filled this offer. /// @param fillAmount The amount of the offer that was filled in the market input token /// @param weirollWallet The address of the weiroll wallet containing the AP's funds, created on fill, used to execute the recipes /// @param incentiveAmounts The amount of incentives allocated to the AP on fill (claimable as per the market's reward type) /// @param protocolFeeAmounts The protocol fee per incentive on fill (claimable as per the market's reward type) /// @param frontendFeeAmounts The rewards frontend fee per incentive on fill (claimable as per the market's reward type) event IPOfferFilled( bytes32 indexed offerHash, address indexed ap, uint256 fillAmount, address weirollWallet, uint256[] incentiveAmounts, uint256[] protocolFeeAmounts, uint256[] frontendFeeAmounts ); /// @param offerID The ID of the AP offer filled /// @param ip The address of the IP that filled this offer. /// @param fillAmount The amount of the offer that was filled in the market input token /// @param weirollWallet The address of the weiroll wallet containing the AP's funds, created on fill, used to execute the recipes /// @param incentiveAmounts The amount of incentives allocated to the AP on fill (claimable as per the market's reward type) /// @param protocolFeeAmounts The amount taken as the protocol fee per incentive on fill (claimable as per the market's reward type) /// @param frontendFeeAmounts The amount taken as the frontend fee per incentive on fill (claimable as per the market's reward type) event APOfferFilled( uint256 indexed offerID, address indexed ip, uint256 fillAmount, address weirollWallet, uint256[] incentiveAmounts, uint256[] protocolFeeAmounts, uint256[] frontendFeeAmounts ); /// @param offerHash The hash of the IP offer that was cancelled event IPOfferCancelled(bytes32 indexed offerHash); /// @param offerID The ID of the AP offer that was cancelled event APOfferCancelled(uint256 indexed offerID); /// @param claimant The address that claimed the fees /// @param incentive The address of the incentive claimed as a fee /// @param amount The amount of fees claimed event FeesClaimed(address indexed claimant, address indexed incentive, uint256 amount); /// @param weirollWallet The address of the weiroll wallet that forfeited event WeirollWalletForfeited(address indexed weirollWallet); /// @param weirollWallet The address of the weiroll wallet that claimed incentives /// @param recipient The address of the incentive recipient /// @param incentive The incentive claimed by the AP event WeirollWalletClaimedIncentive(address indexed weirollWallet, address recipient, address incentive); /// @param weirollWallet The address of the weiroll wallet that executed the withdrawal recipe event WeirollWalletExecutedWithdrawal(address indexed weirollWallet); /// @notice emitted when trying to create a market with address(0) as the input token error InvalidMarketInputToken(); /// @notice emitted when trying to fill an offer that has expired error OfferExpired(); /// @notice emitted when creating an offer with duplicate incentives error OfferCannotContainDuplicates(); /// @notice emitted when trying to fill an offer with more input tokens than the remaining offer quantity error NotEnoughRemainingQuantity(); /// @notice emitted when the base asset of the target vault and the funding vault do not match error MismatchedBaseAsset(); /// @notice emitted if a market with the given ID does not exist error MarketDoesNotExist(); /// @notice emitted when trying to place an offer with an expiry in the past error CannotPlaceExpiredOffer(); /// @notice emitted when trying to place an offer with a quantity of 0 error CannotPlaceZeroQuantityOffer(); /// @notice emitted when incentives and amounts offered/requested arrays are not the same length error ArrayLengthMismatch(); /// @notice emitted when the frontend fee is below the minimum error FrontendFeeTooLow(); /// @notice emitted when trying to forfeit a wallet that is not owned by the caller error NotOwner(); /// @notice emitted when trying to claim rewards of a wallet that is locked error WalletLocked(); /// @notice Emitted when trying to start a rewards campaign with a non-existent incentive error TokenDoesNotExist(); /// @notice Emitted when sum of protocolFee and frontendFee is greater than 100% (1e18) error TotalFeeTooHigh(); /// @notice emitted when trying to fill an offer that doesn't exist anymore/yet error CannotFillZeroQuantityOffer(); /// @notice emitted when funding the weiroll wallet with the market's input token failed error WeirollWalletFundingFailed(); /// @notice emitted when creating an offer with an invalid points program error InvalidPointsProgram(); /// @notice emitted when APOfferFill charges a trivial incentive amount error NoIncentivesPaidOnFill(); /// @notice emitted when trying to fill offers while offers are paused error OffersPaused(); /// @notice emitted when trying to forfeit a wallet where rewards are not forfeitable error WalletNotForfeitable(); /// @notice emitted when trying to fill an offer with a quantity below the minimum fill percent error InsufficientFillPercent(); /// @notice Modifier to check if msg.sender is owner of a weirollWallet modifier isWeirollOwner(address weirollWallet) { if (WeirollWallet(payable(weirollWallet)).owner() != msg.sender) { revert NotOwner(); } _; } /// @notice Modifier to check if the weiroll wallet is unlocked modifier weirollIsUnlocked(address weirollWallet) { if (WeirollWallet(payable(weirollWallet)).lockedUntil() > block.timestamp) { revert WalletLocked(); } _; } /// @notice Check if offer fills have been paused modifier offersNotPaused() { if (offersPaused) { revert OffersPaused(); } _; } /// @notice Setter to pause and unpause fills function setOffersPaused(bool _offersPaused) external onlyOwner { offersPaused = _offersPaused; } /// @notice sets the protocol fee recipient, taken on all fills /// @param _protocolFeeClaimant The address allowed to claim protocol fees function setProtocolFeeClaimant(address _protocolFeeClaimant) external payable onlyOwner { protocolFeeClaimant = _protocolFeeClaimant; } /// @notice sets the protocol fee rate, taken on all fills /// @param _protocolFee The percent deducted from the IP's incentive amount and claimable by protocolFeeClaimant, 1e18 == 100% fee function setProtocolFee(uint256 _protocolFee) external payable onlyOwner { protocolFee = _protocolFee; } /// @notice sets the minimum frontend fee that a market can set and is paid to whoever fills the offer /// @param _minimumFrontendFee The minimum frontend fee for a market, 1e18 == 100% fee function setMinimumFrontendFee(uint256 _minimumFrontendFee) external payable onlyOwner { minimumFrontendFee = _minimumFrontendFee; } /// @notice Calculates the hash of a Weiroll Market function getMarketHash(WeirollMarket memory market) public pure returns (bytes32) { return keccak256(abi.encode(market)); } /// @notice Calculates the hash of an AP offer function getAPOfferHash(APOffer memory offer) public pure returns (bytes32) { return keccak256(abi.encode(offer)); } /// @notice Calculates the hash of an IP offer function getIPOfferHash( uint256 offerID, bytes32 targetMarketHash, address ip, uint256 expiry, uint256 quantity, address[] calldata incentivesOffered, uint256[] memory incentiveAmountsOffered ) public pure returns (bytes32) { return keccak256(abi.encodePacked(offerID, targetMarketHash, ip, expiry, quantity, incentivesOffered, incentiveAmountsOffered)); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. abstract contract ERC20 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 amount); /*////////////////////////////////////////////////////////////// METADATA STORAGE //////////////////////////////////////////////////////////////*/ string public name; string public symbol; uint8 public immutable decimals; /*////////////////////////////////////////////////////////////// ERC20 STORAGE //////////////////////////////////////////////////////////////*/ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; /*////////////////////////////////////////////////////////////// EIP-2612 STORAGE //////////////////////////////////////////////////////////////*/ uint256 internal immutable INITIAL_CHAIN_ID; bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor( string memory _name, string memory _symbol, uint8 _decimals ) { name = _name; symbol = _symbol; decimals = _decimals; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); } /*////////////////////////////////////////////////////////////// ERC20 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 amount) public virtual returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); return true; } function transferFrom( address from, address to, uint256 amount ) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount); return true; } /*////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 amount) internal virtual { totalSupply += amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal virtual { balanceOf[from] -= amount; // Cannot underflow because a user's balance // will never be larger than the total supply. unchecked { totalSupply -= amount; } emit Transfer(from, address(0), amount); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "../tokens/ERC20.sol"; import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol"; /// @notice Minimal ERC4626 tokenized Vault implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC4626.sol) abstract contract ERC4626 is ERC20 { using SafeTransferLib for ERC20; using FixedPointMathLib for uint256; /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); event Withdraw( address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); /*////////////////////////////////////////////////////////////// IMMUTABLES //////////////////////////////////////////////////////////////*/ ERC20 public immutable asset; constructor( ERC20 _asset, string memory _name, string memory _symbol ) ERC20(_name, _symbol, _asset.decimals()) { asset = _asset; } /*////////////////////////////////////////////////////////////// DEPOSIT/WITHDRAWAL LOGIC //////////////////////////////////////////////////////////////*/ function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) { // Check for rounding error since we round down in previewDeposit. require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES"); // Need to transfer before minting or ERC777s could reenter. asset.safeTransferFrom(msg.sender, address(this), assets); _mint(receiver, shares); emit Deposit(msg.sender, receiver, assets, shares); afterDeposit(assets, shares); } function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) { assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up. // Need to transfer before minting or ERC777s could reenter. asset.safeTransferFrom(msg.sender, address(this), assets); _mint(receiver, shares); emit Deposit(msg.sender, receiver, assets, shares); afterDeposit(assets, shares); } function withdraw( uint256 assets, address receiver, address owner ) public virtual returns (uint256 shares) { shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up. if (msg.sender != owner) { uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; } beforeWithdraw(assets, shares); _burn(owner, shares); emit Withdraw(msg.sender, receiver, owner, assets, shares); asset.safeTransfer(receiver, assets); } function redeem( uint256 shares, address receiver, address owner ) public virtual returns (uint256 assets) { if (msg.sender != owner) { uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; } // Check for rounding error since we round down in previewRedeem. require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS"); beforeWithdraw(assets, shares); _burn(owner, shares); emit Withdraw(msg.sender, receiver, owner, assets, shares); asset.safeTransfer(receiver, assets); } /*////////////////////////////////////////////////////////////// ACCOUNTING LOGIC //////////////////////////////////////////////////////////////*/ function totalAssets() public view virtual returns (uint256); function convertToShares(uint256 assets) public view virtual returns (uint256) { uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets()); } function convertToAssets(uint256 shares) public view virtual returns (uint256) { uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply); } function previewDeposit(uint256 assets) public view virtual returns (uint256) { return convertToShares(assets); } function previewMint(uint256 shares) public view virtual returns (uint256) { uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply); } function previewWithdraw(uint256 assets) public view virtual returns (uint256) { uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets()); } function previewRedeem(uint256 shares) public view virtual returns (uint256) { return convertToAssets(shares); } /*////////////////////////////////////////////////////////////// DEPOSIT/WITHDRAWAL LIMIT LOGIC //////////////////////////////////////////////////////////////*/ function maxDeposit(address) public view virtual returns (uint256) { return type(uint256).max; } function maxMint(address) public view virtual returns (uint256) { return type(uint256).max; } function maxWithdraw(address owner) public view virtual returns (uint256) { return convertToAssets(balanceOf[owner]); } function maxRedeem(address owner) public view virtual returns (uint256) { return balanceOf[owner]; } /*////////////////////////////////////////////////////////////// INTERNAL HOOKS LOGIC //////////////////////////////////////////////////////////////*/ function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {} function afterDeposit(uint256 assets, uint256 shares) internal virtual {} }
// SPDX-License-Identifier: BSD pragma solidity ^0.8.4; /// @title ClonesWithImmutableArgs /// @author wighawag, zefram.eth /// @notice Enables creating clone contracts with immutable args library ClonesWithImmutableArgs { error CreateFail(); /// @notice Creates a clone proxy of the implementation contract, with immutable args /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length /// @param implementation The implementation contract to clone /// @param data Encoded immutable args /// @return instance The address of the created clone function clone(address implementation, bytes memory data) internal returns (address instance) { // unrealistic for memory ptr or data length to exceed 256 bits unchecked { uint256 extraLength = data.length + 2; // +2 bytes for telling how much data there is appended to the call uint256 creationSize = 0x43 + extraLength; uint256 runSize = creationSize - 11; uint256 dataPtr; uint256 ptr; // solhint-disable-next-line no-inline-assembly assembly { ptr := mload(0x40) // ------------------------------------------------------------------------------------------------------------- // CREATION (11 bytes) // ------------------------------------------------------------------------------------------------------------- // 3d | RETURNDATASIZE | 0 | – // 61 runtime | PUSH2 runtime (r) | r 0 | – mstore( ptr, 0x3d61000000000000000000000000000000000000000000000000000000000000 ) mstore(add(ptr, 0x02), shl(240, runSize)) // size of the contract running bytecode (16 bits) // creation size = 0b // 80 | DUP1 | r r 0 | – // 60 creation | PUSH1 creation (c) | c r r 0 | – // 3d | RETURNDATASIZE | 0 c r r 0 | – // 39 | CODECOPY | r 0 | [0-2d]: runtime code // 81 | DUP2 | 0 c 0 | [0-2d]: runtime code // f3 | RETURN | 0 | [0-2d]: runtime code mstore( add(ptr, 0x04), 0x80600b3d3981f300000000000000000000000000000000000000000000000000 ) // ------------------------------------------------------------------------------------------------------------- // RUNTIME // ------------------------------------------------------------------------------------------------------------- // 36 | CALLDATASIZE | cds | – // 3d | RETURNDATASIZE | 0 cds | – // 3d | RETURNDATASIZE | 0 0 cds | – // 37 | CALLDATACOPY | – | [0, cds] = calldata // 61 | PUSH2 extra | extra | [0, cds] = calldata mstore( add(ptr, 0x0b), 0x363d3d3761000000000000000000000000000000000000000000000000000000 ) mstore(add(ptr, 0x10), shl(240, extraLength)) // 60 0x38 | PUSH1 0x38 | 0x38 extra | [0, cds] = calldata // 0x38 (56) is runtime size - data // 36 | CALLDATASIZE | cds 0x38 extra | [0, cds] = calldata // 39 | CODECOPY | _ | [0, cds] = calldata // 3d | RETURNDATASIZE | 0 | [0, cds] = calldata // 3d | RETURNDATASIZE | 0 0 | [0, cds] = calldata // 3d | RETURNDATASIZE | 0 0 0 | [0, cds] = calldata // 36 | CALLDATASIZE | cds 0 0 0 | [0, cds] = calldata // 61 extra | PUSH2 extra | extra cds 0 0 0 | [0, cds] = calldata mstore( add(ptr, 0x12), 0x603836393d3d3d36610000000000000000000000000000000000000000000000 ) mstore(add(ptr, 0x1b), shl(240, extraLength)) // 01 | ADD | cds+extra 0 0 0 | [0, cds] = calldata // 3d | RETURNDATASIZE | 0 cds 0 0 0 | [0, cds] = calldata // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 | [0, cds] = calldata mstore( add(ptr, 0x1d), 0x013d730000000000000000000000000000000000000000000000000000000000 ) mstore(add(ptr, 0x20), shl(0x60, implementation)) // 5a | GAS | gas addr 0 cds 0 0 0 | [0, cds] = calldata // f4 | DELEGATECALL | success 0 | [0, cds] = calldata // 3d | RETURNDATASIZE | rds success 0 | [0, cds] = calldata // 82 | DUP3 | 0 rds success 0 | [0, cds] = calldata // 80 | DUP1 | 0 0 rds success 0 | [0, cds] = calldata // 3e | RETURNDATACOPY | success 0 | [0, rds] = return data (there might be some irrelevant leftovers in memory [rds, cds] when rds < cds) // 90 | SWAP1 | 0 success | [0, rds] = return data // 3d | RETURNDATASIZE | rds 0 success | [0, rds] = return data // 91 | SWAP2 | success 0 rds | [0, rds] = return data // 60 0x36 | PUSH1 0x36 | 0x36 sucess 0 rds | [0, rds] = return data // 57 | JUMPI | 0 rds | [0, rds] = return data // fd | REVERT | – | [0, rds] = return data // 5b | JUMPDEST | 0 rds | [0, rds] = return data // f3 | RETURN | – | [0, rds] = return data mstore( add(ptr, 0x34), 0x5af43d82803e903d91603657fd5bf30000000000000000000000000000000000 ) } // ------------------------------------------------------------------------------------------------------------- // APPENDED DATA (Accessible from extcodecopy) // (but also send as appended data to the delegatecall) // ------------------------------------------------------------------------------------------------------------- extraLength -= 2; uint256 counter = extraLength; uint256 copyPtr = ptr + 0x43; // solhint-disable-next-line no-inline-assembly assembly { dataPtr := add(data, 32) } for (; counter >= 32; counter -= 32) { // solhint-disable-next-line no-inline-assembly assembly { mstore(copyPtr, mload(dataPtr)) } copyPtr += 32; dataPtr += 32; } uint256 mask = ~(256**(32 - counter) - 1); // solhint-disable-next-line no-inline-assembly assembly { mstore(copyPtr, and(mload(dataPtr), mask)) } copyPtr += counter; // solhint-disable-next-line no-inline-assembly assembly { mstore(copyPtr, shl(240, extraLength)) } // solhint-disable-next-line no-inline-assembly assembly { instance := create(0, ptr, creationSize) } if (instance == address(0)) { revert CreateFail(); } } } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "../tokens/ERC20.sol"; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. /// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. library SafeTransferLib { /*////////////////////////////////////////////////////////////// ETH OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferETH(address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Transfer the ETH and store if it succeeded or not. success := call(gas(), to, amount, 0, 0, 0, 0) } require(success, "ETH_TRANSFER_FAILED"); } /*////////////////////////////////////////////////////////////// ERC20 OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferFrom( ERC20 token, address from, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument. mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 68), 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 100 because the length of our calldata totals up like so: 4 + 32 * 3. // 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(), token, 0, freeMemoryPointer, 100, 0, 32) ) } require(success, "TRANSFER_FROM_FAILED"); } function safeTransfer( ERC20 token, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 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(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "TRANSFER_FAILED"); } function safeApprove( ERC20 token, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 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(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "APPROVE_FAILED"); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Arithmetic library with operations for fixed-point numbers. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) library FixedPointMathLib { /*////////////////////////////////////////////////////////////// SIMPLIFIED FIXED POINT OPERATIONS //////////////////////////////////////////////////////////////*/ uint256 internal constant MAX_UINT256 = 2**256 - 1; uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s. function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down. } function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up. } function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down. } function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up. } /*////////////////////////////////////////////////////////////// LOW LEVEL FIXED POINT OPERATIONS //////////////////////////////////////////////////////////////*/ function mulDivDown( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { revert(0, 0) } // Divide x * y by the denominator. z := div(mul(x, y), denominator) } } function mulDivUp( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { revert(0, 0) } // If x * y modulo the denominator is strictly greater than 0, // 1 is added to round up the division of x * y by the denominator. z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator)) } } function rpow( uint256 x, uint256 n, uint256 scalar ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { switch x case 0 { switch n case 0 { // 0 ** 0 = 1 z := scalar } default { // 0 ** n = 0 z := 0 } } default { switch mod(n, 2) case 0 { // If n is even, store scalar in z for now. z := scalar } default { // If n is odd, store x in z for now. z := x } // Shifting right by 1 is like dividing by 2. let half := shr(1, scalar) for { // Shift n right by 1 before looping to halve it. n := shr(1, n) } n { // Shift n right by 1 each iteration to halve it. n := shr(1, n) } { // Revert immediately if x ** 2 would overflow. // Equivalent to iszero(eq(div(xx, x), x)) here. if shr(128, x) { revert(0, 0) } // Store x squared. let xx := mul(x, x) // Round to the nearest number. let xxRound := add(xx, half) // Revert if xx + half overflowed. if lt(xxRound, xx) { revert(0, 0) } // Set x to scaled xxRound. x := div(xxRound, scalar) // If n is even: if mod(n, 2) { // Compute z * x. let zx := mul(z, x) // If z * x overflowed: if iszero(eq(div(zx, x), z)) { // Revert if x is non-zero. if iszero(iszero(x)) { revert(0, 0) } } // Round to the nearest number. let zxRound := add(zx, half) // Revert if zx + half overflowed. if lt(zxRound, zx) { revert(0, 0) } // Return properly scaled zxRound. z := div(zxRound, scalar) } } } } } /*////////////////////////////////////////////////////////////// GENERAL NUMBER UTILITIES //////////////////////////////////////////////////////////////*/ function sqrt(uint256 x) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { let y := x // We start y at x, which will help us make our initial estimate. z := 181 // The "correct" value is 1, but this saves a multiplication later. // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically. // We check y >= 2^(k + 8) but shift right by k bits // each branch to ensure that if x >= 256, then y >= 256. if iszero(lt(y, 0x10000000000000000000000000000000000)) { y := shr(128, y) z := shl(64, z) } if iszero(lt(y, 0x1000000000000000000)) { y := shr(64, y) z := shl(32, z) } if iszero(lt(y, 0x10000000000)) { y := shr(32, y) z := shl(16, z) } if iszero(lt(y, 0x1000000)) { y := shr(16, y) z := shl(8, z) } // Goal was to get z*z*y within a small factor of x. More iterations could // get y in a tighter range. Currently, we will have y in [256, 256*2^16). // We ensured y >= 256 so that the relative difference between y and y+1 is small. // That's not possible if x < 256 but we can just verify those cases exhaustively. // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256. // Correctness can be checked exhaustively for x < 256, so we assume y >= 256. // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps. // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256. // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18. // There is no overflow risk here since y < 2^136 after the first branch above. z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181. // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough. z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) // If x+1 is a perfect square, the Babylonian method cycles between // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor. // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case. // If you don't care whether the floor or ceil square root is returned, you can remove this statement. z := sub(z, lt(div(x, z), z)) } } function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Mod x by y. Note this will return // 0 instead of reverting if y is zero. z := mod(x, y) } } function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) { /// @solidity memory-safe-assembly assembly { // Divide x by y. Note this will return // 0 instead of reverting if y is zero. r := div(x, y) } } function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Add 1 to x * y if x % y > 0. Note this will // return 0 instead of reverting if y is zero. z := add(gt(mod(x, y), 0), div(x, y)) } } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; import { PointsFactory } from "./PointsFactory.sol"; import { Ownable2Step, Ownable } from "../lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol"; /// @title Points /// @author CopyPaste, Jack Corddry, Shivaansh Kapoor /// @dev A simple contract for running Points Programs contract Points is Ownable2Step { /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ /// @param _name The name of the points program /// @param _symbol The symbol for the points program /// @param _decimals The amount of decimals to use for accounting with points /// @param _owner The owner of the points program constructor(string memory _name, string memory _symbol, uint256 _decimals, address _owner) Ownable(_owner) { name = _name; symbol = _symbol; decimals = _decimals; // Enforces that the Points Program deployer is a factory pointsFactory = PointsFactory(msg.sender); } /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Award(address indexed to, uint256 indexed amount, address indexed awardedBy); event AllowedVaultAdded(address indexed vault); event AllowedIPAdded(address indexed ip); event VaultRemoved(address indexed vault); /*////////////////////////////////////////////////////////////// STORAGE //////////////////////////////////////////////////////////////*/ /// @dev Maps a vault to if the vault is allowed to call this contract mapping(address => bool) public isAllowedVault; /// @dev The PointsFactory used to create this program PointsFactory public immutable pointsFactory; /// @dev The name of the points program string public name; /// @dev The symbol for the points program string public symbol; /// @dev We track all points logic using base 1 uint256 public decimals; /// @dev Track which RecipeMarketHub IPs are allowed to mint mapping(address => bool) public allowedIPs; /*////////////////////////////////////////////////////////////// POINTS AUTH //////////////////////////////////////////////////////////////*/ error VaultIsDuplicate(); /// @param vault The address to add to the allowed vaults for the points program function addAllowedVault(address vault) external onlyOwner { if (isAllowedVault[vault]) { revert VaultIsDuplicate(); } isAllowedVault[vault] = true; emit AllowedVaultAdded(vault); } /// @param ip The incentive provider address to allow to mint points on RecipeMarketHub function addAllowedIP(address ip) external onlyOwner { allowedIPs[ip] = true; emit AllowedIPAdded(ip); } error OnlyAllowedVaults(); error OnlyRecipeMarketHub(); error NotAllowedIP(); modifier onlyAllowedVaults() { if (!isAllowedVault[msg.sender]) { revert OnlyAllowedVaults(); } _; } /// @dev only the RecipeMarketHub can call this function /// @param ip The address to check if allowed modifier onlyRecipeMarketHubAllowedIP(address ip) { if (!pointsFactory.isRecipeMarketHub(msg.sender)) { revert OnlyRecipeMarketHub(); } if (!allowedIPs[ip]) { revert NotAllowedIP(); } _; } /*////////////////////////////////////////////////////////////// POINTS //////////////////////////////////////////////////////////////*/ /// @param to The address to mint points to /// @param amount The amount of points to award to the `to` address function award(address to, uint256 amount) external onlyAllowedVaults { emit Award(to, amount, msg.sender); } /// @param to The address to mint points to /// @param amount The amount of points to award to the `to` address /// @param ip The incentive provider attempting to mint the points function award(address to, uint256 amount, address ip) external onlyRecipeMarketHubAllowedIP(ip) { emit Award(to, amount, ip); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; import { Points } from "./Points.sol"; import { Ownable2Step, Ownable } from "../lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol"; /// @title PointsFactory /// @author CopyPaste, Jack Corddry, Shivaansh Kapoor /// @dev A simple factory for creating Points Programs contract PointsFactory is Ownable2Step { /// @notice Mapping of Points Program address => bool (indicator of if Points Program was deployed using this factory) mapping(address => bool) public isPointsProgram; /// @notice Mapping of RecipeMarketHub address => bool (indicator of if the address is of a Royco RecipeMarketHub) mapping(address => bool) public isRecipeMarketHub; /// @notice Emitted when creating a points program using this factory event NewPointsProgram(Points indexed points, string indexed name, string indexed symbol); /// @notice Emitted when adding an RecipeMarketHub to this Points Factory event RecipeMarketHubAdded(address indexed recipeMarketHub); /// @param _owner The owner of the points factory - responsible for adding valid RecipeMarketHub(s) to the PointsFactory constructor(address _owner) Ownable(_owner) { } /// @param _recipeMarketHub The RecipeMarketHub to mark as valid in the Points Factory function addRecipeMarketHub(address _recipeMarketHub) external onlyOwner { isRecipeMarketHub[_recipeMarketHub] = true; emit RecipeMarketHubAdded(_recipeMarketHub); } /// @param _name The name for the new points program /// @param _symbol The symbol for the new points program /// @param _decimals The amount of decimals per point /// @param _owner The owner of the new points program function createPointsProgram(string memory _name, string memory _symbol, uint256 _decimals, address _owner) external returns (Points points) { bytes32 salt = keccak256(abi.encode(_name, _symbol, _decimals, _owner)); points = new Points{ salt: salt }(_name, _symbol, _decimals, _owner); isPointsProgram[address(points)] = true; emit NewPointsProgram(points, _name, _symbol); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Simple single owner authorization mixin. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol) abstract contract Owned { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event OwnershipTransferred(address indexed user, address indexed newOwner); /*////////////////////////////////////////////////////////////// OWNERSHIP STORAGE //////////////////////////////////////////////////////////////*/ address public owner; modifier onlyOwner() virtual { require(msg.sender == owner, "UNAUTHORIZED"); _; } /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor(address _owner) { owner = _owner; emit OwnershipTransferred(address(0), _owner); } /*////////////////////////////////////////////////////////////// OWNERSHIP LOGIC //////////////////////////////////////////////////////////////*/ function transferOwnership(address newOwner) public virtual onlyOwner { owner = newOwner; emit OwnershipTransferred(msg.sender, newOwner); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; import { VM } from "../lib/enso-weiroll/contracts/VM.sol"; import { Clone } from "../lib/clones-with-immutable-args/src/Clone.sol"; import { IERC1271 } from "./interfaces/IERC1271.sol"; import { ECDSA } from "../lib/solady/src/utils/ECDSA.sol"; /// @title WeirollWallet /// @author Jack Corddry, Shivaansh Kapoor, CopyPaste /// @notice WeirollWallet implementation contract. /// @notice Implements a simple smart contract wallet that can execute Weiroll VM commands contract WeirollWallet is IERC1271, Clone, VM { // Returned to indicate a valid ERC1271 signature bytes4 internal constant ERC1271_MAGIC_VALUE = 0x1626ba7e; // bytes4(keccak256("isValidSignature(bytes32,bytes)") // Returned to indicate an invalid ERC1271 signature bytes4 internal constant INVALID_SIGNATURE = 0x00000000; /// @notice Let the Weiroll Wallet receive ether directly if needed receive() external payable { } /// @notice Also allow a fallback with no logic if erroneous data is provided fallback() external payable { } /*////////////////////////////////////////////////////////////// MODIFIERS //////////////////////////////////////////////////////////////*/ // Emit when owner executes an arbitrary script (not a market script) event WeirollWalletExecutedManually(); error NotOwner(); error NotRecipeMarketHub(); error WalletLocked(); error WalletNotForfeitable(); error OfferUnfilled(); error RawExecutionFailed(); /// @notice Only the owner of the contract can call the function modifier onlyOwner() { if (msg.sender != owner()) { revert NotOwner(); } _; } /// @notice Only the recipeMarketHub contract can call the function modifier onlyRecipeMarketHub() { if (msg.sender != recipeMarketHub()) { revert NotRecipeMarketHub(); } _; } /// @notice The wallet can be locked modifier notLocked() { if (!forfeited && lockedUntil() > block.timestamp) { revert WalletLocked(); } _; } /*////////////////////////////////////////////////////////////// STATE VARIABLES //////////////////////////////////////////////////////////////*/ /// @dev Whether or not this offer has been executed bool public executed; /// @dev Whether or not the wallet has been forfeited bool public forfeited; /// @notice Forfeit all rewards to get control of the wallet back function forfeit() public onlyRecipeMarketHub { if (!isForfeitable() || block.timestamp >= lockedUntil()) { // Can't forfeit if: // 1. Wallet not created through a forfeitable market // 2. Lock time has passed and claim window has started revert WalletNotForfeitable(); } forfeited = true; } /// @notice The address of the offer creator (owner) function owner() public pure returns (address) { return _getArgAddress(0); } /// @notice The address of the RecipeMarketHub contract function recipeMarketHub() public pure returns (address) { return _getArgAddress(20); } /// @notice The amount of tokens deposited into this wallet from the recipeMarketHub function amount() public pure returns (uint256) { return _getArgUint256(40); } /// @notice The timestamp after which the wallet may be interacted with function lockedUntil() public pure returns (uint256) { return _getArgUint256(72); } /// @notice Returns whether or not the wallet is forfeitable function isForfeitable() public pure returns (bool) { return _getArgUint8(104) != 0; } /// @notice Returns the hash of the market associated with this weiroll wallet function marketHash() public pure returns (bytes32) { return bytes32(_getArgUint256(105)); } /*////////////////////////////////////////////////////////////// EXECUTION LOGIC //////////////////////////////////////////////////////////////*/ /// @notice Execute the Weiroll VM with the given commands. /// @param commands The commands to be executed by the Weiroll VM. function executeWeiroll(bytes32[] calldata commands, bytes[] calldata state) public payable onlyRecipeMarketHub returns (bytes[] memory) { executed = true; // Execute the Weiroll VM. return _execute(commands, state); } /// @notice Execute the Weiroll VM with the given commands. /// @param commands The commands to be executed by the Weiroll VM. function manualExecuteWeiroll(bytes32[] calldata commands, bytes[] calldata state) public payable onlyOwner notLocked returns (bytes[] memory) { // Prevent people from approving w/e then rugging during vesting if (!executed) revert OfferUnfilled(); emit WeirollWalletExecutedManually(); // Execute the Weiroll VM. return _execute(commands, state); } /// @notice Execute a generic call to another contract. /// @param to The address to call /// @param value The ether value of the execution /// @param data The data to pass along with the call function execute(address to, uint256 value, bytes memory data) public payable onlyOwner notLocked returns (bytes memory) { // Prevent people from approving w/e then rugging during vesting if (!executed) revert OfferUnfilled(); // Execute the call. (bool success, bytes memory result) = to.call{ value: value }(data); if (!success) revert RawExecutionFailed(); emit WeirollWalletExecutedManually(); return result; } /// @notice Check if signature is valid for this contract /// @dev Signature is valid if the signer is the owner of this wallet /// @param digest Hash of the message to validate the signature against /// @param signature Signature produced for the provided digest function isValidSignature(bytes32 digest, bytes calldata signature) external view returns (bytes4) { // Modify digest to include the chainId and address of this wallet to prevent replay attacks bytes32 walletSpecificDigest = keccak256(abi.encode(digest, block.chainid, address(this))); // Check if signature was produced by owner of this wallet // Don't revert on failure. Simply return INVALID_SIGNATURE. if (ECDSA.tryRecover(walletSpecificDigest, signature) == owner()) return ERC1271_MAGIC_VALUE; else return INVALID_SIGNATURE; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {StorageSlot} from "./StorageSlot.sol"; /** * @dev Variant of {ReentrancyGuard} that uses transient storage. * * NOTE: This variant only works on networks where EIP-1153 is available. */ abstract contract ReentrancyGuardTransient { using StorageSlot for *; // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant REENTRANCY_GUARD_STORAGE = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00; /** * @dev Unauthorized reentrant call. */ error ReentrancyGuardReentrantCall(); /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be NOT_ENTERED if (_reentrancyGuardEntered()) { revert ReentrancyGuardReentrantCall(); } // Any calls to nonReentrant after this point will fail REENTRANCY_GUARD_STORAGE.asBoolean().tstore(true); } function _nonReentrantAfter() private { REENTRANCY_GUARD_STORAGE.asBoolean().tstore(false); } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return REENTRANCY_GUARD_STORAGE.asBoolean().tload(); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol) pragma solidity ^0.8.20; import {Ownable} from "./Ownable.sol"; /** * @dev Contract module which provides access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * This extension of the {Ownable} contract includes a two-step mechanism to transfer * ownership, where the new owner must call {acceptOwnership} in order to replace the * old one. This can help prevent common mistakes, such as transfers of ownership to * incorrect accounts, or to contracts that are unable to interact with the * permission system. * * The initial owner is specified at deployment time in the constructor for `Ownable`. This * can later be changed with {transferOwnership} and {acceptOwnership}. * * This module is used through inheritance. It will make available all functions * from parent (Ownable). */ abstract contract Ownable2Step is Ownable { address private _pendingOwner; event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); /** * @dev Returns the address of the pending owner. */ function pendingOwner() public view virtual returns (address) { return _pendingOwner; } /** * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. * Can only be called by the current owner. * * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer. */ function transferOwnership(address newOwner) public virtual override onlyOwner { _pendingOwner = newOwner; emit OwnershipTransferStarted(owner(), newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual override { delete _pendingOwner; super._transferOwnership(newOwner); } /** * @dev The new owner accepts the ownership transfer. */ function acceptOwnership() public virtual { address sender = _msgSender(); if (pendingOwner() != sender) { revert OwnableUnauthorizedAccount(sender); } _transferOwnership(sender); } }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.16; import "./CommandBuilder.sol"; abstract contract VM { using CommandBuilder for bytes[]; uint256 constant FLAG_CT_DELEGATECALL = 0x00; // Delegate call not currently supported uint256 constant FLAG_CT_CALL = 0x01; uint256 constant FLAG_CT_STATICCALL = 0x02; uint256 constant FLAG_CT_VALUECALL = 0x03; uint256 constant FLAG_CT_MASK = 0x03; uint256 constant FLAG_DATA = 0x20; uint256 constant FLAG_EXTENDED_COMMAND = 0x40; uint256 constant FLAG_TUPLE_RETURN = 0x80; uint256 constant SHORT_COMMAND_FILL = 0x000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; error ExecutionFailed( uint256 command_index, address target, string message ); function _execute(bytes32[] calldata commands, bytes[] memory state) internal returns (bytes[] memory) { bytes32 command; uint256 flags; bytes32 indices; bool success; bytes memory outData; uint256 commandsLength = commands.length; uint256 indicesLength; for (uint256 i; i < commandsLength; i = _uncheckedIncrement(i)) { command = commands[i]; flags = uint256(uint8(bytes1(command << 32))); if (flags & FLAG_EXTENDED_COMMAND != 0) { i = _uncheckedIncrement(i); indices = commands[i]; indicesLength = 32; } else { indices = bytes32(uint256(command << 40) | SHORT_COMMAND_FILL); indicesLength = 6; } if (flags & FLAG_CT_MASK == FLAG_CT_CALL) { (success, outData) = address(uint160(uint256(command))).call( // target // inputs flags & FLAG_DATA == 0 ? state.buildInputs( bytes4(command), // selector indices, indicesLength ) : state[ uint8(bytes1(indices)) & CommandBuilder.IDX_VALUE_MASK ] ); } else if (flags & FLAG_CT_MASK == FLAG_CT_STATICCALL) { (success, outData) = address(uint160(uint256(command))) // target .staticcall( // inputs flags & FLAG_DATA == 0 ? state.buildInputs( bytes4(command), // selector indices, indicesLength ) : state[ uint8(bytes1(indices)) & CommandBuilder.IDX_VALUE_MASK ] ); } else if (flags & FLAG_CT_MASK == FLAG_CT_VALUECALL) { bytes memory v = state[ uint8(bytes1(indices)) & CommandBuilder.IDX_VALUE_MASK ]; require(v.length == 32, "Value must be 32 bytes"); uint256 callEth = uint256(bytes32(v)); (success, outData) = address(uint160(uint256(command))).call{ // target value: callEth }( // inputs flags & FLAG_DATA == 0 ? state.buildInputs( bytes4(command), // selector indices << 8, // skip value input indicesLength - 1 // max indices length reduced by value input ) : state[ uint8(bytes1(indices << 8)) & // first byte after value input CommandBuilder.IDX_VALUE_MASK ] ); } else { revert("Invalid calltype"); } if (!success) { string memory message = "Unknown"; if (outData.length > 68) { // This might be an error message, parse the outData // Estimate the bytes length of the possible error message uint256 estimatedLength = _estimateBytesLength(outData, 68); // Remove selector. First 32 bytes should be a pointer that indicates the start of data in memory assembly { outData := add(outData, 4) } uint256 pointer = uint256(bytes32(outData)); if (pointer == 32) { // Remove pointer. If it is a string, the next 32 bytes will hold the size assembly { outData := add(outData, 32) } uint256 size = uint256(bytes32(outData)); // If the size variable is the same as the estimated bytes length, we can be fairly certain // this is a dynamic string, so convert the bytes to a string and emit the message. While an // error function with 3 static parameters is capable of producing a similar output, there is // low risk of a contract unintentionally emitting a message. if (size == estimatedLength) { // Remove size. The remaining data should be the string content assembly { outData := add(outData, 32) } message = string(outData); } } } revert ExecutionFailed({ command_index: flags & FLAG_EXTENDED_COMMAND == 0 ? i : i - 1, target: address(uint160(uint256(command))), message: message }); } if (flags & FLAG_TUPLE_RETURN != 0) { state.writeTuple(bytes1(command << 88), outData); } else { state = state.writeOutputs(bytes1(command << 88), outData); } } return state; } function _estimateBytesLength(bytes memory data, uint256 pos) internal pure returns (uint256 estimate) { uint256 length = data.length; estimate = length - pos; // Assume length equals alloted space for (uint256 i = pos; i < length; ) { if (data[i] == 0) { // Zero bytes found, adjust estimated length estimate = i - pos; break; } unchecked { ++i; } } } function _uncheckedIncrement(uint256 i) private pure returns (uint256) { unchecked { ++i; } return i; } }
// SPDX-License-Identifier: BSD pragma solidity ^0.8.4; /// @title Clone /// @author zefram.eth /// @notice Provides helper functions for reading immutable args from calldata contract Clone { /// @notice Reads an immutable arg with type address /// @param argOffset The offset of the arg in the packed data /// @return arg The arg value function _getArgAddress(uint256 argOffset) internal pure returns (address arg) { uint256 offset = _getImmutableArgsOffset(); assembly { arg := shr(0x60, calldataload(add(offset, argOffset))) } } /// @notice Reads an immutable arg with type uint256 /// @param argOffset The offset of the arg in the packed data /// @return arg The arg value function _getArgUint256(uint256 argOffset) internal pure returns (uint256 arg) { uint256 offset = _getImmutableArgsOffset(); // solhint-disable-next-line no-inline-assembly assembly { arg := calldataload(add(offset, argOffset)) } } /// @notice Reads an immutable arg with type uint64 /// @param argOffset The offset of the arg in the packed data /// @return arg The arg value function _getArgUint64(uint256 argOffset) internal pure returns (uint64 arg) { uint256 offset = _getImmutableArgsOffset(); // solhint-disable-next-line no-inline-assembly assembly { arg := shr(0xc0, calldataload(add(offset, argOffset))) } } /// @notice Reads an immutable arg with type uint8 /// @param argOffset The offset of the arg in the packed data /// @return arg The arg value function _getArgUint8(uint256 argOffset) internal pure returns (uint8 arg) { uint256 offset = _getImmutableArgsOffset(); // solhint-disable-next-line no-inline-assembly assembly { arg := shr(0xf8, calldataload(add(offset, argOffset))) } } /// @return offset The offset of the packed immutable args in calldata function _getImmutableArgsOffset() internal pure returns (uint256 offset) { // solhint-disable-next-line no-inline-assembly assembly { offset := sub( calldatasize(), add(shr(240, calldataload(sub(calldatasize(), 2))), 2) ) } } }
/// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /// @title IERC1271 /// @notice Interface defined by EIP-1271 /// @dev Interface for verifying contract account signatures interface IERC1271 { /// @notice Returns whether the provided signature is valid for the provided data /// @dev Returns 0x1626ba7e (magic value) when function passes. /// @param digest Hash of the message to validate the signature against /// @param signature Signature produced for the provided digest function isValidSignature(bytes32 digest, bytes memory signature) external view returns (bytes4); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Gas optimized ECDSA wrapper. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol) /// /// @dev Note: /// - The recovery functions use the ecrecover precompile (0x1). /// - As of Solady version 0.0.68, the `recover` variants will revert upon recovery failure. /// This is for more safety by default. /// Use the `tryRecover` variants if you need to get the zero address back /// upon recovery failure instead. /// - As of Solady version 0.0.134, all `bytes signature` variants accept both /// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures. /// See: https://eips.ethereum.org/EIPS/eip-2098 /// This is for calldata efficiency on smart accounts prevalent on L2s. /// /// WARNING! Do NOT use signatures as unique identifiers: /// - Use a nonce in the digest to prevent replay attacks on the same contract. /// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts. /// EIP-712 also enables readable signing of typed data for better user safety. /// This implementation does NOT check if a signature is non-malleable. library ECDSA { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The signature is invalid. error InvalidSignature(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* RECOVERY OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. function recover(bytes32 hash, bytes memory signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { result := 1 let m := mload(0x40) // Cache the free memory pointer. for {} 1 {} { mstore(0x00, hash) mstore(0x40, mload(add(signature, 0x20))) // `r`. if eq(mload(signature), 64) { let vs := mload(add(signature, 0x40)) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x60, shr(1, shl(1, vs))) // `s`. break } if eq(mload(signature), 65) { mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. mstore(0x60, mload(add(signature, 0x40))) // `s`. break } result := 0 break } result := mload( staticcall( gas(), // Amount of gas left for the transaction. result, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. function recoverCalldata(bytes32 hash, bytes calldata signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { result := 1 let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) for {} 1 {} { if eq(signature.length, 64) { let vs := calldataload(add(signature.offset, 0x20)) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x40, calldataload(signature.offset)) // `r`. mstore(0x60, shr(1, shl(1, vs))) // `s`. break } if eq(signature.length, 65) { mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. break } result := 0 break } result := mload( staticcall( gas(), // Amount of gas left for the transaction. result, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the EIP-2098 short form signature defined by `r` and `vs`. function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x40, r) mstore(0x60, shr(1, shl(1, vs))) // `s`. result := mload( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the signature defined by `v`, `r`, `s`. function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, and(v, 0xff)) mstore(0x40, r) mstore(0x60, s) result := mload( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* TRY-RECOVER OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // WARNING! // These functions will NOT revert upon recovery failure. // Instead, they will return the zero address upon recovery failure. // It is critical that the returned address is NEVER compared against // a zero address (e.g. an uninitialized address variable). /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. function tryRecover(bytes32 hash, bytes memory signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { result := 1 let m := mload(0x40) // Cache the free memory pointer. for {} 1 {} { mstore(0x00, hash) mstore(0x40, mload(add(signature, 0x20))) // `r`. if eq(mload(signature), 64) { let vs := mload(add(signature, 0x40)) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x60, shr(1, shl(1, vs))) // `s`. break } if eq(mload(signature), 65) { mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. mstore(0x60, mload(add(signature, 0x40))) // `s`. break } result := 0 break } pop( staticcall( gas(), // Amount of gas left for the transaction. result, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. function tryRecoverCalldata(bytes32 hash, bytes calldata signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { result := 1 let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) for {} 1 {} { if eq(signature.length, 64) { let vs := calldataload(add(signature.offset, 0x20)) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x40, calldataload(signature.offset)) // `r`. mstore(0x60, shr(1, shl(1, vs))) // `s`. break } if eq(signature.length, 65) { mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. break } result := 0 break } pop( staticcall( gas(), // Amount of gas left for the transaction. result, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the EIP-2098 short form signature defined by `r` and `vs`. function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x40, r) mstore(0x60, shr(1, shl(1, vs))) // `s`. pop( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the signature defined by `v`, `r`, `s`. function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, and(v, 0xff)) mstore(0x40, r) mstore(0x60, s) pop( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HASHING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns an Ethereum Signed Message, created from a `hash`. /// This produces a hash corresponding to the one signed with the /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) /// JSON-RPC method as part of EIP-191. function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { mstore(0x20, hash) // Store into scratch space for keccak256. mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. } } /// @dev Returns an Ethereum Signed Message, created from `s`. /// This produces a hash corresponding to the one signed with the /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) /// JSON-RPC method as part of EIP-191. /// Note: Supports lengths of `s` up to 999999 bytes. function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { let sLength := mload(s) let o := 0x20 mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded. mstore(0x00, 0x00) // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`. for { let temp := sLength } 1 {} { o := sub(o, 1) mstore8(o, add(48, mod(temp, 10))) temp := div(temp, 10) if iszero(temp) { break } } let n := sub(0x3a, o) // Header length: `26 + 32 - o`. // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes. returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20)) mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header. result := keccak256(add(s, sub(0x20, n)), add(n, sLength)) mstore(s, sLength) // Restore the length. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EMPTY CALLDATA HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns an empty calldata bytes. function emptySignature() internal pure returns (bytes calldata signature) { /// @solidity memory-safe-assembly assembly { signature.length := 0 } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. pragma solidity ^0.8.24; /** * @dev Library for reading and writing primitive types to specific storage slots. * * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. * This library helps with reading and writing to such slots without the need for inline assembly. * * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. * * Example usage to set ERC-1967 implementation slot: * ```solidity * contract ERC1967 { * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(newImplementation.code.length > 0); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` * * Since version 5.1, this library also support writing and reading value types to and from transient storage. * * * Example using transient storage: * ```solidity * contract Lock { * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. * bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542; * * modifier locked() { * require(!_LOCK_SLOT.asBoolean().tload()); * * _LOCK_SLOT.asBoolean().tstore(true); * _; * _LOCK_SLOT.asBoolean().tstore(false); * } * } * ``` * * TIP: Consider using this library along with {SlotDerivation}. */ library StorageSlot { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } struct Int256Slot { int256 value; } struct StringSlot { string value; } struct BytesSlot { bytes value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { assembly ("memory-safe") { r.slot := slot } } /** * @dev Returns a `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { assembly ("memory-safe") { r.slot := slot } } /** * @dev Returns a `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { assembly ("memory-safe") { r.slot := slot } } /** * @dev Returns a `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { assembly ("memory-safe") { r.slot := slot } } /** * @dev Returns a `Int256Slot` with member `value` located at `slot`. */ function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) { assembly ("memory-safe") { r.slot := slot } } /** * @dev Returns a `StringSlot` with member `value` located at `slot`. */ function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { assembly ("memory-safe") { r.slot := slot } } /** * @dev Returns an `StringSlot` representation of the string storage pointer `store`. */ function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { assembly ("memory-safe") { r.slot := store.slot } } /** * @dev Returns a `BytesSlot` with member `value` located at `slot`. */ function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { assembly ("memory-safe") { r.slot := slot } } /** * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. */ function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { assembly ("memory-safe") { r.slot := store.slot } } /** * @dev UDVT that represent a slot holding a address. */ type AddressSlotType is bytes32; /** * @dev Cast an arbitrary slot to a AddressSlotType. */ function asAddress(bytes32 slot) internal pure returns (AddressSlotType) { return AddressSlotType.wrap(slot); } /** * @dev UDVT that represent a slot holding a bool. */ type BooleanSlotType is bytes32; /** * @dev Cast an arbitrary slot to a BooleanSlotType. */ function asBoolean(bytes32 slot) internal pure returns (BooleanSlotType) { return BooleanSlotType.wrap(slot); } /** * @dev UDVT that represent a slot holding a bytes32. */ type Bytes32SlotType is bytes32; /** * @dev Cast an arbitrary slot to a Bytes32SlotType. */ function asBytes32(bytes32 slot) internal pure returns (Bytes32SlotType) { return Bytes32SlotType.wrap(slot); } /** * @dev UDVT that represent a slot holding a uint256. */ type Uint256SlotType is bytes32; /** * @dev Cast an arbitrary slot to a Uint256SlotType. */ function asUint256(bytes32 slot) internal pure returns (Uint256SlotType) { return Uint256SlotType.wrap(slot); } /** * @dev UDVT that represent a slot holding a int256. */ type Int256SlotType is bytes32; /** * @dev Cast an arbitrary slot to a Int256SlotType. */ function asInt256(bytes32 slot) internal pure returns (Int256SlotType) { return Int256SlotType.wrap(slot); } /** * @dev Load the value held at location `slot` in transient storage. */ function tload(AddressSlotType slot) internal view returns (address value) { assembly ("memory-safe") { value := tload(slot) } } /** * @dev Store `value` at location `slot` in transient storage. */ function tstore(AddressSlotType slot, address value) internal { assembly ("memory-safe") { tstore(slot, value) } } /** * @dev Load the value held at location `slot` in transient storage. */ function tload(BooleanSlotType slot) internal view returns (bool value) { assembly ("memory-safe") { value := tload(slot) } } /** * @dev Store `value` at location `slot` in transient storage. */ function tstore(BooleanSlotType slot, bool value) internal { assembly ("memory-safe") { tstore(slot, value) } } /** * @dev Load the value held at location `slot` in transient storage. */ function tload(Bytes32SlotType slot) internal view returns (bytes32 value) { assembly ("memory-safe") { value := tload(slot) } } /** * @dev Store `value` at location `slot` in transient storage. */ function tstore(Bytes32SlotType slot, bytes32 value) internal { assembly ("memory-safe") { tstore(slot, value) } } /** * @dev Load the value held at location `slot` in transient storage. */ function tload(Uint256SlotType slot) internal view returns (uint256 value) { assembly ("memory-safe") { value := tload(slot) } } /** * @dev Store `value` at location `slot` in transient storage. */ function tstore(Uint256SlotType slot, uint256 value) internal { assembly ("memory-safe") { tstore(slot, value) } } /** * @dev Load the value held at location `slot` in transient storage. */ function tload(Int256SlotType slot) internal view returns (int256 value) { assembly ("memory-safe") { value := tload(slot) } } /** * @dev Store `value` at location `slot` in transient storage. */ function tstore(Int256SlotType slot, int256 value) internal { assembly ("memory-safe") { tstore(slot, value) } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol) pragma solidity ^0.8.20; import {Context} from "../utils/Context.sol"; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * The initial owner is set to the address provided by the deployer. This can * later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; /** * @dev The caller account is not authorized to perform an operation. */ error OwnableUnauthorizedAccount(address account); /** * @dev The owner is not a valid owner account. (eg. `address(0)`) */ error OwnableInvalidOwner(address owner); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the address provided by the deployer as the initial owner. */ constructor(address initialOwner) { if (initialOwner == address(0)) { revert OwnableInvalidOwner(address(0)); } _transferOwnership(initialOwner); } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { _checkOwner(); _; } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view virtual { if (owner() != _msgSender()) { revert OwnableUnauthorizedAccount(_msgSender()); } } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby disabling any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { if (newOwner == address(0)) { revert OwnableInvalidOwner(address(0)); } _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.16; library CommandBuilder { uint256 constant IDX_VARIABLE_LENGTH = 0x80; uint256 constant IDX_VALUE_MASK = 0x7f; uint256 constant IDX_END_OF_ARGS = 0xff; uint256 constant IDX_USE_STATE = 0xfe; uint256 constant IDX_ARRAY_START = 0xfd; uint256 constant IDX_TUPLE_START = 0xfc; uint256 constant IDX_DYNAMIC_END = 0xfb; function buildInputs( bytes[] memory state, bytes4 selector, bytes32 indices, uint256 indicesLength ) internal view returns (bytes memory ret) { uint256 idx; // The current command index uint256 offsetIdx; // The index of the current free offset uint256 count; // Number of bytes in whole ABI encoded message uint256 free; // Pointer to first free byte in tail part of message uint256[] memory dynamicLengths = new uint256[](10); // Optionally store the length of all dynamic types (a command cannot fit more than 10 dynamic types) bytes memory stateData; // Optionally encode the current state if the call requires it // Determine the length of the encoded data for (uint256 i; i < indicesLength; ) { idx = uint8(indices[i]); if (idx == IDX_END_OF_ARGS) { indicesLength = i; break; } if (idx & IDX_VARIABLE_LENGTH != 0) { if (idx == IDX_USE_STATE) { if (stateData.length == 0) { stateData = abi.encode(state); } unchecked { count += stateData.length; } } else { (dynamicLengths, offsetIdx, count, i) = setupDynamicType( state, indices, dynamicLengths, idx, offsetIdx, count, i ); } } else { count = setupStaticVariable(state, count, idx); } unchecked { free += 32; ++i; } } // Encode it ret = new bytes(count + 4); assembly { mstore(add(ret, 32), selector) } offsetIdx = 0; // Use count to track current memory slot assembly { count := add(ret, 36) } for (uint256 i; i < indicesLength; ) { idx = uint8(indices[i]); if (idx & IDX_VARIABLE_LENGTH != 0) { if (idx == IDX_USE_STATE) { assembly { mstore(count, free) } memcpy(stateData, 32, ret, free + 4, stateData.length - 32); unchecked { free += stateData.length - 32; } } else if (idx == IDX_ARRAY_START) { // Start of dynamic type, put pointer in current slot assembly { mstore(count, free) } (offsetIdx, free, i, ) = encodeDynamicArray( ret, state, indices, dynamicLengths, offsetIdx, free, i ); } else if (idx == IDX_TUPLE_START) { // Start of dynamic type, put pointer in current slot assembly { mstore(count, free) } (offsetIdx, free, i, ) = encodeDynamicTuple( ret, state, indices, dynamicLengths, offsetIdx, free, i ); } else { // Variable length data uint256 argLen = state[idx & IDX_VALUE_MASK].length; // Put a pointer in the current slot and write the data to first free slot assembly { mstore(count, free) } memcpy( state[idx & IDX_VALUE_MASK], 0, ret, free + 4, argLen ); unchecked { free += argLen; } } } else { // Fixed length data (length previously checked to be 32 bytes) bytes memory stateVar = state[idx & IDX_VALUE_MASK]; // Write the data to current slot assembly { mstore(count, mload(add(stateVar, 32))) } } unchecked { count += 32; ++i; } } } function setupStaticVariable( bytes[] memory state, uint256 count, uint256 idx ) internal pure returns (uint256 newCount) { require( state[idx & IDX_VALUE_MASK].length == 32, "Static state variables must be 32 bytes" ); unchecked { newCount = count + 32; } } function setupDynamicVariable( bytes[] memory state, uint256 count, uint256 idx ) internal pure returns (uint256 newCount) { bytes memory arg = state[idx & IDX_VALUE_MASK]; // Validate the length of the data in state is a multiple of 32 uint256 argLen = arg.length; require( argLen != 0 && argLen % 32 == 0, "Dynamic state variables must be a multiple of 32 bytes" ); // Add the length of the value, rounded up to the next word boundary, plus space for pointer unchecked { newCount = count + argLen + 32; } } function setupDynamicType( bytes[] memory state, bytes32 indices, uint256[] memory dynamicLengths, uint256 idx, uint256 offsetIdx, uint256 count, uint256 index ) internal view returns ( uint256[] memory newDynamicLengths, uint256 newOffsetIdx, uint256 newCount, uint256 newIndex ) { if (idx == IDX_ARRAY_START) { (newDynamicLengths, newOffsetIdx, newCount, newIndex) = setupDynamicArray( state, indices, dynamicLengths, offsetIdx, count, index ); } else if (idx == IDX_TUPLE_START) { (newDynamicLengths, newOffsetIdx, newCount, newIndex) = setupDynamicTuple( state, indices, dynamicLengths, offsetIdx, count, index ); } else { newDynamicLengths = dynamicLengths; newOffsetIdx = offsetIdx; newIndex = index; newCount = setupDynamicVariable(state, count, idx); } } function setupDynamicArray( bytes[] memory state, bytes32 indices, uint256[] memory dynamicLengths, uint256 offsetIdx, uint256 count, uint256 index ) internal view returns ( uint256[] memory newDynamicLengths, uint256 newOffsetIdx, uint256 newCount, uint256 newIndex ) { // Current idx is IDX_ARRAY_START, next idx will contain the array length unchecked { newIndex = index + 1; newCount = count + 32; } uint256 idx = uint8(indices[newIndex]); require( state[idx & IDX_VALUE_MASK].length == 32, "Array length must be 32 bytes" ); (newDynamicLengths, newOffsetIdx, newCount, newIndex) = setupDynamicTuple( state, indices, dynamicLengths, offsetIdx, newCount, newIndex ); } function setupDynamicTuple( bytes[] memory state, bytes32 indices, uint256[] memory dynamicLengths, uint256 offsetIdx, uint256 count, uint256 index ) internal view returns ( uint256[] memory newDynamicLengths, uint256 newOffsetIdx, uint256 newCount, uint256 newIndex ) { uint256 idx; uint256 offset; newDynamicLengths = dynamicLengths; // Progress to first index of the data and progress the next offset idx unchecked { newIndex = index + 1; newOffsetIdx = offsetIdx + 1; newCount = count + 32; } while (newIndex < 32) { idx = uint8(indices[newIndex]); if (idx & IDX_VARIABLE_LENGTH != 0) { if (idx == IDX_DYNAMIC_END) { newDynamicLengths[offsetIdx] = offset; // explicit return saves gas ¯\_(ツ)_/¯ return (newDynamicLengths, newOffsetIdx, newCount, newIndex); } else { require(idx != IDX_USE_STATE, "Cannot use state from inside dynamic type"); (newDynamicLengths, newOffsetIdx, newCount, newIndex) = setupDynamicType( state, indices, newDynamicLengths, idx, newOffsetIdx, newCount, newIndex ); } } else { newCount = setupStaticVariable(state, newCount, idx); } unchecked { offset += 32; ++newIndex; } } revert("Dynamic type was not properly closed"); } function encodeDynamicArray( bytes memory ret, bytes[] memory state, bytes32 indices, uint256[] memory dynamicLengths, uint256 offsetIdx, uint256 currentSlot, uint256 index ) internal view returns ( uint256 newOffsetIdx, uint256 newSlot, uint256 newIndex, uint256 length ) { // Progress to array length metadata unchecked { newIndex = index + 1; newSlot = currentSlot + 32; } // Encode array length uint256 idx = uint8(indices[newIndex]); // Array length value previously checked to be 32 bytes bytes memory stateVar = state[idx & IDX_VALUE_MASK]; assembly { mstore(add(add(ret, 36), currentSlot), mload(add(stateVar, 32))) } (newOffsetIdx, newSlot, newIndex, length) = encodeDynamicTuple( ret, state, indices, dynamicLengths, offsetIdx, newSlot, newIndex ); unchecked { length += 32; // Increase length to account for array length metadata } } function encodeDynamicTuple( bytes memory ret, bytes[] memory state, bytes32 indices, uint256[] memory dynamicLengths, uint256 offsetIdx, uint256 currentSlot, uint256 index ) internal view returns ( uint256 newOffsetIdx, uint256 newSlot, uint256 newIndex, uint256 length ) { uint256 idx; uint256 argLen; uint256 freePointer = dynamicLengths[offsetIdx]; // The pointer to the next free slot unchecked { newSlot = currentSlot + freePointer; // Update the next slot newOffsetIdx = offsetIdx + 1; // Progress to next offsetIdx newIndex = index + 1; // Progress to first index of the data } // Shift currentSlot to correct location in memory assembly { currentSlot := add(add(ret, 36), currentSlot) } while (newIndex < 32) { idx = uint8(indices[newIndex]); if (idx & IDX_VARIABLE_LENGTH != 0) { if (idx == IDX_DYNAMIC_END) { break; } else if (idx == IDX_ARRAY_START) { // Start of dynamic type, put pointer in current slot assembly { mstore(currentSlot, freePointer) } (newOffsetIdx, newSlot, newIndex, argLen) = encodeDynamicArray( ret, state, indices, dynamicLengths, newOffsetIdx, newSlot, newIndex ); unchecked { freePointer += argLen; length += (argLen + 32); // data + pointer } } else if (idx == IDX_TUPLE_START) { // Start of dynamic type, put pointer in current slot assembly { mstore(currentSlot, freePointer) } (newOffsetIdx, newSlot, newIndex, argLen) = encodeDynamicTuple( ret, state, indices, dynamicLengths, newOffsetIdx, newSlot, newIndex ); unchecked { freePointer += argLen; length += (argLen + 32); // data + pointer } } else { // Variable length data argLen = state[idx & IDX_VALUE_MASK].length; // Start of dynamic type, put pointer in current slot assembly { mstore(currentSlot, freePointer) } memcpy( state[idx & IDX_VALUE_MASK], 0, ret, newSlot + 4, argLen ); unchecked { newSlot += argLen; freePointer += argLen; length += (argLen + 32); // data + pointer } } } else { // Fixed length data (length previously checked to be 32 bytes) bytes memory stateVar = state[idx & IDX_VALUE_MASK]; // Write to first free slot assembly { mstore(currentSlot, mload(add(stateVar, 32))) } unchecked { length += 32; } } unchecked { currentSlot += 32; ++newIndex; } } } function writeOutputs( bytes[] memory state, bytes1 index, bytes memory output ) internal pure returns (bytes[] memory) { uint256 idx = uint8(index); if (idx == IDX_END_OF_ARGS) return state; if (idx & IDX_VARIABLE_LENGTH != 0) { if (idx == IDX_USE_STATE) { state = abi.decode(output, (bytes[])); } else { require(idx & IDX_VALUE_MASK < state.length, "Index out-of-bounds"); // Check the first field is 0x20 (because we have only a single return value) uint256 argPtr; assembly { argPtr := mload(add(output, 32)) } require( argPtr == 32, "Only one return value permitted (variable)" ); assembly { // Overwrite the first word of the return data with the length - 32 mstore(add(output, 32), sub(mload(output), 32)) // Insert a pointer to the return data, starting at the second word, into state mstore( add(add(state, 32), mul(and(idx, IDX_VALUE_MASK), 32)), add(output, 32) ) } } } else { require(idx & IDX_VALUE_MASK < state.length, "Index out-of-bounds"); // Single word require( output.length == 32, "Only one return value permitted (static)" ); state[idx & IDX_VALUE_MASK] = output; } return state; } function writeTuple( bytes[] memory state, bytes1 index, bytes memory output ) internal view { uint256 idx = uint8(index); if (idx == IDX_END_OF_ARGS) return; bytes memory entry = state[idx & IDX_VALUE_MASK] = new bytes(output.length + 32); memcpy(output, 0, entry, 32, output.length); assembly { let l := mload(output) mstore(add(entry, 32), l) } } function memcpy( bytes memory src, uint256 srcIdx, bytes memory dest, uint256 destIdx, uint256 len ) internal view { assembly { pop( staticcall( gas(), 4, add(add(src, 32), srcIdx), len, add(add(dest, 32), destIdx), len ) ) } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) pragma solidity ^0.8.20; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } function _contextSuffixLength() internal view virtual returns (uint256) { return 0; } }
{ "remappings": [ "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", "clones-with-immutable-args/=lib/clones-with-immutable-args/src/", "ds-test/=lib/solmate/lib/ds-test/src/", "enso-weiroll/=lib/enso-weiroll/contracts/", "erc4626-tests/=lib/erc4626-tests/", "forge-std/=lib/forge-std/src/", "halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/", "openzeppelin-contracts/=lib/openzeppelin-contracts/", "solady/=lib/solady/src/", "solmate/=lib/solmate/src/" ], "optimizer": { "enabled": false, "runs": 5000, "details": { "constantOptimizer": true, "yul": true, "yulDetails": { "stackAllocation": true } } }, "metadata": { "useLiteralContent": false, "bytecodeHash": "none", "appendCBOR": false }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "cancun", "viaIR": true, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"address","name":"_weirollWalletImplementation","type":"address"},{"internalType":"uint256","name":"_protocolFee","type":"uint256"},{"internalType":"uint256","name":"_minimumFrontendFee","type":"uint256"},{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_pointsFactory","type":"address"}],"stateMutability":"payable","type":"constructor"},{"inputs":[],"name":"ArrayLengthMismatch","type":"error"},{"inputs":[],"name":"CannotFillZeroQuantityOffer","type":"error"},{"inputs":[],"name":"CannotPlaceExpiredOffer","type":"error"},{"inputs":[],"name":"CannotPlaceZeroQuantityOffer","type":"error"},{"inputs":[],"name":"CreateFail","type":"error"},{"inputs":[],"name":"FrontendFeeTooLow","type":"error"},{"inputs":[],"name":"InsufficientFillPercent","type":"error"},{"inputs":[],"name":"InvalidMarketInputToken","type":"error"},{"inputs":[],"name":"InvalidPointsProgram","type":"error"},{"inputs":[],"name":"MarketDoesNotExist","type":"error"},{"inputs":[],"name":"MismatchedBaseAsset","type":"error"},{"inputs":[],"name":"NoIncentivesPaidOnFill","type":"error"},{"inputs":[],"name":"NotEnoughRemainingQuantity","type":"error"},{"inputs":[],"name":"NotOwner","type":"error"},{"inputs":[],"name":"OfferCannotContainDuplicates","type":"error"},{"inputs":[],"name":"OfferExpired","type":"error"},{"inputs":[],"name":"OffersPaused","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"TokenDoesNotExist","type":"error"},{"inputs":[],"name":"TotalFeeTooHigh","type":"error"},{"inputs":[],"name":"WalletLocked","type":"error"},{"inputs":[],"name":"WalletNotForfeitable","type":"error"},{"inputs":[],"name":"WeirollWalletFundingFailed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"offerID","type":"uint256"}],"name":"APOfferCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"offerID","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"marketHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"ap","type":"address"},{"indexed":false,"internalType":"address","name":"fundingVault","type":"address"},{"indexed":false,"internalType":"uint256","name":"quantity","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"incentiveAddresses","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"incentiveAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"expiry","type":"uint256"}],"name":"APOfferCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"offerID","type":"uint256"},{"indexed":true,"internalType":"address","name":"ip","type":"address"},{"indexed":false,"internalType":"uint256","name":"fillAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"weirollWallet","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"incentiveAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"protocolFeeAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"frontendFeeAmounts","type":"uint256[]"}],"name":"APOfferFilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"claimant","type":"address"},{"indexed":true,"internalType":"address","name":"incentive","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeesClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"offerHash","type":"bytes32"}],"name":"IPOfferCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"offerID","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"offerHash","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"marketHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"ip","type":"address"},{"indexed":false,"internalType":"uint256","name":"quantity","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"incentivesOffered","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"incentiveAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"protocolFeeAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"frontendFeeAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"expiry","type":"uint256"}],"name":"IPOfferCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"offerHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"ap","type":"address"},{"indexed":false,"internalType":"uint256","name":"fillAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"weirollWallet","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"incentiveAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"protocolFeeAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"frontendFeeAmounts","type":"uint256[]"}],"name":"IPOfferFilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"marketID","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"marketHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"inputToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"lockupTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"frontendFee","type":"uint256"},{"indexed":false,"internalType":"enum RewardStyle","name":"rewardStyle","type":"uint8"}],"name":"MarketCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"weirollWallet","type":"address"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"address","name":"incentive","type":"address"}],"name":"WeirollWalletClaimedIncentive","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"weirollWallet","type":"address"}],"name":"WeirollWalletExecutedWithdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"weirollWallet","type":"address"}],"name":"WeirollWalletForfeited","type":"event"},{"inputs":[],"name":"MIN_FILL_PERCENT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"POINTS_FACTORY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WEIROLL_WALLET_IMPLEMENTATION","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"offerID","type":"uint256"},{"internalType":"bytes32","name":"targetMarketHash","type":"bytes32"},{"internalType":"address","name":"ap","type":"address"},{"internalType":"address","name":"fundingVault","type":"address"},{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"address[]","name":"incentivesRequested","type":"address[]"},{"internalType":"uint256[]","name":"incentiveAmountsRequested","type":"uint256[]"}],"internalType":"struct RecipeMarketHubBase.APOffer","name":"offer","type":"tuple"}],"name":"cancelAPOffer","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"offerHash","type":"bytes32"}],"name":"cancelIPOffer","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"weirollWallet","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"claim","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"weirollWallet","type":"address"},{"internalType":"address","name":"incentiveToken","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"claim","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"incentiveToken","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"claimFees","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"targetMarketHash","type":"bytes32"},{"internalType":"address","name":"fundingVault","type":"address"},{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"address[]","name":"incentivesRequested","type":"address[]"},{"internalType":"uint256[]","name":"incentiveAmountsRequested","type":"uint256[]"}],"name":"createAPOffer","outputs":[{"internalType":"bytes32","name":"offerHash","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"targetMarketHash","type":"bytes32"},{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"address[]","name":"incentivesOffered","type":"address[]"},{"internalType":"uint256[]","name":"incentiveAmountsPaid","type":"uint256[]"}],"name":"createIPOffer","outputs":[{"internalType":"bytes32","name":"offerHash","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"inputToken","type":"address"},{"internalType":"uint256","name":"lockupTime","type":"uint256"},{"internalType":"uint256","name":"frontendFee","type":"uint256"},{"components":[{"internalType":"bytes32[]","name":"weirollCommands","type":"bytes32[]"},{"internalType":"bytes[]","name":"weirollState","type":"bytes[]"}],"internalType":"struct RecipeMarketHubBase.Recipe","name":"depositRecipe","type":"tuple"},{"components":[{"internalType":"bytes32[]","name":"weirollCommands","type":"bytes32[]"},{"internalType":"bytes[]","name":"weirollState","type":"bytes[]"}],"internalType":"struct RecipeMarketHubBase.Recipe","name":"withdrawRecipe","type":"tuple"},{"internalType":"enum RewardStyle","name":"rewardStyle","type":"uint8"}],"name":"createMarket","outputs":[{"internalType":"bytes32","name":"marketHash","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"weirollWallet","type":"address"}],"name":"executeWithdrawalScript","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"feeClaimantToTokenToAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"offerID","type":"uint256"},{"internalType":"bytes32","name":"targetMarketHash","type":"bytes32"},{"internalType":"address","name":"ap","type":"address"},{"internalType":"address","name":"fundingVault","type":"address"},{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"address[]","name":"incentivesRequested","type":"address[]"},{"internalType":"uint256[]","name":"incentiveAmountsRequested","type":"uint256[]"}],"internalType":"struct RecipeMarketHubBase.APOffer[]","name":"offers","type":"tuple[]"},{"internalType":"uint256[]","name":"fillAmounts","type":"uint256[]"},{"internalType":"address","name":"frontendFeeRecipient","type":"address"}],"name":"fillAPOffers","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"ipOfferHashes","type":"bytes32[]"},{"internalType":"uint256[]","name":"fillAmounts","type":"uint256[]"},{"internalType":"address","name":"fundingVault","type":"address"},{"internalType":"address","name":"frontendFeeRecipient","type":"address"}],"name":"fillIPOffers","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"weirollWallet","type":"address"},{"internalType":"bool","name":"executeWithdrawal","type":"bool"}],"name":"forfeit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"offerID","type":"uint256"},{"internalType":"bytes32","name":"targetMarketHash","type":"bytes32"},{"internalType":"address","name":"ap","type":"address"},{"internalType":"address","name":"fundingVault","type":"address"},{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"address[]","name":"incentivesRequested","type":"address[]"},{"internalType":"uint256[]","name":"incentiveAmountsRequested","type":"uint256[]"}],"internalType":"struct RecipeMarketHubBase.APOffer","name":"offer","type":"tuple"}],"name":"getAPOfferHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"offerID","type":"uint256"},{"internalType":"bytes32","name":"targetMarketHash","type":"bytes32"},{"internalType":"address","name":"ip","type":"address"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"address[]","name":"incentivesOffered","type":"address[]"},{"internalType":"uint256[]","name":"incentiveAmountsOffered","type":"uint256[]"}],"name":"getIPOfferHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"marketID","type":"uint256"},{"internalType":"contract ERC20","name":"inputToken","type":"address"},{"internalType":"uint256","name":"lockupTime","type":"uint256"},{"internalType":"uint256","name":"frontendFee","type":"uint256"},{"components":[{"internalType":"bytes32[]","name":"weirollCommands","type":"bytes32[]"},{"internalType":"bytes[]","name":"weirollState","type":"bytes[]"}],"internalType":"struct RecipeMarketHubBase.Recipe","name":"depositRecipe","type":"tuple"},{"components":[{"internalType":"bytes32[]","name":"weirollCommands","type":"bytes32[]"},{"internalType":"bytes[]","name":"weirollState","type":"bytes[]"}],"internalType":"struct RecipeMarketHubBase.Recipe","name":"withdrawRecipe","type":"tuple"},{"internalType":"enum RewardStyle","name":"rewardStyle","type":"uint8"}],"internalType":"struct RecipeMarketHubBase.WeirollMarket","name":"market","type":"tuple"}],"name":"getMarketHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"marketHashToWeirollMarket","outputs":[{"internalType":"uint256","name":"marketID","type":"uint256"},{"internalType":"contract ERC20","name":"inputToken","type":"address"},{"internalType":"uint256","name":"lockupTime","type":"uint256"},{"internalType":"uint256","name":"frontendFee","type":"uint256"},{"components":[{"internalType":"bytes32[]","name":"weirollCommands","type":"bytes32[]"},{"internalType":"bytes[]","name":"weirollState","type":"bytes[]"}],"internalType":"struct RecipeMarketHubBase.Recipe","name":"depositRecipe","type":"tuple"},{"components":[{"internalType":"bytes32[]","name":"weirollCommands","type":"bytes32[]"},{"internalType":"bytes[]","name":"weirollState","type":"bytes[]"}],"internalType":"struct RecipeMarketHubBase.Recipe","name":"withdrawRecipe","type":"tuple"},{"internalType":"enum RewardStyle","name":"rewardStyle","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minimumFrontendFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numAPOffers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numIPOffers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numMarkets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"offerHashToIPOffer","outputs":[{"internalType":"uint256","name":"offerID","type":"uint256"},{"internalType":"bytes32","name":"targetMarketHash","type":"bytes32"},{"internalType":"address","name":"ip","type":"address"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"uint256","name":"remainingQuantity","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"offerHashToRemainingQuantity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFeeClaimant","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minimumFrontendFee","type":"uint256"}],"name":"setMinimumFrontendFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bool","name":"_offersPaused","type":"bool"}],"name":"setOffersPaused","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_protocolFee","type":"uint256"}],"name":"setProtocolFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_protocolFeeClaimant","type":"address"}],"name":"setProtocolFeeClaimant","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"weirollWalletToLockedIncentivesParams","outputs":[{"internalType":"address","name":"ip","type":"address"},{"internalType":"address","name":"frontendFeeRecipient","type":"address"},{"internalType":"bool","name":"wasIPOffer","type":"bool"},{"internalType":"bytes32","name":"offerHash","type":"bytes32"},{"internalType":"uint256","name":"protocolFeeAtFill","type":"uint256"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
60c0615ed880380390601f19601f83011683019183831060018060401b0384111761012c5780849260a0946040528339810103126101285761004081610140565b90602081015190604081015191610065608061005e60608501610140565b9301610140565b9160018060a01b0316918260018060a01b03195f5416175f5560405194835f7f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a360805260a05260055560018060a01b03196006541617600655600755615d809081610158823960805181818161065f015281816112f80152612cd5015260a0518181816102da015281816113430152818161157001528181611850015281816135360152818161514401528181615399015281816159e30152615b450152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b519060018060a01b038216820361015357565b5f80fdfe60806040526004361015610012575b5f80fd5b5f3560e01c806305c21582146102415780630600fc6f1461023c5780630718f4a514610237578063072fc961146102325780630cadba1a1461022d57806315a0c8bd1461022857806315a2fdee1461022357806321c0b3421461021e57806322b04e1414610219578063250b9ded1461021457806326e04f9d1461020f57806328b0c2351461020a5780632dbfa735146102055780632e0a11221461020057806338e96749146101fb57806345ccb466146101f6578063508d629d146101f15780635e1c75e1146101ec578063645b1b5b146101e757806369029181146101e2578063787dce3d146101dd57806380dd127a146101d857806382ae91d1146101d35780638d084ba3146101ce5780638da5cb5b146101c9578063b0e21e8a146101c4578063cd175e66146101bf578063d1216f24146101ba578063ec1d465e146101b5578063ee46ebc6146101b0578063f2fde38b146101ab578063f3256c81146101a6578063f6242301146101a1578063f94b51991461019c5763fcd9664b0361000e57613dc2565b613d16565b613b1a565b613ae3565b613a54565b613449565b6133fa565b6132fe565b612b1d565b612af8565b612acb565b612a7c565b6128b7565b612892565b612863565b6126b9565b61263c565b6120e6565b612069565b611c94565b611c4e565b611b49565b611acb565b6113a2565b61136f565b611324565b6112d9565b610e3a565b610dda565b610d63565b610d2a565b610c8c565b610c19565b6105ca565b610252565b60043590565b60243590565b602060031936011261055b57610266610246565b61026e614c4c565b61027781610be7565b9060028201610286815461059c565b6001600160a01b033391160361053357600583015492831561050b576102c96102bd60086102b76001850154610bf5565b016124e5565b94600483015490615620565b5f9060068301956001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169460078501978215945b8154811015610482578061032561031e6103559385613e05565b9054610c59565b602061033b6103348487613e05565b9054610c59565b6040518095819263679d119760e01b835260048301610c77565b03818d5afa92831561047d57610397828e888b8e6001995f97889161044f575b50156103d5575b50505061039261038c878a613e05565b90613e8d565b610d14565b5588886103a3886125a7565b6103b0575b505001610304565b815f6103c3819460086103cd9501610d14565b5560098b01610d14565b555f886103a8565b610447926104326104379261042c6104256103fa876103f48c8c610d14565b546155f4565b9661041f8b6008610418846104128460098b01610d14565b546155f4565b9601610d14565b546155f4565b935461059c565b94613e7b565b613e7b565b906001600160a01b038516614ce5565b888b8e61037c565b610470915060203d8111610476575b61046881836122e7565b810190613e1f565b5f610375565b503d61045e565b613e3b565b888661048d866125a7565b155f146104f5575f60016104a083610be7565b01556104b660026104b083610be7565b01613ea3565b5f60056104c283610be7565b01555b7fa8192679f2a98ef0f7c48a03dd5d944c563888af9eb9c9da2f8932a2d84afa235f80a26104f1614cc0565b5f80f35b61050661050182610be7565b613f61565b6104c5565b7fc6c2d6bf000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b9181601f840112156105985782359167ffffffffffffffff8311610594576020808501948460051b01011161059057565b5f80fd5b5f80fd5b5f80fd5b6001600160a01b031690565b6001600160a01b038116036105b957565b5f80fd5b35906105c8826105a8565b565b6080600319360112610be35760043567ffffffffffffffff8111610bdf576105f690369060040161055f565b9060243567ffffffffffffffff8111610bdb5761061790369060040161055f565b906106236044356105a8565b61062e6064356105a8565b610636614c4c565b60ff60045416610bb357818403610b8b575f929192916001600160a01b036044351694851515917f0000000000000000000000000000000000000000000000000000000000000000945b8281106106935761068f614cc0565b5f80f35b61069e818484613f90565b35906106ab818988613f90565b359182906106b881610be7565b93600185016106c78154610bf5565b9160038701548015159081610b81575b50610b5957600587019182549180831080610b4e575b610b26578f5f198d9214610b1d575b81610a85575b50610a5d578515610a3557600261077561077b92610785956107278a61078097614d9d565b905561076761073960088901546124df565b91610743836125a7565b610750858a015442613e7b565b90549060405195869414908c303360208801614daf565b03601f1981018352826122e7565b8d615660565b61059c565b61059c565b836006870154610794816146f6565b928561079f836146f6565b996107a9846146f6565b935f5b8c828210610920575050505f92879261080492886107d0600861083f9c01546124df565b6107d9816125a7565b6108b7575b5050506001600160a01b036107f6600185015461059c565b91169586913360443561581a565b604051809681927f0e4ab719000000000000000000000000000000000000000000000000000000008352600460058201910160048401614fe1565b038183865af19081156108b2576001987f9270fe8e320eb3bcb9e204acc32a4e15807595e19f754caa9b6efc42bb49ad1a9561088993610892575b50604051948594339986615081565b0390a301610680565b6108ad903d805f833e6108a581836122e7565b810190614eed565b61087a565b613e3b565b6108fe6108f560026004946108ee6108d76001600160a01b038a16610ce6565b956108e56006840188614df3565b60018701614e5f565b015461059c565b6002830161400a565b610916600382016109116064358261400a565b614eae565b0155885f886107de565b8192945060096109b36109938961099f86899b9c9e9f9d8a61094c61099992600460019e015490615620565b958161096661095f8660068b9601613e05565b9054610c59565b99818c61098c886109868f966109808860088c9301610d14565b546155f4565b92614727565b5201610d14565b546155f4565b92614727565b526109ad8360078b01610d14565b546155f4565b6109bd848d614727565b528a6109cc60088b01546124df565b6109d5816125a7565b156109ee575b50505050019187918995979694936107ac565b836109ff610a2c95610a0693614727565b5193614727565b51610a11858c614727565b519060643593610a2460028b015461059c565b9333906150f3565b808c5f8a6109db565b7f9750f074000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f380b4009000000000000000000000000000000000000000000000000000000005f5260045ffd5b905060046020610a98600188015461059c565b92604051928380927f38d52e0f0000000000000000000000000000000000000000000000000000000082525afa908115610b18576001600160a01b039182915f91610aea575b5016911614158f610702565b610b0b915060203d8111610b11575b610b0381836122e7565b81019061403d565b5f610ade565b503d610af9565b613e3b565b965082966106fc565b7fc6c2d6bf000000000000000000000000000000000000000000000000000000005f5260045ffd5b505f198114156106ed565b7f9cb13087000000000000000000000000000000000000000000000000000000005f5260045ffd5b905042115f6106d7565b7fa24a13a6000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2bae2620000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b5f52600960205260405f2090565b5f52600860205260405f2090565b5f52600a60205260405f2090565b9060031b1c90565b34610c47576020600319360112610c43576004355f52600a602052602060405f2054604051908152f35b5f80fd5b5f80fd5b5f910312610c5557565b5f80fd5b6001600160a01b039160031b1c1690565b6001600160a01b03169052565b9190916001600160a01b036020820193169052565b34610cb6575f600319360112610cb25760206001600160a01b0360065416604051908152f35b5f80fd5b5f80fd5b6003196040910112610ce257600435610cd2816105a8565b90602435610cdf816105a8565b90565b5f80fd5b6001600160a01b03165f52600b60205260405f2090565b6001600160a01b03165f52600c60205260405f2090565b906001600160a01b03165f5260205260405f2090565b34610d5f576020610d566001600160a01b03610d4536610cba565b91165f52600c835260405f20610d14565b54604051908152f35b5f80fd5b34610dd6576020600319360112610dd257610d7c610246565b5f52600960205260c060405f208054906001810154906001600160a01b036002820154166003820154906005600484015493015493604051958652602086015260408501526060840152608083015260a0820152f35b5f80fd5b5f80fd5b6020600319360112610e36576001600160a01b03600435610dfa816105a8565b610e08825f54163314613fa5565b167fffffffffffffffffffffffff000000000000000000000000000000000000000060065416176006555f80f35b5f80fd5b610e4336610cba565b6001600160a01b0382166040517f8da5cb5b000000000000000000000000000000000000000000000000000000008152602081600481855afa9081156112d4575f916112a5575b506001600160a01b033391160361127d576040517fce0617ec000000000000000000000000000000000000000000000000000000008152602081600481855afa908115611278575f91611249575b50421061122157610ee7614c4c565b610ef083610ce6565b600381015491610eff8361059c565b93610f16610f10600285015461059c565b9461202e565b5f146110be57610f296004840154610be7565b936040517faa8c217c000000000000000000000000000000000000000000000000000000008152602081600481875afa80156110b957610f78915f9161108a575b506004879897015490615620565b955f9560096008830192019260018701975b8754811015611062578685858585858f8f908f83610fa791613e05565b905490610fb391610c59565b96610fbf888098610d14565b5482610fca916155f4565b96610fd491610d14565b5490610fdf916155f4565b91610fe991613e05565b905490610ff591610c11565b93611001948d876150f3565b60405161101081928a8361406c565b037f9b6560650784d2522f3cbec920ec885ddafaecd6aabc1611996b0936002c7ddf91a261103e8189613e05565b61104791613e8d565b611051818a613e05565b61105a91613ecb565b600101610f8a565b5050505050505050505061107961107e915b610ce6565b614089565b611086614cc0565b5f80f35b6110ac915060203d6020116110b2575b6110a481836122e7565b810190614059565b5f610f6a565b503d61109a565b613e3b565b6005830154946040517f31574546000000000000000000000000000000000000000000000000000000008152602081600481875afa90811561121c57600391611111915f916111ed575b50979697610bf5565b0154955f9560018601965b86548110156111d85780867f9b6560650784d2522f3cbec920ec885ddafaecd6aabc1611996b0936002c7ddf6111a98b61119c898f8c8f918b61119561118e8f9561118060019f61117a6111738261118795613e05565b9054610c59565b9b613e05565b9054610c11565b92836155f4565b92826155f4565b92876150f3565b6040519182918b8361406c565b0390a26111bf6111b9828a613e05565b90613e8d565b6111d26111cc828b613e05565b90613ecb565b0161111c565b50505050505050505061107961107e91611074565b61120f915060203d602011611215575b61120781836122e7565b810190614059565b5f611108565b503d6111fd565b613e3b565b7fd550ed24000000000000000000000000000000000000000000000000000000005f5260045ffd5b61126b915060203d602011611271575b61126381836122e7565b810190614059565b5f610ed8565b503d611259565b613e3b565b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b6112c7915060203d6020116112cd575b6112bf81836122e7565b81019061403d565b5f610e8a565b503d6112b5565b613e3b565b34611320575f60031936011261131c5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5f80fd5b5f80fd5b3461136b575f6003193601126113675760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5f80fd5b5f80fd5b34611390575f60031936011261138c576020600354604051908152f35b5f80fd5b5f80fd5b8015150361139e57565b5f80fd5b6040600319360112611ac7576004356113ba816105a8565b602435906113c782611394565b6001600160a01b038116906040517f8da5cb5b000000000000000000000000000000000000000000000000000000008152602081600481865afa908115611ac2575f91611a93575b506001600160a01b0333911603611a6b57611428614c4c565b6040517f6d710c72000000000000000000000000000000000000000000000000000000008152602081600481865afa908115611a66575f91611a37575b5015611a0f5761147481610ce6565b92823b15611a0b576040517ff3d86e4a0000000000000000000000000000000000000000000000000000000081525f8160048183885af18015611a06576119ec575b506119de575b6114c9600384015461202e565b5f146117da576114dc6004840154610be7565b916040517faa8c217c000000000000000000000000000000000000000000000000000000008152602081600481855afa9081156117d5575f916117a6575b506001600160a01b03611530600286015461059c565b16158015611786575b5f1461172157611550906004859495015490615620565b9161155e600286015461059c565b905f9060088101936001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169060018901935b89548110156116d5576115b46115ad828c613e05565b9054610c59565b906115de876115cd8b6115c7868d610d14565b546155f4565b846115d960065461059c565b61536c565b60405163679d119760e01b8152602081806115fc8660048301610c77565b0381885afa9283156116d057888b848a6001978b965f916116a2575b5015611652575b50915050611639925061163391508d613e05565b90613e8d565b61164c6116468288613e05565b90613ecb565b01611597565b61168b6116846116909361167e6001600160a01b03966116788a60096116979d01610d14565b546155f4565b93613e05565b9054610c11565b613e7b565b9216614ce5565b845f888b848a61161f565b6116c3915060203d81116116c9575b6116bb81836122e7565b810190613e1f565b5f611618565b503d6116b1565b613e3b565b5097505050505050506116ec6116f1915b5b610ce6565b614089565b7f8f8b47094beecc082ab855873c2032f3c512b1f43e07993ec190e7319de5a22e5f80a261171d614cc0565b5f80f35b9093919260056117349101918254613e7b565b90555f9260018201935b8254811015611776578061175d61175760019386613e05565b90613e8d565b61177061176a8288613e05565b90613ecb565b0161173e565b509250506116ec6116f1916116e6565b506003840154801515908161179c575b50611539565b905042115f611796565b6117c8915060203d6020116117ce575b6117c081836122e7565b810190614059565b5f61151a565b503d6117b6565b613e3b565b906005830154916040517f31574546000000000000000000000000000000000000000000000000000000008152602081600481865afa9081156119d95760039161182e915f916119aa575b50949294610bf5565b01549161183e600286015461059c565b905f9260018701916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016945b88548110156119965761188f611888828b613e05565b9054610c59565b906118a461189d8287613e05565b9054610c11565b916118c4876118b387866155f4565b836118bf60065461059c565b61536c565b6040519063679d119760e01b8252602082806118e38460048301610c77565b03818c5afa918215611991576001948b938a925f91611963575b5015611934575b5050505061191b611915828c613e05565b90613e8d565b61192e6119288287613e05565b90613ecb565b01611872565b6119538161194d61195a966001600160a01b03946155f4565b90613e7b565b9216614ce5565b875f8781611904565b611984915060203d811161198a575b61197c81836122e7565b810190613e1f565b5f6118fd565b503d611972565b613e3b565b50965050505050506116ec6116f1916116e7565b6119cc915060203d6020116119d2575b6119c481836122e7565b810190614059565b5f611825565b503d6119ba565b613e3b565b6119e78161524c565b6114bc565b806119fa5f611a00936122e7565b80610c4b565b5f6114b6565b613e3b565b5f80fd5b7fb44bb110000000000000000000000000000000000000000000000000000000005f5260045ffd5b611a59915060203d602011611a5f575b611a5181836122e7565b810190613e1f565b5f611465565b503d611a47565b613e3b565b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b611ab5915060203d602011611abb575b611aad81836122e7565b81019061403d565b5f61140f565b503d611aa3565b613e3b565b5f80fd5b611b196001600160a01b03611adf36610cba565b9290335f52600c602052611af68160405f20610d14565b54928391335f52600c6020525f611b108260408320610d14565b55169384614ce5565b6040519081527ffe3464cd748424446c37877c28ce5b700222c5bc9f90d908afcc4e5cb22707ff60203392a35f80f35b6020600319360112611c4a5760043567ffffffffffffffff8111611c465780600401906101006003198236030112611c425760440135611b88816105a8565b6001600160a01b0333911603611c1a57611baa611ba536836129cb565b614581565b805f52600a60205260405f205415611bf2575f52600a6020525f6040812055357fc119b2a9f2c0af908846721da7bfe8d805bf89852059096b41b6e1bfb808fd785f80a25f80f35b7fc6c2d6bf000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b34611c74575f600319360112611c7057602060405167016345785d8a00008152f35b5f80fd5b5f80fd5b60443590565b60643590565b60843590565b9190602083019252565b60c060031936011261202a57611ca8610246565b602435611cb4816105a8565b611cbc611c78565b611cc4611c7e565b9260843567ffffffffffffffff811161202657611ce590369060040161055f565b909360a43567ffffffffffffffff811161202257611d0790369060040161055f565b939092611d1f6001611d1884610bf5565b015461059c565b6001600160a01b03611d308261059c565b1615611ffa5788151580611ff1575b611fc957620f42408710611fa157858203611f79575f5f905b838a818410611f0757505050506001600160a01b038416908115159182611e79575b5050611e51577f7f090c353d2e41038b0b98062eaa9ef0e5ee47a9f7748a665b828928910950f6611e1f9787611e34611e4d9b611e3c99611e419b611e18600154611dc361230f565b9081528a6020820152611dd933604083016140d4565b611de68c606083016140d4565b8260808201528460a0820152611dfd368b8a6128dc565b60c0820152611e0e8d853691612958565b60e0820152614581565b9d8e610c03565b55600154998a97604051968796339c88614127565b0390a46141a0565b6140b8565b60405191829182611c8a565b0390f35b7f380b4009000000000000000000000000000000000000000000000000000000005f5260045ffd5b6004919250602090604051928380927f38d52e0f0000000000000000000000000000000000000000000000000000000082525afa908115611f02576001600160a01b039182915f91611ed3575b5016911614155f80611d7a565b611ef5915060203d602011611efb575b611eed81836122e7565b81019061403d565b5f611ec6565b503d611ee3565b613e3b565b611f23846bffffffffffffffffffffffff1993611f2893613f90565b6140c7565b9160601b166bffffffffffffffffffffffff198260601b161115611f5157600190910190611d58565b7f84845e2d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fa24a13a6000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9750f074000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f5b22d1a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b50428910611d3f565b7fb0cfa447000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b60ff9060a01c1690565b91926001600160a01b0360809497969592978160a08601991685521660208401521515604083015260608201520152565b346120e25760206003193601126120de576001600160a01b0360043561208e816105a8565b165f52600b60205260405f206001600160a01b036002820154166120da60038301549260056004820154910154906040519485946001600160a01b0360ff8360a01c1692169086612038565b0390f35b5f80fd5b5f80fd5b60606003193601126122a5576004356120fe816105a8565b60243561210a816105a8565b60443590612117826105a8565b6001600160a01b0383166040517f8da5cb5b000000000000000000000000000000000000000000000000000000008152602081600481855afa9081156122a0575f91612271575b506001600160a01b0333911603612249576020600491604051928380927fce0617ec0000000000000000000000000000000000000000000000000000000082525afa908115612244575f91612215575b5042106121ed576121c6926121c1614c4c565b6141b3565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d5f80f35b7fd550ed24000000000000000000000000000000000000000000000000000000005f5260045ffd5b612237915060203d60201161223d575b61222f81836122e7565b810190614059565b5f6121ae565b503d612225565b613e3b565b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b612293915060203d602011612299575b61228b81836122e7565b81019061403d565b5f61215e565b503d612281565b613e3b565b5f80fd5b5f5260205f2090565b634e487b7160e01b5f52604160045260245ffd5b6040810190811067ffffffffffffffff8211176122e257604052565b6122b2565b90601f601f19910116810190811067ffffffffffffffff82111761230a57604052565b6122b2565b6040519061231f610100836122e7565b565b6040519061233060e0836122e7565b565b67ffffffffffffffff811161234b5760209060051b0190565b6122b2565b90600182811c9216801561237e575b602083101461236a57565b634e487b7160e01b5f52602260045260245ffd5b91607f169161235f565b5f929181549161239783612350565b928383526001811690815f146123f557506001146123b5575b505050565b90919293505f5260205f20905f915b8383106123db5750602092500101905f80806123b0565b6001816020929493945483858701015201910191906123c4565b9050602094955060ff1991509291921683830152151560051b0101905f80806123b0565b90604051612426816122c6565b80926040518060208354918281520190835f5260205f20905f5b8181106124c5575050509061245a816001949303826122e7565b83520190815461246981612332565b9261247760405194856122e7565b81845260208401905f5260205f205f915b838310612499575050505060200152565b6001602081926040516124b7816124b08189612388565b03826122e7565b815201920192019190612488565b909192600160208192865481520194019101919091612440565b60ff1690565b60ff90541690565b60408201908051916040845282518091526020606085019301905f5b81811061258d5750505060200151916020818303910152815180825260208201916020808360051b8301019401925f915b83831061254957505050505090565b90919293946020808083601f198660019603018752601f19601f838c518051918291828752018686015e5f858286010152011601019701930193019193929061253a565b909193602080600192875181520195019101919091612509565b600311156125b157565b634e487b7160e01b5f52602160045260245ffd5b9060038210156125d25752565b634e487b7160e01b5f52602160045260245ffd5b97969460c094612632946001600160a01b0361263a9995612624958d521660208c015260408b015260608a015260e060808a015260e08901906124ed565b9087820360a08901526124ed565b9401906125c5565b565b346126b55760206003193601126126b157612655610246565b5f52600860205260405f2080546126ad6001600160a01b0360018401541692600281015490600381015461268b60048301612419565b9060ff600861269c60068601612419565b9401541693604051978897886125e6565b0390f35b5f80fd5b5f80fd5b602060031936011261285f576004356126d1816105a8565b6001600160a01b0381166040517f8da5cb5b000000000000000000000000000000000000000000000000000000008152602081600481855afa90811561285a575f9161282b575b506001600160a01b0333911603612803576020600491604051928380927fce0617ec0000000000000000000000000000000000000000000000000000000082525afa9081156127fe575f916127cf575b5042106127a7576127809061277b614c4c565b61524c565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d5f80f35b7fd550ed24000000000000000000000000000000000000000000000000000000005f5260045ffd5b6127f1915060203d6020116127f7575b6127e981836122e7565b810190614059565b5f612768565b503d6127df565b613e3b565b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b61284d915060203d602011612853575b61284581836122e7565b81019061403d565b5f612718565b503d61283b565b613e3b565b5f80fd5b602060031936011261288e576004356128876001600160a01b035f54163314613fa5565b6005555f80f35b5f80fd5b346128b3575f6003193601126128af576020600154604051908152f35b5f80fd5b5f80fd5b346128d8575f6003193601126128d4576020600254604051908152f35b5f80fd5b5f80fd5b9291906128e881612332565b936128f660405195866122e7565b602085838152019160051b810192831161293257905b82821061291857505050565b602080918335612927816105a8565b81520191019061290c565b5f80fd5b9080601f8301121561295457816020612951933591016128dc565b90565b5f80fd5b92919061296481612332565b9361297260405195866122e7565b602085838152019160051b81019283116129a557905b82821061299457505050565b602080918335815201910190612988565b5f80fd5b9080601f830112156129c7578160206129c493359101612958565b90565b5f80fd5b91909161010081840312612a78576129e161230f565b9281358452602082013560208501526129fc604083016105bd565b6040850152612a0d606083016105bd565b60608501526080820135608085015260a082013560a085015260c082013567ffffffffffffffff8111612a745781612a46918401612936565b60c085015260e082013567ffffffffffffffff8111612a7057612a6992016129a9565b60e0830152565b5f80fd5b5f80fd5b5f80fd5b34612ac7576020600319360112612ac35760043567ffffffffffffffff8111612abf57612ab7612ab260209236906004016129cb565b614581565b604051908152f35b5f80fd5b5f80fd5b5f80fd5b34612af4575f600319360112612af05760206001600160a01b035f5416604051908152f35b5f80fd5b5f80fd5b34612b19575f600319360112612b15576020600554604051908152f35b5f80fd5b5f80fd5b606060031936011261311e5760043567ffffffffffffffff811161311a57612b4990369060040161055f565b60243567ffffffffffffffff811161311657612b6990369060040161055f565b612b746044356105a8565b612b7c614c4c565b60ff600454166130ee578083036130c6575f5b838110156130ba578060051b850135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01863603018212156130b657612bd6818486613f90565b358060a0848901013580151590816130ac575b5061308457612c02612bfd36868b016129cb565b614581565b612c0b81610c03565b549182811161304a575b50821561302257612c2590610c03565b612c30838254614d9d565b9055612c426080858a01013583615620565b9067016345785d8a000082109081613017575b50612fef576040612d03612cfe612cf96002612cd38d612cc560208c830101358c612cb0612c8283610bf5565b9a8b92612c9f6008612c978b87015442613e7b565b9501546124df565b96612ca9886125a7565b01016140c7565b908c6040519788961492309060208801614daf565b03601f1981018352826122e7565b7f0000000000000000000000000000000000000000000000000000000000000000615660565b61059c565b61059c565b612d1460c0878c0101878c016154ac565b9050612d1f816146f6565b928b8689612d2c856146f6565b97612d36866146f6565b95600554916003870154905f5b838110612edb5750505050928492612d986040612db8948a8c5f99612d71612df39f9e9d9c600801546124df565b612d7a816125a7565b612e6f575b505050612d906060828701016140c7565b9401016140c7565b966001600160a01b03612dae600187015461059c565b911697889361581a565b604051809681927f0e4ab719000000000000000000000000000000000000000000000000000000008352600460058201910160048401614fe1565b038183865af1908115612e6a576001987fed42b8a5eb619c450391ce89ca3cf47ad0e3e2f643c3c081f573a3d5f7bb5d0b95612e4193612e4a575b508d013595604051948594339986615081565b0390a301612b8f565b612e65903d805f833e612e5d81836122e7565b810190614eed565b612e2e565b613e3b565b612eac612e876001600160a01b03612ed39516610ce6565b92612ea3612e9c60c0888d0101888d016154ac565b9086614740565b60018401614e5f565b612eb9336002840161400a565b60056003830192612ecc6044358561400a565b015561550c565b898b5f612d7f565b909294879294965083612f2783612f218460e081612f16612f1186612f0b8d8560c0612f2d9e83010191016154ac565b90613f90565b6140c7565b990101908d016154ac565b90613f90565b356155f4565b918215612fc7578f92612fba918a8f8f612fb08f91612fa88f948f9b612f9f8f612f9860019f8f9295612f888f9788612f78879560409b612f7160089f8990614727565b52826155f4565b612f828686614727565b526155f4565b612f92838d614727565b52614727565b5197614727565b519601016140c7565b9401546124df565b946044359461597d565b01918d93918c9593612d43565b7fb35f9418000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fb2eaa0b5000000000000000000000000000000000000000000000000000000005f5260045ffd5b90508214155f612c55565b7fdc7076fe000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f199193500361305c5780915f612c15565b7fc6c2d6bf000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9cb13087000000000000000000000000000000000000000000000000000000005f5260045ffd5b905042115f612be9565b5f80fd5b6130c2614cc0565b5f80f35b7fa24a13a6000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2bae2620000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b67ffffffffffffffff811161313f57601f19601f60209201160190565b6122b2565b9080601f8301121561320f5781359161315c83612332565b9261316a60405194856122e7565b80845260208085019160051b8301019183831161320b5760208101915b83831061319657505050505090565b823567ffffffffffffffff811161320757820185603f82011215613203576020810135916131c383613122565b6131d060405191826122e7565b838152876020808686010101116131ff575f602085819660408397018386013783010152815201920191613187565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b91906040838203126132d6576040519061322c826122c6565b8193803567ffffffffffffffff81116132d257810182601f820112156132ce5780359061325882612332565b9161326660405193846122e7565b80835260208084019160051b830101918583116132ca57602001905b8282106132b957505050835260208101359167ffffffffffffffff83116132b5576020926132b09201613144565b910152565b5f80fd5b602080918335815201910190613282565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b60a4359060038210156132e957565b5f80fd5b359060038210156132fa57565b5f80fd5b346133f65760206003193601126133f25760043567ffffffffffffffff81116133ee5760e060031982360301126133ea57613337612321565b8160040135815261334a602483016105bd565b60208201526044820135604082015260648201356060820152608482013567ffffffffffffffff81116133e6576133879060043691850101613213565b608082015260a48201359067ffffffffffffffff82116133e2576133c860c46133de946133bd6133d29560043691840101613213565b60a0850152016132ed565b60c0820152614641565b60405191829182611c8a565b0390f35b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b346134455760206003193601126134415760043561341781611394565b61342c6001600160a01b035f54163314613fa5565b60ff60ff196004541691151516176004555f80f35b5f80fd5b5f80fd5b60a0600319360112613a505761345d610246565b61346561024c565b9061346e611c78565b60643567ffffffffffffffff8111613a4c5761348e90369060040161055f565b909260843567ffffffffffffffff8111613a48576134b090369060040161055f565b9490936134bb614c4c565b6134c483610bf5565b946001600160a01b036134e26134dd600189015461059c565b61059c565b1615613a205781151580613a17575b6139ef578685036139c757620f4240881061399f5794613510856146f6565b9561351a866146f6565b97613524876146f6565b925f60035f9201906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016925b8a81106136a4575050505050506002549661357a8787868c878d8b3391614b85565b9761358489610be7565b90815585600182015561359a336002830161400a565b8960048201558960058201558360038201556135ba878660068401614740565b5f60078201916009600882019101915b898110613644575050505061362795936136409961361f61362c9997958b957f1404c3f3dbc248c41c0fa6ab8af72db47cdb2ccf29b304c34c2ac67d3add4cab956002549a8b9a604051978897339d89614792565b0390a46141a0565b6140bd565b613634614cc0565b60405191829182611c8a565b0390f35b808b6136668261366061365b8f966001978f613f90565b6140c7565b92614727565b516136718288610d14565b5561367c8288614727565b516136878286610d14565b5561369d613695838a614727565b519186610d14565b55016135ca565b6136b76136b2828d8c613f90565b6140c7565b916bffffffffffffffffffffffff196001600160a01b0384169160601b166bffffffffffffffffffffffff198460601b161115613977578c828f85828c818d818e9b9e61370392613f90565b35948b5490600554968261371689613e5a565b9061372091613e7b565b61372991615620565b97889761373690896155f4565b976137428994826155f4565b9a8b9761374e91614727565b5261375891614727565b5261376291614727565b5260405163679d119760e01b815289818084600482019061378291610c77565b03815a93602094fa908115613972575f91613944575b505f146138ea57505050506040517f1281cd00000000000000000000000000000000000000000000000000000000008152602081600481855afa80156138e5576001600160a01b03915f916138b7575b501685149081159161382f575b50613807576001905b01939093613558565b7fbfc5dab8000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050602060405180927fe91929b6000000000000000000000000000000000000000000000000000000008252818061386a3360048301610c77565b03915afa9081156138b2575f91613884575b50155f6137f5565b6138a5915060203d81116138ab575b61389d81836122e7565b810190613e1f565b5f61387c565b503d613893565b613e3b565b6138d8915060203d81116138de575b6138d081836122e7565b81019061403d565b5f6137e8565b503d6138c6565b613e3b565b94939192943b1561391c5760019461390861390d9261391795613e7b565b613e7b565b9030903390615534565b6137fe565b7fceea21b6000000000000000000000000000000000000000000000000000000005f5260045ffd5b613965915060203d811161396b575b61395d81836122e7565b810190613e1f565b5f613798565b503d613953565b613e3b565b7f84845e2d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9750f074000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fa24a13a6000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f5b22d1a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b504282106134f1565b7fb0cfa447000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b34613adf576020600319360112613adb577fffffffffffffffffffffffff0000000000000000000000000000000000000000600435613a92816105a8565b6001600160a01b035f5491613aaa8284163314613fa5565b1691829116175f55337f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a35f80f35b5f80fd5b5f80fd5b34613b04575f600319360112613b00576020600754604051908152f35b5f80fd5b5f80fd5b90816040910312613b165790565b5f80fd5b60c0600319360112613d1257600435613b32816105a8565b613b3a61024c565b90613b43611c78565b9160643567ffffffffffffffff8111613d0e57613b64903690600401613b08565b9260843567ffffffffffffffff8111613d0a57613b85903690600401613b08565b916001600160a01b03613b966132da565b94168015613ce2576007548310613cba57670de0b6b3a7640000613bbc60055485613e7b565b11613c9257613c8e95613c63957f0c001567d2743a4cf169e9e0f5f5fc1e1d2e9b95b02d2d22a5ae74c499481cf7613c7d95613c7595613c37613c8299613c2b60035497613c08612321565b988952613c188a60208b016140d4565b8460408a01528560608a01523690613213565b60808801523690613213565b60a0860152613c498460c087016147f1565b613c5285614641565b998a95613c5e87610bf5565b614ad1565b60035496879460405193849384614b65565b0390a46141a0565b6140c2565b60405191829182611c8a565b0390f35b7f064a3ff8000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f320c7a15000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fd0f1a993000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b34613dbe5760e0600319360112613dba57600435613d3261024c565b9060443590613d40826105a8565b613d48611c7e565b613d50611c84565b9060a43567ffffffffffffffff8111613db657613d7190369060040161055f565b92909160c4359567ffffffffffffffff8711613db257613dae97613d9c613da29836906004016129a9565b96614b85565b60405191829182611c8a565b0390f35b5f80fd5b5f80fd5b5f80fd5b5f80fd5b6020600319360112613ded57600435613de66001600160a01b035f54163314613fa5565b6007555f80f35b5f80fd5b634e487b7160e01b5f52603260045260245ffd5b8054821015613e1a575f5260205f2001905f90565b613df1565b90816020910312613e375751613e3481611394565b90565b5f80fd5b6040513d5f823e3d90fd5b634e487b7160e01b5f52601160045260245ffd5b670de0b6b3a7640000019081670de0b6b3a764000011613e7657565b613e46565b91908201809211613e8857565b613e46565b906001600160a01b0382549160031b1b19169055565b7fffffffffffffffffffffffff00000000000000000000000000000000000000008154169055565b905f1982549160031b1b19169055565b5b818110613ee7575050565b6001905f815501613edc565b80545f825580613f02575b5050565b613f15915f5260205f2090810190613edb565b5f80613efe565b90680100000000000000008111613f5c57815490808355818110613f40575b505050565b613f54925f5260205f209182019101613edb565b5f8080613f3b565b6122b2565b6006613f8e915f81555f60018201555f60028201555f60038201555f60048201555f600582015501613ef3565b565b9190811015613fa05760051b0190565b613df1565b15613fac57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f554e415554484f52495a454400000000000000000000000000000000000000006044820152fd5b906001600160a01b03167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b908160209103126140555751614052816105a8565b90565b5f80fd5b90816020910312614068575190565b5f80fd5b9092916001600160a01b0360209181604085019616845216910152565b60055f9161409681613ef3565b6140a260018201613ef3565b8260028201558260038201558260048201550155565b600155565b600255565b600355565b356140d1816105a8565b90565b906001600160a01b03169052565b916020908281520191905f5b8181106140fb5750505090565b9091926020806001926001600160a01b038735614117816105a8565b16815201940191019190916140ee565b9796959390916001600160a01b036141539493168952602089015260a0604089015260a08801916140e2565b9185830360608701528183527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821161419c5760809260209260051b8092848301370101930152565b5f80fd5b5f1981146141ae5760010190565b613e46565b90916001600160a01b036141c683610ce6565b9216906003830154936141d88561059c565b946141ef6141e9600287015461059c565b9161202e565b5f14614392576142026004860154610be7565b956040517faa8c217c000000000000000000000000000000000000000000000000000000008152602081600481895afa801561438d5761424f915f9161435e575b50600489015490615620565b915f5b87548110156143525761426f614268828a613e05565b9054610c59565b6001600160a01b0381166001600160a01b038716146142915750600101614252565b9189829a9584806008869d9c9a98969b9901906142ad91610d14565b54826142b8916155f4565b92600901906142c691610d14565b54906142d1916155f4565b908660018b019c6142e2908e613e05565b9054906142ee91610c11565b906142f8966150f3565b604051918291614308918361406c565b037f9b6560650784d2522f3cbec920ec885ddafaecd6aabc1611996b0936002c7ddf91a261433591613e05565b61433e91613e8d565b61434791613e05565b61435091613ecb565b565b5050505050505050505b565b614380915060203d602011614386575b61437881836122e7565b810190614059565b5f614243565b503d61436e565b613e3b565b9593916040939193517f31574546000000000000000000000000000000000000000000000000000000008152602081600481865afa908115614505576003916143e2915f916144d6575b50610bf5565b0154935f5b84548110156144c8576144046143fd8287613e05565b9054610c59565b6001600160a01b0381166001600160a01b0384161461442657506001016143e7565b909698978596988460018a98019a61443e898d613e05565b90549061444a91610c11565b60058b015461445990826155f4565b9161446490826155f4565b9261446e966150f3565b60405191829161447e918361406c565b037f9b6560650784d2522f3cbec920ec885ddafaecd6aabc1611996b0936002c7ddf91a26144ab91613e05565b6144b491613e8d565b6144bd91613e05565b6144c691613ecb565b565b50505050505091505061435c565b6144f8915060203d6020116144fe575b6144f081836122e7565b810190614059565b5f6143dc565b503d6144e6565b613e3b565b90602080835192838152019201905f5b8181106145275750505090565b9091926020806001926001600160a01b038751168152019401910191909161451a565b90602080835192838152019201905f5b8181106145675750505090565b90919260208060019286518152019401910191909161455a565b60405161463b8161462d60208201946020865280516040840152602081015160608401526001600160a01b0360408201511660808401526145ca606082015160a0850190610c6a565b608081015160c084015260a081015160e084015260e06145fa60c08301516101008087015261014086019061450a565b9101517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08483030161012085015261454a565b03601f1981018352826122e7565b51902090565b6040516146f081602081019360208552805160408301526001600160a01b03602082015116606083015260408101516080830152606081015160a08301526146e260c06146d46146a0608085015160e0848801526101208701906124ed565b60a08501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08783030160e08801526124ed565b9201516101008401906125c5565b03601f1981018352826122e7565b51902090565b9061470082612332565b61470d60405191826122e7565b828152601f1961471d8294612332565b0190602036910137565b805182101561473b5760209160051b010190565b613df1565b9067ffffffffffffffff831161478d5761475a8383613f1c565b905f5260205f205f5b8381106147705750505050565b6001906020843594614781866105a8565b01938184015501614763565b6122b2565b98979693946147c26147de946147ec9760e060408e60c09c986147d09882526020820152015260e08d01916140e2565b908a820360608c015261454a565b9088820360808a015261454a565b9086820360a088015261454a565b930152565b60038210156147fd5752565b634e487b7160e01b5f52602160045260245ffd5b5f198260011b9260031b1c19161790565b5f90808252602082209081548360011b905f198560031b1c191617905555565b9190601f8111614852575b505050565b61487c925f5260205f20906020601f840160051c83019310614884575b601f0160051c0190613edb565b5f808061484d565b9150601f8192905061486f565b919091825167ffffffffffffffff8111614951576148b9816148b38454612350565b84614842565b6020601f82116001146148ed5781906148dc9394955f926148e1575b5050614811565b90555b565b90915001515f806148d5565b601f198216906148fc846122a9565b915f5b81811061493957509583600195969710614921575b505050811b0190556148df565b01515f1960f88460031b161c191690555f8080614914565b9192602060018192868b0151815501940192016148ff565b6122b2565b815191680100000000000000008311614a275781548383558084106149b1575b5060206149849101916122a9565b5f915b8383106149945750505050565b60016020826149a583945186614891565b01920192019190614987565b825f528360205f2091820191015b8181106149cc5750614976565b806149d960019254612350565b806149e6575b50016149bf565b601f811183146149fb57505f81555b5f6149df565b614a199083601f614a0b856122a9565b920160051c82019101613edb565b614a2281614822565b6149f5565b6122b2565b90805180519067ffffffffffffffff8211614acc57680100000000000000008211614ac7578354828555808310614aa0575b50602001614a6b846122a9565b5f5b838110614a8c57505050509060016020614a8a9301519101614956565b565b600190602084519401938184015501614a6d565b845f528260205f2091820191015b818110614abb5750614a5e565b6001905f815501614aae565b6122b2565b6122b2565b9060c060089180518455614af56001600160a01b036020830151166001860161400a565b6040810151600285015560608101516003850155614b1a608082015160048601614a2c565b614b2b60a082015160068601614a2c565b015191614b37836125a7565b01906003811015614b515760ff60ff198354169116179055565b634e487b7160e01b5f52602160045260245ffd5b604090614b83939594929560608201968252602082015201906125c5565b565b96929591959390936bffffffffffffffffffffffff19604051978896602088019a8b52604088015260601b1660608601526074850152609484015260b4830193905f5b818110614c1c5750505060208151939101925f5b818110614bfe575050614bf8925003601f1981018352826122e7565b51902090565b91600191935060208091865181520194019101918492939193614bdc565b91935091936020806001926001600160a01b038735614c3a816105a8565b16815201940191019185939492614bc8565b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005c614c985760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d565b5f91826044926020956001600160a01b03604051947fa9059cbb00000000000000000000000000000000000000000000000000000000865216600485015260248401525af13d15601f3d1160015f511416171615614d3f57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152fd5b91908203918211614daa57565b613e46565b94929060899694926bffffffffffffffffffffffff19809260601b16875260601b16601486015260288501526048840152151560f81b606883015260698201520190565b818114614e5b5781549167ffffffffffffffff8311614e5657614e168383613f1c565b5f5260205f20905f5260205f208154915f925b848410614e37575050505050565b60016001600160a01b0381921692019384549281850155019290614e29565b6122b2565b5050565b81519167ffffffffffffffff8311614ea957602090614e7e8484613f1c565b01905f5260205f205f5b838110614e955750505050565b600190602084519401938184015501614e88565b6122b2565b740100000000000000000000000000000000000000007fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff825416179055565b602081830312614fdd5780519067ffffffffffffffff8211614fd957019080601f83011215614fd557815191614f2283612332565b92614f3060405194856122e7565b80845260208085019160051b83010191838311614fd15760208101915b838310614f5c57505050505090565b825167ffffffffffffffff8111614fcd57820185603f82011215614fc957602081015191614f8983613122565b614f9660405191826122e7565b83815287602080868601010111614fc5575f602085819660408397018386015e83010152815201920191614f4d565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b6040810160408252825480915260608201925f5260205f20905f5b8181106150675750505060208183039101528154808252602082019160208260051b820101935f5260205f20925f915b83831061503b57505050505090565b90919293946020600161505883601f198684960301875289612388565b9701930193019193929061502c565b909193600160208192875481520195019101919091614ffc565b93906150cd95936001600160a01b036150bf946150b193885216602087015260a0604087015260a086019061454a565b90848203606086015261454a565b91608081840391015261454a565b90565b9160409194936001600160a01b0391826060860197168552602085015216910152565b92938361511a93976151158880989995998461511060065461059c565b61536c565b61536c565b60405163679d119760e01b8152602081806151388560048301610c77565b03816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa908115615247575f91615218575b505f146151fe576001600160a01b0316803b156151fa576151c8935f8094604051968795869485937f6b1b863a000000000000000000000000000000000000000000000000000000008552600485016150d0565b03925af180156151f5576151db575b505b565b806151e95f6151ef936122e7565b80610c4b565b5f6151d7565b613e3b565b5f80fd5b615213939291506001600160a01b0316614ce5565b6151d9565b61523a915060203d602011615240575b61523281836122e7565b810190613e1f565b5f615174565b503d615228565b613e3b565b6001600160a01b03166040517f31574546000000000000000000000000000000000000000000000000000000008152602081600481855afa8015615367576152a05f916152db938391615338575b50610bf5565b604051809381927f0e4ab719000000000000000000000000000000000000000000000000000000008352600660078201910160048401614fe1565b038183865af1801561533357615313575b507f7b7bc39f04c66f582deafe98cb5ced52fc317e6a41b90b440ba2a24fad2a766f5f80a2565b61532e903d805f833e61532681836122e7565b810190614eed565b6152ec565b613e3b565b61535a915060203d602011615360575b61535281836122e7565b810190614059565b5f61529a565b503d615348565b613e3b565b91929060405163679d119760e01b81526020818061538d8560048301610c77565b03816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa9081156154a7575f91615478575b505f14615453576001600160a01b0316803b1561544f5761541d935f8094604051968795869485937f6b1b863a000000000000000000000000000000000000000000000000000000008552600485016150d0565b03925af1801561544a57615430575b505b565b8061543e5f615444936122e7565b80610c4b565b5f61542c565b613e3b565b5f80fd5b615469915061546461547193610cfd565b610d14565b918254613e7b565b905561542e565b61549a915060203d6020116154a0575b61549281836122e7565b810190613e1f565b5f6153c9565b503d615488565b613e3b565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215615508570180359067ffffffffffffffff821161550457602001918160051b3603831361550057565b5f80fd5b5f80fd5b5f80fd5b7fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff8154169055565b905f6064926020956001600160a01b03839681604051967f23b872dd00000000000000000000000000000000000000000000000000000000885216600487015216602485015260448401525af13d15601f3d1160015f51141617161561559657565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b90805f19048211810215670de0b6b3a7640000021561561c57670de0b6b3a764000091020490565b5f80fd5b7812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218111670de0b6b3a7640000021582021561565c57670de0b6b3a7640000020490565b5f80fd5b9190918251604051917f3d610000000000000000000000000000000000000000000000000000000000008352603a820160f01b60028401527f80600b3d3981f30000000000000000000000000000000000000000000000000060048401527f363d3d3761000000000000000000000000000000000000000000000000000000600b8401526002820160f01b8060108501527f603836393d3d3d366100000000000000000000000000000000000000000000006012850152601b8401527f013d730000000000000000000000000000000000000000000000000000000000601d84015260601b60208301527f5af43d82803e903d91603657fd5bf300000000000000000000000000000000006034830152809360206043840191015b60208610156157e0579460459394955f19826020036101000a011990511682528260f01b91015201905ff0906001600160a01b038216156157b857565b7febfef188000000000000000000000000000000000000000000000000000000005f5260045ffd5b90602080601f19928451815201920195019461577b565b9160409194936001600160a01b0380926060860197865216602085015216910152565b6001600160a01b03909493949291921680155f1461584257509061583f939291615534565b5b565b6158816020915f946040519586809481937fb460af940000000000000000000000000000000000000000000000000000000083528a8c600485016157f7565b03925af1908115615978576001600160a01b03936020936158d79361594d575b506040519485809481937f70a0823100000000000000000000000000000000000000000000000000000000835260048301610c77565b0392165afa908115615948575f91615919575b501015615840577fd5a801fc000000000000000000000000000000000000000000000000000000005f5260045ffd5b61593b915060203d602011615941575b61593381836122e7565b810190614059565b5f6158ea565b503d615929565b613e3b565b61596c90853d8711615971575b61596481836122e7565b810190614059565b6158a1565b503d61595a565b613e3b565b939594909492919261598e816125a7565b155f14615b35576159b9906159b06159a760065461059c565b8587339261536c565b8285339261536c565b60405163679d119760e01b8152602081806159d78760048301610c77565b03816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa908115615b30575f91615b01575b505f14615aa25750506001600160a01b031691823b15615a9e57615a6b925f92836040518096819582947f6b1b863a0000000000000000000000000000000000000000000000000000000084523391600485016150d0565b03925af18015615a9957615a7f575b505b5b565b80615a8d5f615a93936122e7565b80610c4b565b5f615a7a565b613e3b565b5f80fd5b8294923b15615ad957615ac5615acd916001600160a01b03615ad4971693613e7b565b303384615534565b3390615534565b615a7c565b7fceea21b6000000000000000000000000000000000000000000000000000000005f5260045ffd5b615b23915060203d602011615b29575b615b1b81836122e7565b810190613e1f565b5f615a13565b503d615b11565b613e3b565b5093506001600160a01b039291927f0000000000000000000000000000000000000000000000000000000000000000169360405163679d119760e01b815260208180615b848860048301610c77565b0381895afa908115615d7b575f91615d4c575b505f14615cec575050506001600160a01b0316906040517f1281cd00000000000000000000000000000000000000000000000000000000008152602081600481865afa8015615ce7576001600160a01b03915f91615cb8575b50161490811591615c2f575b50615c07575b615a7d565b7fbfc5dab8000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050602060405180927fe91929b60000000000000000000000000000000000000000000000000000000082528180615c6a3360048301610c77565b03915afa908115615cb3575f91615c84575b50155f615bfc565b615ca6915060203d602011615cac575b615c9e81836122e7565b810190613e1f565b5f615c7c565b503d615c94565b613e3b565b615cda915060203d602011615ce0575b615cd281836122e7565b81019061403d565b5f615bf0565b503d615cc8565b613e3b565b90919350823b15615d2457615d07615d0c92615d1f95613e7b565b613e7b565b9030906001600160a01b03339116615534565b615c02565b7fceea21b6000000000000000000000000000000000000000000000000000000005f5260045ffd5b615d6e915060203d602011615d74575b615d6681836122e7565b810190613e1f565b5f615b97565b503d615d5c565b613e3b5600000000000000000000000040a1c08084671e9a799b73853e82308225309dc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000e27ee92e591d4fb7a6237cba4c7b4b81bbbdca8000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d
Deployed Bytecode
0x60806040526004361015610012575b5f80fd5b5f3560e01c806305c21582146102415780630600fc6f1461023c5780630718f4a514610237578063072fc961146102325780630cadba1a1461022d57806315a0c8bd1461022857806315a2fdee1461022357806321c0b3421461021e57806322b04e1414610219578063250b9ded1461021457806326e04f9d1461020f57806328b0c2351461020a5780632dbfa735146102055780632e0a11221461020057806338e96749146101fb57806345ccb466146101f6578063508d629d146101f15780635e1c75e1146101ec578063645b1b5b146101e757806369029181146101e2578063787dce3d146101dd57806380dd127a146101d857806382ae91d1146101d35780638d084ba3146101ce5780638da5cb5b146101c9578063b0e21e8a146101c4578063cd175e66146101bf578063d1216f24146101ba578063ec1d465e146101b5578063ee46ebc6146101b0578063f2fde38b146101ab578063f3256c81146101a6578063f6242301146101a1578063f94b51991461019c5763fcd9664b0361000e57613dc2565b613d16565b613b1a565b613ae3565b613a54565b613449565b6133fa565b6132fe565b612b1d565b612af8565b612acb565b612a7c565b6128b7565b612892565b612863565b6126b9565b61263c565b6120e6565b612069565b611c94565b611c4e565b611b49565b611acb565b6113a2565b61136f565b611324565b6112d9565b610e3a565b610dda565b610d63565b610d2a565b610c8c565b610c19565b6105ca565b610252565b60043590565b60243590565b602060031936011261055b57610266610246565b61026e614c4c565b61027781610be7565b9060028201610286815461059c565b6001600160a01b033391160361053357600583015492831561050b576102c96102bd60086102b76001850154610bf5565b016124e5565b94600483015490615620565b5f9060068301956001600160a01b037f000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d169460078501978215945b8154811015610482578061032561031e6103559385613e05565b9054610c59565b602061033b6103348487613e05565b9054610c59565b6040518095819263679d119760e01b835260048301610c77565b03818d5afa92831561047d57610397828e888b8e6001995f97889161044f575b50156103d5575b50505061039261038c878a613e05565b90613e8d565b610d14565b5588886103a3886125a7565b6103b0575b505001610304565b815f6103c3819460086103cd9501610d14565b5560098b01610d14565b555f886103a8565b610447926104326104379261042c6104256103fa876103f48c8c610d14565b546155f4565b9661041f8b6008610418846104128460098b01610d14565b546155f4565b9601610d14565b546155f4565b935461059c565b94613e7b565b613e7b565b906001600160a01b038516614ce5565b888b8e61037c565b610470915060203d8111610476575b61046881836122e7565b810190613e1f565b5f610375565b503d61045e565b613e3b565b888661048d866125a7565b155f146104f5575f60016104a083610be7565b01556104b660026104b083610be7565b01613ea3565b5f60056104c283610be7565b01555b7fa8192679f2a98ef0f7c48a03dd5d944c563888af9eb9c9da2f8932a2d84afa235f80a26104f1614cc0565b5f80f35b61050661050182610be7565b613f61565b6104c5565b7fc6c2d6bf000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b9181601f840112156105985782359167ffffffffffffffff8311610594576020808501948460051b01011161059057565b5f80fd5b5f80fd5b5f80fd5b6001600160a01b031690565b6001600160a01b038116036105b957565b5f80fd5b35906105c8826105a8565b565b6080600319360112610be35760043567ffffffffffffffff8111610bdf576105f690369060040161055f565b9060243567ffffffffffffffff8111610bdb5761061790369060040161055f565b906106236044356105a8565b61062e6064356105a8565b610636614c4c565b60ff60045416610bb357818403610b8b575f929192916001600160a01b036044351694851515917f00000000000000000000000040a1c08084671e9a799b73853e82308225309dc0945b8281106106935761068f614cc0565b5f80f35b61069e818484613f90565b35906106ab818988613f90565b359182906106b881610be7565b93600185016106c78154610bf5565b9160038701548015159081610b81575b50610b5957600587019182549180831080610b4e575b610b26578f5f198d9214610b1d575b81610a85575b50610a5d578515610a3557600261077561077b92610785956107278a61078097614d9d565b905561076761073960088901546124df565b91610743836125a7565b610750858a015442613e7b565b90549060405195869414908c303360208801614daf565b03601f1981018352826122e7565b8d615660565b61059c565b61059c565b836006870154610794816146f6565b928561079f836146f6565b996107a9846146f6565b935f5b8c828210610920575050505f92879261080492886107d0600861083f9c01546124df565b6107d9816125a7565b6108b7575b5050506001600160a01b036107f6600185015461059c565b91169586913360443561581a565b604051809681927f0e4ab719000000000000000000000000000000000000000000000000000000008352600460058201910160048401614fe1565b038183865af19081156108b2576001987f9270fe8e320eb3bcb9e204acc32a4e15807595e19f754caa9b6efc42bb49ad1a9561088993610892575b50604051948594339986615081565b0390a301610680565b6108ad903d805f833e6108a581836122e7565b810190614eed565b61087a565b613e3b565b6108fe6108f560026004946108ee6108d76001600160a01b038a16610ce6565b956108e56006840188614df3565b60018701614e5f565b015461059c565b6002830161400a565b610916600382016109116064358261400a565b614eae565b0155885f886107de565b8192945060096109b36109938961099f86899b9c9e9f9d8a61094c61099992600460019e015490615620565b958161096661095f8660068b9601613e05565b9054610c59565b99818c61098c886109868f966109808860088c9301610d14565b546155f4565b92614727565b5201610d14565b546155f4565b92614727565b526109ad8360078b01610d14565b546155f4565b6109bd848d614727565b528a6109cc60088b01546124df565b6109d5816125a7565b156109ee575b50505050019187918995979694936107ac565b836109ff610a2c95610a0693614727565b5193614727565b51610a11858c614727565b519060643593610a2460028b015461059c565b9333906150f3565b808c5f8a6109db565b7f9750f074000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f380b4009000000000000000000000000000000000000000000000000000000005f5260045ffd5b905060046020610a98600188015461059c565b92604051928380927f38d52e0f0000000000000000000000000000000000000000000000000000000082525afa908115610b18576001600160a01b039182915f91610aea575b5016911614158f610702565b610b0b915060203d8111610b11575b610b0381836122e7565b81019061403d565b5f610ade565b503d610af9565b613e3b565b965082966106fc565b7fc6c2d6bf000000000000000000000000000000000000000000000000000000005f5260045ffd5b505f198114156106ed565b7f9cb13087000000000000000000000000000000000000000000000000000000005f5260045ffd5b905042115f6106d7565b7fa24a13a6000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2bae2620000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b5f52600960205260405f2090565b5f52600860205260405f2090565b5f52600a60205260405f2090565b9060031b1c90565b34610c47576020600319360112610c43576004355f52600a602052602060405f2054604051908152f35b5f80fd5b5f80fd5b5f910312610c5557565b5f80fd5b6001600160a01b039160031b1c1690565b6001600160a01b03169052565b9190916001600160a01b036020820193169052565b34610cb6575f600319360112610cb25760206001600160a01b0360065416604051908152f35b5f80fd5b5f80fd5b6003196040910112610ce257600435610cd2816105a8565b90602435610cdf816105a8565b90565b5f80fd5b6001600160a01b03165f52600b60205260405f2090565b6001600160a01b03165f52600c60205260405f2090565b906001600160a01b03165f5260205260405f2090565b34610d5f576020610d566001600160a01b03610d4536610cba565b91165f52600c835260405f20610d14565b54604051908152f35b5f80fd5b34610dd6576020600319360112610dd257610d7c610246565b5f52600960205260c060405f208054906001810154906001600160a01b036002820154166003820154906005600484015493015493604051958652602086015260408501526060840152608083015260a0820152f35b5f80fd5b5f80fd5b6020600319360112610e36576001600160a01b03600435610dfa816105a8565b610e08825f54163314613fa5565b167fffffffffffffffffffffffff000000000000000000000000000000000000000060065416176006555f80f35b5f80fd5b610e4336610cba565b6001600160a01b0382166040517f8da5cb5b000000000000000000000000000000000000000000000000000000008152602081600481855afa9081156112d4575f916112a5575b506001600160a01b033391160361127d576040517fce0617ec000000000000000000000000000000000000000000000000000000008152602081600481855afa908115611278575f91611249575b50421061122157610ee7614c4c565b610ef083610ce6565b600381015491610eff8361059c565b93610f16610f10600285015461059c565b9461202e565b5f146110be57610f296004840154610be7565b936040517faa8c217c000000000000000000000000000000000000000000000000000000008152602081600481875afa80156110b957610f78915f9161108a575b506004879897015490615620565b955f9560096008830192019260018701975b8754811015611062578685858585858f8f908f83610fa791613e05565b905490610fb391610c59565b96610fbf888098610d14565b5482610fca916155f4565b96610fd491610d14565b5490610fdf916155f4565b91610fe991613e05565b905490610ff591610c11565b93611001948d876150f3565b60405161101081928a8361406c565b037f9b6560650784d2522f3cbec920ec885ddafaecd6aabc1611996b0936002c7ddf91a261103e8189613e05565b61104791613e8d565b611051818a613e05565b61105a91613ecb565b600101610f8a565b5050505050505050505061107961107e915b610ce6565b614089565b611086614cc0565b5f80f35b6110ac915060203d6020116110b2575b6110a481836122e7565b810190614059565b5f610f6a565b503d61109a565b613e3b565b6005830154946040517f31574546000000000000000000000000000000000000000000000000000000008152602081600481875afa90811561121c57600391611111915f916111ed575b50979697610bf5565b0154955f9560018601965b86548110156111d85780867f9b6560650784d2522f3cbec920ec885ddafaecd6aabc1611996b0936002c7ddf6111a98b61119c898f8c8f918b61119561118e8f9561118060019f61117a6111738261118795613e05565b9054610c59565b9b613e05565b9054610c11565b92836155f4565b92826155f4565b92876150f3565b6040519182918b8361406c565b0390a26111bf6111b9828a613e05565b90613e8d565b6111d26111cc828b613e05565b90613ecb565b0161111c565b50505050505050505061107961107e91611074565b61120f915060203d602011611215575b61120781836122e7565b810190614059565b5f611108565b503d6111fd565b613e3b565b7fd550ed24000000000000000000000000000000000000000000000000000000005f5260045ffd5b61126b915060203d602011611271575b61126381836122e7565b810190614059565b5f610ed8565b503d611259565b613e3b565b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b6112c7915060203d6020116112cd575b6112bf81836122e7565b81019061403d565b5f610e8a565b503d6112b5565b613e3b565b34611320575f60031936011261131c5760206040516001600160a01b037f00000000000000000000000040a1c08084671e9a799b73853e82308225309dc0168152f35b5f80fd5b5f80fd5b3461136b575f6003193601126113675760206040516001600160a01b037f000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d168152f35b5f80fd5b5f80fd5b34611390575f60031936011261138c576020600354604051908152f35b5f80fd5b5f80fd5b8015150361139e57565b5f80fd5b6040600319360112611ac7576004356113ba816105a8565b602435906113c782611394565b6001600160a01b038116906040517f8da5cb5b000000000000000000000000000000000000000000000000000000008152602081600481865afa908115611ac2575f91611a93575b506001600160a01b0333911603611a6b57611428614c4c565b6040517f6d710c72000000000000000000000000000000000000000000000000000000008152602081600481865afa908115611a66575f91611a37575b5015611a0f5761147481610ce6565b92823b15611a0b576040517ff3d86e4a0000000000000000000000000000000000000000000000000000000081525f8160048183885af18015611a06576119ec575b506119de575b6114c9600384015461202e565b5f146117da576114dc6004840154610be7565b916040517faa8c217c000000000000000000000000000000000000000000000000000000008152602081600481855afa9081156117d5575f916117a6575b506001600160a01b03611530600286015461059c565b16158015611786575b5f1461172157611550906004859495015490615620565b9161155e600286015461059c565b905f9060088101936001600160a01b037f000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d169060018901935b89548110156116d5576115b46115ad828c613e05565b9054610c59565b906115de876115cd8b6115c7868d610d14565b546155f4565b846115d960065461059c565b61536c565b60405163679d119760e01b8152602081806115fc8660048301610c77565b0381885afa9283156116d057888b848a6001978b965f916116a2575b5015611652575b50915050611639925061163391508d613e05565b90613e8d565b61164c6116468288613e05565b90613ecb565b01611597565b61168b6116846116909361167e6001600160a01b03966116788a60096116979d01610d14565b546155f4565b93613e05565b9054610c11565b613e7b565b9216614ce5565b845f888b848a61161f565b6116c3915060203d81116116c9575b6116bb81836122e7565b810190613e1f565b5f611618565b503d6116b1565b613e3b565b5097505050505050506116ec6116f1915b5b610ce6565b614089565b7f8f8b47094beecc082ab855873c2032f3c512b1f43e07993ec190e7319de5a22e5f80a261171d614cc0565b5f80f35b9093919260056117349101918254613e7b565b90555f9260018201935b8254811015611776578061175d61175760019386613e05565b90613e8d565b61177061176a8288613e05565b90613ecb565b0161173e565b509250506116ec6116f1916116e6565b506003840154801515908161179c575b50611539565b905042115f611796565b6117c8915060203d6020116117ce575b6117c081836122e7565b810190614059565b5f61151a565b503d6117b6565b613e3b565b906005830154916040517f31574546000000000000000000000000000000000000000000000000000000008152602081600481865afa9081156119d95760039161182e915f916119aa575b50949294610bf5565b01549161183e600286015461059c565b905f9260018701916001600160a01b037f000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d16945b88548110156119965761188f611888828b613e05565b9054610c59565b906118a461189d8287613e05565b9054610c11565b916118c4876118b387866155f4565b836118bf60065461059c565b61536c565b6040519063679d119760e01b8252602082806118e38460048301610c77565b03818c5afa918215611991576001948b938a925f91611963575b5015611934575b5050505061191b611915828c613e05565b90613e8d565b61192e6119288287613e05565b90613ecb565b01611872565b6119538161194d61195a966001600160a01b03946155f4565b90613e7b565b9216614ce5565b875f8781611904565b611984915060203d811161198a575b61197c81836122e7565b810190613e1f565b5f6118fd565b503d611972565b613e3b565b50965050505050506116ec6116f1916116e7565b6119cc915060203d6020116119d2575b6119c481836122e7565b810190614059565b5f611825565b503d6119ba565b613e3b565b6119e78161524c565b6114bc565b806119fa5f611a00936122e7565b80610c4b565b5f6114b6565b613e3b565b5f80fd5b7fb44bb110000000000000000000000000000000000000000000000000000000005f5260045ffd5b611a59915060203d602011611a5f575b611a5181836122e7565b810190613e1f565b5f611465565b503d611a47565b613e3b565b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b611ab5915060203d602011611abb575b611aad81836122e7565b81019061403d565b5f61140f565b503d611aa3565b613e3b565b5f80fd5b611b196001600160a01b03611adf36610cba565b9290335f52600c602052611af68160405f20610d14565b54928391335f52600c6020525f611b108260408320610d14565b55169384614ce5565b6040519081527ffe3464cd748424446c37877c28ce5b700222c5bc9f90d908afcc4e5cb22707ff60203392a35f80f35b6020600319360112611c4a5760043567ffffffffffffffff8111611c465780600401906101006003198236030112611c425760440135611b88816105a8565b6001600160a01b0333911603611c1a57611baa611ba536836129cb565b614581565b805f52600a60205260405f205415611bf2575f52600a6020525f6040812055357fc119b2a9f2c0af908846721da7bfe8d805bf89852059096b41b6e1bfb808fd785f80a25f80f35b7fc6c2d6bf000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b34611c74575f600319360112611c7057602060405167016345785d8a00008152f35b5f80fd5b5f80fd5b60443590565b60643590565b60843590565b9190602083019252565b60c060031936011261202a57611ca8610246565b602435611cb4816105a8565b611cbc611c78565b611cc4611c7e565b9260843567ffffffffffffffff811161202657611ce590369060040161055f565b909360a43567ffffffffffffffff811161202257611d0790369060040161055f565b939092611d1f6001611d1884610bf5565b015461059c565b6001600160a01b03611d308261059c565b1615611ffa5788151580611ff1575b611fc957620f42408710611fa157858203611f79575f5f905b838a818410611f0757505050506001600160a01b038416908115159182611e79575b5050611e51577f7f090c353d2e41038b0b98062eaa9ef0e5ee47a9f7748a665b828928910950f6611e1f9787611e34611e4d9b611e3c99611e419b611e18600154611dc361230f565b9081528a6020820152611dd933604083016140d4565b611de68c606083016140d4565b8260808201528460a0820152611dfd368b8a6128dc565b60c0820152611e0e8d853691612958565b60e0820152614581565b9d8e610c03565b55600154998a97604051968796339c88614127565b0390a46141a0565b6140b8565b60405191829182611c8a565b0390f35b7f380b4009000000000000000000000000000000000000000000000000000000005f5260045ffd5b6004919250602090604051928380927f38d52e0f0000000000000000000000000000000000000000000000000000000082525afa908115611f02576001600160a01b039182915f91611ed3575b5016911614155f80611d7a565b611ef5915060203d602011611efb575b611eed81836122e7565b81019061403d565b5f611ec6565b503d611ee3565b613e3b565b611f23846bffffffffffffffffffffffff1993611f2893613f90565b6140c7565b9160601b166bffffffffffffffffffffffff198260601b161115611f5157600190910190611d58565b7f84845e2d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fa24a13a6000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9750f074000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f5b22d1a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b50428910611d3f565b7fb0cfa447000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b60ff9060a01c1690565b91926001600160a01b0360809497969592978160a08601991685521660208401521515604083015260608201520152565b346120e25760206003193601126120de576001600160a01b0360043561208e816105a8565b165f52600b60205260405f206001600160a01b036002820154166120da60038301549260056004820154910154906040519485946001600160a01b0360ff8360a01c1692169086612038565b0390f35b5f80fd5b5f80fd5b60606003193601126122a5576004356120fe816105a8565b60243561210a816105a8565b60443590612117826105a8565b6001600160a01b0383166040517f8da5cb5b000000000000000000000000000000000000000000000000000000008152602081600481855afa9081156122a0575f91612271575b506001600160a01b0333911603612249576020600491604051928380927fce0617ec0000000000000000000000000000000000000000000000000000000082525afa908115612244575f91612215575b5042106121ed576121c6926121c1614c4c565b6141b3565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d5f80f35b7fd550ed24000000000000000000000000000000000000000000000000000000005f5260045ffd5b612237915060203d60201161223d575b61222f81836122e7565b810190614059565b5f6121ae565b503d612225565b613e3b565b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b612293915060203d602011612299575b61228b81836122e7565b81019061403d565b5f61215e565b503d612281565b613e3b565b5f80fd5b5f5260205f2090565b634e487b7160e01b5f52604160045260245ffd5b6040810190811067ffffffffffffffff8211176122e257604052565b6122b2565b90601f601f19910116810190811067ffffffffffffffff82111761230a57604052565b6122b2565b6040519061231f610100836122e7565b565b6040519061233060e0836122e7565b565b67ffffffffffffffff811161234b5760209060051b0190565b6122b2565b90600182811c9216801561237e575b602083101461236a57565b634e487b7160e01b5f52602260045260245ffd5b91607f169161235f565b5f929181549161239783612350565b928383526001811690815f146123f557506001146123b5575b505050565b90919293505f5260205f20905f915b8383106123db5750602092500101905f80806123b0565b6001816020929493945483858701015201910191906123c4565b9050602094955060ff1991509291921683830152151560051b0101905f80806123b0565b90604051612426816122c6565b80926040518060208354918281520190835f5260205f20905f5b8181106124c5575050509061245a816001949303826122e7565b83520190815461246981612332565b9261247760405194856122e7565b81845260208401905f5260205f205f915b838310612499575050505060200152565b6001602081926040516124b7816124b08189612388565b03826122e7565b815201920192019190612488565b909192600160208192865481520194019101919091612440565b60ff1690565b60ff90541690565b60408201908051916040845282518091526020606085019301905f5b81811061258d5750505060200151916020818303910152815180825260208201916020808360051b8301019401925f915b83831061254957505050505090565b90919293946020808083601f198660019603018752601f19601f838c518051918291828752018686015e5f858286010152011601019701930193019193929061253a565b909193602080600192875181520195019101919091612509565b600311156125b157565b634e487b7160e01b5f52602160045260245ffd5b9060038210156125d25752565b634e487b7160e01b5f52602160045260245ffd5b97969460c094612632946001600160a01b0361263a9995612624958d521660208c015260408b015260608a015260e060808a015260e08901906124ed565b9087820360a08901526124ed565b9401906125c5565b565b346126b55760206003193601126126b157612655610246565b5f52600860205260405f2080546126ad6001600160a01b0360018401541692600281015490600381015461268b60048301612419565b9060ff600861269c60068601612419565b9401541693604051978897886125e6565b0390f35b5f80fd5b5f80fd5b602060031936011261285f576004356126d1816105a8565b6001600160a01b0381166040517f8da5cb5b000000000000000000000000000000000000000000000000000000008152602081600481855afa90811561285a575f9161282b575b506001600160a01b0333911603612803576020600491604051928380927fce0617ec0000000000000000000000000000000000000000000000000000000082525afa9081156127fe575f916127cf575b5042106127a7576127809061277b614c4c565b61524c565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d5f80f35b7fd550ed24000000000000000000000000000000000000000000000000000000005f5260045ffd5b6127f1915060203d6020116127f7575b6127e981836122e7565b810190614059565b5f612768565b503d6127df565b613e3b565b7f30cd7471000000000000000000000000000000000000000000000000000000005f5260045ffd5b61284d915060203d602011612853575b61284581836122e7565b81019061403d565b5f612718565b503d61283b565b613e3b565b5f80fd5b602060031936011261288e576004356128876001600160a01b035f54163314613fa5565b6005555f80f35b5f80fd5b346128b3575f6003193601126128af576020600154604051908152f35b5f80fd5b5f80fd5b346128d8575f6003193601126128d4576020600254604051908152f35b5f80fd5b5f80fd5b9291906128e881612332565b936128f660405195866122e7565b602085838152019160051b810192831161293257905b82821061291857505050565b602080918335612927816105a8565b81520191019061290c565b5f80fd5b9080601f8301121561295457816020612951933591016128dc565b90565b5f80fd5b92919061296481612332565b9361297260405195866122e7565b602085838152019160051b81019283116129a557905b82821061299457505050565b602080918335815201910190612988565b5f80fd5b9080601f830112156129c7578160206129c493359101612958565b90565b5f80fd5b91909161010081840312612a78576129e161230f565b9281358452602082013560208501526129fc604083016105bd565b6040850152612a0d606083016105bd565b60608501526080820135608085015260a082013560a085015260c082013567ffffffffffffffff8111612a745781612a46918401612936565b60c085015260e082013567ffffffffffffffff8111612a7057612a6992016129a9565b60e0830152565b5f80fd5b5f80fd5b5f80fd5b34612ac7576020600319360112612ac35760043567ffffffffffffffff8111612abf57612ab7612ab260209236906004016129cb565b614581565b604051908152f35b5f80fd5b5f80fd5b5f80fd5b34612af4575f600319360112612af05760206001600160a01b035f5416604051908152f35b5f80fd5b5f80fd5b34612b19575f600319360112612b15576020600554604051908152f35b5f80fd5b5f80fd5b606060031936011261311e5760043567ffffffffffffffff811161311a57612b4990369060040161055f565b60243567ffffffffffffffff811161311657612b6990369060040161055f565b612b746044356105a8565b612b7c614c4c565b60ff600454166130ee578083036130c6575f5b838110156130ba578060051b850135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01863603018212156130b657612bd6818486613f90565b358060a0848901013580151590816130ac575b5061308457612c02612bfd36868b016129cb565b614581565b612c0b81610c03565b549182811161304a575b50821561302257612c2590610c03565b612c30838254614d9d565b9055612c426080858a01013583615620565b9067016345785d8a000082109081613017575b50612fef576040612d03612cfe612cf96002612cd38d612cc560208c830101358c612cb0612c8283610bf5565b9a8b92612c9f6008612c978b87015442613e7b565b9501546124df565b96612ca9886125a7565b01016140c7565b908c6040519788961492309060208801614daf565b03601f1981018352826122e7565b7f00000000000000000000000040a1c08084671e9a799b73853e82308225309dc0615660565b61059c565b61059c565b612d1460c0878c0101878c016154ac565b9050612d1f816146f6565b928b8689612d2c856146f6565b97612d36866146f6565b95600554916003870154905f5b838110612edb5750505050928492612d986040612db8948a8c5f99612d71612df39f9e9d9c600801546124df565b612d7a816125a7565b612e6f575b505050612d906060828701016140c7565b9401016140c7565b966001600160a01b03612dae600187015461059c565b911697889361581a565b604051809681927f0e4ab719000000000000000000000000000000000000000000000000000000008352600460058201910160048401614fe1565b038183865af1908115612e6a576001987fed42b8a5eb619c450391ce89ca3cf47ad0e3e2f643c3c081f573a3d5f7bb5d0b95612e4193612e4a575b508d013595604051948594339986615081565b0390a301612b8f565b612e65903d805f833e612e5d81836122e7565b810190614eed565b612e2e565b613e3b565b612eac612e876001600160a01b03612ed39516610ce6565b92612ea3612e9c60c0888d0101888d016154ac565b9086614740565b60018401614e5f565b612eb9336002840161400a565b60056003830192612ecc6044358561400a565b015561550c565b898b5f612d7f565b909294879294965083612f2783612f218460e081612f16612f1186612f0b8d8560c0612f2d9e83010191016154ac565b90613f90565b6140c7565b990101908d016154ac565b90613f90565b356155f4565b918215612fc7578f92612fba918a8f8f612fb08f91612fa88f948f9b612f9f8f612f9860019f8f9295612f888f9788612f78879560409b612f7160089f8990614727565b52826155f4565b612f828686614727565b526155f4565b612f92838d614727565b52614727565b5197614727565b519601016140c7565b9401546124df565b946044359461597d565b01918d93918c9593612d43565b7fb35f9418000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fb2eaa0b5000000000000000000000000000000000000000000000000000000005f5260045ffd5b90508214155f612c55565b7fdc7076fe000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f199193500361305c5780915f612c15565b7fc6c2d6bf000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9cb13087000000000000000000000000000000000000000000000000000000005f5260045ffd5b905042115f612be9565b5f80fd5b6130c2614cc0565b5f80f35b7fa24a13a6000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2bae2620000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b67ffffffffffffffff811161313f57601f19601f60209201160190565b6122b2565b9080601f8301121561320f5781359161315c83612332565b9261316a60405194856122e7565b80845260208085019160051b8301019183831161320b5760208101915b83831061319657505050505090565b823567ffffffffffffffff811161320757820185603f82011215613203576020810135916131c383613122565b6131d060405191826122e7565b838152876020808686010101116131ff575f602085819660408397018386013783010152815201920191613187565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b91906040838203126132d6576040519061322c826122c6565b8193803567ffffffffffffffff81116132d257810182601f820112156132ce5780359061325882612332565b9161326660405193846122e7565b80835260208084019160051b830101918583116132ca57602001905b8282106132b957505050835260208101359167ffffffffffffffff83116132b5576020926132b09201613144565b910152565b5f80fd5b602080918335815201910190613282565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b60a4359060038210156132e957565b5f80fd5b359060038210156132fa57565b5f80fd5b346133f65760206003193601126133f25760043567ffffffffffffffff81116133ee5760e060031982360301126133ea57613337612321565b8160040135815261334a602483016105bd565b60208201526044820135604082015260648201356060820152608482013567ffffffffffffffff81116133e6576133879060043691850101613213565b608082015260a48201359067ffffffffffffffff82116133e2576133c860c46133de946133bd6133d29560043691840101613213565b60a0850152016132ed565b60c0820152614641565b60405191829182611c8a565b0390f35b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b346134455760206003193601126134415760043561341781611394565b61342c6001600160a01b035f54163314613fa5565b60ff60ff196004541691151516176004555f80f35b5f80fd5b5f80fd5b60a0600319360112613a505761345d610246565b61346561024c565b9061346e611c78565b60643567ffffffffffffffff8111613a4c5761348e90369060040161055f565b909260843567ffffffffffffffff8111613a48576134b090369060040161055f565b9490936134bb614c4c565b6134c483610bf5565b946001600160a01b036134e26134dd600189015461059c565b61059c565b1615613a205781151580613a17575b6139ef578685036139c757620f4240881061399f5794613510856146f6565b9561351a866146f6565b97613524876146f6565b925f60035f9201906001600160a01b037f000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d16925b8a81106136a4575050505050506002549661357a8787868c878d8b3391614b85565b9761358489610be7565b90815585600182015561359a336002830161400a565b8960048201558960058201558360038201556135ba878660068401614740565b5f60078201916009600882019101915b898110613644575050505061362795936136409961361f61362c9997958b957f1404c3f3dbc248c41c0fa6ab8af72db47cdb2ccf29b304c34c2ac67d3add4cab956002549a8b9a604051978897339d89614792565b0390a46141a0565b6140bd565b613634614cc0565b60405191829182611c8a565b0390f35b808b6136668261366061365b8f966001978f613f90565b6140c7565b92614727565b516136718288610d14565b5561367c8288614727565b516136878286610d14565b5561369d613695838a614727565b519186610d14565b55016135ca565b6136b76136b2828d8c613f90565b6140c7565b916bffffffffffffffffffffffff196001600160a01b0384169160601b166bffffffffffffffffffffffff198460601b161115613977578c828f85828c818d818e9b9e61370392613f90565b35948b5490600554968261371689613e5a565b9061372091613e7b565b61372991615620565b97889761373690896155f4565b976137428994826155f4565b9a8b9761374e91614727565b5261375891614727565b5261376291614727565b5260405163679d119760e01b815289818084600482019061378291610c77565b03815a93602094fa908115613972575f91613944575b505f146138ea57505050506040517f1281cd00000000000000000000000000000000000000000000000000000000008152602081600481855afa80156138e5576001600160a01b03915f916138b7575b501685149081159161382f575b50613807576001905b01939093613558565b7fbfc5dab8000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050602060405180927fe91929b6000000000000000000000000000000000000000000000000000000008252818061386a3360048301610c77565b03915afa9081156138b2575f91613884575b50155f6137f5565b6138a5915060203d81116138ab575b61389d81836122e7565b810190613e1f565b5f61387c565b503d613893565b613e3b565b6138d8915060203d81116138de575b6138d081836122e7565b81019061403d565b5f6137e8565b503d6138c6565b613e3b565b94939192943b1561391c5760019461390861390d9261391795613e7b565b613e7b565b9030903390615534565b6137fe565b7fceea21b6000000000000000000000000000000000000000000000000000000005f5260045ffd5b613965915060203d811161396b575b61395d81836122e7565b810190613e1f565b5f613798565b503d613953565b613e3b565b7f84845e2d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9750f074000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fa24a13a6000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f5b22d1a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b504282106134f1565b7fb0cfa447000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b34613adf576020600319360112613adb577fffffffffffffffffffffffff0000000000000000000000000000000000000000600435613a92816105a8565b6001600160a01b035f5491613aaa8284163314613fa5565b1691829116175f55337f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a35f80f35b5f80fd5b5f80fd5b34613b04575f600319360112613b00576020600754604051908152f35b5f80fd5b5f80fd5b90816040910312613b165790565b5f80fd5b60c0600319360112613d1257600435613b32816105a8565b613b3a61024c565b90613b43611c78565b9160643567ffffffffffffffff8111613d0e57613b64903690600401613b08565b9260843567ffffffffffffffff8111613d0a57613b85903690600401613b08565b916001600160a01b03613b966132da565b94168015613ce2576007548310613cba57670de0b6b3a7640000613bbc60055485613e7b565b11613c9257613c8e95613c63957f0c001567d2743a4cf169e9e0f5f5fc1e1d2e9b95b02d2d22a5ae74c499481cf7613c7d95613c7595613c37613c8299613c2b60035497613c08612321565b988952613c188a60208b016140d4565b8460408a01528560608a01523690613213565b60808801523690613213565b60a0860152613c498460c087016147f1565b613c5285614641565b998a95613c5e87610bf5565b614ad1565b60035496879460405193849384614b65565b0390a46141a0565b6140c2565b60405191829182611c8a565b0390f35b7f064a3ff8000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f320c7a15000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fd0f1a993000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b5f80fd5b5f80fd5b34613dbe5760e0600319360112613dba57600435613d3261024c565b9060443590613d40826105a8565b613d48611c7e565b613d50611c84565b9060a43567ffffffffffffffff8111613db657613d7190369060040161055f565b92909160c4359567ffffffffffffffff8711613db257613dae97613d9c613da29836906004016129a9565b96614b85565b60405191829182611c8a565b0390f35b5f80fd5b5f80fd5b5f80fd5b5f80fd5b6020600319360112613ded57600435613de66001600160a01b035f54163314613fa5565b6007555f80f35b5f80fd5b634e487b7160e01b5f52603260045260245ffd5b8054821015613e1a575f5260205f2001905f90565b613df1565b90816020910312613e375751613e3481611394565b90565b5f80fd5b6040513d5f823e3d90fd5b634e487b7160e01b5f52601160045260245ffd5b670de0b6b3a7640000019081670de0b6b3a764000011613e7657565b613e46565b91908201809211613e8857565b613e46565b906001600160a01b0382549160031b1b19169055565b7fffffffffffffffffffffffff00000000000000000000000000000000000000008154169055565b905f1982549160031b1b19169055565b5b818110613ee7575050565b6001905f815501613edc565b80545f825580613f02575b5050565b613f15915f5260205f2090810190613edb565b5f80613efe565b90680100000000000000008111613f5c57815490808355818110613f40575b505050565b613f54925f5260205f209182019101613edb565b5f8080613f3b565b6122b2565b6006613f8e915f81555f60018201555f60028201555f60038201555f60048201555f600582015501613ef3565b565b9190811015613fa05760051b0190565b613df1565b15613fac57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f554e415554484f52495a454400000000000000000000000000000000000000006044820152fd5b906001600160a01b03167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b908160209103126140555751614052816105a8565b90565b5f80fd5b90816020910312614068575190565b5f80fd5b9092916001600160a01b0360209181604085019616845216910152565b60055f9161409681613ef3565b6140a260018201613ef3565b8260028201558260038201558260048201550155565b600155565b600255565b600355565b356140d1816105a8565b90565b906001600160a01b03169052565b916020908281520191905f5b8181106140fb5750505090565b9091926020806001926001600160a01b038735614117816105a8565b16815201940191019190916140ee565b9796959390916001600160a01b036141539493168952602089015260a0604089015260a08801916140e2565b9185830360608701528183527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821161419c5760809260209260051b8092848301370101930152565b5f80fd5b5f1981146141ae5760010190565b613e46565b90916001600160a01b036141c683610ce6565b9216906003830154936141d88561059c565b946141ef6141e9600287015461059c565b9161202e565b5f14614392576142026004860154610be7565b956040517faa8c217c000000000000000000000000000000000000000000000000000000008152602081600481895afa801561438d5761424f915f9161435e575b50600489015490615620565b915f5b87548110156143525761426f614268828a613e05565b9054610c59565b6001600160a01b0381166001600160a01b038716146142915750600101614252565b9189829a9584806008869d9c9a98969b9901906142ad91610d14565b54826142b8916155f4565b92600901906142c691610d14565b54906142d1916155f4565b908660018b019c6142e2908e613e05565b9054906142ee91610c11565b906142f8966150f3565b604051918291614308918361406c565b037f9b6560650784d2522f3cbec920ec885ddafaecd6aabc1611996b0936002c7ddf91a261433591613e05565b61433e91613e8d565b61434791613e05565b61435091613ecb565b565b5050505050505050505b565b614380915060203d602011614386575b61437881836122e7565b810190614059565b5f614243565b503d61436e565b613e3b565b9593916040939193517f31574546000000000000000000000000000000000000000000000000000000008152602081600481865afa908115614505576003916143e2915f916144d6575b50610bf5565b0154935f5b84548110156144c8576144046143fd8287613e05565b9054610c59565b6001600160a01b0381166001600160a01b0384161461442657506001016143e7565b909698978596988460018a98019a61443e898d613e05565b90549061444a91610c11565b60058b015461445990826155f4565b9161446490826155f4565b9261446e966150f3565b60405191829161447e918361406c565b037f9b6560650784d2522f3cbec920ec885ddafaecd6aabc1611996b0936002c7ddf91a26144ab91613e05565b6144b491613e8d565b6144bd91613e05565b6144c691613ecb565b565b50505050505091505061435c565b6144f8915060203d6020116144fe575b6144f081836122e7565b810190614059565b5f6143dc565b503d6144e6565b613e3b565b90602080835192838152019201905f5b8181106145275750505090565b9091926020806001926001600160a01b038751168152019401910191909161451a565b90602080835192838152019201905f5b8181106145675750505090565b90919260208060019286518152019401910191909161455a565b60405161463b8161462d60208201946020865280516040840152602081015160608401526001600160a01b0360408201511660808401526145ca606082015160a0850190610c6a565b608081015160c084015260a081015160e084015260e06145fa60c08301516101008087015261014086019061450a565b9101517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08483030161012085015261454a565b03601f1981018352826122e7565b51902090565b6040516146f081602081019360208552805160408301526001600160a01b03602082015116606083015260408101516080830152606081015160a08301526146e260c06146d46146a0608085015160e0848801526101208701906124ed565b60a08501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08783030160e08801526124ed565b9201516101008401906125c5565b03601f1981018352826122e7565b51902090565b9061470082612332565b61470d60405191826122e7565b828152601f1961471d8294612332565b0190602036910137565b805182101561473b5760209160051b010190565b613df1565b9067ffffffffffffffff831161478d5761475a8383613f1c565b905f5260205f205f5b8381106147705750505050565b6001906020843594614781866105a8565b01938184015501614763565b6122b2565b98979693946147c26147de946147ec9760e060408e60c09c986147d09882526020820152015260e08d01916140e2565b908a820360608c015261454a565b9088820360808a015261454a565b9086820360a088015261454a565b930152565b60038210156147fd5752565b634e487b7160e01b5f52602160045260245ffd5b5f198260011b9260031b1c19161790565b5f90808252602082209081548360011b905f198560031b1c191617905555565b9190601f8111614852575b505050565b61487c925f5260205f20906020601f840160051c83019310614884575b601f0160051c0190613edb565b5f808061484d565b9150601f8192905061486f565b919091825167ffffffffffffffff8111614951576148b9816148b38454612350565b84614842565b6020601f82116001146148ed5781906148dc9394955f926148e1575b5050614811565b90555b565b90915001515f806148d5565b601f198216906148fc846122a9565b915f5b81811061493957509583600195969710614921575b505050811b0190556148df565b01515f1960f88460031b161c191690555f8080614914565b9192602060018192868b0151815501940192016148ff565b6122b2565b815191680100000000000000008311614a275781548383558084106149b1575b5060206149849101916122a9565b5f915b8383106149945750505050565b60016020826149a583945186614891565b01920192019190614987565b825f528360205f2091820191015b8181106149cc5750614976565b806149d960019254612350565b806149e6575b50016149bf565b601f811183146149fb57505f81555b5f6149df565b614a199083601f614a0b856122a9565b920160051c82019101613edb565b614a2281614822565b6149f5565b6122b2565b90805180519067ffffffffffffffff8211614acc57680100000000000000008211614ac7578354828555808310614aa0575b50602001614a6b846122a9565b5f5b838110614a8c57505050509060016020614a8a9301519101614956565b565b600190602084519401938184015501614a6d565b845f528260205f2091820191015b818110614abb5750614a5e565b6001905f815501614aae565b6122b2565b6122b2565b9060c060089180518455614af56001600160a01b036020830151166001860161400a565b6040810151600285015560608101516003850155614b1a608082015160048601614a2c565b614b2b60a082015160068601614a2c565b015191614b37836125a7565b01906003811015614b515760ff60ff198354169116179055565b634e487b7160e01b5f52602160045260245ffd5b604090614b83939594929560608201968252602082015201906125c5565b565b96929591959390936bffffffffffffffffffffffff19604051978896602088019a8b52604088015260601b1660608601526074850152609484015260b4830193905f5b818110614c1c5750505060208151939101925f5b818110614bfe575050614bf8925003601f1981018352826122e7565b51902090565b91600191935060208091865181520194019101918492939193614bdc565b91935091936020806001926001600160a01b038735614c3a816105a8565b16815201940191019185939492614bc8565b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005c614c985760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d565b5f91826044926020956001600160a01b03604051947fa9059cbb00000000000000000000000000000000000000000000000000000000865216600485015260248401525af13d15601f3d1160015f511416171615614d3f57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152fd5b91908203918211614daa57565b613e46565b94929060899694926bffffffffffffffffffffffff19809260601b16875260601b16601486015260288501526048840152151560f81b606883015260698201520190565b818114614e5b5781549167ffffffffffffffff8311614e5657614e168383613f1c565b5f5260205f20905f5260205f208154915f925b848410614e37575050505050565b60016001600160a01b0381921692019384549281850155019290614e29565b6122b2565b5050565b81519167ffffffffffffffff8311614ea957602090614e7e8484613f1c565b01905f5260205f205f5b838110614e955750505050565b600190602084519401938184015501614e88565b6122b2565b740100000000000000000000000000000000000000007fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff825416179055565b602081830312614fdd5780519067ffffffffffffffff8211614fd957019080601f83011215614fd557815191614f2283612332565b92614f3060405194856122e7565b80845260208085019160051b83010191838311614fd15760208101915b838310614f5c57505050505090565b825167ffffffffffffffff8111614fcd57820185603f82011215614fc957602081015191614f8983613122565b614f9660405191826122e7565b83815287602080868601010111614fc5575f602085819660408397018386015e83010152815201920191614f4d565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b6040810160408252825480915260608201925f5260205f20905f5b8181106150675750505060208183039101528154808252602082019160208260051b820101935f5260205f20925f915b83831061503b57505050505090565b90919293946020600161505883601f198684960301875289612388565b9701930193019193929061502c565b909193600160208192875481520195019101919091614ffc565b93906150cd95936001600160a01b036150bf946150b193885216602087015260a0604087015260a086019061454a565b90848203606086015261454a565b91608081840391015261454a565b90565b9160409194936001600160a01b0391826060860197168552602085015216910152565b92938361511a93976151158880989995998461511060065461059c565b61536c565b61536c565b60405163679d119760e01b8152602081806151388560048301610c77565b03816001600160a01b037f000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d165afa908115615247575f91615218575b505f146151fe576001600160a01b0316803b156151fa576151c8935f8094604051968795869485937f6b1b863a000000000000000000000000000000000000000000000000000000008552600485016150d0565b03925af180156151f5576151db575b505b565b806151e95f6151ef936122e7565b80610c4b565b5f6151d7565b613e3b565b5f80fd5b615213939291506001600160a01b0316614ce5565b6151d9565b61523a915060203d602011615240575b61523281836122e7565b810190613e1f565b5f615174565b503d615228565b613e3b565b6001600160a01b03166040517f31574546000000000000000000000000000000000000000000000000000000008152602081600481855afa8015615367576152a05f916152db938391615338575b50610bf5565b604051809381927f0e4ab719000000000000000000000000000000000000000000000000000000008352600660078201910160048401614fe1565b038183865af1801561533357615313575b507f7b7bc39f04c66f582deafe98cb5ced52fc317e6a41b90b440ba2a24fad2a766f5f80a2565b61532e903d805f833e61532681836122e7565b810190614eed565b6152ec565b613e3b565b61535a915060203d602011615360575b61535281836122e7565b810190614059565b5f61529a565b503d615348565b613e3b565b91929060405163679d119760e01b81526020818061538d8560048301610c77565b03816001600160a01b037f000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d165afa9081156154a7575f91615478575b505f14615453576001600160a01b0316803b1561544f5761541d935f8094604051968795869485937f6b1b863a000000000000000000000000000000000000000000000000000000008552600485016150d0565b03925af1801561544a57615430575b505b565b8061543e5f615444936122e7565b80610c4b565b5f61542c565b613e3b565b5f80fd5b615469915061546461547193610cfd565b610d14565b918254613e7b565b905561542e565b61549a915060203d6020116154a0575b61549281836122e7565b810190613e1f565b5f6153c9565b503d615488565b613e3b565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215615508570180359067ffffffffffffffff821161550457602001918160051b3603831361550057565b5f80fd5b5f80fd5b5f80fd5b7fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff8154169055565b905f6064926020956001600160a01b03839681604051967f23b872dd00000000000000000000000000000000000000000000000000000000885216600487015216602485015260448401525af13d15601f3d1160015f51141617161561559657565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b90805f19048211810215670de0b6b3a7640000021561561c57670de0b6b3a764000091020490565b5f80fd5b7812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218111670de0b6b3a7640000021582021561565c57670de0b6b3a7640000020490565b5f80fd5b9190918251604051917f3d610000000000000000000000000000000000000000000000000000000000008352603a820160f01b60028401527f80600b3d3981f30000000000000000000000000000000000000000000000000060048401527f363d3d3761000000000000000000000000000000000000000000000000000000600b8401526002820160f01b8060108501527f603836393d3d3d366100000000000000000000000000000000000000000000006012850152601b8401527f013d730000000000000000000000000000000000000000000000000000000000601d84015260601b60208301527f5af43d82803e903d91603657fd5bf300000000000000000000000000000000006034830152809360206043840191015b60208610156157e0579460459394955f19826020036101000a011990511682528260f01b91015201905ff0906001600160a01b038216156157b857565b7febfef188000000000000000000000000000000000000000000000000000000005f5260045ffd5b90602080601f19928451815201920195019461577b565b9160409194936001600160a01b0380926060860197865216602085015216910152565b6001600160a01b03909493949291921680155f1461584257509061583f939291615534565b5b565b6158816020915f946040519586809481937fb460af940000000000000000000000000000000000000000000000000000000083528a8c600485016157f7565b03925af1908115615978576001600160a01b03936020936158d79361594d575b506040519485809481937f70a0823100000000000000000000000000000000000000000000000000000000835260048301610c77565b0392165afa908115615948575f91615919575b501015615840577fd5a801fc000000000000000000000000000000000000000000000000000000005f5260045ffd5b61593b915060203d602011615941575b61593381836122e7565b810190614059565b5f6158ea565b503d615929565b613e3b565b61596c90853d8711615971575b61596481836122e7565b810190614059565b6158a1565b503d61595a565b613e3b565b939594909492919261598e816125a7565b155f14615b35576159b9906159b06159a760065461059c565b8587339261536c565b8285339261536c565b60405163679d119760e01b8152602081806159d78760048301610c77565b03816001600160a01b037f000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d165afa908115615b30575f91615b01575b505f14615aa25750506001600160a01b031691823b15615a9e57615a6b925f92836040518096819582947f6b1b863a0000000000000000000000000000000000000000000000000000000084523391600485016150d0565b03925af18015615a9957615a7f575b505b5b565b80615a8d5f615a93936122e7565b80610c4b565b5f615a7a565b613e3b565b5f80fd5b8294923b15615ad957615ac5615acd916001600160a01b03615ad4971693613e7b565b303384615534565b3390615534565b615a7c565b7fceea21b6000000000000000000000000000000000000000000000000000000005f5260045ffd5b615b23915060203d602011615b29575b615b1b81836122e7565b810190613e1f565b5f615a13565b503d615b11565b613e3b565b5093506001600160a01b039291927f000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d169360405163679d119760e01b815260208180615b848860048301610c77565b0381895afa908115615d7b575f91615d4c575b505f14615cec575050506001600160a01b0316906040517f1281cd00000000000000000000000000000000000000000000000000000000008152602081600481865afa8015615ce7576001600160a01b03915f91615cb8575b50161490811591615c2f575b50615c07575b615a7d565b7fbfc5dab8000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050602060405180927fe91929b60000000000000000000000000000000000000000000000000000000082528180615c6a3360048301610c77565b03915afa908115615cb3575f91615c84575b50155f615bfc565b615ca6915060203d602011615cac575b615c9e81836122e7565b810190613e1f565b5f615c7c565b503d615c94565b613e3b565b615cda915060203d602011615ce0575b615cd281836122e7565b81019061403d565b5f615bf0565b503d615cc8565b613e3b565b90919350823b15615d2457615d07615d0c92615d1f95613e7b565b613e7b565b9030906001600160a01b03339116615534565b615c02565b7fceea21b6000000000000000000000000000000000000000000000000000000005f5260045ffd5b615d6e915060203d602011615d74575b615d6681836122e7565b810190613e1f565b5f615b97565b503d615d5c565b613e3b56
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
00000000000000000000000040a1c08084671e9a799b73853e82308225309dc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000e27ee92e591d4fb7a6237cba4c7b4b81bbbdca8000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d
-----Decoded View---------------
Arg [0] : _weirollWalletImplementation (address): 0x40a1c08084671E9A799B73853E82308225309Dc0
Arg [1] : _protocolFee (uint256): 0
Arg [2] : _minimumFrontendFee (uint256): 5000000000000000
Arg [3] : _owner (address): 0x0e27Ee92E591D4fB7A6237CBA4c7b4B81bBBDCa8
Arg [4] : _pointsFactory (address): 0xD3B5beD62038d520FE659C01B03e2727377c8B8d
-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 00000000000000000000000040a1c08084671e9a799b73853e82308225309dc0
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [2] : 0000000000000000000000000000000000000000000000000011c37937e08000
Arg [3] : 0000000000000000000000000e27ee92e591d4fb7a6237cba4c7b4b81bbbdca8
Arg [4] : 000000000000000000000000d3b5bed62038d520fe659c01b03e2727377c8b8d
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.