FRAX Price: $0.99 (+2.58%)

Contract

0x8F1E629536C74e8d8f58cfcbb44aD9CDf66a2433

Overview

FRAX Balance | FXTL Balance

0 FRAX | 2,288 FXTL

FRAX Value

$0.00

Token Holdings

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To
Change Implement...252625802025-09-08 12:51:11138 days ago1757335871IN
0x8F1E6295...Df66a2433
0 FRAX0.000123120.001
Change Implement...250521772025-09-03 15:57:45143 days ago1756915065IN
0x8F1E6295...Df66a2433
0 FRAX0.000101750.0012
Create Campaign249677532025-09-01 17:03:37144 days ago1756746217IN
0x8F1E6295...Df66a2433
0 FRAX0.001347150.0012
Create Campaign247632552025-08-27 23:27:01149 days ago1756337221IN
0x8F1E6295...Df66a2433
0 FRAX0.000360720.0012
Create Campaign246465792025-08-25 6:37:49152 days ago1756103869IN
0x8F1E6295...Df66a2433
0 FRAX0.00005850.00110025
Create Campaign242777032025-08-16 17:41:57160 days ago1755366117IN
0x8F1E6295...Df66a2433
0 FRAX0.000062720.00010025
Create Campaign241608872025-08-14 0:48:05163 days ago1755132485IN
0x8F1E6295...Df66a2433
0 FRAX0.000556040.0012
Create Campaign241590942025-08-13 23:48:19163 days ago1755128899IN
0x8F1E6295...Df66a2433
0 FRAX0.000204620.0012
Create Campaign238928912025-08-07 19:54:53169 days ago1754596493IN
0x8F1E6295...Df66a2433
0 FRAX0.000254910.00100025
Create Campaign238895502025-08-07 18:03:31169 days ago1754589811IN
0x8F1E6295...Df66a2433
0 FRAX0.00020530.00109925
Create Campaign238878142025-08-07 17:05:39169 days ago1754586339IN
0x8F1E6295...Df66a2433
0 FRAX0.000325620.0015
Create Campaign236909752025-08-03 3:44:21174 days ago1754192661IN
0x8F1E6295...Df66a2433
0 FRAX0.000094910.001
Create Campaign236747372025-08-02 18:43:05174 days ago1754160185IN
0x8F1E6295...Df66a2433
0 FRAX0.000246770.0012
Create Campaign236356502025-08-01 21:00:11175 days ago1754082011IN
0x8F1E6295...Df66a2433
0 FRAX0.000128860.00110026
Create Campaign236102372025-08-01 6:53:05176 days ago1754031185IN
0x8F1E6295...Df66a2433
0 FRAX0.000110070.00110025
Create Campaign235728782025-07-31 10:07:47177 days ago1753956467IN
0x8F1E6295...Df66a2433
0 FRAX0.000874170.0012
Create Campaign235691742025-07-31 8:04:19177 days ago1753949059IN
0x8F1E6295...Df66a2433
0 FRAX0.000804920.00110025
Create Campaign235683842025-07-31 7:37:59177 days ago1753947479IN
0x8F1E6295...Df66a2433
0 FRAX0.000808320.00110025
Create Campaign235471072025-07-30 19:48:45177 days ago1753904925IN
0x8F1E6295...Df66a2433
0 FRAX0.000813430.0011
Create Campaign235064092025-07-29 21:12:09178 days ago1753823529IN
0x8F1E6295...Df66a2433
0 FRAX0.000524980.0012
Create Campaign232877182025-07-24 19:42:27183 days ago1753386147IN
0x8F1E6295...Df66a2433
0 FRAX0.000226420.0012
Create Campaign230255062025-07-18 18:02:03189 days ago1752861723IN
0x8F1E6295...Df66a2433
0 FRAX0.001078720.00110025
Create Campaign229518312025-07-17 1:06:13191 days ago1752714373IN
0x8F1E6295...Df66a2433
0 FRAX0.000367260.00110025
Create Campaign226824462025-07-10 19:26:43197 days ago1752175603IN
0x8F1E6295...Df66a2433
0 FRAX0.001062290.00110025
Create Campaign219892372025-06-24 18:19:45213 days ago1750789185IN
0x8F1E6295...Df66a2433
0 FRAX0.00475350.00110025
View all transactions

Latest 25 internal transactions (View All)

Advanced mode:
Parent Transaction Hash Block From To
249677532025-09-01 17:03:37144 days ago1756746217
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
247632552025-08-27 23:27:01149 days ago1756337221
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
246465792025-08-25 6:37:49152 days ago1756103869
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
242777032025-08-16 17:41:57160 days ago1755366117
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
241608872025-08-14 0:48:05163 days ago1755132485
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
241590942025-08-13 23:48:19163 days ago1755128899
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
238928912025-08-07 19:54:53169 days ago1754596493
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
238895502025-08-07 18:03:31169 days ago1754589811
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
238878142025-08-07 17:05:39169 days ago1754586339
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
236909752025-08-03 3:44:21174 days ago1754192661
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
236747372025-08-02 18:43:05174 days ago1754160185
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
236356502025-08-01 21:00:11175 days ago1754082011
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
236102372025-08-01 6:53:05176 days ago1754031185
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
235728782025-07-31 10:07:47177 days ago1753956467
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
235691742025-07-31 8:04:19177 days ago1753949059
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
235683842025-07-31 7:37:59177 days ago1753947479
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
235471072025-07-30 19:48:45177 days ago1753904925
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
235064092025-07-29 21:12:09178 days ago1753823529
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
232877182025-07-24 19:42:27183 days ago1753386147
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
230255062025-07-18 18:02:03189 days ago1752861723
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
229518312025-07-17 1:06:13191 days ago1752714373
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
219892372025-06-24 18:19:45213 days ago1750789185
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
219890272025-06-24 18:12:45213 days ago1750788765
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
217698602025-06-19 16:27:11218 days ago1750350431
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
214250302025-06-11 16:52:51226 days ago1749660771
0x8F1E6295...Df66a2433
 Contract Creation0 FRAX
View All Internal Transactions

Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
EchoCampaignFactory

Compiler Version
v0.8.30+commit.73712a01

Optimization Enabled:
Yes with 200 runs

Other Settings:
prague EvmVersion
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

// ███████╗ ██████╗██╗  ██╗ ██████╗   ███╗   ███╗ █████╗ ██████╗ ██╗  ██╗███████╗████████╗
// ██╔════╝██╔════╝██║  ██║██╔═══██╗  ████╗ ████║██╔══██╗██╔══██╗██║ ██╔╝██╔════╝╚══██╔══╝
// █████╗  ██║     ███████║██║   ██║  ██╔████╔██║███████║██████╔╝█████╔╝ █████╗     ██║
// ██╔══╝  ██║     ██╔══██║██║   ██║  ██║╚██╔╝██║██╔══██║██╔══██╗██╔═██╗ ██╔══╝     ██║
// ███████╗╚██████╗██║  ██║╚██████╔╝  ██║ ╚═╝ ██║██║  ██║██║  ██║██║  ██╗███████╗   ██║
// ╚══════╝ ╚═════╝╚═╝  ╚═╝ ╚═════╝   ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝   ╚═╝
// ============================== EchoCampaignFactory V1 =============================
// =================================== Spring 2025 ===================================

import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {IEchoAdministration} from "./interfaces/IEchoAdministration.sol";
import {InitCampaign, IEchoCampaignFactory} from "./interfaces/IEchoCampaignFactory.sol";
import {QA_METHOD, CampaignInitData, IEchoCampaign} from "./interfaces/IEchoCampaign.sol";
import {ClonesWithImmutableArgs} from "./libs/ClonesWithImmutableArgs.sol";
import {EchoContents} from "./EchoContents.sol";

/// @title Echo Campaign Factory contract
/// @author Dynabits.org
contract EchoCampaignFactory is IEchoCampaignFactory {
    /********************************\
    |-*-*-*-*-*   STATES   *-*-*-*-*-|
    \********************************/
    address public implementation;
    address[] public allCampaigns;
    mapping(bytes32 => string) public campaignNameHashToName;
    mapping(string => CampaignInitData) private _campaignNameToInitData;

    /*******************************\
    |-*-*-*-*   CONSTANTS   *-*-*-*-|
    \*******************************/
    address public immutable ECHO_ADMIN;
    address public immutable ECHO_CONTENTS;

    /********************************\
    |-*-*-*-*-*   EVENTS   *-*-*-*-*-|
    \********************************/
    event ImplementationChanged(
        address indexed lastImplementation,
        address indexed newImplementation
    );
    event CampaignCreated(
        address indexed clonedCampaign,
        address indexed owner,
        address indexed implementation,
        string name,
        uint256 applicationFee,
        string EchoMarketData,
        uint256 totalClonedCampaigns,
        InitCampaign initCampaign
    );

    /********************************\
    |-*-*-*-*-*   ERRORS   *-*-*-*-*-|
    \********************************/
    error ONLY_PROTOCOL_ADMIN();
    error ZERO_ADDRESS_PROVIDED();
    error NAME_LENGTH_LOWER_THAN_8_BYTES();
    error NULL_CID();
    error NAME_LENGTH_LONGER_THAN_64_BYTES();
    error NAME_EXISTS(string name, address clonedCampaign);
    error START_TIME_IN_PAST();
    error END_TIME_LOWER_THAN_START_TIME();
    error CHECK_MAX_CAMPAIGN_DURATION(uint256 maxCampaignTime);
    error ONLY_WHITELISTED_TOKEN(address token);
    error QA_METHOD_OR_ORACLE_NOT_ALLOWED(string qaActionKind);
    error SUM_OF_QUALIFICATION_PARAMS_MUST_BE_100(uint256 sumOfPct);
    error SUM_OF_SOCIAL_KPI_PARAMS_MUST_BE_100(uint256 sumOfPct);
    error MAX_PER_POST_BIGGER_THAN_RESERVED_AMOUNT();
    error NO_QUALIFICATION();
    error NO_KPIS();
    error SOCIAL_KPI_RATIO_IS_ZERO(string kpiActionKind);
    error ONLY_ALLOWED_SOCIAL_KPIS();

    /*******************************\
    |-*-*-*-*   MODIFIERS   *-*-*-*-|
    \*******************************/
    modifier onlyProtocolAdmin() {
        if (msg.sender != _protocolAdmin()) revert ONLY_PROTOCOL_ADMIN();
        _;
    }

    /******************************\
    |-*-*-*-*   BUILT-IN   *-*-*-*-|
    \******************************/
    constructor(address echoAdministration) {
        _revertZeroAddress(echoAdministration);

        ECHO_ADMIN = echoAdministration;
        ECHO_CONTENTS = address(new EchoContents(echoAdministration));
    }

    /********************************\
    |-*-*-*   ADMINISTRATION   *-*-*-|
    \********************************/
    /// @inheritdoc IEchoCampaignFactory
    function changeImplementation(address newImplementation)
        external
        onlyProtocolAdmin
    {
        _revertZeroAddress(newImplementation);

        emit ImplementationChanged(implementation, newImplementation);

        implementation = newImplementation;
    }

    /*******************************\
    |*-*-*-*   EXTERNALS   *-*-*-*-*|
    \*******************************/
    /// @inheritdoc IEchoCampaignFactory
    function createCampaign(
        address owner,
        string calldata name,
        InitCampaign memory initCampaign
    ) external returns (address clonedCampaign) {
        // Ensure the owner address is not a zero address.
        _revertZeroAddress(owner);

        // Ensure the refund address is valid if maxPerPost is set.
        if (initCampaign.budgetInfo.maxPerPost != 0)
            _revertZeroAddress(initCampaign.refundAddress);
        else initCampaign.refundAddress = address(0);

        // Ensure the campaign name length is between 8 and 64 characters.
        if (bytes(name).length < 8) revert NAME_LENGTH_LOWER_THAN_8_BYTES();
        if (bytes(name).length > 64) revert NAME_LENGTH_LONGER_THAN_64_BYTES();

        // Ensure the campaign has a valid IPFS CID for referencing content.
        if (bytes(initCampaign.ipfsCID).length == 0) revert NULL_CID();

        // Ensure the campaign name is unique and has not been used before.
        if (_campaignNameToInitData[name].clonedCampaign != address(0))
            revert NAME_EXISTS(
                name,
                _campaignNameToInitData[name].clonedCampaign
            );

        // Ensure the campaign start time is not in the past.
        if (initCampaign.startTime < block.timestamp)
            revert START_TIME_IN_PAST();

        // Ensure the campaign end time is later than the start time.
        if (initCampaign.endTime <= initCampaign.startTime)
            revert END_TIME_LOWER_THAN_START_TIME();

        // Validate that the campaign duration does not exceed the max allowed duration.
        if (
            IEchoAdministration(ECHO_ADMIN).maxCampaignTime() != 0 &&
            IEchoAdministration(ECHO_ADMIN).maxCampaignTime() <
            initCampaign.endTime - initCampaign.startTime
        )
            revert CHECK_MAX_CAMPAIGN_DURATION(
                IEchoAdministration(ECHO_ADMIN).maxCampaignTime()
            );

        // Ensure the budget token is whitelisted for use.
        _onlyWhitelistedToken(initCampaign.budgetInfo.token);

        // Ensure maxPerPost does not exceed the reservedAmount if maxPerPost is set.
        if (
            initCampaign.budgetInfo.maxPerPost != 0 &&
            (initCampaign.budgetInfo.reservedAmount <
                initCampaign.budgetInfo.maxPerPost)
        ) revert MAX_PER_POST_BIGGER_THAN_RESERVED_AMOUNT();

        // Ensure at least one qualification method is provided.
        if (initCampaign.qaData.length == 0) revert NO_QUALIFICATION();

        uint256 totalPcts;
        uint256 i;
        string memory qaAction;
        bytes32[] memory qaData = new bytes32[](1);
        while (i < initCampaign.qaData.length) {
            // Determine the qualification method type and concatenate it with the kind.
            if (initCampaign.qaData[i].method == QA_METHOD.AI)
                qaAction = "QA-AI: ";
            else if (initCampaign.qaData[i].method == QA_METHOD.COMMUNITY)
                qaAction = "QA-C: ";
            else qaAction = "QA-V: ";

            qaData[0] = keccak256(
                abi.encodePacked(
                    string.concat(qaAction, initCampaign.qaData[i].kind)
                )
            );

            // Ensure the oracle is allowed to use the specified qualification method.
            if (
                !IEchoAdministration(ECHO_ADMIN).allowedActionKind(
                    initCampaign.qaData[i].oracle,
                    qaData
                )
            )
                revert QA_METHOD_OR_ORACLE_NOT_ALLOWED(
                    string.concat(qaAction, initCampaign.qaData[i].kind)
                );

            totalPcts += initCampaign.qaData[i].pct;

            unchecked {
                i++;
            }
        }

        // Ensure the sum of qualification percentages is exactly 100%.
        if (totalPcts != 100)
            revert SUM_OF_QUALIFICATION_PARAMS_MUST_BE_100(totalPcts);

        delete i;
        delete totalPcts;

        // Ensure at least one KPI is provided.
        if (initCampaign.socialKPIs.kpis.length == 0) revert NO_KPIS();

        bytes32[] memory kpis = new bytes32[](
            initCampaign.socialKPIs.kpis.length
        );

        while (i < initCampaign.socialKPIs.kpis.length) {
            totalPcts += initCampaign.socialKPIs.kpis[i].pct;

            // Concatenate the social network and KPI method.
            kpis[i] = keccak256(
                abi.encodePacked(
                    string.concat(
                        initCampaign.socialKPIs.social,
                        ": ",
                        initCampaign.socialKPIs.kpis[i].method
                    )
                )
            );

            // Ensure the KPI has a valid predefined ratio in the protocol.
            (, uint256 socialKPIratio, , ) = IEchoAdministration(ECHO_ADMIN)
                .modelData(address(0), kpis[i]);

            if (socialKPIratio == 0)
                revert SOCIAL_KPI_RATIO_IS_ZERO(
                    string.concat(
                        initCampaign.socialKPIs.social,
                        ": ",
                        initCampaign.socialKPIs.kpis[i].method
                    )
                );

            initCampaign.socialKPIs.kpis[i].ratio = socialKPIratio;

            unchecked {
                i++;
            }
        }

        // Ensure the sum of KPI percentages is exactly 100%.
        if (totalPcts != 100)
            revert SUM_OF_SOCIAL_KPI_PARAMS_MUST_BE_100(totalPcts);

        // Ensure the provided social network and KPIs are allowed.
        if (
            !IEchoAdministration(ECHO_ADMIN).allowedActionKind(address(0), kpis)
        ) revert ONLY_ALLOWED_SOCIAL_KPIS();

        // Clone the campaign contract and deploy it.
        clonedCampaign = ClonesWithImmutableArgs.clone3(
            implementation,
            abi.encodePacked(
                ECHO_ADMIN,
                address(this),
                owner,
                initCampaign.refundAddress,
                uint256(keccak256(bytes(name))),
                initCampaign.startTime,
                initCampaign.endTime
            ),
            keccak256(bytes(name))
        );

        // Calculate the protocol fee based on the campaign's reserved budget.
        uint128 fee = (initCampaign.budgetInfo.reservedAmount *
            IEchoAdministration(ECHO_ADMIN).campaignFeeRate()) / 1e5;

        // Deduct the protocol fee from the reserved budget.
        initCampaign.budgetInfo.reservedAmount -= fee;

        // Initialize the cloned campaign with the provided parameters.
        IEchoCampaign(clonedCampaign).init(
            initCampaign.ipfsCID,
            initCampaign.budgetInfo,
            initCampaign.socialKPIs,
            initCampaign.qaData
        );

        // Transfer the protocol fee to the protocol admin.
        IERC20(initCampaign.budgetInfo.token).transferFrom(
            msg.sender,
            _protocolAdmin(),
            fee
        );

        // Transfer the remaining reserved budget to the newly deployed campaign.
        IERC20(initCampaign.budgetInfo.token).transferFrom(
            msg.sender,
            clonedCampaign,
            initCampaign.budgetInfo.reservedAmount
        );

        // Store campaign initialization data.
        _campaignNameToInitData[name] = CampaignInitData(
            uint64(block.timestamp),
            clonedCampaign,
            implementation
        );
        campaignNameHashToName[keccak256(bytes(name))] = name;
        allCampaigns.push(clonedCampaign);

        // Emit an event indicating the successful creation of the campaign.
        emit CampaignCreated(
            clonedCampaign,
            owner,
            implementation,
            name,
            IEchoCampaign(clonedCampaign).applicationFee(),
            "EchoMarketData",
            allCampaigns.length,
            initCampaign
        );
    }

    /*****************************\
    |-*-*-*-*   GETTERS   *-*-*-*-|
    \*****************************/
    /// @inheritdoc IEchoCampaignFactory
    function campaignNameToInitData(string calldata name)
        external
        view
        returns (CampaignInitData memory)
    {
        return _campaignNameToInitData[name];
    }

    /// @inheritdoc IEchoCampaignFactory
    function paginatedCampaigns(uint256 page)
        external
        view
        returns (
            uint256 currentPage,
            uint256 totalPages,
            address[] memory pagedArray
        )
    {
        if (allCampaigns.length == 0)
            return (currentPage, totalPages, pagedArray);
        else if (allCampaigns.length < 11) {
            pagedArray = new address[](allCampaigns.length);

            uint256 x;
            while (true) {
                pagedArray[x] = allCampaigns[allCampaigns.length - 1 - x];

                if (x == allCampaigns.length - 1) break;

                unchecked {
                    x++;
                }
            }

            return (1, 1, pagedArray);
        }

        if (page == 0) page = 1;

        totalPages = allCampaigns.length / 10;

        uint256 diffLength = allCampaigns.length - (totalPages * 10);

        if (totalPages * 10 < allCampaigns.length) totalPages++;
        if (page > totalPages) page = totalPages;
        currentPage = page;

        uint256 firstIndex;
        uint256 lastIndex;
        if (page == 1) {
            firstIndex = allCampaigns.length - 1;
            lastIndex = firstIndex - 10;
        } else if (page == totalPages)
            firstIndex = diffLength == 0 ? firstIndex = 9 : diffLength - 1;
        else {
            firstIndex +=
                ((totalPages - page) * 10) +
                (diffLength != 0 ? diffLength - 1 : 0);
            lastIndex +=
                ((totalPages - page - 1) * 10) +
                (diffLength != 0 ? diffLength - 1 : 0);
        }

        pagedArray = new address[]((firstIndex + 1) - lastIndex);

        uint256 i;
        while (true) {
            pagedArray[i] = allCampaigns[firstIndex];

            if (firstIndex == lastIndex) break;
            unchecked {
                i++;
                firstIndex--;
            }
        }
    }

    /*****************************\
    |-*-*-*-*   PRIVATE   *-*-*-*-|
    \*****************************/
    function _protocolAdmin() private view returns (address) {
        return IEchoAdministration(ECHO_ADMIN).protocolAdmin();
    }

    function _revertZeroAddress(address addr) private pure {
        if (addr == address(0)) revert ZERO_ADDRESS_PROVIDED();
    }

    function _onlyWhitelistedToken(address token) private view {
        if (!IEchoAdministration(ECHO_ADMIN).whitelistedToken(token))
            revert ONLY_WHITELISTED_TOKEN(token);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

// ███████╗ ██████╗██╗  ██╗ ██████╗   ███╗   ███╗ █████╗ ██████╗ ██╗  ██╗███████╗████████╗
// ██╔════╝██╔════╝██║  ██║██╔═══██╗  ████╗ ████║██╔══██╗██╔══██╗██║ ██╔╝██╔════╝╚══██╔══╝
// █████╗  ██║     ███████║██║   ██║  ██╔████╔██║███████║██████╔╝█████╔╝ █████╗     ██║
// ██╔══╝  ██║     ██╔══██║██║   ██║  ██║╚██╔╝██║██╔══██║██╔══██╗██╔═██╗ ██╔══╝     ██║
// ███████╗╚██████╗██║  ██║╚██████╔╝  ██║ ╚═╝ ██║██║  ██║██║  ██║██║  ██╗███████╗   ██║
// ╚══════╝ ╚═════╝╚═╝  ╚═╝ ╚═════╝   ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝   ╚═╝
// ================================= EchoContents V1 =================================
// =================================== Spring 2025 ===================================

import {ActionStatus, IEchoAdministration} from "./interfaces/IEchoAdministration.sol";
import {IEchoCampaignFactory} from "./interfaces/IEchoCampaignFactory.sol";
import {IEchoContents, ConfigOracleData, QualificationOracleData, SocialKPIsOracleData, CampaignAndContent, CampaignContentInfo, ContentDetails, ContentsData} from "./interfaces/IEchoContents.sol";
import {QA_METHOD, Status, QualificationData, SocialKPIs, IEchoCampaign} from "./interfaces/IEchoCampaign.sol";
import {UD60x18, ud, unwrap} from "@prb/math/src/UD60x18.sol";

/// @title Echo Contents contract
/// @author Dynabits.org
contract EchoContents is IEchoContents {
    /********************************\
    |-*-*-*-*-*   STATES   *-*-*-*-*-|
    \********************************/
    ContentsData private _contentsData;
    CampaignAndContent[] public contents;
    mapping(string => mapping(address => ContentDetails))
        private _contentToCampaign;
    mapping(address => CampaignContentInfo) private _campaignContentInfo;

    /*******************************\
    |-*-*-*-*   CONSTANTS   *-*-*-*-|
    \*******************************/
    IEchoAdministration public immutable ECHO_ADMIN;
    IEchoCampaignFactory public immutable ECHO_FACTORY;
    UD60x18 private immutable ZERO_UD60x18 = ud(0);

    /********************************\
    |-*-*-*-*-*   EVENTS   *-*-*-*-*-|
    \********************************/
    event ContentApplied(
        address indexed campaign,
        uint256 indexed lastPostTime,
        uint256 indexed campaignTotalPosts,
        string contentLink,
        uint256 campaignMaxPerPost,
        string EchoMarketData,
        uint256 totalPosts
    );
    event ContentConfigAdded(
        address indexed campaign,
        address indexed oracle,
        string contentLink,
        ConfigOracleData newConfig
    );
    event ContentQAoverallScore(
        address indexed campaign,
        string contentLink,
        uint256 indexed QAoverallScore
    );
    event NewQualificationSettled(
        address indexed campaign,
        address indexed oracle,
        string contentLink,
        string qaMethodKind,
        uint256 pctScore
    );
    event ReadyForKPIupdates(address indexed campaign, string contentLink);
    event KPIupdated(
        address indexed campaign,
        address indexed oracle,
        string contentLink,
        uint256 lastCampaignUpdateTime,
        string socialKindKPI,
        uint256 kpiAmount
    );
    event ContentTotalKPIupdated(
        address indexed campaign,
        string contentLink,
        uint256 indexed contentTotalKPI
    );
    event ContentEffectiveKPIupdated(
        address indexed campaign,
        string contentLink,
        bool additionalMode,
        uint256 indexed additionalContentEffectvieKPI,
        uint256 campaignTotalEffectiveKPIs,
        uint256 indexed contentEffectiveKPI,
        uint256 campaignTotalEligibleContents,
        uint256 lastUpdatedTime,
        string EchoMarketData,
        uint256 totalEligibleContents,
        uint256 totalEffectiveKPIs
    );

    /********************************\
    |-*-*-*-*-*   ERRORS   *-*-*-*-*-|
    \********************************/
    error CONTENT_REGISTERED_FOR_CAMPAIGN_BEFORE();
    error ACCESS_DENIED();
    error UNDEFINED_JOB();
    error ORACLE_IS_BAN();
    error ZERO_CONTENT_QA_SCORE();
    error CHECK_QA_PCT(uint256 pct);
    error QA_DATA_SETTLED_BEFORE(string contentLink);
    error CONTENT_DOESNT_EXIST(string contentLink);
    error ONLY_IN_PROGRESS_OR_PAUSED_OR_FINISHED_CAMPAIGN();
    error ORACLE_DOESNT_HAVE_REQUIRED_ACCESS(string actionKind);
    error GIVEN_QA_KIND_DOESNT_EXIST_ON_CAMPAIGN(string qaKind);
    error GIVEN_KPI_KIND_DOESNT_EXIST_ON_CAMPAIGN(string kpiKind);
    error DIFFERENT_SOCIALS();

    /******************************\
    |-*-*-*-*   BUILT-IN   *-*-*-*-|
    \******************************/
    constructor(address echoAdministration) {
        ECHO_ADMIN = IEchoAdministration(echoAdministration);
        ECHO_FACTORY = IEchoCampaignFactory(msg.sender);
    }

    /*******************************\
    |*-*-*-*   EXTERNALS   *-*-*-*-*|
    \*******************************/
    /// @inheritdoc IEchoContents
    function addContentLink(
        string calldata contentLink,
        uint256 campaignMaxPerPost
    ) external {
        // Ensure that the caller is a registered campaign.
        if (
            ECHO_FACTORY
                .campaignNameToInitData(IEchoCampaign(msg.sender).name())
                .clonedCampaign == address(0)
        ) revert ACCESS_DENIED();

        // Check if the content has already been registered for this campaign.
        if (_contentToCampaign[contentLink][msg.sender].contentRegistered)
            revert CONTENT_REGISTERED_FOR_CAMPAIGN_BEFORE();

        // Store the content reference associated with the calling campaign.
        contents.push(CampaignAndContent(msg.sender, contentLink));
        _contentToCampaign[contentLink][msg.sender].contentRegistered = true;

        // Update the campaign's content records.
        _campaignContentInfo[msg.sender].contents.push(contentLink);
        _campaignContentInfo[msg.sender].lastPostTime = uint128(
            block.timestamp
        );

        _contentsData.totalPosts++;

        // Emit an event indicating that new content has been applied to the campaign.
        emit ContentApplied(
            msg.sender,
            block.timestamp,
            _campaignContentInfo[msg.sender].contents.length,
            contentLink,
            campaignMaxPerPost,
            "EchoMarketData",
            _contentsData.totalPosts
        );
    }

    /// @inheritdoc IEchoContents
    function setContentData(
        address campaign,
        string calldata contentLink,
        ConfigOracleData[] calldata configs,
        QualificationOracleData[] calldata qualifications
    ) external {
        // Ensure that at least one configuration or qualification is provided.
        if (configs.length == 0 && qualifications.length == 0)
            revert UNDEFINED_JOB();

        bool isProtocolAdmin = msg.sender == ECHO_ADMIN.protocolAdmin();

        // If the caller is not the protocol admin, verify that they are not a banned oracle.
        if (ECHO_ADMIN.disallowedOracle(msg.sender) && !isProtocolAdmin)
            revert ORACLE_IS_BAN();

        // Verify that the specified content has been registered within the campaign.
        if (!_contentToCampaign[contentLink][campaign].contentRegistered)
            revert CONTENT_DOESNT_EXIST(contentLink);

        string memory actionKind;
        // Process and register configuration data.
        if (configs.length != 0) {
            address oracle;
            uint256 i;
            while (i < configs.length) {
                actionKind = string.concat("CONFIG: ", configs[i].kind);

                // Ensure the caller has the required oracle permissions for configuration.
                (, , , oracle) = ECHO_ADMIN.modelData(
                    address(1),
                    keccak256(abi.encodePacked(actionKind))
                );

                if (oracle != msg.sender && !isProtocolAdmin)
                    revert ORACLE_DOESNT_HAVE_REQUIRED_ACCESS(actionKind);

                // Store the configuration value for the content.
                _contentToCampaign[contentLink][campaign].actionKindToValue[
                    actionKind
                ] = uint256(bytes32(configs[i].value));

                emit ContentConfigAdded(
                    campaign,
                    msg.sender,
                    contentLink,
                    configs[i]
                );

                unchecked {
                    i++;
                }
            }
        }

        // Process and register qualification data.
        if (qualifications.length != 0) {
            uint256 campaignTotalQas = IEchoCampaign(campaign).qaData().length;

            // If the caller is not the protocol admin, ensure they can only set remaining qualifications.
            if (
                campaignTotalQas -
                    _contentToCampaign[contentLink][campaign].totalQaKinds !=
                qualifications.length &&
                !isProtocolAdmin
            ) revert QA_DATA_SETTLED_BEFORE(contentLink);

            ActionStatus actionStat;
            uint256 i;
            while (i < qualifications.length) {
                // Define the action kind based on the qualification method.
                if (qualifications[i].method == QA_METHOD.AI)
                    actionKind = string.concat(
                        "QA-AI: ",
                        qualifications[i].kind
                    );
                else if (qualifications[i].method == QA_METHOD.COMMUNITY)
                    actionKind = string.concat(
                        "QA-C: ",
                        qualifications[i].kind
                    );
                else
                    actionKind = string.concat(
                        "QA-V: ",
                        qualifications[i].kind
                    );

                // Ensure that the qualification kind is valid within the campaign.
                if (!IEchoCampaign(campaign).registeredActionKind(actionKind))
                    revert GIVEN_QA_KIND_DOESNT_EXIST_ON_CAMPAIGN(actionKind);

                // Ensure the caller has the required oracle permissions for qualification.
                (actionStat, , , ) = ECHO_ADMIN.modelData(
                    msg.sender,
                    keccak256(abi.encodePacked(actionKind))
                );
                if (actionStat != ActionStatus.QA && !isProtocolAdmin)
                    revert ORACLE_DOESNT_HAVE_REQUIRED_ACCESS(actionKind);

                // Ensure the qualification percentage is within the valid range (0 < pct ≤ 1e5).
                if (qualifications[i].pct > 1e5 || qualifications[i].pct == 0)
                    revert CHECK_QA_PCT(qualifications[i].pct);

                // Ensure that if the caller is not the protocol admin, they are not setting a qualification again.
                if (
                    _contentToCampaign[contentLink][campaign].actionKindToValue[
                        actionKind
                    ] != 0
                ) {
                    if (!isProtocolAdmin)
                        revert QA_DATA_SETTLED_BEFORE(contentLink);
                } else {
                    _contentToCampaign[contentLink][campaign].totalQaKinds++;
                }

                // Store the qualification percentage for the content.
                _contentToCampaign[contentLink][campaign].actionKindToValue[
                    actionKind
                ] = qualifications[i].pct;

                emit NewQualificationSettled(
                    campaign,
                    msg.sender,
                    contentLink,
                    actionKind,
                    qualifications[i].pct
                );

                unchecked {
                    i++;
                }
            }

            // If all required qualifications are set, compute the overall qualification score.
            if (
                _contentToCampaign[contentLink][campaign].totalQaKinds ==
                campaignTotalQas
            ) {
                UD60x18 overallScore = ud(1e18);
                QualificationData[] memory qaData = IEchoCampaign(campaign)
                    .qaData();

                string memory ANTI_DEEPSTACK_CL = contentLink;
                address ANTI_DEEPSTACK_C = campaign;

                delete i;
                // Compute the overall qualification score using the predefined formula.
                while (i < qaData.length) {
                    // Define the action kind based on the qualification method.
                    if (qaData[i].method == QA_METHOD.AI)
                        actionKind = string.concat("QA-AI: ", qaData[i].kind);
                    else if (qaData[i].method == QA_METHOD.COMMUNITY)
                        actionKind = string.concat("QA-C: ", qaData[i].kind);
                    else actionKind = string.concat("QA-V: ", qaData[i].kind);

                    overallScore = overallScore.mul(
                        ud(
                            _contentToCampaign[ANTI_DEEPSTACK_CL][
                                ANTI_DEEPSTACK_C
                            ].actionKindToValue[actionKind] * 1e13
                        ).pow(ud(qaData[i].pct * 1e16))
                    );

                    // If any qualification score is zero, the final result is zero.
                    if (overallScore == ZERO_UD60x18) break;

                    unchecked {
                        i++;
                    }
                }

                // Store the overall qualification score for the content.
                _contentToCampaign[ANTI_DEEPSTACK_CL][ANTI_DEEPSTACK_C]
                    .contentOverallQaScore = overallScore;

                emit ContentQAoverallScore(
                    ANTI_DEEPSTACK_C,
                    ANTI_DEEPSTACK_CL,
                    unwrap(overallScore)
                );
            }

            // If the overall QA score is set, emit an event to indicate readiness for KPI updates.
            if (
                _contentToCampaign[contentLink][campaign]
                    .contentOverallQaScore != ZERO_UD60x18
            ) emit ReadyForKPIupdates(campaign, contentLink);
        }

        /* 
            EDGE CASE: If Effective KPI already exists for a content and the protocol invokes the function
                again, obtaining a new Effective KPI requires re-invoking the updateKPIs function.
                However, due to high gas costs and potential multiple loops
                the update is not performed automatically.
        */
    }

    /// @inheritdoc IEchoContents
    function updateKPIs(
        address campaign,
        string calldata contentLink,
        SocialKPIsOracleData calldata socialKPIs
    ) external {
        bool isProtocolAdmin = msg.sender == ECHO_ADMIN.protocolAdmin();

        // Ensure the caller is either the protocol admin or a permitted oracle.
        // If the caller is a banned oracle and not the admin, revert.
        if (ECHO_ADMIN.disallowedOracle(msg.sender) && !isProtocolAdmin)
            revert ORACLE_IS_BAN();

        // Verify if the content exists for the given campaign.
        // If not, revert with `CONTENT_DOESNT_EXIST`.
        if (!_contentToCampaign[contentLink][campaign].contentRegistered)
            revert CONTENT_DOESNT_EXIST(contentLink);

        // Ensure that if the caller is not the protocol admin, the campaign must be in
        // a valid status: InProgress, Paused, or Finished.
        Status stat = IEchoCampaign(campaign).currentStatus();
        if (
            (stat != Status.inProgress &&
                stat != Status.paused &&
                stat != Status.finished) && !isProtocolAdmin
        ) revert ONLY_IN_PROGRESS_OR_PAUSED_OR_FINISHED_CAMPAIGN();

        // Check if the content has a valid Quality Assurance (QA) score.
        // If the score is zero, revert.
        if (
            _contentToCampaign[contentLink][campaign]
                .contentOverallQaScore
                .unwrap() == 0
        ) revert ZERO_CONTENT_QA_SCORE();

        // Retrieve the campaign’s social KPIs and validate them.
        SocialKPIs memory campaignSocialKPIs = IEchoCampaign(campaign)
            .socialKPIs();
        string memory kpiAction = socialKPIs.social;

        // Ensure that the social KPI being updated matches the campaign's selected KPI.
        if (
            keccak256(abi.encodePacked(campaignSocialKPIs.social)) !=
            keccak256(abi.encodePacked(kpiAction))
        ) revert DIFFERENT_SOCIALS();

        uint256 i;
        string memory actionKind;

        address oracle;
        while (i < socialKPIs.kpis.length) {
            // Construct the KPI action kind string.
            actionKind = string.concat(
                kpiAction,
                ": ",
                socialKPIs.kpis[i].method
            );

            // Ensure the KPI action is registered in the campaign.
            if (!IEchoCampaign(campaign).registeredActionKind(actionKind))
                revert GIVEN_KPI_KIND_DOESNT_EXIST_ON_CAMPAIGN(actionKind);

            // Verify that the caller is an authorized oracle with permissions to update KPIs.
            (, , , oracle) = ECHO_ADMIN.modelData(
                address(0),
                keccak256(abi.encodePacked(actionKind))
            );
            if (msg.sender != oracle && !isProtocolAdmin)
                revert ORACLE_DOESNT_HAVE_REQUIRED_ACCESS(actionKind);

            // Update the KPI value for the given content and campaign.
            _contentToCampaign[contentLink][campaign].actionKindToValue[
                actionKind
            ] = socialKPIs.kpis[i].value;

            // Emit event to indicate one KPI updated.
            emit KPIupdated(
                campaign,
                msg.sender,
                contentLink,
                block.timestamp,
                actionKind,
                socialKPIs.kpis[i].value
            );

            unchecked {
                i++;
            }
        }

        _campaignContentInfo[campaign].lastUpdatedTime = uint128(
            block.timestamp
        );

        delete i;

        UD60x18 totalKPI = ud(0);
        string memory ANTI_DEEPSTACK_CL = contentLink;
        address ANTI_DEEPSTACK_C = campaign;

        while (i < campaignSocialKPIs.kpis.length) {
            actionKind = string.concat(
                kpiAction,
                ": ",
                campaignSocialKPIs.kpis[i].method
            );

            // Ensure KPI values meet the campaign’s minimum requirements.
            if (
                campaignSocialKPIs.kpis[i].min >
                _contentToCampaign[ANTI_DEEPSTACK_CL][ANTI_DEEPSTACK_C]
                    .actionKindToValue[actionKind]
            ) {
                totalKPI = ZERO_UD60x18;
                break;
            }

            // Compute the overall KPI formula based on campaign settings.
            totalKPI = totalKPI.add(
                ud(uint256(campaignSocialKPIs.kpis[i].pct) * 1e16).mul(
                    ud(
                        _contentToCampaign[ANTI_DEEPSTACK_CL][ANTI_DEEPSTACK_C]
                            .actionKindToValue[actionKind]
                    ).div(ud(campaignSocialKPIs.kpis[i].ratio))
                )
            );

            unchecked {
                i++;
            }
        }

        emit ContentTotalKPIupdated(
            ANTI_DEEPSTACK_C,
            ANTI_DEEPSTACK_CL,
            totalKPI.unwrap()
        );

        // Calculate the Effective KPI impact based on campaign rules.
        (
            UD60x18 contentEffectiveKPI,
            UD60x18 plusMinusContentEffectiveKPI,
            bool isAdditional
        ) = (
                ud(
                    IEchoCampaign(ANTI_DEEPSTACK_C).contentEffectiveKPI(
                        ANTI_DEEPSTACK_CL
                    )
                ),
                ud(0),
                true
            );

        // Determine whether the KPI change is an addition or subtraction.
        if (contentEffectiveKPI != ZERO_UD60x18 && totalKPI == ZERO_UD60x18)
            delete isAdditional;
        else if (
            contentEffectiveKPI == ZERO_UD60x18 && totalKPI != ZERO_UD60x18
        )
            plusMinusContentEffectiveKPI = (
                totalKPI.mul(
                    _contentToCampaign[ANTI_DEEPSTACK_CL][ANTI_DEEPSTACK_C]
                        .contentOverallQaScore
                )
            );
        else if (
            contentEffectiveKPI != ZERO_UD60x18 && totalKPI != ZERO_UD60x18
        ) {
            plusMinusContentEffectiveKPI = (
                totalKPI.mul(
                    _contentToCampaign[ANTI_DEEPSTACK_CL][ANTI_DEEPSTACK_C]
                        .contentOverallQaScore
                )
            );

            if (plusMinusContentEffectiveKPI > contentEffectiveKPI)
                plusMinusContentEffectiveKPI = plusMinusContentEffectiveKPI.sub(
                    contentEffectiveKPI
                );
            else {
                plusMinusContentEffectiveKPI = contentEffectiveKPI.sub(
                    plusMinusContentEffectiveKPI
                );
                delete isAdditional;
            }
        }
        if (plusMinusContentEffectiveKPI != ZERO_UD60x18) {
            uint256 _campaignTotalEffectiveKPIs;
            uint256 _contentEffectiveKPI;
            uint256 _campaignTotalEligibleContents;
            int8 eligibilityPolarity;

            // Update the total and individual Effective KPI values within the campaign.
            (
                _campaignTotalEffectiveKPIs,
                _contentEffectiveKPI,
                _campaignTotalEligibleContents,
                eligibilityPolarity
            ) = IEchoCampaign(ANTI_DEEPSTACK_C).updateKPIs(
                ANTI_DEEPSTACK_CL,
                plusMinusContentEffectiveKPI.unwrap(),
                isAdditional
            );

            _contentToCampaign[ANTI_DEEPSTACK_CL][ANTI_DEEPSTACK_C]
                .lastUpdatedTime = uint240(block.timestamp);

            if (eligibilityPolarity != 0)
                eligibilityPolarity == 1
                    ? _contentsData.totalEligibleContents++
                    : _contentsData.totalEligibleContents--;

            isAdditional
                ? _contentsData
                    .totalEffectiveKPIs += plusMinusContentEffectiveKPI.unwrap()
                : _contentsData
                    .totalEffectiveKPIs -= plusMinusContentEffectiveKPI
                .unwrap();

            // Emit event for KPI updates at the campaign level.
            emit ContentEffectiveKPIupdated(
                ANTI_DEEPSTACK_C,
                ANTI_DEEPSTACK_CL,
                isAdditional,
                plusMinusContentEffectiveKPI.unwrap(),
                _campaignTotalEffectiveKPIs,
                _contentEffectiveKPI,
                _campaignTotalEligibleContents,
                block.timestamp,
                "EchoMarketData",
                _contentsData.totalEligibleContents,
                _contentsData.totalEffectiveKPIs
            );
        }
    }

    /*****************************\
    |-*-*-*-*   GETTERS   *-*-*-*-|
    \*****************************/
    /// @inheritdoc IEchoContents
    function campaignContentInfo(
        address campaign
    ) external view returns (uint256 lastPostTime, uint256 lastUpdatedTime) {
        (lastPostTime, lastUpdatedTime) = (
            _campaignContentInfo[campaign].lastPostTime,
            _campaignContentInfo[campaign].lastUpdatedTime
        );
    }

    /// @inheritdoc IEchoContents
    function contentToCampaign(
        address campaign,
        string calldata contentLink,
        string calldata actionKind
    )
        external
        view
        returns (
            bool contentRegistered,
            uint256 totalQaKinds,
            uint256 lastUpdatedTime,
            uint256 actionKindValue
        )
    {
        contentRegistered = _contentToCampaign[contentLink][campaign]
            .contentRegistered;
        totalQaKinds = _contentToCampaign[contentLink][campaign].totalQaKinds;
        lastUpdatedTime = _contentToCampaign[contentLink][campaign]
            .lastUpdatedTime;

        if (bytes(actionKind).length != 0)
            actionKindValue = _contentToCampaign[contentLink][campaign]
                .actionKindToValue[actionKind];
    }

    /// @inheritdoc IEchoContents
    function paginatedContentsWithCampaigns(
        uint256 page
    )
        external
        view
        returns (
            uint256 currentPage,
            uint256 totalPages,
            CampaignAndContent[] memory pagedArray
        )
    {
        if (contents.length == 0) return (currentPage, totalPages, pagedArray);
        else if (contents.length < 11) {
            pagedArray = new CampaignAndContent[](contents.length);

            uint256 x;
            while (true) {
                pagedArray[x] = contents[contents.length - 1 - x];

                if (x == contents.length - 1) break;

                unchecked {
                    x++;
                }
            }

            return (1, 1, pagedArray);
        }

        if (page == 0) page = 1;

        totalPages = contents.length / 10;

        uint256 diffLength = contents.length - (totalPages * 10);

        if (totalPages * 10 < contents.length) totalPages++;
        if (page > totalPages) page = totalPages;
        currentPage = page;

        uint256 firstIndex;
        uint256 lastIndex;
        if (page == 1) {
            firstIndex = contents.length - 1;
            lastIndex = firstIndex - 10;
        } else if (page == totalPages)
            firstIndex = diffLength == 0 ? firstIndex = 9 : diffLength - 1;
        else {
            firstIndex +=
                ((totalPages - page) * 10) +
                (diffLength != 0 ? diffLength - 1 : 0);
            lastIndex +=
                ((totalPages - page - 1) * 10) +
                (diffLength != 0 ? diffLength - 1 : 0);
        }

        pagedArray = new CampaignAndContent[]((firstIndex + 1) - lastIndex);

        uint256 i;
        while (true) {
            pagedArray[i] = contents[firstIndex];

            if (firstIndex == lastIndex) break;
            unchecked {
                i++;
                firstIndex--;
            }
        }
    }

    /// @inheritdoc IEchoContents
    function paginatedCampaignContents(
        uint256 page,
        address campaign
    )
        external
        view
        returns (
            uint256 currentPage,
            uint256 totalPages,
            string[] memory pagedArray
        )
    {
        if (_campaignContentInfo[campaign].contents.length == 0)
            return (currentPage, totalPages, pagedArray);
        else if (_campaignContentInfo[campaign].contents.length < 11) {
            pagedArray = new string[](
                _campaignContentInfo[campaign].contents.length
            );

            uint256 x;
            while (true) {
                pagedArray[x] = _campaignContentInfo[campaign].contents[
                    _campaignContentInfo[campaign].contents.length - 1 - x
                ];

                if (x == _campaignContentInfo[campaign].contents.length - 1)
                    break;

                unchecked {
                    x++;
                }
            }

            return (1, 1, pagedArray);
        }

        if (page == 0) page = 1;

        totalPages = _campaignContentInfo[campaign].contents.length / 10;

        uint256 diffLength = _campaignContentInfo[campaign].contents.length -
            (totalPages * 10);

        if (totalPages * 10 < _campaignContentInfo[campaign].contents.length)
            totalPages++;
        if (page > totalPages) page = totalPages;
        currentPage = page;

        uint256 firstIndex;
        uint256 lastIndex;
        if (page == 1) {
            firstIndex = _campaignContentInfo[campaign].contents.length - 1;
            lastIndex = firstIndex - 10;
        } else if (page == totalPages)
            firstIndex = diffLength == 0 ? firstIndex = 9 : diffLength - 1;
        else {
            firstIndex +=
                ((totalPages - page) * 10) +
                (diffLength != 0 ? diffLength - 1 : 0);
            lastIndex +=
                ((totalPages - page - 1) * 10) +
                (diffLength != 0 ? diffLength - 1 : 0);
        }

        pagedArray = new string[]((firstIndex + 1) - lastIndex);

        uint256 i;
        while (true) {
            pagedArray[i] = _campaignContentInfo[campaign].contents[firstIndex];

            if (firstIndex == lastIndex) break;
            unchecked {
                i++;
                firstIndex--;
            }
        }
    }
}

// SPDX-License-Identifier: BSD
pragma solidity 0.8.30;

/// @title ClonesWithImmutableArgs
/// @author wighawag, zefram.eth, nick.eth
/// @notice Enables creating clone contracts with immutable args
library ClonesWithImmutableArgs {
    /// @dev The CREATE3 proxy bytecode.
    uint256 private constant _CREATE3_PROXY_BYTECODE =
        0x67363d3d37363d34f03d5260086018f3;

    /// @dev Hash of the `_CREATE3_PROXY_BYTECODE`.
    /// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`.
    bytes32 private constant _CREATE3_PROXY_BYTECODE_HASH =
        0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;

    error CreateFail();
    error InitializeFail();

    enum CloneType {
        CREATE,
        CREATE2,
        PREDICT_CREATE2
    }

    /// @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 payable instance) {
        return clone(implementation, data, 0);
    }

    /// @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
    /// @param value The amount of wei to transfer to the created clone
    /// @return instance The address of the created clone
    function clone(
        address implementation,
        bytes memory data,
        uint256 value
    ) internal returns (address payable instance) {
        bytes memory creationcode = getCreationBytecode(implementation, data);
        // solhint-disable-next-line no-inline-assembly
        assembly {
            instance := create(
                value,
                add(creationcode, 0x20),
                mload(creationcode)
            )
        }
        if (instance == address(0)) {
            revert CreateFail();
        }
    }

    /// @notice Creates a clone proxy of the implementation contract, with immutable args,
    ///         using CREATE2
    /// @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 clone2(
        address implementation,
        bytes memory data
    ) internal returns (address payable instance) {
        return clone2(implementation, data, 0);
    }

    /// @notice Creates a clone proxy of the implementation contract, with immutable args,
    ///         using CREATE2
    /// @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
    /// @param value The amount of wei to transfer to the created clone
    /// @return instance The address of the created clone
    function clone2(
        address implementation,
        bytes memory data,
        uint256 value
    ) internal returns (address payable instance) {
        bytes memory creationcode = getCreationBytecode(implementation, data);
        // solhint-disable-next-line no-inline-assembly
        assembly {
            instance := create2(
                value,
                add(creationcode, 0x20),
                mload(creationcode),
                0
            )
        }
        if (instance == address(0)) {
            revert CreateFail();
        }
    }

    /// @notice Computes the address of a clone created using CREATE2
    /// @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 clone
    function addressOfClone2(
        address implementation,
        bytes memory data
    ) internal view returns (address payable instance) {
        bytes memory creationcode = getCreationBytecode(implementation, data);
        bytes32 bytecodeHash = keccak256(creationcode);
        instance = payable(
            address(
                uint160(
                    uint256(
                        keccak256(
                            abi.encodePacked(
                                bytes1(0xff),
                                address(this),
                                bytes32(0),
                                bytecodeHash
                            )
                        )
                    )
                )
            )
        );
    }

    /// @notice Computes bytecode for a clone
    /// @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 ret Creation bytecode for the clone contract
    function getCreationBytecode(
        address implementation,
        bytes memory data
    ) internal pure returns (bytes memory ret) {
        // 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 = 0x41 + extraLength;
            uint256 runSize = creationSize - 10;
            uint256 dataPtr;
            uint256 ptr;

            // solhint-disable-next-line no-inline-assembly
            assembly {
                ret := mload(0x40)
                mstore(ret, creationSize)
                mstore(0x40, add(ret, creationSize))
                ptr := add(ret, 0x20)

                // -------------------------------------------------------------------------------------------------------------
                // CREATION (10 bytes)
                // -------------------------------------------------------------------------------------------------------------

                // 61 runtime  | PUSH2 runtime (r)     | r                             | –
                mstore(
                    ptr,
                    0x6100000000000000000000000000000000000000000000000000000000000000
                )
                mstore(add(ptr, 0x01), shl(240, runSize)) // size of the contract running bytecode (16 bits)

                // creation size = 0a
                // 3d          | RETURNDATASIZE        | 0 r                           | –
                // 81          | DUP2                  | r 0 r                         | –
                // 60 creation | PUSH1 creation (c)    | c r 0 r                       | –
                // 3d          | RETURNDATASIZE        | 0 c r 0 r                     | –
                // 39          | CODECOPY              | 0 r                           | [0-runSize): runtime code
                // f3          | RETURN                |                               | [0-runSize): runtime code

                // -------------------------------------------------------------------------------------------------------------
                // RUNTIME (55 bytes + extraLength)
                // -------------------------------------------------------------------------------------------------------------

                // 3d          | RETURNDATASIZE        | 0                             | –
                // 3d          | RETURNDATASIZE        | 0 0                           | –
                // 3d          | RETURNDATASIZE        | 0 0 0                         | –
                // 3d          | RETURNDATASIZE        | 0 0 0 0                       | –
                // 36          | CALLDATASIZE          | cds 0 0 0 0                   | –
                // 3d          | RETURNDATASIZE        | 0 cds 0 0 0 0                 | –
                // 3d          | RETURNDATASIZE        | 0 0 cds 0 0 0 0               | –
                // 37          | CALLDATACOPY          | 0 0 0 0                       | [0, cds) = calldata
                // 61          | PUSH2 extra           | extra 0 0 0 0                 | [0, cds) = calldata
                mstore(
                    add(ptr, 0x03),
                    0x3d81600a3d39f33d3d3d3d363d3d376100000000000000000000000000000000
                )
                mstore(add(ptr, 0x13), shl(240, extraLength))

                // 60 0x37     | PUSH1 0x37            | 0x37 extra 0 0 0 0            | [0, cds) = calldata // 0x37 (55) is runtime size - data
                // 36          | CALLDATASIZE          | cds 0x37 extra 0 0 0 0        | [0, cds) = calldata
                // 39          | CODECOPY              | 0 0 0 0                       | [0, cds) = calldata, [cds, cds+extra) = extraData
                // 36          | CALLDATASIZE          | cds 0 0 0 0                   | [0, cds) = calldata, [cds, cds+extra) = extraData
                // 61 extra    | PUSH2 extra           | extra cds 0 0 0 0             | [0, cds) = calldata, [cds, cds+extra) = extraData
                mstore(
                    add(ptr, 0x15),
                    0x6037363936610000000000000000000000000000000000000000000000000000
                )
                mstore(add(ptr, 0x1b), shl(240, extraLength))

                // 01          | ADD                   | cds+extra 0 0 0 0             | [0, cds) = calldata, [cds, cds+extra) = extraData
                // 3d          | RETURNDATASIZE        | 0 cds+extra 0 0 0 0           | [0, cds) = calldata, [cds, cds+extra) = extraData
                // 73 addr     | PUSH20 0x123…         | addr 0 cds+extra 0 0 0 0      | [0, cds) = calldata, [cds, cds+extra) = extraData
                mstore(
                    add(ptr, 0x1d),
                    0x013d730000000000000000000000000000000000000000000000000000000000
                )
                mstore(add(ptr, 0x20), shl(0x60, implementation))

                // 5a          | GAS                   | gas addr 0 cds+extra 0 0 0 0  | [0, cds) = calldata, [cds, cds+extra) = extraData
                // f4          | DELEGATECALL          | success 0 0                   | [0, cds) = calldata, [cds, cds+extra) = extraData
                // 3d          | RETURNDATASIZE        | rds success 0 0               | [0, cds) = calldata, [cds, cds+extra) = extraData
                // 3d          | RETURNDATASIZE        | rds rds success 0 0           | [0, cds) = calldata, [cds, cds+extra) = extraData
                // 93          | SWAP4                 | 0 rds success 0 rds           | [0, cds) = calldata, [cds, cds+extra) = extraData
                // 80          | DUP1                  | 0 0 rds success 0 rds         | [0, cds) = calldata, [cds, cds+extra) = extraData
                // 3e          | RETURNDATACOPY        | success 0 rds                 | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37)
                // 60 0x35     | PUSH1 0x35            | 0x35 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),
                    0x5af43d3d93803e603557fd5bf300000000000000000000000000000000000000
                )
            }

            // -------------------------------------------------------------------------------------------------------------
            // APPENDED DATA (Accessible from extcodecopy)
            // (but also send as appended data to the delegatecall)
            // -------------------------------------------------------------------------------------------------------------

            extraLength -= 2;
            uint256 counter = extraLength;
            uint256 copyPtr = ptr + 0x41;
            // 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))
            }
        }
    }

    /// @notice Creates a clone proxy of the implementation contract, with immutable args. Uses CREATE3
    /// to implement deterministic deployment.
    /// @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
    /// @param salt The salt used by the CREATE3 deployment
    /// @return deployed The address of the created clone
    function clone3(
        address implementation,
        bytes memory data,
        bytes32 salt
    ) internal returns (address deployed) {
        return clone3(implementation, data, salt, 0);
    }

    /// @notice Creates a clone proxy of the implementation contract, with immutable args. Uses CREATE3
    /// to implement deterministic deployment.
    /// @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
    /// @param salt The salt used by the CREATE3 deployment
    /// @param value The amount of wei to transfer to the created clone
    /// @return deployed The address of the created clone
    function clone3(
        address implementation,
        bytes memory data,
        bytes32 salt,
        uint256 value
    ) internal returns (address deployed) {
        // 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 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, sub(creationSize, 11))) // 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;
            uint256 dataPtr;
            // 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))
            }

            /// @solidity memory-safe-assembly
            // solhint-disable-next-line no-inline-assembly
            assembly {
                // Store the `_PROXY_BYTECODE` into scratch space.
                mstore(0x00, _CREATE3_PROXY_BYTECODE)
                // Deploy a new contract with our pre-made bytecode via CREATE2.
                let proxy := create2(0, 0x10, 0x10, salt)

                // If the result of `create2` is the zero address, revert.
                if iszero(proxy) {
                    // Store the function selector of `CreateFail()`.
                    mstore(0x00, 0xebfef188)
                    // Revert with (offset, size).
                    revert(0x1c, 0x04)
                }

                // Store the proxy's address.
                mstore(0x14, proxy)
                // 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
                // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
                mstore(0x00, 0xd694)
                // Nonce of the proxy contract (1).
                mstore8(0x34, 0x01)

                deployed := and(
                    keccak256(0x1e, 0x17),
                    0xffffffffffffffffffffffffffffffffffffffff
                )

                // If the `call` fails or the code size of `deployed` is zero, revert.
                // The second argument of the or() call is evaluated first, which is important
                // here because extcodesize(deployed) is only non-zero after the call() to the proxy
                // is made and the contract is successfully deployed.
                if or(
                    iszero(extcodesize(deployed)),
                    iszero(
                        call(
                            gas(), // Gas remaining.
                            proxy, // Proxy's address.
                            value, // Ether value.
                            ptr, // Pointer to the creation code
                            creationSize, // Size of the creation code
                            0x00, // Offset of output.
                            0x00 // Length of output.
                        )
                    )
                ) {
                    // Store the function selector of `InitializeFail()`.
                    mstore(0x00, 0x8f86d2f1)
                    // Revert with (offset, size).
                    revert(0x1c, 0x04)
                }
            }
        }
    }

    /// @notice Returns the CREATE3 deterministic address of the contract deployed via cloneDeterministic().
    /// @dev Forked from https://github.com/Vectorized/solady/blob/main/src/utils/CREATE3.sol
    /// @param salt The salt used by the CREATE3 deployment
    function addressOfClone3(
        bytes32 salt
    ) internal view returns (address deployed) {
        /// @solidity memory-safe-assembly
        // solhint-disable-next-line no-inline-assembly
        assembly {
            // Cache the free memory pointer.
            let m := mload(0x40)
            // Store `address(this)`.
            mstore(0x00, address())
            // Store the prefix.
            mstore8(0x0b, 0xff)
            // Store the salt.
            mstore(0x20, salt)
            // Store the bytecode hash.
            mstore(0x40, _CREATE3_PROXY_BYTECODE_HASH)

            // Store the proxy's address.
            mstore(0x14, keccak256(0x0b, 0x55))
            // Restore the free memory pointer.
            mstore(0x40, m)
            // 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
            // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
            mstore(0x00, 0xd694)
            // Nonce of the proxy contract (1).
            mstore8(0x34, 0x01)

            deployed := and(
                keccak256(0x1e, 0x17),
                0xffffffffffffffffffffffffffffffffffffffff
            )
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

/***************************\
|*-*-*-*    TYPES    *-*-*-*|
\***************************/

enum QA_METHOD {
    AI,
    COMMUNITY,
    VOTE
}

enum Status {
    upcoming,
    inProgress,
    paused,
    finished,
    claimable,
    finalized
}

enum Finalizer {
    protocolAdmin,
    finalizerOracle
}

struct CampaignInitData {
    uint64 initTime;
    address clonedCampaign;
    address implementation;
}

struct BasicInfo {
    string name;
    string ipfsCID;
    address owner;
    uint256 startTime;
    uint256 endTime;
}

struct PostInfo {
    uint256 totalPosts;
    uint256 totalEligibleContents;
    uint256 totalEffectiveKPIs;
    uint256 lastPostTime;
    uint256 lastUpdatedTime;
    uint256 applicationFee;
}

struct Token {
    string name;
    string symbol;
    uint256 decimals;
}

struct BudgetInfo {
    address token;
    uint128 maxPerPost;
    uint128 reservedAmount;
}

struct PostData {
    string postLink;
    address applicant;
}

struct QualificationData {
    QA_METHOD method;
    uint88 pct;
    address oracle;
    string kind;
}

struct KPI {
    uint8 pct;
    uint248 min;
    uint256 ratio;
    string method;
    string extra;
}

struct SocialKPIs {
    string social;
    KPI[] kpis;
}

/**
 * @title IEchoCampaign
 * @notice Defines the interface for Echo Campaign contracts, which manage campaign
 *         settings, budgets, KPIs, content submissions, and reward distribution.
 * @dev This interface provides function definitions for interacting with campaign
 *      data, including retrieving budget details, tracking content KPIs, and
 *      managing administrative actions.
 * @author Dynabits.org
 */
interface IEchoCampaign {
    /*******************************\
    |*-*-*-*   EXTERNALS   *-*-*-*-*|
    \*******************************/

    /**
     * @notice Initializes the contract with provided parameters.
     * @dev This function can only be called by the factory contract.
     * It sets the IPFS CID, budget information, social KPIs, and qualification data.
     * Additionally, it registers action kinds based on QA methods and social KPIs.
     * @param ipfsCID The IPFS Content Identifier (CID) associated with the data.
     * @param budgetInfo_ Struct containing budget-related information.
     * @param socialKPIs_ Struct containing social Key Performance Indicators (KPIs).
     * @param qaData_ An array of QualificationData structs defining QA methods and kinds.
     * Reverts:
     * - `ONLY_FACTORY` if the caller is not the factory contract.
     * - `DUPLICATE_QA` if there is a duplicate qualification.
     * - `DUPLICATE_KPI` if there is a duplicate KPI.
     */
    function init(
        string calldata ipfsCID,
        BudgetInfo calldata budgetInfo_,
        SocialKPIs calldata socialKPIs_,
        QualificationData[] calldata qaData_
    ) external;

    /**
     * @notice Toggles the campaign's pause state.
     * @dev Only callable by the protocol administrator.
     *
     * Emits:
     * - `CampaignPauseToggled` with the new state.
     *
     * Reverts:
     * - `UNAUTHORIZED_CALLER` if the caller is not the protocol administrator.
     */
    function togglePauseCampaign() external;

    /**
     * @notice Finalizes a campaign that is in progress.
     * @dev Only callable by the protocol administrator when the campaign status is `inProgress`.
     *      Refunds any unspent budget and marks the campaign as finalized.
     *
     * Emits:
     * - `CampaignFinalized` with the protocol administrator as the finalizer and the refunded amount.
     *
     * Reverts:
     * - `UNAUTHORIZED_CALLER` if the caller is not the protocol administrator.
     * - `ONLY_IN_PROGRESS_STATUS` if the campaign is not currently in progress.
     */
    function finalizeInProgressCampaign() external;

    /**
     * @notice Increases the campaign budget by a specified amount.
     * @dev Transfers the specified amount of tokens from the caller to the campaign
     *      and updates the reserved budget.
     * @param amount The amount of tokens to add to the campaign budget.
     *
     * Emits:
     * - `BudgetIncreased` with the added amount, previous budget, new budget, and sender address.
     *
     * Reverts:
     * - `ONLY_UPCOMING_OR_IN_PROGRESS_STATUS` if the campaign is not in `upcoming` or `inProgress` status.
     */
    function increaseBudget(uint256 amount) external;

    /**
     * @notice Submits content for the campaign.
     * @dev Adds a content link to the campaign without performing authentication checks at submission time.
     *      Authentication is enforced at the claiming stage.
     * @param contentLink The link to the submitted content.
     *
     * Reverts:
     * - `ONLY_IN_PROGRESS_STATUS` if the campaign is not in `inProgress` status.
     * - `INVALID_CONTENT_LINK` if the provided content link is empty.
     */
    function applyContent(string calldata contentLink) external;

    /**
     * @notice Finalizes a campaign that has reached the finished status.
     * @dev Ensures the campaign is in the `finished` status before finalization.
     *      Refunds any unspent budget and marks the campaign as finalized.
     *
     * Emits:
     * - `CampaignFinalized` with the finalizer and the refunded amount.
     *
     * Reverts:
     * - `ONLY_FINISHED_CAMPAIGN_STATUS` if the campaign is not in `finished` status.
     */
    function finalizeFinishedCampaign() external;

    /**
     * @notice Updates the KPI metrics for a specific content link.
     * @dev Modifies the total and per-content effective KPIs based on the provided values.
     *      If the content has no prior KPI value, it is counted as an eligible content.
     *
     * @param contentLink The unique identifier (link) of the content being updated.
     * @param additionalContentEffectiveKPI The KPI value to be added or subtracted.
     * @param additional A boolean indicating whether to increase or decrease the KPI.
     *
     * @return totalEffectiveKPIs The updated total sum of all effective KPIs.
     * @return contentEffectiveKPI_ The updated KPI for the specified content.
     * @return totalEligibleContents The updated count of eligible contents.
     * @return eligibilityPolarity must be either 1, 0, or -1 and determines how the total eligible content count is updated.
     *
     * Requirements:
     * - Caller must be the EchoContetns contract.
     */
    function updateKPIs(
        string calldata contentLink,
        uint256 additionalContentEffectiveKPI,
        bool additional
    )
        external
        returns (
            uint256 totalEffectiveKPIs,
            uint256 contentEffectiveKPI_,
            uint256 totalEligibleContents,
            int8 eligibilityPolarity
        );

    /**
     * @notice Allows the reward claim for registered and eligible content whose author has been registered by the oracle.
     * @dev This function verifies that the campaign status is `claimable`, ensures the content is registered,
     *      checks its effective KPI score, calculates the reward based on the KPI ratio, and distributes
     *      the appropriate amount. If a max reward per post is defined, any excess funds are refunded.
     *      Once all eligible claims have been processed, the campaign is finished.
     * @param contentLink The unique identifier (URL) of the content for which the claim is being made.
     *
     * Requirements:
     * - The campaign status must be `claimable`.
     * - The content must be registered under the campaign.
     * - The content must have an effective KPI score greater than zero.
     *
     * Emits:
     * - `Claimed` event upon successful claim, indicating the content, recipient, reward amount, and refund amount.
     * - `CampaignFinished` event is emitted when all eligible contents have been claimed and the campaign is finished.
     *
     * Reverts:
     * - `ONLY_CLAIMABLE_STATUS()` if the campaign is not in the `claimable` status.
     * - `INVALID_CONTENT_LINK()` if the content is not registered in the campaign.
     * - `NO_CONTENT_AUTHOR()` if the content's author has not been registered by the oracle before claiming.
     * - `NOT_ELIGIBLE_FOR_WITHDRAW()` if the content has no effective KPI score.
     */
    function claim(string calldata contentLink) external;

    /*****************************\
    |-*-*-*-*   GETTERS   *-*-*-*-|
    \*****************************/

    /// @notice Retrieves the budget information for the campaign.
    /// @return budgetInfo_ The budget details, including reserved amount and token information.
    function budgetInfo() external view returns (BudgetInfo memory budgetInfo_);

    /// @notice Retrieves the social KPIs associated with the campaign.
    /// @return socialKPIs_ The set of social KPIs used for evaluating campaign performance.
    function socialKPIs() external view returns (SocialKPIs memory socialKPIs_);

    /// @notice Retrieves the qualification data for the campaign.
    /// @return qaData_ An array of qualification data required for content evaluation.
    function qaData()
        external
        view
        returns (QualificationData[] memory qaData_);

    /// @notice Checks whether a specific action kind is registered in the campaign.
    /// @param actionKind The string representation of the action kind to check.
    /// @return isRegistered A boolean indicating whether the action kind is registered.
    function registeredActionKind(string calldata actionKind)
        external
        view
        returns (bool);

    /// @notice Retrieves the effective KPI score for a given content link.
    /// @param contentLink The unique identifier (URL or hash) of the submitted content.
    /// @return kpiScore The effective KPI score assigned to the content.
    function contentEffectiveKPI(string calldata contentLink)
        external
        view
        returns (uint256);

    /**
     * @notice Estimates the reward amount for content based on provided QA and KPI data.
     * @dev Ensures QA and KPI inputs match the campaign's predefined requirements.
     * @param qas An array of qualification data for the content.
     * @param kpis An array of KPI data for the content.
     * @return rewardAmount The estimated reward amount in campaign token units.
     */
    function rewardEstimation(
        QualificationData[] calldata qas,
        KPI[] calldata kpis
    ) external view returns (uint256 rewardAmount);

    /**
     * @notice Retrieves comprehensive details about the campaign.
     * @return basicInfo_ Basic campaign details such as name, owner, and time period.
     * @return initData_ Initialization data from the campaign factory.
     * @return budgetInfo_ Budget details including token and reserved amounts.
     * @return token_ Token metadata such as name, symbol, and decimals.
     * @return socialKPIs_ The social KPI details of the campaign.
     * @return qaData_ The qualification data required for content.
     * @return postInfo_ Statistics related to campaign content submissions.
     * @return status_ The current status of the campaign.
     */
    function details()
        external
        view
        returns (
            BasicInfo memory basicInfo_,
            CampaignInitData memory initData_,
            BudgetInfo memory budgetInfo_,
            Token memory token_,
            SocialKPIs memory socialKPIs_,
            QualificationData[] memory qaData_,
            PostInfo memory postInfo_,
            Status status_
        );

    /**
     * @notice Returns the address of the person who created the specified content and is eligible to claim it.
     * @param contentLink The URL of the submitted content.
     * @return The wallet address of the content creator for the given content.
               May return the zero address (0x0) if no record is found in the oracle.
     */
    function getContentCreator(string calldata contentLink)
        external
        view
        returns (address);

    /// @notice Retrieves the current status of the campaign.
    /// @return status_ The campaign's status as an enum value.
    function currentStatus() external view returns (Status status_);

    /// @notice Calculates the application fee required for content submission.
    /// @return fee The total application fee in campaign token units.
    function applicationFee() external view returns (uint256 fee);

    /// @notice Retrieves the campaign's name.
    /// @return campaignName The human-readable name of the campaign.
    function name() external view returns (string memory campaignName);

    /// @notice Retrieves the address of the Echo Contents contract.
    /// @return contentsAddress The address of the content management contract.
    function contents() external view returns (address contentsAddress);

    /// @notice Retrieves the address of the Echo Administration contract.
    /// @return adminAddress The address of the admin contract.
    function admin() external pure returns (address adminAddress);

    /// @notice Retrieves the address of the campaign factory.
    /// @return factoryAddress The address of the campaign factory contract.
    function factory() external pure returns (address factoryAddress);

    /// @notice Retrieves the owner of the campaign.
    /// @return ownerAddress The address of the campaign owner.
    function owner() external pure returns (address ownerAddress);

    /// @notice Retrieves the refund address for the campaign.
    /// @return refundAddr The address where refunds are sent.
    function refundAddress() external pure returns (address refundAddr);

    /// @notice Retrieves the hashed name of the campaign.
    /// @return campaignNameHash The bytes32 representation of the campaign name.
    function nameHash() external pure returns (bytes32 campaignNameHash);

    /// @notice Retrieves the start time of the campaign.
    /// @return startTimestamp The Unix timestamp of the campaign start time.
    function startTime() external pure returns (uint256 startTimestamp);

    /// @notice Retrieves the end time of the campaign.
    /// @return endTimestamp The Unix timestamp of the campaign end time.
    function endTime() external pure returns (uint256 endTimestamp);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import {CampaignInitData, BudgetInfo, SocialKPIs, QualificationData} from "./IEchoCampaign.sol";

/***************************\
|*-*-*-*    TYPES    *-*-*-*|
\***************************/

/// @notice Structure containing the initialization parameters for a new campaign.
/// @dev This struct encapsulates all key configurations required when creating a campaign.
struct InitCampaign {
    uint256 startTime; /// Start time of the campaign (Unix timestamp).
    uint256 endTime; /// End time of the campaign (Unix timestamp).
    address refundAddress; /// Address to receive refunds (required if maxPerPost is set).
    string ipfsCID; /// IPFS content identifier (CID) for storing metadata.
    BudgetInfo budgetInfo; /// Budget-related configurations for the campaign.
    SocialKPIs socialKPIs; /// Social Key Performance Indicators (KPIs) for the campaign.
    QualificationData[] qaData; /// Array of qualification methods used for campaign validation.
}

/// @title Echo Campaign Factory Interface
/// @notice This interface defines the core functionality for deploying campaign contracts.
/// @author Dynabits.org
interface IEchoCampaignFactory {
    /*******************************\
    |*-*-*-*   EXTERNALS   *-*-*-*-*|
    \*******************************/

    /// @notice Updates the campaign implementation contract.
    /// @dev This function allows the protocol admin to change the underlying campaign logic by deploying a new contract.
    /// @param newImplementation The address of the new campaign implementation.
    function changeImplementation(address newImplementation) external;

    /**
     * @notice Deploys a new campaign contract with the specified parameters.
     * @dev Performs necessary validation checks before deployment, including:
     *      - Ensuring valid input parameters (e.g., campaign name, budget, duration).
     *      - Enforcing compliance with qualification and KPI requirements.
     *      - Deducting protocol fees and transferring budget to the campaign contract.
     * @param owner The address of the campaign owner.
     * @param name The unique name of the campaign (must be between 8 and 64 characters).
     * @param initCampaign A struct containing all initial parameters for campaign deployment.
     * @return clonedCampaign The address of the newly deployed campaign contract.
     *
     * Requirements:
     * - `owner` must be a valid, non-zero address.
     * - `name` must be unique and between 8 to 64 characters in length.
     * - `initCampaign.refundAddress` must be valid if `maxPerPost` is set.
     * - `initCampaign.ipfsCID` must be a non-empty string.
     * - The campaign start time must not be in the past.
     * - The campaign end time must be later than the start time.
     * - The campaign duration must not exceed the protocol's maximum allowed time.
     * - The campaign's budget token must be whitelisted.
     * - `maxPerPost` must not exceed `reservedAmount`, if set.
     * - At least one qualification method (QA method) must be provided.
     * - The total qualification percentages (`pct`) must sum up to 100%.
     * - At least one KPI must be registered.
     * - The sum of KPI percentages must be exactly 100%.
     * - The provided social network and KPIs must be allowed by the protocol.
     *
     * Effects:
     * - Deploys a new campaign contract as a clone of the base implementation.
     * - Deducts the protocol fee from the reserved campaign budget.
     * - Initializes the campaign contract with the provided parameters.
     * - Transfers the remaining budget to the newly created campaign.
     * - Stores campaign metadata for tracking and uniqueness validation.
     *
     * Emits:
     * - `CampaignCreated` event upon successful deployment of the campaign.
     *
     * Reverts:
     * - `ZERO_ADDRESS` if `owner` or `refundAddress` (when required) is zero.
     * - `NAME_LENGTH_LOWER_THAN_8_BYTES` if the name is too short.
     * - `NAME_LENGTH_LONGER_THAN_64_BYTES` if the name is too long.
     * - `NULL_CID` if the campaign IPFS CID is empty.
     * - `NAME_EXISTS` if a campaign with the same name already exists.
     * - `START_TIME_IN_PAST` if the campaign start time is invalid.
     * - `END_TIME_LOWER_THAN_START_TIME` if the end time is before or equal to the start time.
     * - `CHECK_MAX_CAMPAIGN_DURATION` if the duration exceeds the protocol’s limit.
     * - `MAX_PER_POST_BIGGER_THAN_RESERVED_AMOUNT` if `maxPerPost` exceeds the available budget.
     * - `NO_QUALIFICATION` if no qualification method is provided.
     * - `QA_METHOD_OR_ORACLE_NOT_ALLOWED` if the qualification method or oracle is unauthorized.
     * - `SUM_OF_QUALIFICATION_PARAMS_MUST_BE_100` if qualification percentages do not total 100%.
     * - `NO_KPIS` if no KPIs are provided.
     * - `SUM_OF_SOCIAL_KPI_PARAMS_MUST_BE_100` if KPI percentages do not sum to 100%.
     * - `ONLY_ALLOWED_SOCIAL_KPIS` if an unapproved social KPI is used.
     * - `SOCIAL_KPI_RATIO_IS_ZERO` if a KPI lacks a predefined ratio in the protocol.
     */
    function createCampaign(
        address owner,
        string calldata name,
        InitCampaign memory initCampaign
    ) external returns (address clonedCampaign);

    /*****************************\
    |-*-*-*-*   GETTERS   *-*-*-*-|
    \*****************************/

    /// @notice Retrieves the current implementation contract for campaigns.
    /// @dev This address is used for cloning new campaign instances.
    /// @return The address of the campaign implementation contract.
    function implementation() external view returns (address);

    /// @notice Retrieves the address of a deployed campaign by its index.
    /// @param index The index of the campaign in the `allCampaigns` array.
    /// @return The address of the deployed campaign.
    function allCampaigns(uint256 index) external view returns (address);

    /**
     * @notice Retrieves the original campaign name from its hashed value.
     * @dev Useful for verifying unique campaign names.
     * @param nameHash The hashed value of the campaign name.
     * @return The original campaign name.
     */
    function campaignNameHashToName(bytes32 nameHash)
        external
        view
        returns (string memory);

    /// @notice Retrieves the address of the Echo Administration contract.
    /// @return The address of the Echo Administration contract.
    function ECHO_ADMIN() external view returns (address);

    /// @notice Retrieves the address of the Echo Contents contract.
    /// @return The address of the Echo Contents contract.
    function ECHO_CONTENTS() external view returns (address);

    /**
     * @notice Retrieves initialization details for a specific campaign.
     * @dev Provides useful data for off-chain applications and analytics.
     * @param name The name of the deployed campaign.
     * @return The timestamp of deployment, the deployed contract address, and the structural contract used.
     */
    function campaignNameToInitData(string memory name)
        external
        view
        returns (CampaignInitData memory);

    /**
     * @notice Retrieves a paginated list of recently deployed campaigns.
     * @dev Returns 10 campaign addresses per page, with pagination for historical retrieval.
     *      - If `page = 0`, returns the 10 most recent campaigns.
     *      - Optimized for off-chain use cases as an alternative to event logs.
     * @param page The page number requested.
     * @return currentPage The current page number.
     * @return totalPages The total number of available pages.
     * @return pagedArray A list of up to 10 recently deployed campaign addresses.
     */
    function paginatedCampaigns(uint256 page)
        external
        view
        returns (
            uint256 currentPage,
            uint256 totalPages,
            address[] memory pagedArray
        );
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

/***************************\
|*-*-*-*    TYPES    *-*-*-*|
\***************************/

enum ActionStatus {
    NA,
    Config,
    QA,
    KPI
}

struct ActionAndKind {
    ActionStatus actionStatus;
    string action;
    string kind;
}

struct ModelData {
    ActionStatus actionStatus;
    uint8 socialKPIratio;
    uint248 fee;
    address oracle;
}

/// @title Echo Administration Interface
/// @notice Defines administrative functions for managing campaign settings, token whitelisting, and oracle permissions.
/// @author Dynabits.org
interface IEchoAdministration {
    /*******************************\
    |*-*-*-*   EXTERNALS   *-*-*-*-*|
    \*******************************/

    /// @notice Transfers protocol admin rights to a new address.
    /// @dev Only the current protocol admin can call this function. The new admin address cannot be the zero address.
    /// @param newProtocolAdmin The address of the new protocol admin.
    function changeProtocolAdmin(address newProtocolAdmin) external;

    /// @notice Sets the campaign fee rate as a percentage of the allocated token at creation.
    /// @dev The value must be between 0 and 1e5.
    /// @param newCampaignFeeRate The new campaign creation fee rate.
    function changeCampaignFeeRate(uint24 newCampaignFeeRate) external;

    /// @notice Sets the maximum campaign duration.
    /// @dev A value of zero means there is no maximum campaign duration.
    /// @param newMaxCampaignTime The maximum campaign duration in seconds.
    function setMaxCampaignTime(uint72 newMaxCampaignTime) external;

    /// @notice Whitelists a new ERC20 token for use in campaigns.
    /// @dev A token cannot be whitelisted more than once. The whitelist status is checked during campaign creation.
    /// @param token The ERC20 token address.
    function whitelistToken(address token) external;

    /// @notice Removes an ERC20 token from the whitelist, preventing its selection as a campaign budget token.
    /// @param token The ERC20 token address.
    function removeWhitelistedToken(address token) external;

    /// @notice Toggles full access permissions for an oracle.
    /// @dev This function can be used to temporarily or permanently disable an oracle’s access to campaign-related functionalities.
    /// @param oracle The address of the oracle.
    function toggleOracleFullAccess(address oracle) external;

    /**
     * @notice Sets or updates the model data for a given action and kind.
     * @dev Configures an action-kind mapping with an associated oracle, fee, and status.
     * @param actionAndKind The struct containing the action and kind identifiers.
     * @param model The struct containing model details such as the oracle, fee, status, and social KPI ratio.
     *
     * Requirements:
     * - `actionAndKind.action` and `actionAndKind.kind` must not be empty.
     * - `model.actionStatus` must be one of `QA`, `KPI`, or `CONFIG`.
     * - Actions must match their respective `actionStatus`:
     *   - `QA` actions: `QA-AI`, `QA-C`, `QA-V`
     *   - `KPI` actions: `KPI`
     *   - `CONFIG` actions: `CONFIG`
     * - QA oracles must have a nonzero fee.
     * - KPI oracles must have both a nonzero fee and a nonzero social KPI ratio.
     * - CONFIG actions always have a zero fee.
     *
     * Effects:
     * - Adds or updates an action-kind mapping.
     * - Removes model data if `model.oracle` is zero.
     *
     * Emits:
     * - `ModelSettled` if a model is set or updated.
     * - `ModelRemoved` if a model is removed.
     *
     * Reverts:
     * - `EMPTY_ACTION_OR_KIND_PROVIDED` if either action or kind is empty.
     * - `NA_ACTION_STATUS` if the provided action status is `NA`.
     * - `NOT_RELATIVE_ACTION` if the action does not match its required type.
     * - `UNDEFINED_PROCESS` if a QA oracle has a fee but no valid address.
     * - `ZERO_FEE_PROVIDED` if a required fee is zero.
     * - `ZERO_SOCIAL_KPI_PROVIDED` if a required social KPI ratio is zero.
     */
    function setModel(
        ActionAndKind calldata actionAndKind,
        ModelData memory model
    ) external;

    /*****************************\
    |-*-*-*-*   GETTERS   *-*-*-*-|
    \*****************************/

    /// @notice Returns the campaign creation fee rate as a percentage.
    /// @dev This value is deducted from the allocated budget immediately upon campaign creation.
    /// @return The campaign creation fee rate.
    function campaignFeeRate() external view returns (uint24);

    /// @notice Returns the maximum allowed duration for campaigns.
    /// @dev A campaign’s duration (end time - start time) must be less than or equal to this value at creation.
    /// @return The maximum campaign duration in seconds.
    function maxCampaignTime() external view returns (uint72);

    /// @notice Returns the protocol admin address.
    /// @dev The protocol admin has full access to infrastructural modifications.
    /// @return The address of the protocol admin.
    function protocolAdmin() external view returns (address);

    /// @notice Checks if a given ERC20 token is whitelisted for campaign use.
    /// @param token The ERC20 token address.
    /// @return A boolean indicating whether the token is whitelisted.
    function whitelistedToken(address token) external view returns (bool);

    /**
     * @notice Checks if an oracle has been banned.
     * @dev A banned oracle loses all permissions to interact with EchoContents and campaign contracts.
     * @param oracle The address of the oracle.
     * @return A boolean indicating whether the oracle is banned.
     */
    function disallowedOracle(address oracle) external view returns (bool);

    /**
     * @notice Returns the number of available methods and parameters for a given action.
     * @dev Examples:
     * - KPI actions (e.g., `X`) can have parameters like `Like` and `Retweet`.
     * - Qualification actions (`QA-V`) can have parameters like `Accuracy` and `Originality`.
     * @param action The action type, such as `X`, `QA-V`, or `Config`.
     * @return The number of available methods for the action.
     */
    function totalKindMethods(string calldata action)
        external
        view
        returns (uint256);

    /**
     * @notice Retrieves the original action and kind from a given hashed identifier.
     * @dev Useful for reducing gas costs and simplifying comparisons.
     * @param hashedActionKind The hash of the concatenated action and kind.
     * @return The decoded action and kind as a string.
     */
    function hashToActionKind(bytes32 hashedActionKind)
        external
        view
        returns (string memory);

    /**
     * @notice Returns model data associated with an oracle and a hashed action-kind.
     * @param oracle_ The address of the oracle.
     * @param hashedActionKind The hashed action-kind identifier.
     * @return actionStatus The action status of the model.
     * @return socialKPIratio The social KPI ratio.
     * @return fee The associated fee for the action.
     * @return oracle The address of the oracle managing the action.
     */
    function modelData(address oracle_, bytes32 hashedActionKind)
        external
        view
        returns (
            ActionStatus actionStatus,
            uint8 socialKPIratio,
            uint248 fee,
            address oracle
        );

    /**
     * @notice Checks if an oracle has the required permissions for given action-kind hashes.
     * @dev Access is determined based on the oracle's address:
     * - `address(0)`: Checks if the hashed action-kind exists for KPI.
     * - `address(1)`: Checks if the hashed action-kind exists for CONFIG.
     * - Any other address: Checks if the oracle has QA access.
     * @param oracle The oracle’s address.
     * @param hashedActionsKind An array of hashed action-kind values to verify.
     * @return A boolean indicating whether the oracle is authorized.
     */
    function allowedActionKind(
        address oracle,
        bytes32[] calldata hashedActionsKind
    ) external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

File 10 of 33 : UD60x18.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

/*

██████╗ ██████╗ ██████╗ ███╗   ███╗ █████╗ ████████╗██╗  ██╗
██╔══██╗██╔══██╗██╔══██╗████╗ ████║██╔══██╗╚══██╔══╝██║  ██║
██████╔╝██████╔╝██████╔╝██╔████╔██║███████║   ██║   ███████║
██╔═══╝ ██╔══██╗██╔══██╗██║╚██╔╝██║██╔══██║   ██║   ██╔══██║
██║     ██║  ██║██████╔╝██║ ╚═╝ ██║██║  ██║   ██║   ██║  ██║
╚═╝     ╚═╝  ╚═╝╚═════╝ ╚═╝     ╚═╝╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝

██╗   ██╗██████╗  ██████╗  ██████╗ ██╗  ██╗ ██╗ █████╗
██║   ██║██╔══██╗██╔════╝ ██╔═████╗╚██╗██╔╝███║██╔══██╗
██║   ██║██║  ██║███████╗ ██║██╔██║ ╚███╔╝ ╚██║╚█████╔╝
██║   ██║██║  ██║██╔═══██╗████╔╝██║ ██╔██╗  ██║██╔══██╗
╚██████╔╝██████╔╝╚██████╔╝╚██████╔╝██╔╝ ██╗ ██║╚█████╔╝
 ╚═════╝ ╚═════╝  ╚═════╝  ╚═════╝ ╚═╝  ╚═╝ ╚═╝ ╚════╝

*/

import "./ud60x18/Casting.sol";
import "./ud60x18/Constants.sol";
import "./ud60x18/Conversions.sol";
import "./ud60x18/Errors.sol";
import "./ud60x18/Helpers.sol";
import "./ud60x18/Math.sol";
import "./ud60x18/ValueType.sol";

// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import {IEchoAdministration} from "./IEchoAdministration.sol";
import {IEchoCampaignFactory} from "./IEchoCampaignFactory.sol";
import {QA_METHOD} from "./IEchoCampaign.sol";
import {UD60x18} from "@prb/math/src/UD60x18.sol";

/***************************\
|*-*-*-*    TYPES    *-*-*-*|
\***************************/

struct ConfigOracleData {
    string kind;
    bytes value;
}

struct QualificationOracleData {
    QA_METHOD method;
    uint256 pct;
    string kind;
}

struct KPIsOracleData {
    string method;
    uint256 value;
}

struct SocialKPIsOracleData {
    string social;
    KPIsOracleData[] kpis;
}

struct CampaignAndContent {
    address campaign;
    string content;
}

struct CampaignContentInfo {
    uint128 lastUpdatedTime;
    uint128 lastPostTime;
    string[] contents;
}

struct ContentDetails {
    bool contentRegistered;
    uint8 totalQaKinds;
    uint240 lastUpdatedTime;
    UD60x18 contentOverallQaScore;
    mapping(string => uint256) actionKindToValue;
}

struct ContentsData {
    uint256 totalEffectiveKPIs;
    uint256 totalPosts;
    uint256 totalEligibleContents;
}

/**
 * @title Echo Contents Interface
 * @notice Manages content within Echo campaigns by enabling registration, updates, and evaluation.
 * @dev This interface facilitates content tracking, quality assessment through oracles, and KPI updates.
 *      - Allows campaigns to associate and manage content.
 *      - Supports qualification and configuration updates via oracles and protocol admin.
 *      - Enables KPI tracking based on predefined metrics.
 *      - Provides getter functions for efficient content retrieval and analytics.
 * @author Dynabits.org
 */
interface IEchoContents {
    /*******************************\
    |*-*-*-*   EXTERNALS   *-*-*-*-*|
    \*******************************/

    /**
     * @notice Registers a new content link for a campaign.
     * @dev Associates content with a campaign, ensuring only authorized campaigns can register.
     * @param contentLink The unique identifier (e.g., IPFS hash) of the content.
     * @param campaignMaxPerPost The campaign’s maximum payment per post.
     *
     * Requirements:
     * - The caller must be a registered campaign.
     * - The content must not have been previously registered for the same campaign.
     *
     * Effects:
     * - Stores the content reference associated with the campaign.
     * - Updates the campaign’s content records, including the last post timestamp.
     *
     * Emits:
     * - `ContentApplied` upon successful registration.
     *
     * Reverts:
     * - `ACCESS_DENIED` if the caller is not a registered campaign.
     * - `CONTENT_REGISTERED_FOR_CAMPAIGN_BEFORE` if already registered.
     */
    function addContentLink(string calldata contentLink, uint256 campaignMaxPerPost) external;

    /**
     * @notice Updates configuration and qualification data for a specific content in a campaign.
     * @dev Allows oracles or the protocol admin to set content-related data.
     * @param campaign The campaign's address.
     * @param contentLink The unique identifier of the content.
     * @param configs An array of configuration data from the oracle.
     * @param qualifications Qualification data used to evaluate the content.
     *
     * Requirements:
     * - At least one configuration or qualification must be provided.
     * - The caller must be the protocol admin or an authorized oracle.
     * - If the caller is an oracle, they must not be banned.
     * - The content must be registered under the campaign.
     * - Configuration updates require appropriate oracle permissions.
     * - Qualification updates must adhere to campaign-specific rules and valid percentage ranges.
     *
     * Emits:
     * - `ContentConfigsAdded` on successful configuration update.
     * - `NewQualificationsSettled` on successful qualification update.
     * - `ContentQAoverallScore` if the final QA score is calculated.
     * - `ReadyForKPIupdates` if the content qualifies for KPI updates.
     *
     * Reverts:
     * - `UNDEFINED_JOB` if no configurations or qualifications are provided.
     * - `ORACLE_IS_BAN` if the caller is a banned oracle.
     * - `CONTENT_DOESNT_EXIST` if the content is unregistered.
     * - `ORACLE_DOESNT_HAVE_REQUIRED_ACCESS` if the caller lacks permissions.
     * - `GIVEN_QA_KIND_DOESNT_EXIST_ON_CAMPAIGN` if an invalid qualification is provided.
     * - `QA_DATA_SETTLED_BEFORE` if qualifications were previously set and the caller is not the admin.
     * - `CHECK_QA_PCT` if the qualification percentage is out of range.
     */
    function setContentData(
        address campaign,
        string calldata contentLink,
        ConfigOracleData[] calldata configs,
        QualificationOracleData[] calldata qualifications
    ) external;

    /**
     * @notice Updates the KPI data for registered content within a campaign.
     * @dev Only the protocol admin or an authorized KPI oracle can update KPIs.
     *      If all required KPIs are met, the Effective KPI formula is applied.
     * @param campaign The address of the campaign.
     * @param contentLink The unique identifier of the content (e.g., IPFS hash).
     * @param socialKPIs The KPI data associated with the campaign’s social platform.
     *
     * Requirements:
     * - The caller must be the protocol admin or an authorized KPI oracle.
     * - A banned oracle cannot update KPIs unless they are the admin.
     * - The content must be registered under the specified campaign.
     * - The campaign must be in an `InProgress`, `Paused`, or `Finished` state.
     * - The content must have a valid non-zero QA score.
     * - The KPI action must be registered under the campaign.
     * - The oracle must have the required access to modify the KPI data.
     *
     * Effects:
     * - Updates KPI values for the specified content and campaign.
     * - Computes the Effective KPI value based on campaign rules and content QA score.
     * - Updates the campaign’s total Effective KPIs and eligible content count.
     *
     * Emits:
     * - `KPIsUpdated` when a KPI update is successfully processed.
     * - `ContentEffectiveKPIupdated` when the Effective KPI score is updated.
     *
     * Reverts:
     * - `ORACLE_IS_BAN` if the caller is a banned oracle.
     * - `CONTENT_DOESNT_EXIST` if the content is not registered.
     * - `ONLY_IN_PROGRESS_OR_PAUSED_OR_FINISHED_CAMPAIGN` if the campaign is in an invalid state.
     * - `ZERO_CONTENT_QA_SCORE` if the content has a QA score of zero.
     * - `DIFFERENT_SOCIALS` if the provided KPI does not match the campaign’s social KPI.
     * - `GIVEN_KPI_KIND_DOESNT_EXIST_ON_CAMPAIGN` if the KPI type is not registered in the campaign.
     * - `ORACLE_DOESNT_HAVE_REQUIRED_ACCESS` if the caller lacks KPI update permissions.
     */
    function updateKPIs(
        address campaign,
        string calldata contentLink,
        SocialKPIsOracleData calldata socialKPIs
    ) external;

    /*****************************\
    |-*-*-*-*   GETTERS   *-*-*-*-|
    \*****************************/

    /**
     * @notice Retrieve the registered content along with its associated campaign address.
     * @param index The index in the contents array.
     * @return campaign The address of the campaign associated with the content.
     * @return content The registered content.
     */
    function contents(uint256 index)
        external
        view
        returns (address campaign, string memory content);

    /// @notice Retrieves the address of the Echo Administration contract.
    /// @return The address of the Echo Administration contract.
    function ECHO_ADMIN() external view returns (IEchoAdministration);

    /// @notice Retrieves the address of the Echo Campaign Factory contract.
    /// @return The address of the Echo Campaign Factory contract.
    function ECHO_FACTORY() external view returns (IEchoCampaignFactory);

    /**
     * @notice Retrieve the content-related timestamps for a given campaign.
     * @param campaign The address of the campaign.
     * @return lastUpdatedTime The timestamp of the last update made to the campaign's content.
     * @return lastPostTime The timestamp of the last content post associated with the campaign.
     */
    function campaignContentInfo(address campaign)
        external
        view
        returns (uint256 lastUpdatedTime, uint256 lastPostTime);

    /**
     * @notice Retrieve information about a specific content registered for a given campaign.
     * @param campaign The address of the campaign associated with the content.
     * @param contentLink The unique link or identifier of the content.
     * @param actionKind The concatenated social platform and the intended method, such as "X: Like" (optional).
     * @return contentRegistered A boolean indicating whether the content is registered under the campaign.
     * @return totalQaKinds The total number of qualifications set for the content in the campaign.
     * @return lastUpdatedTime The timestamp of the last update made to the content.
     * @return actionKindValue The corresponding value for the provided action kind, if applicable.
     */
    function contentToCampaign(
        address campaign,
        string calldata contentLink,
        string calldata actionKind
    )
        external
        view
        returns (
            bool contentRegistered,
            uint256 totalQaKinds,
            uint256 lastUpdatedTime,
            uint256 actionKindValue
        );

    /**
     * @notice Returns recent registered contents and their campaigns in paginated format.
     * @dev If page = 0, returns the last 10 registered contents.
     * @param page The page number.
     * @return currentPage The current page number.
     * @return totalPages The total number of pages.
     * @return pagedArray A list of 10 recent contents with campaign addresses.
     */
    function paginatedContentsWithCampaigns(uint256 page)
        external
        view
        returns (
            uint256 currentPage,
            uint256 totalPages,
            CampaignAndContent[] memory pagedArray
        );

    /**
     * @notice Returns recent registered contents for a specific campaign in paginated format.
     * @dev If page = 0, returns the last 10 contents for the campaign.
     * @param campaign The campaign's address.
     * @param page The page number.
     * @return currentPage The current page number.
     * @return totalPages The total number of pages.
     * @return pagedArray A list of 10 recent contents associated with the campaign.
     */
    function paginatedCampaignContents(uint256 page, address campaign)
        external
        view
        returns (
            uint256 currentPage,
            uint256 totalPages,
            string[] memory pagedArray
        );
}

File 12 of 33 : ValueType.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Casting.sol" as Casting;
import "./Helpers.sol" as Helpers;
import "./Math.sol" as Math;

/// @notice The unsigned 60.18-decimal fixed-point number representation, which can have up to 60 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the Solidity type uint256.
/// @dev The value type is defined here so it can be imported in all other files.
type UD60x18 is uint256;

/*//////////////////////////////////////////////////////////////////////////
                                    CASTING
//////////////////////////////////////////////////////////////////////////*/

using {
    Casting.intoSD1x18,
    Casting.intoUD2x18,
    Casting.intoSD59x18,
    Casting.intoUint128,
    Casting.intoUint256,
    Casting.intoUint40,
    Casting.unwrap
} for UD60x18 global;

/*//////////////////////////////////////////////////////////////////////////
                            MATHEMATICAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

// The global "using for" directive makes the functions in this library callable on the UD60x18 type.
using {
    Math.avg,
    Math.ceil,
    Math.div,
    Math.exp,
    Math.exp2,
    Math.floor,
    Math.frac,
    Math.gm,
    Math.inv,
    Math.ln,
    Math.log10,
    Math.log2,
    Math.mul,
    Math.pow,
    Math.powu,
    Math.sqrt
} for UD60x18 global;

/*//////////////////////////////////////////////////////////////////////////
                                HELPER FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

// The global "using for" directive makes the functions in this library callable on the UD60x18 type.
using {
    Helpers.add,
    Helpers.and,
    Helpers.eq,
    Helpers.gt,
    Helpers.gte,
    Helpers.isZero,
    Helpers.lshift,
    Helpers.lt,
    Helpers.lte,
    Helpers.mod,
    Helpers.neq,
    Helpers.not,
    Helpers.or,
    Helpers.rshift,
    Helpers.sub,
    Helpers.uncheckedAdd,
    Helpers.uncheckedSub,
    Helpers.xor
} for UD60x18 global;

/*//////////////////////////////////////////////////////////////////////////
                                    OPERATORS
//////////////////////////////////////////////////////////////////////////*/

// The global "using for" directive makes it possible to use these operators on the UD60x18 type.
using {
    Helpers.add as +,
    Helpers.and2 as &,
    Math.div as /,
    Helpers.eq as ==,
    Helpers.gt as >,
    Helpers.gte as >=,
    Helpers.lt as <,
    Helpers.lte as <=,
    Helpers.or as |,
    Helpers.mod as %,
    Math.mul as *,
    Helpers.neq as !=,
    Helpers.not as ~,
    Helpers.sub as -,
    Helpers.xor as ^
} for UD60x18 global;

File 13 of 33 : Math.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "../Common.sol" as Common;
import "./Errors.sol" as Errors;
import { wrap } from "./Casting.sol";
import {
    uEXP_MAX_INPUT,
    uEXP2_MAX_INPUT,
    uHALF_UNIT,
    uLOG2_10,
    uLOG2_E,
    uMAX_UD60x18,
    uMAX_WHOLE_UD60x18,
    UNIT,
    uUNIT,
    uUNIT_SQUARED,
    ZERO
} from "./Constants.sol";
import { UD60x18 } from "./ValueType.sol";

/*//////////////////////////////////////////////////////////////////////////
                            MATHEMATICAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Calculates the arithmetic average of x and y using the following formula:
///
/// $$
/// avg(x, y) = (x & y) + ((xUint ^ yUint) / 2)
/// $$
///
/// In English, this is what this formula does:
///
/// 1. AND x and y.
/// 2. Calculate half of XOR x and y.
/// 3. Add the two results together.
///
/// This technique is known as SWAR, which stands for "SIMD within a register". You can read more about it here:
/// https://devblogs.microsoft.com/oldnewthing/20220207-00/?p=106223
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// @param x The first operand as a UD60x18 number.
/// @param y The second operand as a UD60x18 number.
/// @return result The arithmetic average as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function avg(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    uint256 yUint = y.unwrap();
    unchecked {
        result = wrap((xUint & yUint) + ((xUint ^ yUint) >> 1));
    }
}

/// @notice Yields the smallest whole number greater than or equal to x.
///
/// @dev This is optimized for fractional value inputs, because for every whole value there are (1e18 - 1) fractional
/// counterparts. See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
///
/// Requirements:
/// - x must be less than or equal to `MAX_WHOLE_UD60x18`.
///
/// @param x The UD60x18 number to ceil.
/// @param result The smallest whole number greater than or equal to x, as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function ceil(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    if (xUint > uMAX_WHOLE_UD60x18) {
        revert Errors.PRBMath_UD60x18_Ceil_Overflow(x);
    }

    assembly ("memory-safe") {
        // Equivalent to `x % UNIT`.
        let remainder := mod(x, uUNIT)

        // Equivalent to `UNIT - remainder`.
        let delta := sub(uUNIT, remainder)

        // Equivalent to `x + remainder > 0 ? delta : 0`.
        result := add(x, mul(delta, gt(remainder, 0)))
    }
}

/// @notice Divides two UD60x18 numbers, returning a new UD60x18 number.
///
/// @dev Uses {Common.mulDiv} to enable overflow-safe multiplication and division.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv}.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv}.
///
/// @param x The numerator as a UD60x18 number.
/// @param y The denominator as a UD60x18 number.
/// @param result The quotient as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function div(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(Common.mulDiv(x.unwrap(), uUNIT, y.unwrap()));
}

/// @notice Calculates the natural exponent of x using the following formula:
///
/// $$
/// e^x = 2^{x * log_2{e}}
/// $$
///
/// @dev Requirements:
/// - x must be less than 133_084258667509499441.
///
/// @param x The exponent as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();

    // This check prevents values greater than 192e18 from being passed to {exp2}.
    if (xUint > uEXP_MAX_INPUT) {
        revert Errors.PRBMath_UD60x18_Exp_InputTooBig(x);
    }

    unchecked {
        // Inline the fixed-point multiplication to save gas.
        uint256 doubleUnitProduct = xUint * uLOG2_E;
        result = exp2(wrap(doubleUnitProduct / uUNIT));
    }
}

/// @notice Calculates the binary exponent of x using the binary fraction method.
///
/// @dev See https://ethereum.stackexchange.com/q/79903/24693
///
/// Requirements:
/// - x must be less than 192e18.
/// - The result must fit in UD60x18.
///
/// @param x The exponent as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp2(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();

    // Numbers greater than or equal to 192e18 don't fit in the 192.64-bit format.
    if (xUint > uEXP2_MAX_INPUT) {
        revert Errors.PRBMath_UD60x18_Exp2_InputTooBig(x);
    }

    // Convert x to the 192.64-bit fixed-point format.
    uint256 x_192x64 = (xUint << 64) / uUNIT;

    // Pass x to the {Common.exp2} function, which uses the 192.64-bit fixed-point number representation.
    result = wrap(Common.exp2(x_192x64));
}

/// @notice Yields the greatest whole number less than or equal to x.
/// @dev Optimized for fractional value inputs, because every whole value has (1e18 - 1) fractional counterparts.
/// See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
/// @param x The UD60x18 number to floor.
/// @param result The greatest whole number less than or equal to x, as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function floor(UD60x18 x) pure returns (UD60x18 result) {
    assembly ("memory-safe") {
        // Equivalent to `x % UNIT`.
        let remainder := mod(x, uUNIT)

        // Equivalent to `x - remainder > 0 ? remainder : 0)`.
        result := sub(x, mul(remainder, gt(remainder, 0)))
    }
}

/// @notice Yields the excess beyond the floor of x using the odd function definition.
/// @dev See https://en.wikipedia.org/wiki/Fractional_part.
/// @param x The UD60x18 number to get the fractional part of.
/// @param result The fractional part of x as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function frac(UD60x18 x) pure returns (UD60x18 result) {
    assembly ("memory-safe") {
        result := mod(x, uUNIT)
    }
}

/// @notice Calculates the geometric mean of x and y, i.e. $\sqrt{x * y}$, rounding down.
///
/// @dev Requirements:
/// - x * y must fit in UD60x18.
///
/// @param x The first operand as a UD60x18 number.
/// @param y The second operand as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function gm(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    uint256 yUint = y.unwrap();
    if (xUint == 0 || yUint == 0) {
        return ZERO;
    }

    unchecked {
        // Checking for overflow this way is faster than letting Solidity do it.
        uint256 xyUint = xUint * yUint;
        if (xyUint / xUint != yUint) {
            revert Errors.PRBMath_UD60x18_Gm_Overflow(x, y);
        }

        // We don't need to multiply the result by `UNIT` here because the x*y product picked up a factor of `UNIT`
        // during multiplication. See the comments in {Common.sqrt}.
        result = wrap(Common.sqrt(xyUint));
    }
}

/// @notice Calculates the inverse of x.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x must not be zero.
///
/// @param x The UD60x18 number for which to calculate the inverse.
/// @return result The inverse as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function inv(UD60x18 x) pure returns (UD60x18 result) {
    unchecked {
        result = wrap(uUNIT_SQUARED / x.unwrap());
    }
}

/// @notice Calculates the natural logarithm of x using the following formula:
///
/// $$
/// ln{x} = log_2{x} / log_2{e}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
/// - The precision isn't sufficiently fine-grained to return exactly `UNIT` when the input is `E`.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The UD60x18 number for which to calculate the natural logarithm.
/// @return result The natural logarithm as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function ln(UD60x18 x) pure returns (UD60x18 result) {
    unchecked {
        // Inline the fixed-point multiplication to save gas. This is overflow-safe because the maximum value that
        // {log2} can return is ~196_205294292027477728.
        result = wrap(log2(x).unwrap() * uUNIT / uLOG2_E);
    }
}

/// @notice Calculates the common logarithm of x using the following formula:
///
/// $$
/// log_{10}{x} = log_2{x} / log_2{10}
/// $$
///
/// However, if x is an exact power of ten, a hard coded value is returned.
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The UD60x18 number for which to calculate the common logarithm.
/// @return result The common logarithm as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function log10(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    if (xUint < uUNIT) {
        revert Errors.PRBMath_UD60x18_Log_InputTooSmall(x);
    }

    // Note that the `mul` in this assembly block is the standard multiplication operation, not {UD60x18.mul}.
    // prettier-ignore
    assembly ("memory-safe") {
        switch x
        case 1 { result := mul(uUNIT, sub(0, 18)) }
        case 10 { result := mul(uUNIT, sub(1, 18)) }
        case 100 { result := mul(uUNIT, sub(2, 18)) }
        case 1000 { result := mul(uUNIT, sub(3, 18)) }
        case 10000 { result := mul(uUNIT, sub(4, 18)) }
        case 100000 { result := mul(uUNIT, sub(5, 18)) }
        case 1000000 { result := mul(uUNIT, sub(6, 18)) }
        case 10000000 { result := mul(uUNIT, sub(7, 18)) }
        case 100000000 { result := mul(uUNIT, sub(8, 18)) }
        case 1000000000 { result := mul(uUNIT, sub(9, 18)) }
        case 10000000000 { result := mul(uUNIT, sub(10, 18)) }
        case 100000000000 { result := mul(uUNIT, sub(11, 18)) }
        case 1000000000000 { result := mul(uUNIT, sub(12, 18)) }
        case 10000000000000 { result := mul(uUNIT, sub(13, 18)) }
        case 100000000000000 { result := mul(uUNIT, sub(14, 18)) }
        case 1000000000000000 { result := mul(uUNIT, sub(15, 18)) }
        case 10000000000000000 { result := mul(uUNIT, sub(16, 18)) }
        case 100000000000000000 { result := mul(uUNIT, sub(17, 18)) }
        case 1000000000000000000 { result := 0 }
        case 10000000000000000000 { result := uUNIT }
        case 100000000000000000000 { result := mul(uUNIT, 2) }
        case 1000000000000000000000 { result := mul(uUNIT, 3) }
        case 10000000000000000000000 { result := mul(uUNIT, 4) }
        case 100000000000000000000000 { result := mul(uUNIT, 5) }
        case 1000000000000000000000000 { result := mul(uUNIT, 6) }
        case 10000000000000000000000000 { result := mul(uUNIT, 7) }
        case 100000000000000000000000000 { result := mul(uUNIT, 8) }
        case 1000000000000000000000000000 { result := mul(uUNIT, 9) }
        case 10000000000000000000000000000 { result := mul(uUNIT, 10) }
        case 100000000000000000000000000000 { result := mul(uUNIT, 11) }
        case 1000000000000000000000000000000 { result := mul(uUNIT, 12) }
        case 10000000000000000000000000000000 { result := mul(uUNIT, 13) }
        case 100000000000000000000000000000000 { result := mul(uUNIT, 14) }
        case 1000000000000000000000000000000000 { result := mul(uUNIT, 15) }
        case 10000000000000000000000000000000000 { result := mul(uUNIT, 16) }
        case 100000000000000000000000000000000000 { result := mul(uUNIT, 17) }
        case 1000000000000000000000000000000000000 { result := mul(uUNIT, 18) }
        case 10000000000000000000000000000000000000 { result := mul(uUNIT, 19) }
        case 100000000000000000000000000000000000000 { result := mul(uUNIT, 20) }
        case 1000000000000000000000000000000000000000 { result := mul(uUNIT, 21) }
        case 10000000000000000000000000000000000000000 { result := mul(uUNIT, 22) }
        case 100000000000000000000000000000000000000000 { result := mul(uUNIT, 23) }
        case 1000000000000000000000000000000000000000000 { result := mul(uUNIT, 24) }
        case 10000000000000000000000000000000000000000000 { result := mul(uUNIT, 25) }
        case 100000000000000000000000000000000000000000000 { result := mul(uUNIT, 26) }
        case 1000000000000000000000000000000000000000000000 { result := mul(uUNIT, 27) }
        case 10000000000000000000000000000000000000000000000 { result := mul(uUNIT, 28) }
        case 100000000000000000000000000000000000000000000000 { result := mul(uUNIT, 29) }
        case 1000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 30) }
        case 10000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 31) }
        case 100000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 32) }
        case 1000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 33) }
        case 10000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 34) }
        case 100000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 35) }
        case 1000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 36) }
        case 10000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 37) }
        case 100000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 38) }
        case 1000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 39) }
        case 10000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 40) }
        case 100000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 41) }
        case 1000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 42) }
        case 10000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 43) }
        case 100000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 44) }
        case 1000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 45) }
        case 10000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 46) }
        case 100000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 47) }
        case 1000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 48) }
        case 10000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 49) }
        case 100000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 50) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 51) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 52) }
        case 100000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 53) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 54) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 55) }
        case 100000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 56) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 57) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 58) }
        case 100000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 59) }
        default { result := uMAX_UD60x18 }
    }

    if (result.unwrap() == uMAX_UD60x18) {
        unchecked {
            // Inline the fixed-point division to save gas.
            result = wrap(log2(x).unwrap() * uUNIT / uLOG2_10);
        }
    }
}

/// @notice Calculates the binary logarithm of x using the iterative approximation algorithm:
///
/// $$
/// log_2{x} = n + log_2{y}, \text{ where } y = x*2^{-n}, \ y \in [1, 2)
/// $$
///
/// For $0 \leq x \lt 1$, the input is inverted:
///
/// $$
/// log_2{x} = -log_2{\frac{1}{x}}
/// $$
///
/// @dev See https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation
///
/// Notes:
/// - Due to the lossy precision of the iterative approximation, the results are not perfectly accurate to the last decimal.
///
/// Requirements:
/// - x must be greater than zero.
///
/// @param x The UD60x18 number for which to calculate the binary logarithm.
/// @return result The binary logarithm as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function log2(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();

    if (xUint < uUNIT) {
        revert Errors.PRBMath_UD60x18_Log_InputTooSmall(x);
    }

    unchecked {
        // Calculate the integer part of the logarithm.
        uint256 n = Common.msb(xUint / uUNIT);

        // This is the integer part of the logarithm as a UD60x18 number. The operation can't overflow because n
        // n is at most 255 and UNIT is 1e18.
        uint256 resultUint = n * uUNIT;

        // Calculate $y = x * 2^{-n}$.
        uint256 y = xUint >> n;

        // If y is the unit number, the fractional part is zero.
        if (y == uUNIT) {
            return wrap(resultUint);
        }

        // Calculate the fractional part via the iterative approximation.
        // The `delta >>= 1` part is equivalent to `delta /= 2`, but shifting bits is more gas efficient.
        uint256 DOUBLE_UNIT = 2e18;
        for (uint256 delta = uHALF_UNIT; delta > 0; delta >>= 1) {
            y = (y * y) / uUNIT;

            // Is y^2 >= 2e18 and so in the range [2e18, 4e18)?
            if (y >= DOUBLE_UNIT) {
                // Add the 2^{-m} factor to the logarithm.
                resultUint += delta;

                // Halve y, which corresponds to z/2 in the Wikipedia article.
                y >>= 1;
            }
        }
        result = wrap(resultUint);
    }
}

/// @notice Multiplies two UD60x18 numbers together, returning a new UD60x18 number.
///
/// @dev Uses {Common.mulDiv} to enable overflow-safe multiplication and division.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv}.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv}.
///
/// @dev See the documentation in {Common.mulDiv18}.
/// @param x The multiplicand as a UD60x18 number.
/// @param y The multiplier as a UD60x18 number.
/// @return result The product as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function mul(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(Common.mulDiv18(x.unwrap(), y.unwrap()));
}

/// @notice Raises x to the power of y.
///
/// For $1 \leq x \leq \infty$, the following standard formula is used:
///
/// $$
/// x^y = 2^{log_2{x} * y}
/// $$
///
/// For $0 \leq x \lt 1$, since the unsigned {log2} is undefined, an equivalent formula is used:
///
/// $$
/// i = \frac{1}{x}
/// w = 2^{log_2{i} * y}
/// x^y = \frac{1}{w}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {log2} and {mul}.
/// - Returns `UNIT` for 0^0.
/// - It may not perform well with very small values of x. Consider using SD59x18 as an alternative.
///
/// Requirements:
/// - Refer to the requirements in {exp2}, {log2}, and {mul}.
///
/// @param x The base as a UD60x18 number.
/// @param y The exponent as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function pow(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    uint256 yUint = y.unwrap();

    // If both x and y are zero, the result is `UNIT`. If just x is zero, the result is always zero.
    if (xUint == 0) {
        return yUint == 0 ? UNIT : ZERO;
    }
    // If x is `UNIT`, the result is always `UNIT`.
    else if (xUint == uUNIT) {
        return UNIT;
    }

    // If y is zero, the result is always `UNIT`.
    if (yUint == 0) {
        return UNIT;
    }
    // If y is `UNIT`, the result is always x.
    else if (yUint == uUNIT) {
        return x;
    }

    // If x is greater than `UNIT`, use the standard formula.
    if (xUint > uUNIT) {
        result = exp2(mul(log2(x), y));
    }
    // Conversely, if x is less than `UNIT`, use the equivalent formula.
    else {
        UD60x18 i = wrap(uUNIT_SQUARED / xUint);
        UD60x18 w = exp2(mul(log2(i), y));
        result = wrap(uUNIT_SQUARED / w.unwrap());
    }
}

/// @notice Raises x (a UD60x18 number) to the power y (an unsigned basic integer) using the well-known
/// algorithm "exponentiation by squaring".
///
/// @dev See https://en.wikipedia.org/wiki/Exponentiation_by_squaring.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv18}.
/// - Returns `UNIT` for 0^0.
///
/// Requirements:
/// - The result must fit in UD60x18.
///
/// @param x The base as a UD60x18 number.
/// @param y The exponent as a uint256.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function powu(UD60x18 x, uint256 y) pure returns (UD60x18 result) {
    // Calculate the first iteration of the loop in advance.
    uint256 xUint = x.unwrap();
    uint256 resultUint = y & 1 > 0 ? xUint : uUNIT;

    // Equivalent to `for(y /= 2; y > 0; y /= 2)`.
    for (y >>= 1; y > 0; y >>= 1) {
        xUint = Common.mulDiv18(xUint, xUint);

        // Equivalent to `y % 2 == 1`.
        if (y & 1 > 0) {
            resultUint = Common.mulDiv18(resultUint, xUint);
        }
    }
    result = wrap(resultUint);
}

/// @notice Calculates the square root of x using the Babylonian method.
///
/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
///
/// Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x must be less than `MAX_UD60x18 / UNIT`.
///
/// @param x The UD60x18 number for which to calculate the square root.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function sqrt(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();

    unchecked {
        if (xUint > uMAX_UD60x18 / uUNIT) {
            revert Errors.PRBMath_UD60x18_Sqrt_Overflow(x);
        }
        // Multiply x by `UNIT` to account for the factor of `UNIT` picked up when multiplying two UD60x18 numbers.
        // In this case, the two numbers are both the square root.
        result = wrap(Common.sqrt(xUint * uUNIT));
    }
}

File 14 of 33 : Helpers.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { wrap } from "./Casting.sol";
import { UD60x18 } from "./ValueType.sol";

/// @notice Implements the checked addition operation (+) in the UD60x18 type.
function add(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() + y.unwrap());
}

/// @notice Implements the AND (&) bitwise operation in the UD60x18 type.
function and(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() & bits);
}

/// @notice Implements the AND (&) bitwise operation in the UD60x18 type.
function and2(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() & y.unwrap());
}

/// @notice Implements the equal operation (==) in the UD60x18 type.
function eq(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() == y.unwrap();
}

/// @notice Implements the greater than operation (>) in the UD60x18 type.
function gt(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() > y.unwrap();
}

/// @notice Implements the greater than or equal to operation (>=) in the UD60x18 type.
function gte(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() >= y.unwrap();
}

/// @notice Implements a zero comparison check function in the UD60x18 type.
function isZero(UD60x18 x) pure returns (bool result) {
    // This wouldn't work if x could be negative.
    result = x.unwrap() == 0;
}

/// @notice Implements the left shift operation (<<) in the UD60x18 type.
function lshift(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() << bits);
}

/// @notice Implements the lower than operation (<) in the UD60x18 type.
function lt(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() < y.unwrap();
}

/// @notice Implements the lower than or equal to operation (<=) in the UD60x18 type.
function lte(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() <= y.unwrap();
}

/// @notice Implements the checked modulo operation (%) in the UD60x18 type.
function mod(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() % y.unwrap());
}

/// @notice Implements the not equal operation (!=) in the UD60x18 type.
function neq(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() != y.unwrap();
}

/// @notice Implements the NOT (~) bitwise operation in the UD60x18 type.
function not(UD60x18 x) pure returns (UD60x18 result) {
    result = wrap(~x.unwrap());
}

/// @notice Implements the OR (|) bitwise operation in the UD60x18 type.
function or(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() | y.unwrap());
}

/// @notice Implements the right shift operation (>>) in the UD60x18 type.
function rshift(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() >> bits);
}

/// @notice Implements the checked subtraction operation (-) in the UD60x18 type.
function sub(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() - y.unwrap());
}

/// @notice Implements the unchecked addition operation (+) in the UD60x18 type.
function uncheckedAdd(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    unchecked {
        result = wrap(x.unwrap() + y.unwrap());
    }
}

/// @notice Implements the unchecked subtraction operation (-) in the UD60x18 type.
function uncheckedSub(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    unchecked {
        result = wrap(x.unwrap() - y.unwrap());
    }
}

/// @notice Implements the XOR (^) bitwise operation in the UD60x18 type.
function xor(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() ^ y.unwrap());
}

File 15 of 33 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { UD60x18 } from "./ValueType.sol";

/// @notice Thrown when ceiling a number overflows UD60x18.
error PRBMath_UD60x18_Ceil_Overflow(UD60x18 x);

/// @notice Thrown when converting a basic integer to the fixed-point format overflows UD60x18.
error PRBMath_UD60x18_Convert_Overflow(uint256 x);

/// @notice Thrown when taking the natural exponent of a base greater than 133_084258667509499441.
error PRBMath_UD60x18_Exp_InputTooBig(UD60x18 x);

/// @notice Thrown when taking the binary exponent of a base greater than 192e18.
error PRBMath_UD60x18_Exp2_InputTooBig(UD60x18 x);

/// @notice Thrown when taking the geometric mean of two numbers and multiplying them overflows UD60x18.
error PRBMath_UD60x18_Gm_Overflow(UD60x18 x, UD60x18 y);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD1x18.
error PRBMath_UD60x18_IntoSD1x18_Overflow(UD60x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD59x18.
error PRBMath_UD60x18_IntoSD59x18_Overflow(UD60x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD2x18.
error PRBMath_UD60x18_IntoUD2x18_Overflow(UD60x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint128.
error PRBMath_UD60x18_IntoUint128_Overflow(UD60x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint40.
error PRBMath_UD60x18_IntoUint40_Overflow(UD60x18 x);

/// @notice Thrown when taking the logarithm of a number less than 1.
error PRBMath_UD60x18_Log_InputTooSmall(UD60x18 x);

/// @notice Thrown when calculating the square root overflows UD60x18.
error PRBMath_UD60x18_Sqrt_Overflow(UD60x18 x);

File 16 of 33 : Conversions.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { uMAX_UD60x18, uUNIT } from "./Constants.sol";
import { PRBMath_UD60x18_Convert_Overflow } from "./Errors.sol";
import { UD60x18 } from "./ValueType.sol";

/// @notice Converts a UD60x18 number to a simple integer by dividing it by `UNIT`.
/// @dev The result is rounded toward zero.
/// @param x The UD60x18 number to convert.
/// @return result The same number in basic integer form.
function convert(UD60x18 x) pure returns (uint256 result) {
    result = UD60x18.unwrap(x) / uUNIT;
}

/// @notice Converts a simple integer to UD60x18 by multiplying it by `UNIT`.
///
/// @dev Requirements:
/// - x must be less than or equal to `MAX_UD60x18 / UNIT`.
///
/// @param x The basic integer to convert.
/// @param result The same number converted to UD60x18.
function convert(uint256 x) pure returns (UD60x18 result) {
    if (x > uMAX_UD60x18 / uUNIT) {
        revert PRBMath_UD60x18_Convert_Overflow(x);
    }
    unchecked {
        result = UD60x18.wrap(x * uUNIT);
    }
}

File 17 of 33 : Constants.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { UD60x18 } from "./ValueType.sol";

// NOTICE: the "u" prefix stands for "unwrapped".

/// @dev Euler's number as a UD60x18 number.
UD60x18 constant E = UD60x18.wrap(2_718281828459045235);

/// @dev The maximum input permitted in {exp}.
uint256 constant uEXP_MAX_INPUT = 133_084258667509499440;
UD60x18 constant EXP_MAX_INPUT = UD60x18.wrap(uEXP_MAX_INPUT);

/// @dev The maximum input permitted in {exp2}.
uint256 constant uEXP2_MAX_INPUT = 192e18 - 1;
UD60x18 constant EXP2_MAX_INPUT = UD60x18.wrap(uEXP2_MAX_INPUT);

/// @dev Half the UNIT number.
uint256 constant uHALF_UNIT = 0.5e18;
UD60x18 constant HALF_UNIT = UD60x18.wrap(uHALF_UNIT);

/// @dev $log_2(10)$ as a UD60x18 number.
uint256 constant uLOG2_10 = 3_321928094887362347;
UD60x18 constant LOG2_10 = UD60x18.wrap(uLOG2_10);

/// @dev $log_2(e)$ as a UD60x18 number.
uint256 constant uLOG2_E = 1_442695040888963407;
UD60x18 constant LOG2_E = UD60x18.wrap(uLOG2_E);

/// @dev The maximum value a UD60x18 number can have.
uint256 constant uMAX_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_584007913129639935;
UD60x18 constant MAX_UD60x18 = UD60x18.wrap(uMAX_UD60x18);

/// @dev The maximum whole value a UD60x18 number can have.
uint256 constant uMAX_WHOLE_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_000000000000000000;
UD60x18 constant MAX_WHOLE_UD60x18 = UD60x18.wrap(uMAX_WHOLE_UD60x18);

/// @dev PI as a UD60x18 number.
UD60x18 constant PI = UD60x18.wrap(3_141592653589793238);

/// @dev The unit number, which gives the decimal precision of UD60x18.
uint256 constant uUNIT = 1e18;
UD60x18 constant UNIT = UD60x18.wrap(uUNIT);

/// @dev The unit number squared.
uint256 constant uUNIT_SQUARED = 1e36;
UD60x18 constant UNIT_SQUARED = UD60x18.wrap(uUNIT_SQUARED);

/// @dev Zero as a UD60x18 number.
UD60x18 constant ZERO = UD60x18.wrap(0);

File 18 of 33 : Casting.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Errors.sol" as CastingErrors;
import { MAX_UINT128, MAX_UINT40 } from "../Common.sol";
import { uMAX_SD1x18 } from "../sd1x18/Constants.sol";
import { SD1x18 } from "../sd1x18/ValueType.sol";
import { uMAX_SD59x18 } from "../sd59x18/Constants.sol";
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { uMAX_UD2x18 } from "../ud2x18/Constants.sol";
import { UD2x18 } from "../ud2x18/ValueType.sol";
import { UD60x18 } from "./ValueType.sol";

/// @notice Casts a UD60x18 number into SD1x18.
/// @dev Requirements:
/// - x must be less than or equal to `uMAX_SD1x18`.
function intoSD1x18(UD60x18 x) pure returns (SD1x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uint256(int256(uMAX_SD1x18))) {
        revert CastingErrors.PRBMath_UD60x18_IntoSD1x18_Overflow(x);
    }
    result = SD1x18.wrap(int64(uint64(xUint)));
}

/// @notice Casts a UD60x18 number into UD2x18.
/// @dev Requirements:
/// - x must be less than or equal to `uMAX_UD2x18`.
function intoUD2x18(UD60x18 x) pure returns (UD2x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uMAX_UD2x18) {
        revert CastingErrors.PRBMath_UD60x18_IntoUD2x18_Overflow(x);
    }
    result = UD2x18.wrap(uint64(xUint));
}

/// @notice Casts a UD60x18 number into SD59x18.
/// @dev Requirements:
/// - x must be less than or equal to `uMAX_SD59x18`.
function intoSD59x18(UD60x18 x) pure returns (SD59x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uint256(uMAX_SD59x18)) {
        revert CastingErrors.PRBMath_UD60x18_IntoSD59x18_Overflow(x);
    }
    result = SD59x18.wrap(int256(xUint));
}

/// @notice Casts a UD60x18 number into uint128.
/// @dev This is basically an alias for {unwrap}.
function intoUint256(UD60x18 x) pure returns (uint256 result) {
    result = UD60x18.unwrap(x);
}

/// @notice Casts a UD60x18 number into uint128.
/// @dev Requirements:
/// - x must be less than or equal to `MAX_UINT128`.
function intoUint128(UD60x18 x) pure returns (uint128 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > MAX_UINT128) {
        revert CastingErrors.PRBMath_UD60x18_IntoUint128_Overflow(x);
    }
    result = uint128(xUint);
}

/// @notice Casts a UD60x18 number into uint40.
/// @dev Requirements:
/// - x must be less than or equal to `MAX_UINT40`.
function intoUint40(UD60x18 x) pure returns (uint40 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > MAX_UINT40) {
        revert CastingErrors.PRBMath_UD60x18_IntoUint40_Overflow(x);
    }
    result = uint40(xUint);
}

/// @notice Alias for {wrap}.
function ud(uint256 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(x);
}

/// @notice Alias for {wrap}.
function ud60x18(uint256 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(x);
}

/// @notice Unwraps a UD60x18 number into uint256.
function unwrap(UD60x18 x) pure returns (uint256 result) {
    result = UD60x18.unwrap(x);
}

/// @notice Wraps a uint256 number into the UD60x18 value type.
function wrap(uint256 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(x);
}

File 19 of 33 : Common.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

// Common.sol
//
// Common mathematical functions used in both SD59x18 and UD60x18. Note that these global functions do not
// always operate with SD59x18 and UD60x18 numbers.

/*//////////////////////////////////////////////////////////////////////////
                                CUSTOM ERRORS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Thrown when the resultant value in {mulDiv} overflows uint256.
error PRBMath_MulDiv_Overflow(uint256 x, uint256 y, uint256 denominator);

/// @notice Thrown when the resultant value in {mulDiv18} overflows uint256.
error PRBMath_MulDiv18_Overflow(uint256 x, uint256 y);

/// @notice Thrown when one of the inputs passed to {mulDivSigned} is `type(int256).min`.
error PRBMath_MulDivSigned_InputTooSmall();

/// @notice Thrown when the resultant value in {mulDivSigned} overflows int256.
error PRBMath_MulDivSigned_Overflow(int256 x, int256 y);

/*//////////////////////////////////////////////////////////////////////////
                                    CONSTANTS
//////////////////////////////////////////////////////////////////////////*/

/// @dev The maximum value a uint128 number can have.
uint128 constant MAX_UINT128 = type(uint128).max;

/// @dev The maximum value a uint40 number can have.
uint40 constant MAX_UINT40 = type(uint40).max;

/// @dev The unit number, which the decimal precision of the fixed-point types.
uint256 constant UNIT = 1e18;

/// @dev The unit number inverted mod 2^256.
uint256 constant UNIT_INVERSE = 78156646155174841979727994598816262306175212592076161876661_508869554232690281;

/// @dev The the largest power of two that divides the decimal value of `UNIT`. The logarithm of this value is the least significant
/// bit in the binary representation of `UNIT`.
uint256 constant UNIT_LPOTD = 262144;

/*//////////////////////////////////////////////////////////////////////////
                                    FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Calculates the binary exponent of x using the binary fraction method.
/// @dev Has to use 192.64-bit fixed-point numbers. See https://ethereum.stackexchange.com/a/96594/24693.
/// @param x The exponent as an unsigned 192.64-bit fixed-point number.
/// @return result The result as an unsigned 60.18-decimal fixed-point number.
/// @custom:smtchecker abstract-function-nondet
function exp2(uint256 x) pure returns (uint256 result) {
    unchecked {
        // Start from 0.5 in the 192.64-bit fixed-point format.
        result = 0x800000000000000000000000000000000000000000000000;

        // The following logic multiplies the result by $\sqrt{2^{-i}}$ when the bit at position i is 1. Key points:
        //
        // 1. Intermediate results will not overflow, as the starting point is 2^191 and all magic factors are under 2^65.
        // 2. The rationale for organizing the if statements into groups of 8 is gas savings. If the result of performing
        // a bitwise AND operation between x and any value in the array [0x80; 0x40; 0x20; 0x10; 0x08; 0x04; 0x02; 0x01] is 1,
        // we know that `x & 0xFF` is also 1.
        if (x & 0xFF00000000000000 > 0) {
            if (x & 0x8000000000000000 > 0) {
                result = (result * 0x16A09E667F3BCC909) >> 64;
            }
            if (x & 0x4000000000000000 > 0) {
                result = (result * 0x1306FE0A31B7152DF) >> 64;
            }
            if (x & 0x2000000000000000 > 0) {
                result = (result * 0x1172B83C7D517ADCE) >> 64;
            }
            if (x & 0x1000000000000000 > 0) {
                result = (result * 0x10B5586CF9890F62A) >> 64;
            }
            if (x & 0x800000000000000 > 0) {
                result = (result * 0x1059B0D31585743AE) >> 64;
            }
            if (x & 0x400000000000000 > 0) {
                result = (result * 0x102C9A3E778060EE7) >> 64;
            }
            if (x & 0x200000000000000 > 0) {
                result = (result * 0x10163DA9FB33356D8) >> 64;
            }
            if (x & 0x100000000000000 > 0) {
                result = (result * 0x100B1AFA5ABCBED61) >> 64;
            }
        }

        if (x & 0xFF000000000000 > 0) {
            if (x & 0x80000000000000 > 0) {
                result = (result * 0x10058C86DA1C09EA2) >> 64;
            }
            if (x & 0x40000000000000 > 0) {
                result = (result * 0x1002C605E2E8CEC50) >> 64;
            }
            if (x & 0x20000000000000 > 0) {
                result = (result * 0x100162F3904051FA1) >> 64;
            }
            if (x & 0x10000000000000 > 0) {
                result = (result * 0x1000B175EFFDC76BA) >> 64;
            }
            if (x & 0x8000000000000 > 0) {
                result = (result * 0x100058BA01FB9F96D) >> 64;
            }
            if (x & 0x4000000000000 > 0) {
                result = (result * 0x10002C5CC37DA9492) >> 64;
            }
            if (x & 0x2000000000000 > 0) {
                result = (result * 0x1000162E525EE0547) >> 64;
            }
            if (x & 0x1000000000000 > 0) {
                result = (result * 0x10000B17255775C04) >> 64;
            }
        }

        if (x & 0xFF0000000000 > 0) {
            if (x & 0x800000000000 > 0) {
                result = (result * 0x1000058B91B5BC9AE) >> 64;
            }
            if (x & 0x400000000000 > 0) {
                result = (result * 0x100002C5C89D5EC6D) >> 64;
            }
            if (x & 0x200000000000 > 0) {
                result = (result * 0x10000162E43F4F831) >> 64;
            }
            if (x & 0x100000000000 > 0) {
                result = (result * 0x100000B1721BCFC9A) >> 64;
            }
            if (x & 0x80000000000 > 0) {
                result = (result * 0x10000058B90CF1E6E) >> 64;
            }
            if (x & 0x40000000000 > 0) {
                result = (result * 0x1000002C5C863B73F) >> 64;
            }
            if (x & 0x20000000000 > 0) {
                result = (result * 0x100000162E430E5A2) >> 64;
            }
            if (x & 0x10000000000 > 0) {
                result = (result * 0x1000000B172183551) >> 64;
            }
        }

        if (x & 0xFF00000000 > 0) {
            if (x & 0x8000000000 > 0) {
                result = (result * 0x100000058B90C0B49) >> 64;
            }
            if (x & 0x4000000000 > 0) {
                result = (result * 0x10000002C5C8601CC) >> 64;
            }
            if (x & 0x2000000000 > 0) {
                result = (result * 0x1000000162E42FFF0) >> 64;
            }
            if (x & 0x1000000000 > 0) {
                result = (result * 0x10000000B17217FBB) >> 64;
            }
            if (x & 0x800000000 > 0) {
                result = (result * 0x1000000058B90BFCE) >> 64;
            }
            if (x & 0x400000000 > 0) {
                result = (result * 0x100000002C5C85FE3) >> 64;
            }
            if (x & 0x200000000 > 0) {
                result = (result * 0x10000000162E42FF1) >> 64;
            }
            if (x & 0x100000000 > 0) {
                result = (result * 0x100000000B17217F8) >> 64;
            }
        }

        if (x & 0xFF000000 > 0) {
            if (x & 0x80000000 > 0) {
                result = (result * 0x10000000058B90BFC) >> 64;
            }
            if (x & 0x40000000 > 0) {
                result = (result * 0x1000000002C5C85FE) >> 64;
            }
            if (x & 0x20000000 > 0) {
                result = (result * 0x100000000162E42FF) >> 64;
            }
            if (x & 0x10000000 > 0) {
                result = (result * 0x1000000000B17217F) >> 64;
            }
            if (x & 0x8000000 > 0) {
                result = (result * 0x100000000058B90C0) >> 64;
            }
            if (x & 0x4000000 > 0) {
                result = (result * 0x10000000002C5C860) >> 64;
            }
            if (x & 0x2000000 > 0) {
                result = (result * 0x1000000000162E430) >> 64;
            }
            if (x & 0x1000000 > 0) {
                result = (result * 0x10000000000B17218) >> 64;
            }
        }

        if (x & 0xFF0000 > 0) {
            if (x & 0x800000 > 0) {
                result = (result * 0x1000000000058B90C) >> 64;
            }
            if (x & 0x400000 > 0) {
                result = (result * 0x100000000002C5C86) >> 64;
            }
            if (x & 0x200000 > 0) {
                result = (result * 0x10000000000162E43) >> 64;
            }
            if (x & 0x100000 > 0) {
                result = (result * 0x100000000000B1721) >> 64;
            }
            if (x & 0x80000 > 0) {
                result = (result * 0x10000000000058B91) >> 64;
            }
            if (x & 0x40000 > 0) {
                result = (result * 0x1000000000002C5C8) >> 64;
            }
            if (x & 0x20000 > 0) {
                result = (result * 0x100000000000162E4) >> 64;
            }
            if (x & 0x10000 > 0) {
                result = (result * 0x1000000000000B172) >> 64;
            }
        }

        if (x & 0xFF00 > 0) {
            if (x & 0x8000 > 0) {
                result = (result * 0x100000000000058B9) >> 64;
            }
            if (x & 0x4000 > 0) {
                result = (result * 0x10000000000002C5D) >> 64;
            }
            if (x & 0x2000 > 0) {
                result = (result * 0x1000000000000162E) >> 64;
            }
            if (x & 0x1000 > 0) {
                result = (result * 0x10000000000000B17) >> 64;
            }
            if (x & 0x800 > 0) {
                result = (result * 0x1000000000000058C) >> 64;
            }
            if (x & 0x400 > 0) {
                result = (result * 0x100000000000002C6) >> 64;
            }
            if (x & 0x200 > 0) {
                result = (result * 0x10000000000000163) >> 64;
            }
            if (x & 0x100 > 0) {
                result = (result * 0x100000000000000B1) >> 64;
            }
        }

        if (x & 0xFF > 0) {
            if (x & 0x80 > 0) {
                result = (result * 0x10000000000000059) >> 64;
            }
            if (x & 0x40 > 0) {
                result = (result * 0x1000000000000002C) >> 64;
            }
            if (x & 0x20 > 0) {
                result = (result * 0x10000000000000016) >> 64;
            }
            if (x & 0x10 > 0) {
                result = (result * 0x1000000000000000B) >> 64;
            }
            if (x & 0x8 > 0) {
                result = (result * 0x10000000000000006) >> 64;
            }
            if (x & 0x4 > 0) {
                result = (result * 0x10000000000000003) >> 64;
            }
            if (x & 0x2 > 0) {
                result = (result * 0x10000000000000001) >> 64;
            }
            if (x & 0x1 > 0) {
                result = (result * 0x10000000000000001) >> 64;
            }
        }

        // In the code snippet below, two operations are executed simultaneously:
        //
        // 1. The result is multiplied by $(2^n + 1)$, where $2^n$ represents the integer part, and the additional 1
        // accounts for the initial guess of 0.5. This is achieved by subtracting from 191 instead of 192.
        // 2. The result is then converted to an unsigned 60.18-decimal fixed-point format.
        //
        // The underlying logic is based on the relationship $2^{191-ip} = 2^{ip} / 2^{191}$, where $ip$ denotes the,
        // integer part, $2^n$.
        result *= UNIT;
        result >>= (191 - (x >> 64));
    }
}

/// @notice Finds the zero-based index of the first 1 in the binary representation of x.
///
/// @dev See the note on "msb" in this Wikipedia article: https://en.wikipedia.org/wiki/Find_first_set
///
/// Each step in this implementation is equivalent to this high-level code:
///
/// ```solidity
/// if (x >= 2 ** 128) {
///     x >>= 128;
///     result += 128;
/// }
/// ```
///
/// Where 128 is replaced with each respective power of two factor. See the full high-level implementation here:
/// https://gist.github.com/PaulRBerg/f932f8693f2733e30c4d479e8e980948
///
/// The Yul instructions used below are:
///
/// - "gt" is "greater than"
/// - "or" is the OR bitwise operator
/// - "shl" is "shift left"
/// - "shr" is "shift right"
///
/// @param x The uint256 number for which to find the index of the most significant bit.
/// @return result The index of the most significant bit as a uint256.
/// @custom:smtchecker abstract-function-nondet
function msb(uint256 x) pure returns (uint256 result) {
    // 2^128
    assembly ("memory-safe") {
        let factor := shl(7, gt(x, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^64
    assembly ("memory-safe") {
        let factor := shl(6, gt(x, 0xFFFFFFFFFFFFFFFF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^32
    assembly ("memory-safe") {
        let factor := shl(5, gt(x, 0xFFFFFFFF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^16
    assembly ("memory-safe") {
        let factor := shl(4, gt(x, 0xFFFF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^8
    assembly ("memory-safe") {
        let factor := shl(3, gt(x, 0xFF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^4
    assembly ("memory-safe") {
        let factor := shl(2, gt(x, 0xF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^2
    assembly ("memory-safe") {
        let factor := shl(1, gt(x, 0x3))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^1
    // No need to shift x any more.
    assembly ("memory-safe") {
        let factor := gt(x, 0x1)
        result := or(result, factor)
    }
}

/// @notice Calculates x*y÷denominator with 512-bit precision.
///
/// @dev Credits to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv.
///
/// Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - The denominator must not be zero.
/// - The result must fit in uint256.
///
/// @param x The multiplicand as a uint256.
/// @param y The multiplier as a uint256.
/// @param denominator The divisor as a uint256.
/// @return result The result as a uint256.
/// @custom:smtchecker abstract-function-nondet
function mulDiv(uint256 x, uint256 y, uint256 denominator) pure returns (uint256 result) {
    // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
    // use the Chinese Remainder Theorem to reconstruct the 512-bit result. The result is stored in two 256
    // variables such that product = prod1 * 2^256 + prod0.
    uint256 prod0; // Least significant 256 bits of the product
    uint256 prod1; // Most significant 256 bits of the product
    assembly ("memory-safe") {
        let mm := mulmod(x, y, not(0))
        prod0 := mul(x, y)
        prod1 := sub(sub(mm, prod0), lt(mm, prod0))
    }

    // Handle non-overflow cases, 256 by 256 division.
    if (prod1 == 0) {
        unchecked {
            return prod0 / denominator;
        }
    }

    // Make sure the result is less than 2^256. Also prevents denominator == 0.
    if (prod1 >= denominator) {
        revert PRBMath_MulDiv_Overflow(x, y, denominator);
    }

    ////////////////////////////////////////////////////////////////////////////
    // 512 by 256 division
    ////////////////////////////////////////////////////////////////////////////

    // Make division exact by subtracting the remainder from [prod1 prod0].
    uint256 remainder;
    assembly ("memory-safe") {
        // Compute remainder using the mulmod Yul instruction.
        remainder := mulmod(x, y, denominator)

        // Subtract 256 bit number from 512-bit number.
        prod1 := sub(prod1, gt(remainder, prod0))
        prod0 := sub(prod0, remainder)
    }

    unchecked {
        // Calculate the largest power of two divisor of the denominator using the unary operator ~. This operation cannot overflow
        // because the denominator cannot be zero at this point in the function execution. The result is always >= 1.
        // For more detail, see https://cs.stackexchange.com/q/138556/92363.
        uint256 lpotdod = denominator & (~denominator + 1);
        uint256 flippedLpotdod;

        assembly ("memory-safe") {
            // Factor powers of two out of denominator.
            denominator := div(denominator, lpotdod)

            // Divide [prod1 prod0] by lpotdod.
            prod0 := div(prod0, lpotdod)

            // Get the flipped value `2^256 / lpotdod`. If the `lpotdod` is zero, the flipped value is one.
            // `sub(0, lpotdod)` produces the two's complement version of `lpotdod`, which is equivalent to flipping all the bits.
            // However, `div` interprets this value as an unsigned value: https://ethereum.stackexchange.com/q/147168/24693
            flippedLpotdod := add(div(sub(0, lpotdod), lpotdod), 1)
        }

        // Shift in bits from prod1 into prod0.
        prod0 |= prod1 * flippedLpotdod;

        // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
        // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
        // four bits. That is, denominator * inv = 1 mod 2^4.
        uint256 inverse = (3 * denominator) ^ 2;

        // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
        // in modular arithmetic, doubling the correct bits in each step.
        inverse *= 2 - denominator * inverse; // inverse mod 2^8
        inverse *= 2 - denominator * inverse; // inverse mod 2^16
        inverse *= 2 - denominator * inverse; // inverse mod 2^32
        inverse *= 2 - denominator * inverse; // inverse mod 2^64
        inverse *= 2 - denominator * inverse; // inverse mod 2^128
        inverse *= 2 - denominator * inverse; // inverse mod 2^256

        // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
        // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
        // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
        // is no longer required.
        result = prod0 * inverse;
    }
}

/// @notice Calculates x*y÷1e18 with 512-bit precision.
///
/// @dev A variant of {mulDiv} with constant folding, i.e. in which the denominator is hard coded to 1e18.
///
/// Notes:
/// - The body is purposely left uncommented; to understand how this works, see the documentation in {mulDiv}.
/// - The result is rounded toward zero.
/// - We take as an axiom that the result cannot be `MAX_UINT256` when x and y solve the following system of equations:
///
/// $$
/// \begin{cases}
///     x * y = MAX\_UINT256 * UNIT \\
///     (x * y) \% UNIT \geq \frac{UNIT}{2}
/// \end{cases}
/// $$
///
/// Requirements:
/// - Refer to the requirements in {mulDiv}.
/// - The result must fit in uint256.
///
/// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number.
/// @param y The multiplier as an unsigned 60.18-decimal fixed-point number.
/// @return result The result as an unsigned 60.18-decimal fixed-point number.
/// @custom:smtchecker abstract-function-nondet
function mulDiv18(uint256 x, uint256 y) pure returns (uint256 result) {
    uint256 prod0;
    uint256 prod1;
    assembly ("memory-safe") {
        let mm := mulmod(x, y, not(0))
        prod0 := mul(x, y)
        prod1 := sub(sub(mm, prod0), lt(mm, prod0))
    }

    if (prod1 == 0) {
        unchecked {
            return prod0 / UNIT;
        }
    }

    if (prod1 >= UNIT) {
        revert PRBMath_MulDiv18_Overflow(x, y);
    }

    uint256 remainder;
    assembly ("memory-safe") {
        remainder := mulmod(x, y, UNIT)
        result :=
            mul(
                or(
                    div(sub(prod0, remainder), UNIT_LPOTD),
                    mul(sub(prod1, gt(remainder, prod0)), add(div(sub(0, UNIT_LPOTD), UNIT_LPOTD), 1))
                ),
                UNIT_INVERSE
            )
    }
}

/// @notice Calculates x*y÷denominator with 512-bit precision.
///
/// @dev This is an extension of {mulDiv} for signed numbers, which works by computing the signs and the absolute values separately.
///
/// Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - Refer to the requirements in {mulDiv}.
/// - None of the inputs can be `type(int256).min`.
/// - The result must fit in int256.
///
/// @param x The multiplicand as an int256.
/// @param y The multiplier as an int256.
/// @param denominator The divisor as an int256.
/// @return result The result as an int256.
/// @custom:smtchecker abstract-function-nondet
function mulDivSigned(int256 x, int256 y, int256 denominator) pure returns (int256 result) {
    if (x == type(int256).min || y == type(int256).min || denominator == type(int256).min) {
        revert PRBMath_MulDivSigned_InputTooSmall();
    }

    // Get hold of the absolute values of x, y and the denominator.
    uint256 xAbs;
    uint256 yAbs;
    uint256 dAbs;
    unchecked {
        xAbs = x < 0 ? uint256(-x) : uint256(x);
        yAbs = y < 0 ? uint256(-y) : uint256(y);
        dAbs = denominator < 0 ? uint256(-denominator) : uint256(denominator);
    }

    // Compute the absolute value of x*y÷denominator. The result must fit in int256.
    uint256 resultAbs = mulDiv(xAbs, yAbs, dAbs);
    if (resultAbs > uint256(type(int256).max)) {
        revert PRBMath_MulDivSigned_Overflow(x, y);
    }

    // Get the signs of x, y and the denominator.
    uint256 sx;
    uint256 sy;
    uint256 sd;
    assembly ("memory-safe") {
        // "sgt" is the "signed greater than" assembly instruction and "sub(0,1)" is -1 in two's complement.
        sx := sgt(x, sub(0, 1))
        sy := sgt(y, sub(0, 1))
        sd := sgt(denominator, sub(0, 1))
    }

    // XOR over sx, sy and sd. What this does is to check whether there are 1 or 3 negative signs in the inputs.
    // If there are, the result should be negative. Otherwise, it should be positive.
    unchecked {
        result = sx ^ sy ^ sd == 0 ? -int256(resultAbs) : int256(resultAbs);
    }
}

/// @notice Calculates the square root of x using the Babylonian method.
///
/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
///
/// Notes:
/// - If x is not a perfect square, the result is rounded down.
/// - Credits to OpenZeppelin for the explanations in comments below.
///
/// @param x The uint256 number for which to calculate the square root.
/// @return result The result as a uint256.
/// @custom:smtchecker abstract-function-nondet
function sqrt(uint256 x) pure returns (uint256 result) {
    if (x == 0) {
        return 0;
    }

    // For our first guess, we calculate the biggest power of 2 which is smaller than the square root of x.
    //
    // We know that the "msb" (most significant bit) of x is a power of 2 such that we have:
    //
    // $$
    // msb(x) <= x <= 2*msb(x)$
    // $$
    //
    // We write $msb(x)$ as $2^k$, and we get:
    //
    // $$
    // k = log_2(x)
    // $$
    //
    // Thus, we can write the initial inequality as:
    //
    // $$
    // 2^{log_2(x)} <= x <= 2*2^{log_2(x)+1} \\
    // sqrt(2^k) <= sqrt(x) < sqrt(2^{k+1}) \\
    // 2^{k/2} <= sqrt(x) < 2^{(k+1)/2} <= 2^{(k/2)+1}
    // $$
    //
    // Consequently, $2^{log_2(x) /2} is a good first approximation of sqrt(x) with at least one correct bit.
    uint256 xAux = uint256(x);
    result = 1;
    if (xAux >= 2 ** 128) {
        xAux >>= 128;
        result <<= 64;
    }
    if (xAux >= 2 ** 64) {
        xAux >>= 64;
        result <<= 32;
    }
    if (xAux >= 2 ** 32) {
        xAux >>= 32;
        result <<= 16;
    }
    if (xAux >= 2 ** 16) {
        xAux >>= 16;
        result <<= 8;
    }
    if (xAux >= 2 ** 8) {
        xAux >>= 8;
        result <<= 4;
    }
    if (xAux >= 2 ** 4) {
        xAux >>= 4;
        result <<= 2;
    }
    if (xAux >= 2 ** 2) {
        result <<= 1;
    }

    // At this point, `result` is an estimation with at least one bit of precision. We know the true value has at
    // most 128 bits, since it is the square root of a uint256. Newton's method converges quadratically (precision
    // doubles at every iteration). We thus need at most 7 iteration to turn our partial result with one bit of
    // precision into the expected uint128 result.
    unchecked {
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1;

        // If x is not a perfect square, round the result toward zero.
        uint256 roundedResult = x / result;
        if (result >= roundedResult) {
            result = roundedResult;
        }
    }
}

File 20 of 33 : ValueType.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Casting.sol" as Casting;

/// @notice The unsigned 2.18-decimal fixed-point number representation, which can have up to 2 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
/// type uint64. This is useful when end users want to use uint64 to save gas, e.g. with tight variable packing in contract
/// storage.
type UD2x18 is uint64;

/*//////////////////////////////////////////////////////////////////////////
                                    CASTING
//////////////////////////////////////////////////////////////////////////*/

using {
    Casting.intoSD1x18,
    Casting.intoSD59x18,
    Casting.intoUD60x18,
    Casting.intoUint256,
    Casting.intoUint128,
    Casting.intoUint40,
    Casting.unwrap
} for UD2x18 global;

File 21 of 33 : Constants.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { UD2x18 } from "./ValueType.sol";

/// @dev Euler's number as a UD2x18 number.
UD2x18 constant E = UD2x18.wrap(2_718281828459045235);

/// @dev The maximum value a UD2x18 number can have.
uint64 constant uMAX_UD2x18 = 18_446744073709551615;
UD2x18 constant MAX_UD2x18 = UD2x18.wrap(uMAX_UD2x18);

/// @dev PI as a UD2x18 number.
UD2x18 constant PI = UD2x18.wrap(3_141592653589793238);

/// @dev The unit number, which gives the decimal precision of UD2x18.
UD2x18 constant UNIT = UD2x18.wrap(1e18);
uint64 constant uUNIT = 1e18;

File 22 of 33 : ValueType.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Casting.sol" as Casting;
import "./Helpers.sol" as Helpers;
import "./Math.sol" as Math;

/// @notice The signed 59.18-decimal fixed-point number representation, which can have up to 59 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
/// type int256.
type SD59x18 is int256;

/*//////////////////////////////////////////////////////////////////////////
                                    CASTING
//////////////////////////////////////////////////////////////////////////*/

using {
    Casting.intoInt256,
    Casting.intoSD1x18,
    Casting.intoUD2x18,
    Casting.intoUD60x18,
    Casting.intoUint256,
    Casting.intoUint128,
    Casting.intoUint40,
    Casting.unwrap
} for SD59x18 global;

/*//////////////////////////////////////////////////////////////////////////
                            MATHEMATICAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

using {
    Math.abs,
    Math.avg,
    Math.ceil,
    Math.div,
    Math.exp,
    Math.exp2,
    Math.floor,
    Math.frac,
    Math.gm,
    Math.inv,
    Math.log10,
    Math.log2,
    Math.ln,
    Math.mul,
    Math.pow,
    Math.powu,
    Math.sqrt
} for SD59x18 global;

/*//////////////////////////////////////////////////////////////////////////
                                HELPER FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

using {
    Helpers.add,
    Helpers.and,
    Helpers.eq,
    Helpers.gt,
    Helpers.gte,
    Helpers.isZero,
    Helpers.lshift,
    Helpers.lt,
    Helpers.lte,
    Helpers.mod,
    Helpers.neq,
    Helpers.not,
    Helpers.or,
    Helpers.rshift,
    Helpers.sub,
    Helpers.uncheckedAdd,
    Helpers.uncheckedSub,
    Helpers.uncheckedUnary,
    Helpers.xor
} for SD59x18 global;

/*//////////////////////////////////////////////////////////////////////////
                                    OPERATORS
//////////////////////////////////////////////////////////////////////////*/

// The global "using for" directive makes it possible to use these operators on the SD59x18 type.
using {
    Helpers.add as +,
    Helpers.and2 as &,
    Math.div as /,
    Helpers.eq as ==,
    Helpers.gt as >,
    Helpers.gte as >=,
    Helpers.lt as <,
    Helpers.lte as <=,
    Helpers.mod as %,
    Math.mul as *,
    Helpers.neq as !=,
    Helpers.not as ~,
    Helpers.or as |,
    Helpers.sub as -,
    Helpers.unary as -,
    Helpers.xor as ^
} for SD59x18 global;

File 23 of 33 : Constants.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { SD59x18 } from "./ValueType.sol";

// NOTICE: the "u" prefix stands for "unwrapped".

/// @dev Euler's number as an SD59x18 number.
SD59x18 constant E = SD59x18.wrap(2_718281828459045235);

/// @dev The maximum input permitted in {exp}.
int256 constant uEXP_MAX_INPUT = 133_084258667509499440;
SD59x18 constant EXP_MAX_INPUT = SD59x18.wrap(uEXP_MAX_INPUT);

/// @dev Any value less than this returns 0 in {exp}.
int256 constant uEXP_MIN_THRESHOLD = -41_446531673892822322;
SD59x18 constant EXP_MIN_THRESHOLD = SD59x18.wrap(uEXP_MIN_THRESHOLD);

/// @dev The maximum input permitted in {exp2}.
int256 constant uEXP2_MAX_INPUT = 192e18 - 1;
SD59x18 constant EXP2_MAX_INPUT = SD59x18.wrap(uEXP2_MAX_INPUT);

/// @dev Any value less than this returns 0 in {exp2}.
int256 constant uEXP2_MIN_THRESHOLD = -59_794705707972522261;
SD59x18 constant EXP2_MIN_THRESHOLD = SD59x18.wrap(uEXP2_MIN_THRESHOLD);

/// @dev Half the UNIT number.
int256 constant uHALF_UNIT = 0.5e18;
SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT);

/// @dev $log_2(10)$ as an SD59x18 number.
int256 constant uLOG2_10 = 3_321928094887362347;
SD59x18 constant LOG2_10 = SD59x18.wrap(uLOG2_10);

/// @dev $log_2(e)$ as an SD59x18 number.
int256 constant uLOG2_E = 1_442695040888963407;
SD59x18 constant LOG2_E = SD59x18.wrap(uLOG2_E);

/// @dev The maximum value an SD59x18 number can have.
int256 constant uMAX_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_792003956564819967;
SD59x18 constant MAX_SD59x18 = SD59x18.wrap(uMAX_SD59x18);

/// @dev The maximum whole value an SD59x18 number can have.
int256 constant uMAX_WHOLE_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_000000000000000000;
SD59x18 constant MAX_WHOLE_SD59x18 = SD59x18.wrap(uMAX_WHOLE_SD59x18);

/// @dev The minimum value an SD59x18 number can have.
int256 constant uMIN_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_792003956564819968;
SD59x18 constant MIN_SD59x18 = SD59x18.wrap(uMIN_SD59x18);

/// @dev The minimum whole value an SD59x18 number can have.
int256 constant uMIN_WHOLE_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_000000000000000000;
SD59x18 constant MIN_WHOLE_SD59x18 = SD59x18.wrap(uMIN_WHOLE_SD59x18);

/// @dev PI as an SD59x18 number.
SD59x18 constant PI = SD59x18.wrap(3_141592653589793238);

/// @dev The unit number, which gives the decimal precision of SD59x18.
int256 constant uUNIT = 1e18;
SD59x18 constant UNIT = SD59x18.wrap(1e18);

/// @dev The unit number squared.
int256 constant uUNIT_SQUARED = 1e36;
SD59x18 constant UNIT_SQUARED = SD59x18.wrap(uUNIT_SQUARED);

/// @dev Zero as an SD59x18 number.
SD59x18 constant ZERO = SD59x18.wrap(0);

File 24 of 33 : ValueType.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Casting.sol" as Casting;

/// @notice The signed 1.18-decimal fixed-point number representation, which can have up to 1 digit and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
/// type int64. This is useful when end users want to use int64 to save gas, e.g. with tight variable packing in contract
/// storage.
type SD1x18 is int64;

/*//////////////////////////////////////////////////////////////////////////
                                    CASTING
//////////////////////////////////////////////////////////////////////////*/

using {
    Casting.intoSD59x18,
    Casting.intoUD2x18,
    Casting.intoUD60x18,
    Casting.intoUint256,
    Casting.intoUint128,
    Casting.intoUint40,
    Casting.unwrap
} for SD1x18 global;

File 25 of 33 : Constants.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { SD1x18 } from "./ValueType.sol";

/// @dev Euler's number as an SD1x18 number.
SD1x18 constant E = SD1x18.wrap(2_718281828459045235);

/// @dev The maximum value an SD1x18 number can have.
int64 constant uMAX_SD1x18 = 9_223372036854775807;
SD1x18 constant MAX_SD1x18 = SD1x18.wrap(uMAX_SD1x18);

/// @dev The maximum value an SD1x18 number can have.
int64 constant uMIN_SD1x18 = -9_223372036854775808;
SD1x18 constant MIN_SD1x18 = SD1x18.wrap(uMIN_SD1x18);

/// @dev PI as an SD1x18 number.
SD1x18 constant PI = SD1x18.wrap(3_141592653589793238);

/// @dev The unit number, which gives the decimal precision of SD1x18.
SD1x18 constant UNIT = SD1x18.wrap(1e18);
int64 constant uUNIT = 1e18;

File 26 of 33 : Casting.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "../Common.sol" as Common;
import "./Errors.sol" as Errors;
import { uMAX_SD1x18 } from "../sd1x18/Constants.sol";
import { SD1x18 } from "../sd1x18/ValueType.sol";
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { UD60x18 } from "../ud60x18/ValueType.sol";
import { UD2x18 } from "./ValueType.sol";

/// @notice Casts a UD2x18 number into SD1x18.
/// - x must be less than or equal to `uMAX_SD1x18`.
function intoSD1x18(UD2x18 x) pure returns (SD1x18 result) {
    uint64 xUint = UD2x18.unwrap(x);
    if (xUint > uint64(uMAX_SD1x18)) {
        revert Errors.PRBMath_UD2x18_IntoSD1x18_Overflow(x);
    }
    result = SD1x18.wrap(int64(xUint));
}

/// @notice Casts a UD2x18 number into SD59x18.
/// @dev There is no overflow check because the domain of UD2x18 is a subset of SD59x18.
function intoSD59x18(UD2x18 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(int256(uint256(UD2x18.unwrap(x))));
}

/// @notice Casts a UD2x18 number into UD60x18.
/// @dev There is no overflow check because the domain of UD2x18 is a subset of UD60x18.
function intoUD60x18(UD2x18 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(UD2x18.unwrap(x));
}

/// @notice Casts a UD2x18 number into uint128.
/// @dev There is no overflow check because the domain of UD2x18 is a subset of uint128.
function intoUint128(UD2x18 x) pure returns (uint128 result) {
    result = uint128(UD2x18.unwrap(x));
}

/// @notice Casts a UD2x18 number into uint256.
/// @dev There is no overflow check because the domain of UD2x18 is a subset of uint256.
function intoUint256(UD2x18 x) pure returns (uint256 result) {
    result = uint256(UD2x18.unwrap(x));
}

/// @notice Casts a UD2x18 number into uint40.
/// @dev Requirements:
/// - x must be less than or equal to `MAX_UINT40`.
function intoUint40(UD2x18 x) pure returns (uint40 result) {
    uint64 xUint = UD2x18.unwrap(x);
    if (xUint > uint64(Common.MAX_UINT40)) {
        revert Errors.PRBMath_UD2x18_IntoUint40_Overflow(x);
    }
    result = uint40(xUint);
}

/// @notice Alias for {wrap}.
function ud2x18(uint64 x) pure returns (UD2x18 result) {
    result = UD2x18.wrap(x);
}

/// @notice Unwrap a UD2x18 number into uint64.
function unwrap(UD2x18 x) pure returns (uint64 result) {
    result = UD2x18.unwrap(x);
}

/// @notice Wraps a uint64 number into UD2x18.
function wrap(uint64 x) pure returns (UD2x18 result) {
    result = UD2x18.wrap(x);
}

File 27 of 33 : Math.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "../Common.sol" as Common;
import "./Errors.sol" as Errors;
import {
    uEXP_MAX_INPUT,
    uEXP2_MAX_INPUT,
    uEXP_MIN_THRESHOLD,
    uEXP2_MIN_THRESHOLD,
    uHALF_UNIT,
    uLOG2_10,
    uLOG2_E,
    uMAX_SD59x18,
    uMAX_WHOLE_SD59x18,
    uMIN_SD59x18,
    uMIN_WHOLE_SD59x18,
    UNIT,
    uUNIT,
    uUNIT_SQUARED,
    ZERO
} from "./Constants.sol";
import { wrap } from "./Helpers.sol";
import { SD59x18 } from "./ValueType.sol";

/// @notice Calculates the absolute value of x.
///
/// @dev Requirements:
/// - x must be greater than `MIN_SD59x18`.
///
/// @param x The SD59x18 number for which to calculate the absolute value.
/// @param result The absolute value of x as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function abs(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt == uMIN_SD59x18) {
        revert Errors.PRBMath_SD59x18_Abs_MinSD59x18();
    }
    result = xInt < 0 ? wrap(-xInt) : x;
}

/// @notice Calculates the arithmetic average of x and y.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// @param x The first operand as an SD59x18 number.
/// @param y The second operand as an SD59x18 number.
/// @return result The arithmetic average as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function avg(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    int256 yInt = y.unwrap();

    unchecked {
        // This operation is equivalent to `x / 2 +  y / 2`, and it can never overflow.
        int256 sum = (xInt >> 1) + (yInt >> 1);

        if (sum < 0) {
            // If at least one of x and y is odd, add 1 to the result, because shifting negative numbers to the right
            // rounds toward negative infinity. The right part is equivalent to `sum + (x % 2 == 1 || y % 2 == 1)`.
            assembly ("memory-safe") {
                result := add(sum, and(or(xInt, yInt), 1))
            }
        } else {
            // Add 1 if both x and y are odd to account for the double 0.5 remainder truncated after shifting.
            result = wrap(sum + (xInt & yInt & 1));
        }
    }
}

/// @notice Yields the smallest whole number greater than or equal to x.
///
/// @dev Optimized for fractional value inputs, because every whole value has (1e18 - 1) fractional counterparts.
/// See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
///
/// Requirements:
/// - x must be less than or equal to `MAX_WHOLE_SD59x18`.
///
/// @param x The SD59x18 number to ceil.
/// @param result The smallest whole number greater than or equal to x, as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function ceil(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt > uMAX_WHOLE_SD59x18) {
        revert Errors.PRBMath_SD59x18_Ceil_Overflow(x);
    }

    int256 remainder = xInt % uUNIT;
    if (remainder == 0) {
        result = x;
    } else {
        unchecked {
            // Solidity uses C fmod style, which returns a modulus with the same sign as x.
            int256 resultInt = xInt - remainder;
            if (xInt > 0) {
                resultInt += uUNIT;
            }
            result = wrap(resultInt);
        }
    }
}

/// @notice Divides two SD59x18 numbers, returning a new SD59x18 number.
///
/// @dev This is an extension of {Common.mulDiv} for signed numbers, which works by computing the signs and the absolute
/// values separately.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv}.
/// - The result is rounded toward zero.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv}.
/// - None of the inputs can be `MIN_SD59x18`.
/// - The denominator must not be zero.
/// - The result must fit in SD59x18.
///
/// @param x The numerator as an SD59x18 number.
/// @param y The denominator as an SD59x18 number.
/// @param result The quotient as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function div(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    int256 yInt = y.unwrap();
    if (xInt == uMIN_SD59x18 || yInt == uMIN_SD59x18) {
        revert Errors.PRBMath_SD59x18_Div_InputTooSmall();
    }

    // Get hold of the absolute values of x and y.
    uint256 xAbs;
    uint256 yAbs;
    unchecked {
        xAbs = xInt < 0 ? uint256(-xInt) : uint256(xInt);
        yAbs = yInt < 0 ? uint256(-yInt) : uint256(yInt);
    }

    // Compute the absolute value (x*UNIT÷y). The resulting value must fit in SD59x18.
    uint256 resultAbs = Common.mulDiv(xAbs, uint256(uUNIT), yAbs);
    if (resultAbs > uint256(uMAX_SD59x18)) {
        revert Errors.PRBMath_SD59x18_Div_Overflow(x, y);
    }

    // Check if x and y have the same sign using two's complement representation. The left-most bit represents the sign (1 for
    // negative, 0 for positive or zero).
    bool sameSign = (xInt ^ yInt) > -1;

    // If the inputs have the same sign, the result should be positive. Otherwise, it should be negative.
    unchecked {
        result = wrap(sameSign ? int256(resultAbs) : -int256(resultAbs));
    }
}

/// @notice Calculates the natural exponent of x using the following formula:
///
/// $$
/// e^x = 2^{x * log_2{e}}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {exp2}.
///
/// Requirements:
/// - Refer to the requirements in {exp2}.
/// - x must be less than 133_084258667509499441.
///
/// @param x The exponent as an SD59x18 number.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();

    // Any input less than the threshold returns zero.
    // This check also prevents an overflow for very small numbers.
    if (xInt < uEXP_MIN_THRESHOLD) {
        return ZERO;
    }

    // This check prevents values greater than 192e18 from being passed to {exp2}.
    if (xInt > uEXP_MAX_INPUT) {
        revert Errors.PRBMath_SD59x18_Exp_InputTooBig(x);
    }

    unchecked {
        // Inline the fixed-point multiplication to save gas.
        int256 doubleUnitProduct = xInt * uLOG2_E;
        result = exp2(wrap(doubleUnitProduct / uUNIT));
    }
}

/// @notice Calculates the binary exponent of x using the binary fraction method using the following formula:
///
/// $$
/// 2^{-x} = \frac{1}{2^x}
/// $$
///
/// @dev See https://ethereum.stackexchange.com/q/79903/24693.
///
/// Notes:
/// - If x is less than -59_794705707972522261, the result is zero.
///
/// Requirements:
/// - x must be less than 192e18.
/// - The result must fit in SD59x18.
///
/// @param x The exponent as an SD59x18 number.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp2(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt < 0) {
        // The inverse of any number less than the threshold is truncated to zero.
        if (xInt < uEXP2_MIN_THRESHOLD) {
            return ZERO;
        }

        unchecked {
            // Inline the fixed-point inversion to save gas.
            result = wrap(uUNIT_SQUARED / exp2(wrap(-xInt)).unwrap());
        }
    } else {
        // Numbers greater than or equal to 192e18 don't fit in the 192.64-bit format.
        if (xInt > uEXP2_MAX_INPUT) {
            revert Errors.PRBMath_SD59x18_Exp2_InputTooBig(x);
        }

        unchecked {
            // Convert x to the 192.64-bit fixed-point format.
            uint256 x_192x64 = uint256((xInt << 64) / uUNIT);

            // It is safe to cast the result to int256 due to the checks above.
            result = wrap(int256(Common.exp2(x_192x64)));
        }
    }
}

/// @notice Yields the greatest whole number less than or equal to x.
///
/// @dev Optimized for fractional value inputs, because for every whole value there are (1e18 - 1) fractional
/// counterparts. See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
///
/// Requirements:
/// - x must be greater than or equal to `MIN_WHOLE_SD59x18`.
///
/// @param x The SD59x18 number to floor.
/// @param result The greatest whole number less than or equal to x, as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function floor(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt < uMIN_WHOLE_SD59x18) {
        revert Errors.PRBMath_SD59x18_Floor_Underflow(x);
    }

    int256 remainder = xInt % uUNIT;
    if (remainder == 0) {
        result = x;
    } else {
        unchecked {
            // Solidity uses C fmod style, which returns a modulus with the same sign as x.
            int256 resultInt = xInt - remainder;
            if (xInt < 0) {
                resultInt -= uUNIT;
            }
            result = wrap(resultInt);
        }
    }
}

/// @notice Yields the excess beyond the floor of x for positive numbers and the part of the number to the right.
/// of the radix point for negative numbers.
/// @dev Based on the odd function definition. https://en.wikipedia.org/wiki/Fractional_part
/// @param x The SD59x18 number to get the fractional part of.
/// @param result The fractional part of x as an SD59x18 number.
function frac(SD59x18 x) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() % uUNIT);
}

/// @notice Calculates the geometric mean of x and y, i.e. $\sqrt{x * y}$.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x * y must fit in SD59x18.
/// - x * y must not be negative, since complex numbers are not supported.
///
/// @param x The first operand as an SD59x18 number.
/// @param y The second operand as an SD59x18 number.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function gm(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    int256 yInt = y.unwrap();
    if (xInt == 0 || yInt == 0) {
        return ZERO;
    }

    unchecked {
        // Equivalent to `xy / x != y`. Checking for overflow this way is faster than letting Solidity do it.
        int256 xyInt = xInt * yInt;
        if (xyInt / xInt != yInt) {
            revert Errors.PRBMath_SD59x18_Gm_Overflow(x, y);
        }

        // The product must not be negative, since complex numbers are not supported.
        if (xyInt < 0) {
            revert Errors.PRBMath_SD59x18_Gm_NegativeProduct(x, y);
        }

        // We don't need to multiply the result by `UNIT` here because the x*y product picked up a factor of `UNIT`
        // during multiplication. See the comments in {Common.sqrt}.
        uint256 resultUint = Common.sqrt(uint256(xyInt));
        result = wrap(int256(resultUint));
    }
}

/// @notice Calculates the inverse of x.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x must not be zero.
///
/// @param x The SD59x18 number for which to calculate the inverse.
/// @return result The inverse as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function inv(SD59x18 x) pure returns (SD59x18 result) {
    result = wrap(uUNIT_SQUARED / x.unwrap());
}

/// @notice Calculates the natural logarithm of x using the following formula:
///
/// $$
/// ln{x} = log_2{x} / log_2{e}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
/// - The precision isn't sufficiently fine-grained to return exactly `UNIT` when the input is `E`.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The SD59x18 number for which to calculate the natural logarithm.
/// @return result The natural logarithm as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function ln(SD59x18 x) pure returns (SD59x18 result) {
    // Inline the fixed-point multiplication to save gas. This is overflow-safe because the maximum value that
    // {log2} can return is ~195_205294292027477728.
    result = wrap(log2(x).unwrap() * uUNIT / uLOG2_E);
}

/// @notice Calculates the common logarithm of x using the following formula:
///
/// $$
/// log_{10}{x} = log_2{x} / log_2{10}
/// $$
///
/// However, if x is an exact power of ten, a hard coded value is returned.
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The SD59x18 number for which to calculate the common logarithm.
/// @return result The common logarithm as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function log10(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt < 0) {
        revert Errors.PRBMath_SD59x18_Log_InputTooSmall(x);
    }

    // Note that the `mul` in this block is the standard multiplication operation, not {SD59x18.mul}.
    // prettier-ignore
    assembly ("memory-safe") {
        switch x
        case 1 { result := mul(uUNIT, sub(0, 18)) }
        case 10 { result := mul(uUNIT, sub(1, 18)) }
        case 100 { result := mul(uUNIT, sub(2, 18)) }
        case 1000 { result := mul(uUNIT, sub(3, 18)) }
        case 10000 { result := mul(uUNIT, sub(4, 18)) }
        case 100000 { result := mul(uUNIT, sub(5, 18)) }
        case 1000000 { result := mul(uUNIT, sub(6, 18)) }
        case 10000000 { result := mul(uUNIT, sub(7, 18)) }
        case 100000000 { result := mul(uUNIT, sub(8, 18)) }
        case 1000000000 { result := mul(uUNIT, sub(9, 18)) }
        case 10000000000 { result := mul(uUNIT, sub(10, 18)) }
        case 100000000000 { result := mul(uUNIT, sub(11, 18)) }
        case 1000000000000 { result := mul(uUNIT, sub(12, 18)) }
        case 10000000000000 { result := mul(uUNIT, sub(13, 18)) }
        case 100000000000000 { result := mul(uUNIT, sub(14, 18)) }
        case 1000000000000000 { result := mul(uUNIT, sub(15, 18)) }
        case 10000000000000000 { result := mul(uUNIT, sub(16, 18)) }
        case 100000000000000000 { result := mul(uUNIT, sub(17, 18)) }
        case 1000000000000000000 { result := 0 }
        case 10000000000000000000 { result := uUNIT }
        case 100000000000000000000 { result := mul(uUNIT, 2) }
        case 1000000000000000000000 { result := mul(uUNIT, 3) }
        case 10000000000000000000000 { result := mul(uUNIT, 4) }
        case 100000000000000000000000 { result := mul(uUNIT, 5) }
        case 1000000000000000000000000 { result := mul(uUNIT, 6) }
        case 10000000000000000000000000 { result := mul(uUNIT, 7) }
        case 100000000000000000000000000 { result := mul(uUNIT, 8) }
        case 1000000000000000000000000000 { result := mul(uUNIT, 9) }
        case 10000000000000000000000000000 { result := mul(uUNIT, 10) }
        case 100000000000000000000000000000 { result := mul(uUNIT, 11) }
        case 1000000000000000000000000000000 { result := mul(uUNIT, 12) }
        case 10000000000000000000000000000000 { result := mul(uUNIT, 13) }
        case 100000000000000000000000000000000 { result := mul(uUNIT, 14) }
        case 1000000000000000000000000000000000 { result := mul(uUNIT, 15) }
        case 10000000000000000000000000000000000 { result := mul(uUNIT, 16) }
        case 100000000000000000000000000000000000 { result := mul(uUNIT, 17) }
        case 1000000000000000000000000000000000000 { result := mul(uUNIT, 18) }
        case 10000000000000000000000000000000000000 { result := mul(uUNIT, 19) }
        case 100000000000000000000000000000000000000 { result := mul(uUNIT, 20) }
        case 1000000000000000000000000000000000000000 { result := mul(uUNIT, 21) }
        case 10000000000000000000000000000000000000000 { result := mul(uUNIT, 22) }
        case 100000000000000000000000000000000000000000 { result := mul(uUNIT, 23) }
        case 1000000000000000000000000000000000000000000 { result := mul(uUNIT, 24) }
        case 10000000000000000000000000000000000000000000 { result := mul(uUNIT, 25) }
        case 100000000000000000000000000000000000000000000 { result := mul(uUNIT, 26) }
        case 1000000000000000000000000000000000000000000000 { result := mul(uUNIT, 27) }
        case 10000000000000000000000000000000000000000000000 { result := mul(uUNIT, 28) }
        case 100000000000000000000000000000000000000000000000 { result := mul(uUNIT, 29) }
        case 1000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 30) }
        case 10000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 31) }
        case 100000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 32) }
        case 1000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 33) }
        case 10000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 34) }
        case 100000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 35) }
        case 1000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 36) }
        case 10000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 37) }
        case 100000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 38) }
        case 1000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 39) }
        case 10000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 40) }
        case 100000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 41) }
        case 1000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 42) }
        case 10000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 43) }
        case 100000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 44) }
        case 1000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 45) }
        case 10000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 46) }
        case 100000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 47) }
        case 1000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 48) }
        case 10000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 49) }
        case 100000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 50) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 51) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 52) }
        case 100000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 53) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 54) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 55) }
        case 100000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 56) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 57) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 58) }
        default { result := uMAX_SD59x18 }
    }

    if (result.unwrap() == uMAX_SD59x18) {
        unchecked {
            // Inline the fixed-point division to save gas.
            result = wrap(log2(x).unwrap() * uUNIT / uLOG2_10);
        }
    }
}

/// @notice Calculates the binary logarithm of x using the iterative approximation algorithm:
///
/// $$
/// log_2{x} = n + log_2{y}, \text{ where } y = x*2^{-n}, \ y \in [1, 2)
/// $$
///
/// For $0 \leq x \lt 1$, the input is inverted:
///
/// $$
/// log_2{x} = -log_2{\frac{1}{x}}
/// $$
///
/// @dev See https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation.
///
/// Notes:
/// - Due to the lossy precision of the iterative approximation, the results are not perfectly accurate to the last decimal.
///
/// Requirements:
/// - x must be greater than zero.
///
/// @param x The SD59x18 number for which to calculate the binary logarithm.
/// @return result The binary logarithm as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function log2(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt <= 0) {
        revert Errors.PRBMath_SD59x18_Log_InputTooSmall(x);
    }

    unchecked {
        int256 sign;
        if (xInt >= uUNIT) {
            sign = 1;
        } else {
            sign = -1;
            // Inline the fixed-point inversion to save gas.
            xInt = uUNIT_SQUARED / xInt;
        }

        // Calculate the integer part of the logarithm.
        uint256 n = Common.msb(uint256(xInt / uUNIT));

        // This is the integer part of the logarithm as an SD59x18 number. The operation can't overflow
        // because n is at most 255, `UNIT` is 1e18, and the sign is either 1 or -1.
        int256 resultInt = int256(n) * uUNIT;

        // Calculate $y = x * 2^{-n}$.
        int256 y = xInt >> n;

        // If y is the unit number, the fractional part is zero.
        if (y == uUNIT) {
            return wrap(resultInt * sign);
        }

        // Calculate the fractional part via the iterative approximation.
        // The `delta >>= 1` part is equivalent to `delta /= 2`, but shifting bits is more gas efficient.
        int256 DOUBLE_UNIT = 2e18;
        for (int256 delta = uHALF_UNIT; delta > 0; delta >>= 1) {
            y = (y * y) / uUNIT;

            // Is y^2 >= 2e18 and so in the range [2e18, 4e18)?
            if (y >= DOUBLE_UNIT) {
                // Add the 2^{-m} factor to the logarithm.
                resultInt = resultInt + delta;

                // Halve y, which corresponds to z/2 in the Wikipedia article.
                y >>= 1;
            }
        }
        resultInt *= sign;
        result = wrap(resultInt);
    }
}

/// @notice Multiplies two SD59x18 numbers together, returning a new SD59x18 number.
///
/// @dev Notes:
/// - Refer to the notes in {Common.mulDiv18}.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv18}.
/// - None of the inputs can be `MIN_SD59x18`.
/// - The result must fit in SD59x18.
///
/// @param x The multiplicand as an SD59x18 number.
/// @param y The multiplier as an SD59x18 number.
/// @return result The product as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function mul(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    int256 yInt = y.unwrap();
    if (xInt == uMIN_SD59x18 || yInt == uMIN_SD59x18) {
        revert Errors.PRBMath_SD59x18_Mul_InputTooSmall();
    }

    // Get hold of the absolute values of x and y.
    uint256 xAbs;
    uint256 yAbs;
    unchecked {
        xAbs = xInt < 0 ? uint256(-xInt) : uint256(xInt);
        yAbs = yInt < 0 ? uint256(-yInt) : uint256(yInt);
    }

    // Compute the absolute value (x*y÷UNIT). The resulting value must fit in SD59x18.
    uint256 resultAbs = Common.mulDiv18(xAbs, yAbs);
    if (resultAbs > uint256(uMAX_SD59x18)) {
        revert Errors.PRBMath_SD59x18_Mul_Overflow(x, y);
    }

    // Check if x and y have the same sign using two's complement representation. The left-most bit represents the sign (1 for
    // negative, 0 for positive or zero).
    bool sameSign = (xInt ^ yInt) > -1;

    // If the inputs have the same sign, the result should be positive. Otherwise, it should be negative.
    unchecked {
        result = wrap(sameSign ? int256(resultAbs) : -int256(resultAbs));
    }
}

/// @notice Raises x to the power of y using the following formula:
///
/// $$
/// x^y = 2^{log_2{x} * y}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {exp2}, {log2}, and {mul}.
/// - Returns `UNIT` for 0^0.
///
/// Requirements:
/// - Refer to the requirements in {exp2}, {log2}, and {mul}.
///
/// @param x The base as an SD59x18 number.
/// @param y Exponent to raise x to, as an SD59x18 number
/// @return result x raised to power y, as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function pow(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    int256 yInt = y.unwrap();

    // If both x and y are zero, the result is `UNIT`. If just x is zero, the result is always zero.
    if (xInt == 0) {
        return yInt == 0 ? UNIT : ZERO;
    }
    // If x is `UNIT`, the result is always `UNIT`.
    else if (xInt == uUNIT) {
        return UNIT;
    }

    // If y is zero, the result is always `UNIT`.
    if (yInt == 0) {
        return UNIT;
    }
    // If y is `UNIT`, the result is always x.
    else if (yInt == uUNIT) {
        return x;
    }

    // Calculate the result using the formula.
    result = exp2(mul(log2(x), y));
}

/// @notice Raises x (an SD59x18 number) to the power y (an unsigned basic integer) using the well-known
/// algorithm "exponentiation by squaring".
///
/// @dev See https://en.wikipedia.org/wiki/Exponentiation_by_squaring.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv18}.
/// - Returns `UNIT` for 0^0.
///
/// Requirements:
/// - Refer to the requirements in {abs} and {Common.mulDiv18}.
/// - The result must fit in SD59x18.
///
/// @param x The base as an SD59x18 number.
/// @param y The exponent as a uint256.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function powu(SD59x18 x, uint256 y) pure returns (SD59x18 result) {
    uint256 xAbs = uint256(abs(x).unwrap());

    // Calculate the first iteration of the loop in advance.
    uint256 resultAbs = y & 1 > 0 ? xAbs : uint256(uUNIT);

    // Equivalent to `for(y /= 2; y > 0; y /= 2)`.
    uint256 yAux = y;
    for (yAux >>= 1; yAux > 0; yAux >>= 1) {
        xAbs = Common.mulDiv18(xAbs, xAbs);

        // Equivalent to `y % 2 == 1`.
        if (yAux & 1 > 0) {
            resultAbs = Common.mulDiv18(resultAbs, xAbs);
        }
    }

    // The result must fit in SD59x18.
    if (resultAbs > uint256(uMAX_SD59x18)) {
        revert Errors.PRBMath_SD59x18_Powu_Overflow(x, y);
    }

    unchecked {
        // Is the base negative and the exponent odd? If yes, the result should be negative.
        int256 resultInt = int256(resultAbs);
        bool isNegative = x.unwrap() < 0 && y & 1 == 1;
        if (isNegative) {
            resultInt = -resultInt;
        }
        result = wrap(resultInt);
    }
}

/// @notice Calculates the square root of x using the Babylonian method.
///
/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
///
/// Notes:
/// - Only the positive root is returned.
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x cannot be negative, since complex numbers are not supported.
/// - x must be less than `MAX_SD59x18 / UNIT`.
///
/// @param x The SD59x18 number for which to calculate the square root.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function sqrt(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt < 0) {
        revert Errors.PRBMath_SD59x18_Sqrt_NegativeInput(x);
    }
    if (xInt > uMAX_SD59x18 / uUNIT) {
        revert Errors.PRBMath_SD59x18_Sqrt_Overflow(x);
    }

    unchecked {
        // Multiply x by `UNIT` to account for the factor of `UNIT` picked up when multiplying two SD59x18 numbers.
        // In this case, the two numbers are both the square root.
        uint256 resultUint = Common.sqrt(uint256(xInt * uUNIT));
        result = wrap(int256(resultUint));
    }
}

File 28 of 33 : Helpers.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { wrap } from "./Casting.sol";
import { SD59x18 } from "./ValueType.sol";

/// @notice Implements the checked addition operation (+) in the SD59x18 type.
function add(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    return wrap(x.unwrap() + y.unwrap());
}

/// @notice Implements the AND (&) bitwise operation in the SD59x18 type.
function and(SD59x18 x, int256 bits) pure returns (SD59x18 result) {
    return wrap(x.unwrap() & bits);
}

/// @notice Implements the AND (&) bitwise operation in the SD59x18 type.
function and2(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    return wrap(x.unwrap() & y.unwrap());
}

/// @notice Implements the equal (=) operation in the SD59x18 type.
function eq(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() == y.unwrap();
}

/// @notice Implements the greater than operation (>) in the SD59x18 type.
function gt(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() > y.unwrap();
}

/// @notice Implements the greater than or equal to operation (>=) in the SD59x18 type.
function gte(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() >= y.unwrap();
}

/// @notice Implements a zero comparison check function in the SD59x18 type.
function isZero(SD59x18 x) pure returns (bool result) {
    result = x.unwrap() == 0;
}

/// @notice Implements the left shift operation (<<) in the SD59x18 type.
function lshift(SD59x18 x, uint256 bits) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() << bits);
}

/// @notice Implements the lower than operation (<) in the SD59x18 type.
function lt(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() < y.unwrap();
}

/// @notice Implements the lower than or equal to operation (<=) in the SD59x18 type.
function lte(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() <= y.unwrap();
}

/// @notice Implements the unchecked modulo operation (%) in the SD59x18 type.
function mod(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() % y.unwrap());
}

/// @notice Implements the not equal operation (!=) in the SD59x18 type.
function neq(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() != y.unwrap();
}

/// @notice Implements the NOT (~) bitwise operation in the SD59x18 type.
function not(SD59x18 x) pure returns (SD59x18 result) {
    result = wrap(~x.unwrap());
}

/// @notice Implements the OR (|) bitwise operation in the SD59x18 type.
function or(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() | y.unwrap());
}

/// @notice Implements the right shift operation (>>) in the SD59x18 type.
function rshift(SD59x18 x, uint256 bits) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() >> bits);
}

/// @notice Implements the checked subtraction operation (-) in the SD59x18 type.
function sub(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() - y.unwrap());
}

/// @notice Implements the checked unary minus operation (-) in the SD59x18 type.
function unary(SD59x18 x) pure returns (SD59x18 result) {
    result = wrap(-x.unwrap());
}

/// @notice Implements the unchecked addition operation (+) in the SD59x18 type.
function uncheckedAdd(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    unchecked {
        result = wrap(x.unwrap() + y.unwrap());
    }
}

/// @notice Implements the unchecked subtraction operation (-) in the SD59x18 type.
function uncheckedSub(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    unchecked {
        result = wrap(x.unwrap() - y.unwrap());
    }
}

/// @notice Implements the unchecked unary minus operation (-) in the SD59x18 type.
function uncheckedUnary(SD59x18 x) pure returns (SD59x18 result) {
    unchecked {
        result = wrap(-x.unwrap());
    }
}

/// @notice Implements the XOR (^) bitwise operation in the SD59x18 type.
function xor(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() ^ y.unwrap());
}

File 29 of 33 : Casting.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Errors.sol" as CastingErrors;
import { MAX_UINT128, MAX_UINT40 } from "../Common.sol";
import { uMAX_SD1x18, uMIN_SD1x18 } from "../sd1x18/Constants.sol";
import { SD1x18 } from "../sd1x18/ValueType.sol";
import { uMAX_UD2x18 } from "../ud2x18/Constants.sol";
import { UD2x18 } from "../ud2x18/ValueType.sol";
import { UD60x18 } from "../ud60x18/ValueType.sol";
import { SD59x18 } from "./ValueType.sol";

/// @notice Casts an SD59x18 number into int256.
/// @dev This is basically a functional alias for {unwrap}.
function intoInt256(SD59x18 x) pure returns (int256 result) {
    result = SD59x18.unwrap(x);
}

/// @notice Casts an SD59x18 number into SD1x18.
/// @dev Requirements:
/// - x must be greater than or equal to `uMIN_SD1x18`.
/// - x must be less than or equal to `uMAX_SD1x18`.
function intoSD1x18(SD59x18 x) pure returns (SD1x18 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < uMIN_SD1x18) {
        revert CastingErrors.PRBMath_SD59x18_IntoSD1x18_Underflow(x);
    }
    if (xInt > uMAX_SD1x18) {
        revert CastingErrors.PRBMath_SD59x18_IntoSD1x18_Overflow(x);
    }
    result = SD1x18.wrap(int64(xInt));
}

/// @notice Casts an SD59x18 number into UD2x18.
/// @dev Requirements:
/// - x must be positive.
/// - x must be less than or equal to `uMAX_UD2x18`.
function intoUD2x18(SD59x18 x) pure returns (UD2x18 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD59x18_IntoUD2x18_Underflow(x);
    }
    if (xInt > int256(uint256(uMAX_UD2x18))) {
        revert CastingErrors.PRBMath_SD59x18_IntoUD2x18_Overflow(x);
    }
    result = UD2x18.wrap(uint64(uint256(xInt)));
}

/// @notice Casts an SD59x18 number into UD60x18.
/// @dev Requirements:
/// - x must be positive.
function intoUD60x18(SD59x18 x) pure returns (UD60x18 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD59x18_IntoUD60x18_Underflow(x);
    }
    result = UD60x18.wrap(uint256(xInt));
}

/// @notice Casts an SD59x18 number into uint256.
/// @dev Requirements:
/// - x must be positive.
function intoUint256(SD59x18 x) pure returns (uint256 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD59x18_IntoUint256_Underflow(x);
    }
    result = uint256(xInt);
}

/// @notice Casts an SD59x18 number into uint128.
/// @dev Requirements:
/// - x must be positive.
/// - x must be less than or equal to `uMAX_UINT128`.
function intoUint128(SD59x18 x) pure returns (uint128 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD59x18_IntoUint128_Underflow(x);
    }
    if (xInt > int256(uint256(MAX_UINT128))) {
        revert CastingErrors.PRBMath_SD59x18_IntoUint128_Overflow(x);
    }
    result = uint128(uint256(xInt));
}

/// @notice Casts an SD59x18 number into uint40.
/// @dev Requirements:
/// - x must be positive.
/// - x must be less than or equal to `MAX_UINT40`.
function intoUint40(SD59x18 x) pure returns (uint40 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD59x18_IntoUint40_Underflow(x);
    }
    if (xInt > int256(uint256(MAX_UINT40))) {
        revert CastingErrors.PRBMath_SD59x18_IntoUint40_Overflow(x);
    }
    result = uint40(uint256(xInt));
}

/// @notice Alias for {wrap}.
function sd(int256 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(x);
}

/// @notice Alias for {wrap}.
function sd59x18(int256 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(x);
}

/// @notice Unwraps an SD59x18 number into int256.
function unwrap(SD59x18 x) pure returns (int256 result) {
    result = SD59x18.unwrap(x);
}

/// @notice Wraps an int256 number into SD59x18.
function wrap(int256 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(x);
}

File 30 of 33 : Casting.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "../Common.sol" as Common;
import "./Errors.sol" as CastingErrors;
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { UD2x18 } from "../ud2x18/ValueType.sol";
import { UD60x18 } from "../ud60x18/ValueType.sol";
import { SD1x18 } from "./ValueType.sol";

/// @notice Casts an SD1x18 number into SD59x18.
/// @dev There is no overflow check because the domain of SD1x18 is a subset of SD59x18.
function intoSD59x18(SD1x18 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(int256(SD1x18.unwrap(x)));
}

/// @notice Casts an SD1x18 number into UD2x18.
/// - x must be positive.
function intoUD2x18(SD1x18 x) pure returns (UD2x18 result) {
    int64 xInt = SD1x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD1x18_ToUD2x18_Underflow(x);
    }
    result = UD2x18.wrap(uint64(xInt));
}

/// @notice Casts an SD1x18 number into UD60x18.
/// @dev Requirements:
/// - x must be positive.
function intoUD60x18(SD1x18 x) pure returns (UD60x18 result) {
    int64 xInt = SD1x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD1x18_ToUD60x18_Underflow(x);
    }
    result = UD60x18.wrap(uint64(xInt));
}

/// @notice Casts an SD1x18 number into uint256.
/// @dev Requirements:
/// - x must be positive.
function intoUint256(SD1x18 x) pure returns (uint256 result) {
    int64 xInt = SD1x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD1x18_ToUint256_Underflow(x);
    }
    result = uint256(uint64(xInt));
}

/// @notice Casts an SD1x18 number into uint128.
/// @dev Requirements:
/// - x must be positive.
function intoUint128(SD1x18 x) pure returns (uint128 result) {
    int64 xInt = SD1x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD1x18_ToUint128_Underflow(x);
    }
    result = uint128(uint64(xInt));
}

/// @notice Casts an SD1x18 number into uint40.
/// @dev Requirements:
/// - x must be positive.
/// - x must be less than or equal to `MAX_UINT40`.
function intoUint40(SD1x18 x) pure returns (uint40 result) {
    int64 xInt = SD1x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD1x18_ToUint40_Underflow(x);
    }
    if (xInt > int64(uint64(Common.MAX_UINT40))) {
        revert CastingErrors.PRBMath_SD1x18_ToUint40_Overflow(x);
    }
    result = uint40(uint64(xInt));
}

/// @notice Alias for {wrap}.
function sd1x18(int64 x) pure returns (SD1x18 result) {
    result = SD1x18.wrap(x);
}

/// @notice Unwraps an SD1x18 number into int64.
function unwrap(SD1x18 x) pure returns (int64 result) {
    result = SD1x18.unwrap(x);
}

/// @notice Wraps an int64 number into SD1x18.
function wrap(int64 x) pure returns (SD1x18 result) {
    result = SD1x18.wrap(x);
}

File 31 of 33 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { UD2x18 } from "./ValueType.sol";

/// @notice Thrown when trying to cast a UD2x18 number that doesn't fit in SD1x18.
error PRBMath_UD2x18_IntoSD1x18_Overflow(UD2x18 x);

/// @notice Thrown when trying to cast a UD2x18 number that doesn't fit in uint40.
error PRBMath_UD2x18_IntoUint40_Overflow(UD2x18 x);

File 32 of 33 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { SD59x18 } from "./ValueType.sol";

/// @notice Thrown when taking the absolute value of `MIN_SD59x18`.
error PRBMath_SD59x18_Abs_MinSD59x18();

/// @notice Thrown when ceiling a number overflows SD59x18.
error PRBMath_SD59x18_Ceil_Overflow(SD59x18 x);

/// @notice Thrown when converting a basic integer to the fixed-point format overflows SD59x18.
error PRBMath_SD59x18_Convert_Overflow(int256 x);

/// @notice Thrown when converting a basic integer to the fixed-point format underflows SD59x18.
error PRBMath_SD59x18_Convert_Underflow(int256 x);

/// @notice Thrown when dividing two numbers and one of them is `MIN_SD59x18`.
error PRBMath_SD59x18_Div_InputTooSmall();

/// @notice Thrown when dividing two numbers and one of the intermediary unsigned results overflows SD59x18.
error PRBMath_SD59x18_Div_Overflow(SD59x18 x, SD59x18 y);

/// @notice Thrown when taking the natural exponent of a base greater than 133_084258667509499441.
error PRBMath_SD59x18_Exp_InputTooBig(SD59x18 x);

/// @notice Thrown when taking the binary exponent of a base greater than 192e18.
error PRBMath_SD59x18_Exp2_InputTooBig(SD59x18 x);

/// @notice Thrown when flooring a number underflows SD59x18.
error PRBMath_SD59x18_Floor_Underflow(SD59x18 x);

/// @notice Thrown when taking the geometric mean of two numbers and their product is negative.
error PRBMath_SD59x18_Gm_NegativeProduct(SD59x18 x, SD59x18 y);

/// @notice Thrown when taking the geometric mean of two numbers and multiplying them overflows SD59x18.
error PRBMath_SD59x18_Gm_Overflow(SD59x18 x, SD59x18 y);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD1x18.
error PRBMath_SD59x18_IntoSD1x18_Overflow(SD59x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD1x18.
error PRBMath_SD59x18_IntoSD1x18_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD2x18.
error PRBMath_SD59x18_IntoUD2x18_Overflow(SD59x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD2x18.
error PRBMath_SD59x18_IntoUD2x18_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD60x18.
error PRBMath_SD59x18_IntoUD60x18_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint128.
error PRBMath_SD59x18_IntoUint128_Overflow(SD59x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint128.
error PRBMath_SD59x18_IntoUint128_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint256.
error PRBMath_SD59x18_IntoUint256_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint40.
error PRBMath_SD59x18_IntoUint40_Overflow(SD59x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint40.
error PRBMath_SD59x18_IntoUint40_Underflow(SD59x18 x);

/// @notice Thrown when taking the logarithm of a number less than or equal to zero.
error PRBMath_SD59x18_Log_InputTooSmall(SD59x18 x);

/// @notice Thrown when multiplying two numbers and one of the inputs is `MIN_SD59x18`.
error PRBMath_SD59x18_Mul_InputTooSmall();

/// @notice Thrown when multiplying two numbers and the intermediary absolute result overflows SD59x18.
error PRBMath_SD59x18_Mul_Overflow(SD59x18 x, SD59x18 y);

/// @notice Thrown when raising a number to a power and the intermediary absolute result overflows SD59x18.
error PRBMath_SD59x18_Powu_Overflow(SD59x18 x, uint256 y);

/// @notice Thrown when taking the square root of a negative number.
error PRBMath_SD59x18_Sqrt_NegativeInput(SD59x18 x);

/// @notice Thrown when the calculating the square root overflows SD59x18.
error PRBMath_SD59x18_Sqrt_Overflow(SD59x18 x);

File 33 of 33 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { SD1x18 } from "./ValueType.sol";

/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in UD2x18.
error PRBMath_SD1x18_ToUD2x18_Underflow(SD1x18 x);

/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in UD60x18.
error PRBMath_SD1x18_ToUD60x18_Underflow(SD1x18 x);

/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint128.
error PRBMath_SD1x18_ToUint128_Underflow(SD1x18 x);

/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint256.
error PRBMath_SD1x18_ToUint256_Underflow(SD1x18 x);

/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint40.
error PRBMath_SD1x18_ToUint40_Overflow(SD1x18 x);

/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint40.
error PRBMath_SD1x18_ToUint40_Underflow(SD1x18 x);

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "remappings": [
    "forge-std/=lib/forge-std/src/",
    "@prb/math/=lib/prb-math/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
    "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/",
    "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/"
  ],
  "evmVersion": "prague"
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"echoAdministration","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"maxCampaignTime","type":"uint256"}],"name":"CHECK_MAX_CAMPAIGN_DURATION","type":"error"},{"inputs":[],"name":"END_TIME_LOWER_THAN_START_TIME","type":"error"},{"inputs":[],"name":"MAX_PER_POST_BIGGER_THAN_RESERVED_AMOUNT","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"clonedCampaign","type":"address"}],"name":"NAME_EXISTS","type":"error"},{"inputs":[],"name":"NAME_LENGTH_LONGER_THAN_64_BYTES","type":"error"},{"inputs":[],"name":"NAME_LENGTH_LOWER_THAN_8_BYTES","type":"error"},{"inputs":[],"name":"NO_KPIS","type":"error"},{"inputs":[],"name":"NO_QUALIFICATION","type":"error"},{"inputs":[],"name":"NULL_CID","type":"error"},{"inputs":[],"name":"ONLY_ALLOWED_SOCIAL_KPIS","type":"error"},{"inputs":[],"name":"ONLY_PROTOCOL_ADMIN","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"ONLY_WHITELISTED_TOKEN","type":"error"},{"inputs":[{"internalType":"string","name":"qaActionKind","type":"string"}],"name":"QA_METHOD_OR_ORACLE_NOT_ALLOWED","type":"error"},{"inputs":[{"internalType":"string","name":"kpiActionKind","type":"string"}],"name":"SOCIAL_KPI_RATIO_IS_ZERO","type":"error"},{"inputs":[],"name":"START_TIME_IN_PAST","type":"error"},{"inputs":[{"internalType":"uint256","name":"sumOfPct","type":"uint256"}],"name":"SUM_OF_QUALIFICATION_PARAMS_MUST_BE_100","type":"error"},{"inputs":[{"internalType":"uint256","name":"sumOfPct","type":"uint256"}],"name":"SUM_OF_SOCIAL_KPI_PARAMS_MUST_BE_100","type":"error"},{"inputs":[],"name":"ZERO_ADDRESS_PROVIDED","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"clonedCampaign","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"implementation","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"uint256","name":"applicationFee","type":"uint256"},{"indexed":false,"internalType":"string","name":"EchoMarketData","type":"string"},{"indexed":false,"internalType":"uint256","name":"totalClonedCampaigns","type":"uint256"},{"components":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"address","name":"refundAddress","type":"address"},{"internalType":"string","name":"ipfsCID","type":"string"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint128","name":"maxPerPost","type":"uint128"},{"internalType":"uint128","name":"reservedAmount","type":"uint128"}],"internalType":"struct BudgetInfo","name":"budgetInfo","type":"tuple"},{"components":[{"internalType":"string","name":"social","type":"string"},{"components":[{"internalType":"uint8","name":"pct","type":"uint8"},{"internalType":"uint248","name":"min","type":"uint248"},{"internalType":"uint256","name":"ratio","type":"uint256"},{"internalType":"string","name":"method","type":"string"},{"internalType":"string","name":"extra","type":"string"}],"internalType":"struct KPI[]","name":"kpis","type":"tuple[]"}],"internalType":"struct SocialKPIs","name":"socialKPIs","type":"tuple"},{"components":[{"internalType":"enum QA_METHOD","name":"method","type":"uint8"},{"internalType":"uint88","name":"pct","type":"uint88"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"string","name":"kind","type":"string"}],"internalType":"struct QualificationData[]","name":"qaData","type":"tuple[]"}],"indexed":false,"internalType":"struct InitCampaign","name":"initCampaign","type":"tuple"}],"name":"CampaignCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"lastImplementation","type":"address"},{"indexed":true,"internalType":"address","name":"newImplementation","type":"address"}],"name":"ImplementationChanged","type":"event"},{"inputs":[],"name":"ECHO_ADMIN","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ECHO_CONTENTS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allCampaigns","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"campaignNameHashToName","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"campaignNameToInitData","outputs":[{"components":[{"internalType":"uint64","name":"initTime","type":"uint64"},{"internalType":"address","name":"clonedCampaign","type":"address"},{"internalType":"address","name":"implementation","type":"address"}],"internalType":"struct CampaignInitData","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"}],"name":"changeImplementation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"string","name":"name","type":"string"},{"components":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"address","name":"refundAddress","type":"address"},{"internalType":"string","name":"ipfsCID","type":"string"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint128","name":"maxPerPost","type":"uint128"},{"internalType":"uint128","name":"reservedAmount","type":"uint128"}],"internalType":"struct BudgetInfo","name":"budgetInfo","type":"tuple"},{"components":[{"internalType":"string","name":"social","type":"string"},{"components":[{"internalType":"uint8","name":"pct","type":"uint8"},{"internalType":"uint248","name":"min","type":"uint248"},{"internalType":"uint256","name":"ratio","type":"uint256"},{"internalType":"string","name":"method","type":"string"},{"internalType":"string","name":"extra","type":"string"}],"internalType":"struct KPI[]","name":"kpis","type":"tuple[]"}],"internalType":"struct SocialKPIs","name":"socialKPIs","type":"tuple"},{"components":[{"internalType":"enum QA_METHOD","name":"method","type":"uint8"},{"internalType":"uint88","name":"pct","type":"uint88"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"string","name":"kind","type":"string"}],"internalType":"struct QualificationData[]","name":"qaData","type":"tuple[]"}],"internalType":"struct InitCampaign","name":"initCampaign","type":"tuple"}],"name":"createCampaign","outputs":[{"internalType":"address","name":"clonedCampaign","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"page","type":"uint256"}],"name":"paginatedCampaigns","outputs":[{"internalType":"uint256","name":"currentPage","type":"uint256"},{"internalType":"uint256","name":"totalPages","type":"uint256"},{"internalType":"address[]","name":"pagedArray","type":"address[]"}],"stateMutability":"view","type":"function"}]

60c060405234801561000f575f5ffd5b5060405161761f38038061761f83398101604081905261002e916100c5565b6100378161008e565b6001600160a01b0381166080526040518190610052906100b8565b6001600160a01b039091168152602001604051809103905ff08015801561007b573d5f5f3e3d5ffd5b506001600160a01b031660a052506100f2565b6001600160a01b0381166100b55760405163140efc4d60e21b815260040160405180910390fd5b50565b614c868061299983390190565b5f602082840312156100d5575f5ffd5b81516001600160a01b03811681146100eb575f5ffd5b9392505050565b60805160a0516128406101595f395f61016101525f818161019b015281816103650152818161040a0152818161049c0152818161077201528181610a3f01528181610bda01528181610c8601528181610d68015281816116ed015261179601526128405ff3fe608060405234801561000f575f5ffd5b5060043610610090575f3560e01c80635c60da1b116100635780635c60da1b1461014a5780639b5e6a8b1461015c578063a509f4d114610183578063cfce1a9914610196578063fe4d5536146101bd575f5ffd5b80630897d6f0146100945780630caba05e146100c457806317a68dd8146100e65780632c960900146100fb575b5f5ffd5b6100a76100a2366004611e68565b6101dd565b6040516001600160a01b0390911681526020015b60405180910390f35b6100d76100d2366004611fa0565b6111ac565b6040516100bb93929190611fb7565b6100f96100f4366004612014565b6114ce565b005b61010e610109366004612036565b611569565b6040805182516001600160401b031681526020808401516001600160a01b039081169183019190915292820151909216908201526060016100bb565b5f546100a7906001600160a01b031681565b6100a77f000000000000000000000000000000000000000000000000000000000000000081565b6100a7610191366004611fa0565b6115e5565b6100a77f000000000000000000000000000000000000000000000000000000000000000081565b6101d06101cb366004611fa0565b61160d565b6040516100bb91906120a2565b5f6101e7856116a4565b6080820151602001516001600160801b0316156102105761020b82604001516116a4565b610217565b5f60408301525b60088310156102395760405163a9cac3f760e01b815260040160405180910390fd5b604083111561025b57604051638af6981560e01b815260040160405180910390fd5b8160600151515f036102805760405163d57e933160e01b815260040160405180910390fd5b5f6001600160a01b03166003858560405161029c9291906120b4565b908152604051908190036020019020546001600160a01b03600160401b909104161461031c578383600386866040516102d69291906120b4565b908152604051908190036020018120546307bbbbbf60e21b82526103139392916001600160a01b03600160401b90920491909116906004016120eb565b60405180910390fd5b815142111561033e57604051634ef659b360e11b815260040160405180910390fd5b815160208301511161036357604051634d9b1b8b60e01b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663bec4d54e6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103bf573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103e39190612116565b68ffffffffffffffffff16158015906104955750815160208301516104089190612152565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663bec4d54e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610464573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104889190612116565b68ffffffffffffffffff16105b15610541577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663bec4d54e6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104f6573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061051a9190612116565b604051637d1cd12360e11b815268ffffffffffffffffff9091166004820152602401610313565b608082015151610550906116ce565b6080820151602001516001600160801b03161580159061059157508160800151602001516001600160801b03168260800151604001516001600160801b0316105b156105af576040516308cd810560e01b815260040160405180910390fd5b8160c00151515f036105d45760405163656f350160e01b815260040160405180910390fd5b6040805160018082528183019092525f918291606091839190602080830190803683370190505090505b8560c00151518310156108c4575f8660c00151848151811061062257610622612179565b60200260200101515f0151600281111561063e5761063e612165565b0361066a5760405180604001604052806007815260200166028a096a0a49d160cd1b81525091506106eb565b60018660c00151848151811061068257610682612179565b60200260200101515f0151600281111561069e5761069e612165565b036106c95760405180604001604052806006815260200165028a096a19d160d51b81525091506106eb565b60405180604001604052806006815260200165028a096ab1d160d51b81525091505b818660c00151848151811061070257610702612179565b60200260200101516060015160405160200161071f9291906121a4565b60408051601f198184030181529082905261073c916020016121b8565b60405160208183030381529060405280519060200120815f8151811061076457610764612179565b6020026020010181815250507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e6e2be168760c0015185815181106107b5576107b5612179565b602002602001015160400151836040518363ffffffff1660e01b81526004016107df9291906121c3565b602060405180830381865afa1580156107fa573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061081e9190612218565b61087d57818660c00151848151811061083957610839612179565b6020026020010151606001516040516020016108569291906121a4565b60408051601f1981840301815290829052633ddcaa5960e11b8252610313916004016120a2565b8560c00151838151811061089357610893612179565b6020026020010151602001516affffffffffffffffffffff16846108b79190612237565b93506001909201916105fe565b836064146108e85760405163ecf0160d60e01b815260048101859052602401610313565b5f92505f93508560a0015160200151515f03610917576040516379a7f53760e01b815260040160405180910390fd5b5f8660a0015160200151516001600160401b03811115610939576109396119db565b604051908082528060200260200182016040528015610962578160200160208202803683370190505b5090505b8660a001516020015151841015610b9f578660a0015160200151848151811061099157610991612179565b60200260200101515f015160ff16856109aa9190612237565b94508660a001515f01518760a001516020015185815181106109ce576109ce612179565b6020026020010151606001516040516020016109eb92919061224a565b60408051601f1981840301815290829052610a08916020016121b8565b60405160208183030381529060405280519060200120818581518110610a3057610a30612179565b6020026020010181815250505f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b6001e245f848881518110610a7f57610a7f612179565b60200260200101516040518363ffffffff1660e01b8152600401610ab89291906001600160a01b03929092168252602082015260400190565b608060405180830381865afa158015610ad3573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610af79190612273565b505060ff16915050805f03610b695760a08801518051602090910151805187908110610b2557610b25612179565b602002602001015160600151604051602001610b4292919061224a565b60408051601f198184030181529082905263ba7fb2ed60e01b8252610313916004016120a2565b808860a00151602001518681518110610b8457610b84612179565b60209081029190910101516040015250600190930192610966565b84606414610bc357604051630d738e1160e31b815260048101869052602401610313565b6040516373715f0b60e11b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063e6e2be1690610c11905f9085906004016121c3565b602060405180830381865afa158015610c2c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c509190612218565b610c6d57604051630361692160e51b815260040160405180910390fd5b610d5f5f5f9054906101000a90046001600160a01b03167f0000000000000000000000000000000000000000000000000000000000000000308d8b604001518e8e604051610cbc9291906120b4565b60405180910390205f1c8d5f01518e60200151604051602001610d339796959493929190606097881b6bffffffffffffffffffffffff19908116825296881b8716601482015294871b861660288601529290951b909316603c83015260508201929092526070810192909252609082015260b00190565b6040516020818303038152906040528b8b604051610d529291906120b4565b604051809103902061177e565b95505f620186a07f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316638ddc321f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610dc2573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610de691906122d2565b62ffffff16896080015160400151610dfe91906122f4565b610e089190612331565b9050808860800151604001818151610e20919061235e565b6001600160801b03169052506060880151608089015160a08a015160c08b0151604051631d00132960e31b81526001600160a01b038c169463e800994894610e6e9491939092600401612520565b5f604051808303815f87803b158015610e85575f5ffd5b505af1158015610e97573d5f5f3e3d5ffd5b505050506080880151516001600160a01b03166323b872dd33610eb8611793565b6040516001600160e01b031960e085901b1681526001600160a01b039283166004820152911660248201526001600160801b03841660448201526064016020604051808303815f875af1158015610f11573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f359190612218565b506080880151805160409182015191516323b872dd60e01b81523360048201526001600160a01b038a811660248301526001600160801b0390931660448201529116906323b872dd906064016020604051808303815f875af1158015610f9d573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fc19190612218565b50604080516060810182526001600160401b03421681526001600160a01b03808a1660208301525f5416818301529051600390611001908d908d906120b4565b908152604080516020928190038301812084518154948601516001600160401b039091166001600160e01b031990951694909417600160401b6001600160a01b03958616021781559390910151600190930180546001600160a01b031916939092169290921790558a908a906002905f9061107f90859085906120b4565b604051809103902081526020019081526020015f2091826110a19291906125f4565b506001805480820182555f9182527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60180546001600160a01b0319166001600160a01b038a81169182179092559154604080516304cbb47f60e01b8152905191831693928f1692917f95f1e03f5a5037130736d042aaa0cf4d85d403655b81b55612c57126f636fa92918f918f9185916304cbb47f916004808201926020929091908290030181865afa15801561115a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061117e91906126ad565b6001805490508f6040516111969594939291906126c4565b60405180910390a4505050505050949350505050565b6001545f908190606090156114c757600154600b11156112a7576001546001600160401b038111156111e0576111e06119db565b604051908082528060200260200182016040528015611209578160200160208202803683370190505b5090505f5b60018054829061121f908390612152565b6112299190612152565b8154811061123957611239612179565b905f5260205f20015f9054906101000a90046001600160a01b031682828151811061126657611266612179565b6001600160a01b03909216602092830291909101909101526001805461128c9190612152565b811461129a5760010161120e565b60018093509350506114c7565b835f036112b357600193505b6001546112c290600a906127ad565b91505f6112d083600a6127c0565b6001546112dd9190612152565b6001549091506112ee84600a6127c0565b101561130257826112fe816127d7565b9350505b8285111561130e578294505b8493505f5f8660010361133d57600180546113299190612152565b9150611336600a83612152565b90506113fa565b84870361136757821561135a57611355600184612152565b611360565b60099150815b91506113fa565b825f03611374575f61137f565b61137f600184612152565b6113898887612152565b61139490600a6127c0565b61139e9190612237565b6113a89083612237565b9150825f036113b7575f6113c2565b6113c2600184612152565b60016113ce8988612152565b6113d89190612152565b6113e390600a6127c0565b6113ed9190612237565b6113f79082612237565b90505b80611406836001612237565b6114109190612152565b6001600160401b03811115611427576114276119db565b604051908082528060200260200182016040528015611450578160200160208202803683370190505b5093505f5b6001838154811061146857611468612179565b905f5260205f20015f9054906101000a90046001600160a01b031685828151811061149557611495612179565b6001600160a01b03909216602092830291909101909101528282146114c2575f1990920191600101611455565b505050505b9193909250565b6114d6611793565b6001600160a01b0316336001600160a01b031614611507576040516315cead3960e31b815260040160405180910390fd5b611510816116a4565b5f80546040516001600160a01b03808516939216917fcfbf4028add9318bbf716f08c348595afb063b0e9feed1f86d33681a4b3ed4d391a35f80546001600160a01b0319166001600160a01b0392909216919091179055565b604080516060810182525f8082526020820181905281830152905160039061159490859085906120b4565b90815260408051918290036020908101832060608401835280546001600160401b03811685526001600160a01b03600160401b90910481169285019290925260010154169082015290505b92915050565b600181815481106115f4575f80fd5b5f918252602090912001546001600160a01b0316905081565b60026020525f90815260409020805461162590612570565b80601f016020809104026020016040519081016040528092919081815260200182805461165190612570565b801561169c5780601f106116735761010080835404028352916020019161169c565b820191905f5260205f20905b81548152906001019060200180831161167f57829003601f168201915b505050505081565b6001600160a01b0381166116cb5760405163140efc4d60e21b815260040160405180910390fd5b50565b6040516338b317cf60e21b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063e2cc5f3c90602401602060405180830381865afa158015611732573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117569190612218565b6116cb5760405163c1c1013b60e01b81526001600160a01b0382166004820152602401610313565b5f61178b8484845f611819565b949350505050565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663420f68616040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117f0573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061181491906127ef565b905090565b8251604051613d6160f01b8152603a820160f090811b6002838101919091526680600b3d3981f360c81b600484015264363d3d376160d81b600b8401528301901b6010820181905268603836393d3d3d366160b81b6012830152601b82015262013d7360e81b601d820152606086901b6020808301919091526e5af43d82803e903d91603657fd5bf360881b60348301525f929160458301918390604383019089015b602083106118db5780518252601f1990920191602091820191016118bc565b5f6001846020036101000a0319905080825116835283830192508660f01b83526f67363d3d37363d34f03d5260086018f35f52896010805ff5806119265763ebfef1885f526004601cfd5b8060145261d6945f5260016034536001600160a01b036017601e201698505f5f88888d855af115893b15171561196357638f86d2f15f526004601cfd5b5050505050505050949350505050565b6001600160a01b03811681146116cb575f5ffd5b803561199281611973565b919050565b5f5f83601f8401126119a7575f5ffd5b5081356001600160401b038111156119bd575f5ffd5b6020830191508360208285010111156119d4575f5ffd5b9250929050565b634e487b7160e01b5f52604160045260245ffd5b604080519081016001600160401b0381118282101715611a1157611a116119db565b60405290565b60405160a081016001600160401b0381118282101715611a1157611a116119db565b604051608081016001600160401b0381118282101715611a1157611a116119db565b60405160e081016001600160401b0381118282101715611a1157611a116119db565b604051601f8201601f191681016001600160401b0381118282101715611aa557611aa56119db565b604052919050565b5f82601f830112611abc575f5ffd5b81356001600160401b03811115611ad557611ad56119db565b611ae8601f8201601f1916602001611a7d565b818152846020838601011115611afc575f5ffd5b816020850160208301375f918101602001919091529392505050565b80356001600160801b0381168114611992575f5ffd5b5f60608284031215611b3e575f5ffd5b604051606081016001600160401b0381118282101715611b6057611b606119db565b6040529050808235611b7181611973565b8152611b7f60208401611b18565b6020820152611b9060408401611b18565b60408201525092915050565b5f6001600160401b03821115611bb457611bb46119db565b5060051b60200190565b60ff811681146116cb575f5ffd5b6001600160f81b03811681146116cb575f5ffd5b5f60408284031215611bf0575f5ffd5b611bf86119ef565b905081356001600160401b03811115611c0f575f5ffd5b611c1b84828501611aad565b82525060208201356001600160401b03811115611c36575f5ffd5b8201601f81018413611c46575f5ffd5b8035611c59611c5482611b9c565b611a7d565b8082825260208201915060208360051b850101925086831115611c7a575f5ffd5b602084015b83811015611d4f5780356001600160401b03811115611c9c575f5ffd5b850160a0818a03601f19011215611cb1575f5ffd5b611cb9611a17565b6020820135611cc781611bbe565b81526040820135611cd781611bcc565b60208201526060820135604082015260808201356001600160401b03811115611cfe575f5ffd5b611d0d8b602083860101611aad565b60608301525060a08201356001600160401b03811115611d2b575f5ffd5b611d3a8b602083860101611aad565b60808301525084525060209283019201611c7f565b5060208501525091949350505050565b5f82601f830112611d6e575f5ffd5b8135611d7c611c5482611b9c565b8082825260208201915060208360051b860101925085831115611d9d575f5ffd5b602085015b83811015611e5e5780356001600160401b03811115611dbf575f5ffd5b86016080818903601f19011215611dd4575f5ffd5b611ddc611a39565b602082013560038110611ded575f5ffd5b815260408201356affffffffffffffffffffff81168114611e0c575f5ffd5b6020820152611e1d60608301611987565b604082015260808201356001600160401b03811115611e3a575f5ffd5b611e498a602083860101611aad565b60608301525084525060209283019201611da2565b5095945050505050565b5f5f5f5f60608587031215611e7b575f5ffd5b8435611e8681611973565b935060208501356001600160401b03811115611ea0575f5ffd5b611eac87828801611997565b90945092505060408501356001600160401b03811115611eca575f5ffd5b85016101208188031215611edc575f5ffd5b611ee4611a5b565b8135815260208083013590820152611efe60408301611987565b604082015260608201356001600160401b03811115611f1b575f5ffd5b611f2789828501611aad565b606083015250611f3a8860808401611b2e565b608082015260e08201356001600160401b03811115611f57575f5ffd5b611f6389828501611be0565b60a0830152506101008201356001600160401b03811115611f82575f5ffd5b611f8e89828501611d5f565b60c08301525094979396509194505050565b5f60208284031215611fb0575f5ffd5b5035919050565b5f60608201858352846020840152606060408401528084518083526080850191506020860192505f5b818110156120075783516001600160a01b0316835260209384019390920191600101611fe0565b5090979650505050505050565b5f60208284031215612024575f5ffd5b813561202f81611973565b9392505050565b5f5f60208385031215612047575f5ffd5b82356001600160401b0381111561205c575f5ffd5b61206885828601611997565b90969095509350505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f61202f6020830184612074565b818382375f9101908152919050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b604081525f6120fe6040830185876120c3565b905060018060a01b0383166020830152949350505050565b5f60208284031215612126575f5ffd5b815168ffffffffffffffffff8116811461202f575f5ffd5b634e487b7160e01b5f52601160045260245ffd5b818103818111156115df576115df61213e565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f81518060208401855e5f93019283525090919050565b5f61178b6121b2838661218d565b8461218d565b5f61202f828461218d565b6001600160a01b03831681526040602080830182905283519183018290525f91908401906060840190835b8181101561220c5783518352602093840193909201916001016121ee565b50909695505050505050565b5f60208284031215612228575f5ffd5b8151801515811461202f575f5ffd5b808201808211156115df576115df61213e565b5f612255828561218d565b6101d160f51b815261226a600282018561218d565b95945050505050565b5f5f5f5f60808587031215612286575f5ffd5b845160048110612294575f5ffd5b60208601519094506122a581611bbe565b60408601519093506122b681611bcc565b60608601519092506122c781611973565b939692955090935050565b5f602082840312156122e2575f5ffd5b815162ffffff8116811461202f575f5ffd5b6001600160801b0381811683821602908116908181146123165761231661213e565b5092915050565b634e487b7160e01b5f52601260045260245ffd5b5f6001600160801b038316806123495761234961231d565b806001600160801b0384160491505092915050565b6001600160801b0382811682821603908111156115df576115df61213e565b80516001600160a01b031682526020808201516001600160801b039081169184019190915260409182015116910152565b5f8151604084526123c26040850182612074565b60208481015186830387830152805180845292935081019181840191600582901b8501015f5b8281101561246957601f19868303018452845160ff815116835260018060f81b03602082015116602084015260408101516040840152606081015160a0606085015261243760a0850182612074565b90506080820151915083810360808501526124528183612074565b6020978801979690960195935050506001016123e8565b50979650505050505050565b5f82825180855260208501945060208160051b830101602085015f5b8381101561220c57848303601f1901885281518051600381106124c257634e487b7160e01b5f52602160045260245ffd5b84526020818101516affffffffffffffffffffff16908501526040808201516001600160a01b03169085015260609081015160809185018290529061250990850182612074565b6020998a0199909450929092019150600101612491565b60c081525f61253260c0830187612074565b61253f602084018761237d565b828103608084015261255181866123ae565b905082810360a08401526125658185612475565b979650505050505050565b600181811c9082168061258457607f821691505b6020821081036125a257634e487b7160e01b5f52602260045260245ffd5b50919050565b601f8211156125ef57805f5260205f20601f840160051c810160208510156125cd5750805b601f840160051c820191505b818110156125ec575f81556001016125d9565b50505b505050565b6001600160401b0383111561260b5761260b6119db565b61261f836126198354612570565b836125a8565b5f601f841160018114612650575f85156126395750838201355b5f19600387901b1c1916600186901b1783556125ec565b5f83815260208120601f198716915b8281101561267f578685013582556020948501946001909201910161265f565b508682101561269b575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b5f602082840312156126bd575f5ffd5b5051919050565b60a081525f6126d760a0830187896120c3565b856020840152828103806040850152600e82526d4563686f4d61726b65744461746160901b602083015285606085015260408101608085015250835160408201526020840151606082015260018060a01b036040850151166080820152606084015161012060a083015261274f610160830182612074565b9050608085015161276360c084018261237d565b5060a0850151828203603f190161012084015261278082826123ae565b91505060c0850151603f198383030161014084015261279f8282612475565b9a9950505050505050505050565b5f826127bb576127bb61231d565b500490565b80820281158282048414176115df576115df61213e565b5f600182016127e8576127e861213e565b5060010190565b5f602082840312156127ff575f5ffd5b815161202f8161197356fea2646970667358221220322ce205386d4bf53fbedf82ee35313fb501b6116510630b14d572520a19de4264736f6c634300081e003360e06040525f60c052348015610013575f5ffd5b50604051614c86380380614c8683398101604081905261003291610047565b6001600160a01b03166080523360a052610074565b5f60208284031215610057575f5ffd5b81516001600160a01b038116811461006d575f5ffd5b9392505050565b60805160a05160c051614b716101155f395f8181611ea101528181611f9a0152818161287601528181612a3b01528181612a6601528181612a9601528181612ac001528181612b3201528181612b5d0152612bbe01525f818161013101526106c601525f81816101fb015281816110dc0152818161117f015281816112da015281816117f201528181612019015281816120bc01526124fa0152614b715ff3fe608060405234801561000f575f5ffd5b506004361061009b575f3560e01c8063aa8ff04e11610063578063aa8ff04e1461018d578063b5ecf912146101c2578063b6967b3b146101e3578063cfce1a99146101f6578063efdbb6311461021d575f5ffd5b806310cf02b91461009f578063431facbc146100f5578063603ab8b71461011757806395953ee11461012c5780639bff4ada1461016b575b5f5ffd5b6100db6100ad366004613970565b6001600160a01b03165f908152600560205260409020546001600160801b03600160801b8204811692911690565b604080519283526020830191909152015b60405180910390f35b61010861010336600461398b565b610230565b6040516100ec939291906139d0565b61012a610125366004613aa5565b6106ba565b005b6101537f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100ec565b61017e610179366004613aec565b6109c8565b6040516100ec93929190613b1a565b6101a061019b366004613b7d565b610ec5565b60408051941515855260208501939093529183015260608201526080016100ec565b6101d56101d036600461398b565b610ff1565b6040516100ec929190613bfd565b61012a6101f1366004613c60565b6110af565b6101537f000000000000000000000000000000000000000000000000000000000000000081565b61012a61022b366004613d0e565b612016565b6003545f908190606090156106b357600354600b11156103df576003546001600160401b0381111561026457610264613d8c565b6040519080825280602002602001820160405280156102a957816020015b604080518082019091525f8152606060208201528152602001906001900390816102825790505b5090505f5b6003805482906102c090600190613db4565b6102ca9190613db4565b815481106102da576102da613dc7565b5f9182526020918290206040805180820190915260029092020180546001600160a01b03168252600181018054929391929184019161031890613ddb565b80601f016020809104026020016040519081016040528092919081815260200182805461034490613ddb565b801561038f5780601f106103665761010080835404028352916020019161038f565b820191905f5260205f20905b81548152906001019060200180831161037257829003601f168201915b5050505050815250508282815181106103aa576103aa613dc7565b60209081029190910101526003546103c490600190613db4565b81146103d2576001016102ae565b60018093509350506106b3565b835f036103eb57600193505b6003546103fa90600a90613e27565b91505f61040883600a613e46565b6003546104159190613db4565b60035490915061042684600a613e46565b101561043a578261043681613e5d565b9350505b82851115610446578294505b8493505f5f866001036104765760035461046290600190613db4565b915061046f600a83613db4565b9050610533565b8487036104a05782156104935761048e600184613db4565b610499565b60099150815b9150610533565b825f036104ad575f6104b8565b6104b8600184613db4565b6104c28887613db4565b6104cd90600a613e46565b6104d79190613e75565b6104e19083613e75565b9150825f036104f0575f6104fb565b6104fb600184613db4565b60016105078988613db4565b6105119190613db4565b61051c90600a613e46565b6105269190613e75565b6105309082613e75565b90505b8061053f836001613e75565b6105499190613db4565b6001600160401b0381111561056057610560613d8c565b6040519080825280602002602001820160405280156105a557816020015b604080518082019091525f81526060602082015281526020019060019003908161057e5790505b5093505f5b600383815481106105bd576105bd613dc7565b5f9182526020918290206040805180820190915260029092020180546001600160a01b0316825260018101805492939192918401916105fb90613ddb565b80601f016020809104026020016040519081016040528092919081815260200182805461062790613ddb565b80156106725780601f1061064957610100808354040283529160200191610672565b820191905f5260205f20905b81548152906001019060200180831161065557829003601f168201915b50505050508152505085828151811061068d5761068d613dc7565b6020026020010181905250818303156106ae575f19909201916001016105aa565b505050505b9193909250565b5f6001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316632c960900336001600160a01b03166306fdde036040518163ffffffff1660e01b81526004015f60405180830381865afa15801561072e573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526107559190810190613f8f565b6040518263ffffffff1660e01b81526004016107719190613fc0565b606060405180830381865afa15801561078c573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107b09190613fd2565b602001516001600160a01b0316036107db576040516395383ea160e01b815260040160405180910390fd5b600483836040516107ed92919061404e565b9081526040805160209281900383019020335f908152925290205460ff1615610829576040516321947e9960e21b815260040160405180910390fd5b60036040518060400160405280336001600160a01b0316815260200185858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920182905250939094525050835460018082018655948252602091829020845160029092020180546001600160a01b0319166001600160a01b03909216919091178155908301519293909290830191506108ca90826140a9565b5050506001600484846040516108e192919061404e565b9081526040805160209281900383019020335f908152908352818120805460ff191694151594909417909355600582528220600190810180549182018155835291200161092f838583614163565b50335f90815260056020526040812080546001600160801b03428116600160801b029116179055600180549161096483613e5d565b9091555050335f8181526005602052604090819020600190810154905491519092429290917fe50964cbc470160e6c3556ba4ea3ba3e84778d99446d737c1d1a8a4c9c50eb03916109bb9189918991899190614244565b60405180910390a4505050565b6001600160a01b0381165f90815260056020526040812060010154819060609015610ebe576001600160a01b0384165f90815260056020526040902060010154600b1115610bb2576001600160a01b0384165f908152600560205260409020600101546001600160401b03811115610a4257610a42613d8c565b604051908082528060200260200182016040528015610a7557816020015b6060815260200190600190039081610a605790505b5090505f5b6001600160a01b0385165f9081526005602052604090206001908101805490918391610aa69190613db4565b610ab09190613db4565b81548110610ac057610ac0613dc7565b905f5260205f20018054610ad390613ddb565b80601f0160208091040260200160405190810160405280929190818152602001828054610aff90613ddb565b8015610b4a5780601f10610b2157610100808354040283529160200191610b4a565b820191905f5260205f20905b815481529060010190602001808311610b2d57829003601f168201915b5050505050828281518110610b6157610b61613dc7565b6020908102919091018101919091526001600160a01b0386165f908152600590915260409020600190810154610b979190613db4565b8114610ba557600101610a7a565b6001809350935050610ebe565b845f03610bbe57600194505b6001600160a01b0384165f90815260056020526040902060010154610be590600a90613e27565b91505f610bf383600a613e46565b6001600160a01b0386165f90815260056020526040902060010154610c189190613db4565b6001600160a01b0386165f90815260056020526040902060010154909150610c4184600a613e46565b1015610c555782610c5181613e5d565b9350505b82861115610c61578295505b8593505f5f87600103610ca9576001600160a01b0387165f908152600560205260409020600190810154610c959190613db4565b9150610ca2600a83613db4565b9050610d66565b848803610cd3578215610cc657610cc1600184613db4565b610ccc565b60099150815b9150610d66565b825f03610ce0575f610ceb565b610ceb600184613db4565b610cf58987613db4565b610d0090600a613e46565b610d0a9190613e75565b610d149083613e75565b9150825f03610d23575f610d2e565b610d2e600184613db4565b6001610d3a8a88613db4565b610d449190613db4565b610d4f90600a613e46565b610d599190613e75565b610d639082613e75565b90505b80610d72836001613e75565b610d7c9190613db4565b6001600160401b03811115610d9357610d93613d8c565b604051908082528060200260200182016040528015610dc657816020015b6060815260200190600190039081610db15790505b5093505f5b6001600160a01b0388165f908152600560205260409020600101805484908110610df757610df7613dc7565b905f5260205f20018054610e0a90613ddb565b80601f0160208091040260200160405190810160405280929190818152602001828054610e3690613ddb565b8015610e815780601f10610e5857610100808354040283529160200191610e81565b820191905f5260205f20905b815481529060010190602001808311610e6457829003601f168201915b5050505050858281518110610e9857610e98613dc7565b602002602001018190525081830315610eb9575f1990920191600101610dcb565b505050505b9250925092565b5f5f5f5f60048888604051610edb92919061404e565b9081526040805191829003602090810183206001600160a01b038d165f9081529152205460ff169450600490610f14908a908a9061404e565b90815260408051602092819003830181206001600160a01b038d165f9081529352912054610100900460ff169350600490610f52908a908a9061404e565b90815260408051602092819003830190206001600160a01b038c165f90815292529020546201000090046001600160f01b031691508415610fe55760048888604051610f9f92919061404e565b9081526040805191829003602090810183206001600160a01b038d165f90815291522060020190610fd3908890889061404e565b90815260200160405180910390205490505b95509550955095915050565b60038181548110611000575f80fd5b5f918252602090912060029091020180546001820180546001600160a01b0390921693509061102e90613ddb565b80601f016020809104026020016040519081016040528092919081815260200182805461105a90613ddb565b80156110a55780601f1061107c576101008083540402835291602001916110a5565b820191905f5260205f20905b81548152906001019060200180831161108857829003601f168201915b5050505050905082565b821580156110bb575080155b156110d957604051635b9e1d4560e11b815260040160405180910390fd5b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663420f68616040518163ffffffff1660e01b8152600401602060405180830381865afa158015611136573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061115a919061429c565b604051632ac1326b60e01b815233600482018190526001600160a01b039283161492507f000000000000000000000000000000000000000000000000000000000000000090911690632ac1326b90602401602060405180830381865afa1580156111c6573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111ea91906142b7565b80156111f4575080155b1561121257604051631e4230c760e21b815260040160405180910390fd5b6004878760405161122492919061404e565b90815260408051602092819003830190206001600160a01b038b165f908152925290205460ff16611275578686604051633c00a7af60e01b815260040161126c9291906142d6565b60405180910390fd5b606084156114f1575f5f5b868110156114ee5787878281811061129a5761129a613dc7565b90506020028101906112ac91906142e9565b6112b69080614307565b6040516020016112c7929190614349565b60405160208183030381529060405292507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b6001e2460018560405160200161131a9190614383565b604051602081830303815290604052805190602001206040518363ffffffff1660e01b81526004016113619291906001600160a01b03929092168252602082015260400190565b608060405180830381865afa15801561137c573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113a091906143b9565b94505050506001600160a01b03821633148015906113bc575083155b156113df578260405160016218620560e01b0319815260040161126c9190613fc0565b8787828181106113f1576113f1613dc7565b905060200281019061140391906142e9565b611411906020810190614307565b61141a91614406565b60405160049061142d908d908d9061404e565b90815260200160405180910390205f8d6001600160a01b03166001600160a01b031681526020019081526020015f206002018460405161146d9190614383565b90815260405190819003602001902055336001600160a01b038c167fd7b65b61e10654aaf9ab3a7af2b11e30c5c7e3b2ce4dfe8378f8ca14687c2d1e8c8c8c8c878181106114bd576114bd613dc7565b90506020028101906114cf91906142e9565b6040516114de93929190614464565b60405180910390a3600101611280565b50505b821561200b575f896001600160a01b03166326f016376040518163ffffffff1660e01b81526004015f60405180830381865afa158015611533573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261155a91908101906144f9565b5190508484905060048a8a60405161157392919061404e565b90815260408051602092819003830190206001600160a01b038e165f90815292529020546115a990610100900460ff1683613db4565b141580156115b5575082155b156115d7578888604051635170461160e11b815260040161126c9291906142d6565b5f5f5b85811015611ba1575f8787838181106115f5576115f5613dc7565b90506020028101906116079190614641565b611615906020810190614655565b60028111156116265761162661462d565b036116845786868281811061163d5761163d613dc7565b905060200281019061164f9190614641565b61165d906040810190614307565b60405160200161166e929190614670565b6040516020818303038152906040529350611766565b600187878381811061169857611698613dc7565b90506020028101906116aa9190614641565b6116b8906020810190614655565b60028111156116c9576116c961462d565b03611711578686828181106116e0576116e0613dc7565b90506020028101906116f29190614641565b611700906040810190614307565b60405160200161166e929190614692565b86868281811061172357611723613dc7565b90506020028101906117359190614641565b611743906040810190614307565b6040516020016117549291906146b3565b60405160208183030381529060405293505b60405163aa588b4b60e01b81526001600160a01b038d169063aa588b4b90611792908790600401613fc0565b602060405180830381865afa1580156117ad573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117d191906142b7565b6117f05783604051633cfbd52760e21b815260040161126c9190613fc0565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b6001e2433866040516020016118319190614383565b604051602081830303815290604052805190602001206040518363ffffffff1660e01b81526004016118789291906001600160a01b03929092168252602082015260400190565b608060405180830381865afa158015611893573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118b791906143b9565b50919350600291506118c69050565b8260038111156118d8576118d861462d565b141580156118e4575084155b15611907578360405160016218620560e01b0319815260040161126c9190613fc0565b620186a087878381811061191d5761191d613dc7565b905060200281019061192f9190614641565b602001351180611964575086868281811061194c5761194c613dc7565b905060200281019061195e9190614641565b60200135155b156119ad5786868281811061197b5761197b613dc7565b905060200281019061198d9190614641565b602001356040516370e00d7760e01b815260040161126c91815260200190565b60048b8b6040516119bf92919061404e565b90815260200160405180910390205f8d6001600160a01b03166001600160a01b031681526020019081526020015f20600201846040516119ff9190614383565b9081526020016040518091039020545f14611a3b5784611a36578a8a604051635170461160e11b815260040161126c9291906142d6565b611aa0565b60048b8b604051611a4d92919061404e565b90815260408051602092819003830190206001600160a01b038f165f908152925290208054610100900460ff16906001611a86836146d4565b91906101000a81548160ff021916908360ff160217905550505b868682818110611ab257611ab2613dc7565b9050602002810190611ac49190614641565b6020013560048c8c604051611ada92919061404e565b90815260200160405180910390205f8e6001600160a01b03166001600160a01b031681526020019081526020015f2060020185604051611b1a9190614383565b90815260405190819003602001902055336001600160a01b038d167fb72e5373934391ba6c4fc5b9542ee21c20a5d890a85cfdd16d446357e83af2888d8d888c8c88818110611b6b57611b6b613dc7565b9050602002810190611b7d9190614641565b60200135604051611b9194939291906146f2565b60405180910390a36001016115da565b8260048c8c604051611bb492919061404e565b90815260200160405180910390205f8e6001600160a01b03166001600160a01b031681526020019081526020015f205f0160019054906101000a900460ff1660ff1603611f50575f670de0b6b3a764000090505f8d6001600160a01b03166326f016376040518163ffffffff1660e01b81526004015f60405180830381865afa158015611c43573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052611c6a91908101906144f9565b90505f8d8d8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f81840152601f19601f8201169050808301925050505050505090505f8f90505f94505b8251851015611ed1575f838681518110611cd757611cd7613dc7565b60200260200101515f01516002811115611cf357611cf361462d565b03611d3c57828581518110611d0a57611d0a613dc7565b602002602001015160600151604051602001611d269190614729565b6040516020818303038152906040529750611ddf565b6001838681518110611d5057611d50613dc7565b60200260200101515f01516002811115611d6c57611d6c61462d565b03611d9f57828581518110611d8357611d83613dc7565b602002602001015160600151604051602001611d269190614744565b828581518110611db157611db1613dc7565b602002602001015160600151604051602001611dcd919061475e565b60405160208183030381529060405297505b611e9d611e96611e28858881518110611dfa57611dfa613dc7565b602002602001015160200151662386f26fc10000611e189190614778565b6affffffffffffffffffffff1690565b611e90600486604051611e3b9190614383565b9081526040805191829003602090810183206001600160a01b0389165f90815291522060020190611e6d908e90614383565b9081526020016040518091039020546509184e72a000611e8d9190613e46565b90565b90612da0565b8590612eae565b93507f00000000000000000000000000000000000000000000000000000000000000008414611ed157600190940193611cbb565b83600483604051611ee29190614383565b90815260408051602092819003830190206001600160a01b0385165f908152925290206001015583816001600160a01b03167ffd61a2d01d4536539eb3388a9803c27ef3a5b28bb51773f83534e77bf02770d784604051611f439190613fc0565b60405180910390a3505050505b611fbe60048c8c604051611f6592919061404e565b90815260200160405180910390205f8e6001600160a01b03166001600160a01b031681526020019081526020015f20600101547f0000000000000000000000000000000000000000000000000000000000000000141590565b15612007578b6001600160a01b03167ffb227420d8844fd0693b8044d6444d0d36e18e02034526f9d379c05612e719838c8c604051611ffe9291906142d6565b60405180910390a25b5050505b505050505050505050565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663420f68616040518163ffffffff1660e01b8152600401602060405180830381865afa158015612073573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612097919061429c565b604051632ac1326b60e01b815233600482018190526001600160a01b039283161492507f000000000000000000000000000000000000000000000000000000000000000090911690632ac1326b90602401602060405180830381865afa158015612103573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061212791906142b7565b8015612131575080155b1561214f57604051631e4230c760e21b815260040160405180910390fd5b6004848460405161216192919061404e565b90815260408051602092819003830190206001600160a01b0388165f908152925290205460ff166121a9578383604051633c00a7af60e01b815260040161126c9291906142d6565b5f856001600160a01b031663ef8a92356040518163ffffffff1660e01b8152600401602060405180830381865afa1580156121e6573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061220a91906147a5565b905060018160058111156122205761222061462d565b141580156122405750600281600581111561223d5761223d61462d565b14155b801561225e5750600381600581111561225b5761225b61462d565b14155b8015612268575081155b156122865760405163700f030f60e01b815260040160405180910390fd5b6122c56004868660405161229b92919061404e565b90815260408051602092819003830190206001600160a01b038a165f908152925290206001015490565b5f036122e4576040516306b2e62960e11b815260040160405180910390fd5b5f866001600160a01b0316638c7a8eb36040518163ffffffff1660e01b81526004015f60405180830381865afa158015612320573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261234791908101906147c3565b90505f6123548580614307565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505060405192935061239c928492506020019050614383565b60408051601f19818403018152908290528051602091820120845190926123c39201614383565b60405160208183030381529060405280519060200120146123f757604051637128647b60e11b815260040160405180910390fd5b5f60605f5b6124096020890189614962565b9050831015612728578361242060208a018a614962565b8581811061243057612430613dc7565b905060200281019061244291906142e9565b61244c9080614307565b60405160200161245e939291906149a7565b60408051601f198184030181529082905263aa588b4b60e01b825292506001600160a01b038c169063aa588b4b9061249a908590600401613fc0565b602060405180830381865afa1580156124b5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906124d991906142b7565b6124f85781604051631f3f577f60e21b815260040161126c9190613fc0565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b6001e245f846040516020016125399190614383565b604051602081830303815290604052805190602001206040518363ffffffff1660e01b81526004016125809291906001600160a01b03929092168252602082015260400190565b608060405180830381865afa15801561259b573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906125bf91906143b9565b93505050336001600160a01b03831614801591506125db575086155b156125fe578160405160016218620560e01b0319815260040161126c9190613fc0565b61260b6020890189614962565b8481811061261b5761261b613dc7565b905060200281019061262d91906142e9565b6020013560048b8b60405161264392919061404e565b90815260200160405180910390205f8d6001600160a01b03166001600160a01b031681526020019081526020015f20600201836040516126839190614383565b908152602001604051809103902081905550336001600160a01b03168b6001600160a01b03167f9e9ec73da222bb34e89c444ee2841702e1509018ad901b4deb68b0ef9b859f398c8c42878e80602001906126de9190614962565b8b8181106126ee576126ee613dc7565b905060200281019061270091906142e9565b602001356040516127159594939291906149d3565b60405180910390a36001909201916123fc565b6001600160a01b038b165f90815260056020526040812080546fffffffffffffffffffffffffffffffff1916426001600160801b03161790559250825f8b8b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152509293508f925050505b87602001515186101561297d5786886020015187815181106127c4576127c4613dc7565b6020026020010151606001516040516020016127e1929190614a11565b60405160208183030381529060405294506004826040516128029190614383565b9081526040805191829003602090810183206001600160a01b0385165f90815291522060020190612834908790614383565b9081526020016040518091039020548860200151878151811061285957612859613dc7565b6020026020010151602001516001600160f81b0316111561289c577f0000000000000000000000000000000000000000000000000000000000000000925061297d565b61297061296961292a6128cd8b602001518a815181106128be576128be613dc7565b60200260200101516040015190565b6129246004876040516128e09190614383565b9081526040805191829003602090810183206001600160a01b038a165f90815291522060020190612912908c90614383565b90815260200160405180910390205490565b90612ec3565b6129638b602001518a8151811061294357612943613dc7565b60200260200101515f015160ff16662386f26fc10000611e8d9190613e46565b90612eae565b8490612eda565b60019096019592506127a0565b82816001600160a01b03167fc8d7d7296c924c3920078949d915bb20c80351022f3d90dda892192ad65cd974846040516129b79190613fc0565b60405180910390a35f5f5f612a30846001600160a01b03166324034a0e876040518263ffffffff1660e01b81526004016129f19190613fc0565b602060405180830381865afa158015612a0c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e8d9190614a3a565b92505f9150600190507f00000000000000000000000000000000000000000000000000000000000000008314158015612a8857507f000000000000000000000000000000000000000000000000000000000000000086145b15612a9457505f612bbc565b7f000000000000000000000000000000000000000000000000000000000000000083148015612ae357507f00000000000000000000000000000000000000000000000000000000000000008614155b15612b3057612b29600486604051612afb9190614383565b90815260408051602092819003830190206001600160a01b0388165f90815292529020600101548790612eae565b9150612bbc565b7f00000000000000000000000000000000000000000000000000000000000000008314158015612b8057507f00000000000000000000000000000000000000000000000000000000000000008614155b15612bbc57612b98600486604051612afb9190614383565b915081831015612bac57612b298284612ee8565b612bb68383612ee8565b91505f90505b7f00000000000000000000000000000000000000000000000000000000000000008214612d8d575f8080806001600160a01b03881663a7c64c708a88886040518463ffffffff1660e01b8152600401612c1793929190614a51565b6080604051808303815f875af1158015612c33573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612c579190614a7a565b604051939750919550935091504290600490612c74908c90614383565b90815260408051602092819003830190206001600160a01b038c165f90815292528120805461ffff16620100006001600160f01b0394909416939093029290921790915581900b15612cf857805f0b600114612ce25760028054905f612cd983614ab4565b91905055612cf6565b60028054905f612cf183613e5d565b919050555b505b84612d1957855f80548190612d0e908490613db4565b925050819055612d31565b855f80548190612d2a908490613e75565b9250508190555b508286896001600160a01b03167fc75368439f060eb528fcbcf2dcce8dbca5f2e6a36d590b59a7d354d4c6ce96f28c898988425f600201545f5f0154604051612d809796959493929190614ac9565b60405180910390a4505050505b5050505050505050505050505050505050565b5f8282818303612dc9578015612db6575f612dc0565b670de0b6b3a76400005b92505050612ea8565b670de0b6b3a76400008203612dea57670de0b6b3a764000092505050612ea8565b805f03612e0357670de0b6b3a764000092505050612ea8565b670de0b6b3a76400008103612e1c578492505050612ea8565b670de0b6b3a7640000821115612e4d57612e46612e41612e3b87612ef6565b86612eae565b61301d565b9250612ea5565b5f612e6a611e8d846ec097ce7bc90715b34b9f1000000000613e27565b90505f612e82612e41612e7c84612ef6565b88612eae565b9050612ea0611e8d826ec097ce7bc90715b34b9f1000000000613e27565b945050505b50505b92915050565b5f612ebc611e8d8484613079565b9392505050565b5f612ebc611e8d84670de0b6b3a76400008561312b565b5f612ebc611e8d8385613e75565b5f612ebc611e8d8385613db4565b5f81670de0b6b3a7640000811015612f245760405163036d32ef60e41b81526004810184905260240161126c565b5f612fa5670de0b6b3a7640000830460016001600160801b03821160071b91821c6001600160401b03811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c60ff8111600390811b91821c600f811160021b90811c918211871b91821c969096119490961792909217171791909117919091171790565b9050670de0b6b3a7640000810282821c670de0b6b3a763ffff198101612fce5750949350505050565b671bc16d674ec800006706f05b59d3b200005b801561301157670de0b6b3a7640000838002049250818310613009579283019260019290921c915b60011c612fe1565b50919695505050505050565b5f81680a688906bd8affffff81111561304c5760405163b3b6ba1f60e01b81526004810184905260240161126c565b5f613063670de0b6b3a7640000604084901b613e27565b9050613071611e8d826131f9565b949350505050565b5f80805f19848609848602925082811083820303915050805f036130aa5750670de0b6b3a764000090049050612ea8565b670de0b6b3a764000081106130dc57604051635173648d60e01b8152600481018690526024810185905260440161126c565b5f670de0b6b3a764000085870962040000818503049310909103600160ee1b02919091177faccb18165bd6fe31ae1cf318dc5b51eee0e1ba569b88cd74c1773b91fac106690291505092915050565b5f80805f19858709858702925082811083820303915050805f036131625783828161315857613158613e13565b0492505050612ebc565b83811061319357604051630c740aef60e31b815260048101879052602481018690526044810185905260640161126c565b5f84868809600260036001881981018916988990049182028318808302840302808302840302808302840302808302840302808302840302918202909203025f889003889004909101858311909403939093029303949094049190911702949350505050565b600160bf1b67ff000000000000008216156133065767800000000000000082161561322d5768016a09e667f3bcc9090260401c5b67400000000000000082161561324c576801306fe0a31b7152df0260401c5b67200000000000000082161561326b576801172b83c7d517adce0260401c5b67100000000000000082161561328a5768010b5586cf9890f62a0260401c5b6708000000000000008216156132a9576801059b0d31585743ae0260401c5b6704000000000000008216156132c857680102c9a3e778060ee70260401c5b6702000000000000008216156132e75768010163da9fb33356d80260401c5b67010000000000000082161561330657680100b1afa5abcbed610260401c5b66ff0000000000008216156134055766800000000000008216156133335768010058c86da1c09ea20260401c5b6640000000000000821615613351576801002c605e2e8cec500260401c5b662000000000000082161561336f57680100162f3904051fa10260401c5b661000000000000082161561338d576801000b175effdc76ba0260401c5b66080000000000008216156133ab57680100058ba01fb9f96d0260401c5b66040000000000008216156133c95768010002c5cc37da94920260401c5b66020000000000008216156133e7576801000162e525ee05470260401c5b66010000000000008216156134055768010000b17255775c040260401c5b65ff00000000008216156134fb5765800000000000821615613430576801000058b91b5bc9ae0260401c5b6540000000000082161561344d57680100002c5c89d5ec6d0260401c5b6520000000000082161561346a5768010000162e43f4f8310260401c5b6510000000000082161561348757680100000b1721bcfc9a0260401c5b650800000000008216156134a45768010000058b90cf1e6e0260401c5b650400000000008216156134c1576801000002c5c863b73f0260401c5b650200000000008216156134de57680100000162e430e5a20260401c5b650100000000008216156134fb576801000000b1721835510260401c5b64ff000000008216156135e85764800000000082161561352457680100000058b90c0b490260401c5b6440000000008216156135405768010000002c5c8601cc0260401c5b64200000000082161561355c576801000000162e42fff00260401c5b6410000000008216156135785768010000000b17217fbb0260401c5b640800000000821615613594576801000000058b90bfce0260401c5b6404000000008216156135b057680100000002c5c85fe30260401c5b6402000000008216156135cc5768010000000162e42ff10260401c5b6401000000008216156135e857680100000000b17217f80260401c5b63ff0000008216156136cc57638000000082161561360f5768010000000058b90bfc0260401c5b634000000082161561362a576801000000002c5c85fe0260401c5b632000000082161561364557680100000000162e42ff0260401c5b6310000000821615613660576801000000000b17217f0260401c5b630800000082161561367b57680100000000058b90c00260401c5b63040000008216156136965768010000000002c5c8600260401c5b63020000008216156136b1576801000000000162e4300260401c5b63010000008216156136cc5768010000000000b172180260401c5b62ff00008216156137a757628000008216156136f1576801000000000058b90c0260401c5b6240000082161561370b57680100000000002c5c860260401c5b622000008216156137255768010000000000162e430260401c5b6210000082161561373f57680100000000000b17210260401c5b620800008216156137595768010000000000058b910260401c5b62040000821615613773576801000000000002c5c80260401c5b6202000082161561378d57680100000000000162e40260401c5b620100008216156137a7576801000000000000b1720260401c5b61ff00821615613879576180008216156137ca57680100000000000058b90260401c5b6140008216156137e35768010000000000002c5d0260401c5b6120008216156137fc576801000000000000162e0260401c5b6110008216156138155768010000000000000b170260401c5b61080082161561382e576801000000000000058c0260401c5b61040082161561384757680100000000000002c60260401c5b61020082161561386057680100000000000001630260401c5b61010082161561387957680100000000000000b10260401c5b60ff82161561394257608082161561389a57680100000000000000590260401c5b60408216156138b2576801000000000000002c0260401c5b60208216156138ca57680100000000000000160260401c5b60108216156138e2576801000000000000000b0260401c5b60088216156138fa57680100000000000000060260401c5b600482161561391257680100000000000000030260401c5b600282161561392a57680100000000000000010260401c5b600182161561394257680100000000000000010260401c5b670de0b6b3a76400000260409190911c60bf031c90565b6001600160a01b038116811461396d575f5ffd5b50565b5f60208284031215613980575f5ffd5b8135612ebc81613959565b5f6020828403121561399b575f5ffd5b5035919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f606082018583528460208401526060604084015280845180835260808501915060808160051b8601019250602086015f5b82811015613a5357868503607f19018452815180516001600160a01b03168652602090810151604091870182905290613a3d908701826139a2565b9550506020938401939190910190600101613a02565b509298975050505050505050565b5f5f83601f840112613a71575f5ffd5b5081356001600160401b03811115613a87575f5ffd5b602083019150836020828501011115613a9e575f5ffd5b9250929050565b5f5f5f60408486031215613ab7575f5ffd5b83356001600160401b03811115613acc575f5ffd5b613ad886828701613a61565b909790965060209590950135949350505050565b5f5f60408385031215613afd575f5ffd5b823591506020830135613b0f81613959565b809150509250929050565b5f606082018583528460208401526060604084015280845180835260808501915060808160051b8601019250602086015f5b82811015613a5357607f19878603018452613b688583516139a2565b94506020938401939190910190600101613b4c565b5f5f5f5f5f60608688031215613b91575f5ffd5b8535613b9c81613959565b945060208601356001600160401b03811115613bb6575f5ffd5b613bc288828901613a61565b90955093505060408601356001600160401b03811115613be0575f5ffd5b613bec88828901613a61565b969995985093965092949392505050565b6001600160a01b03831681526040602082018190525f90613071908301846139a2565b5f5f83601f840112613c30575f5ffd5b5081356001600160401b03811115613c46575f5ffd5b6020830191508360208260051b8501011115613a9e575f5ffd5b5f5f5f5f5f5f5f6080888a031215613c76575f5ffd5b8735613c8181613959565b965060208801356001600160401b03811115613c9b575f5ffd5b613ca78a828b01613a61565b90975095505060408801356001600160401b03811115613cc5575f5ffd5b613cd18a828b01613c20565b90955093505060608801356001600160401b03811115613cef575f5ffd5b613cfb8a828b01613c20565b989b979a50959850939692959293505050565b5f5f5f5f60608587031215613d21575f5ffd5b8435613d2c81613959565b935060208501356001600160401b03811115613d46575f5ffd5b613d5287828801613a61565b90945092505060408501356001600160401b03811115613d70575f5ffd5b850160408188031215613d81575f5ffd5b939692955090935050565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b81810381811115612ea857612ea8613da0565b634e487b7160e01b5f52603260045260245ffd5b600181811c90821680613def57607f821691505b602082108103613e0d57634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601260045260245ffd5b5f82613e4157634e487b7160e01b5f52601260045260245ffd5b500490565b8082028115828204841417612ea857612ea8613da0565b5f60018201613e6e57613e6e613da0565b5060010190565b80820180821115612ea857612ea8613da0565b604051608081016001600160401b0381118282101715613eaa57613eaa613d8c565b60405290565b604080519081016001600160401b0381118282101715613eaa57613eaa613d8c565b60405160a081016001600160401b0381118282101715613eaa57613eaa613d8c565b604051601f8201601f191681016001600160401b0381118282101715613f1c57613f1c613d8c565b604052919050565b5f82601f830112613f33575f5ffd5b81516001600160401b03811115613f4c57613f4c613d8c565b613f5f601f8201601f1916602001613ef4565b818152846020838601011115613f73575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f60208284031215613f9f575f5ffd5b81516001600160401b03811115613fb4575f5ffd5b61307184828501613f24565b602081525f612ebc60208301846139a2565b5f6060828403128015613fe3575f5ffd5b50604051606081016001600160401b038111828210171561400657614006613d8c565b60405282516001600160401b038116811461401f575f5ffd5b8152602083015161402f81613959565b6020820152604083015161404281613959565b60408201529392505050565b818382375f9101908152919050565b601f8211156140a457805f5260205f20601f840160051c810160208510156140825750805b601f840160051c820191505b818110156140a1575f815560010161408e565b50505b505050565b81516001600160401b038111156140c2576140c2613d8c565b6140d6816140d08454613ddb565b8461405d565b6020601f821160018114614108575f83156140f15750848201515b5f19600385901b1c1916600184901b1784556140a1565b5f84815260208120601f198516915b828110156141375787850151825560209485019460019092019101614117565b508482101561415457868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b6001600160401b0383111561417a5761417a613d8c565b61418e836141888354613ddb565b8361405d565b5f601f8411600181146141bf575f85156141a85750838201355b5f19600387901b1c1916600186901b1783556140a1565b5f83815260208120601f198716915b828110156141ee57868501358255602094850194600190920191016141ce565b508682101561420a575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b608081525f61425760808301868861421c565b846020840152828103604084015261428a81600e81526d4563686f4d61726b65744461746160901b602082015260400190565b91505082606083015295945050505050565b5f602082840312156142ac575f5ffd5b8151612ebc81613959565b5f602082840312156142c7575f5ffd5b81518015158114612ebc575f5ffd5b602081525f61307160208301848661421c565b5f8235603e198336030181126142fd575f5ffd5b9190910192915050565b5f5f8335601e1984360301811261431c575f5ffd5b8301803591506001600160401b03821115614335575f5ffd5b602001915036819003821315613a9e575f5ffd5b67021a7a72324a39d160c51b8152818360088301375f9101600801908152919050565b5f81518060208401855e5f93019283525090919050565b5f612ebc828461436c565b805160ff8116811461439e575f5ffd5b919050565b80516001600160f81b038116811461439e575f5ffd5b5f5f5f5f608085870312156143cc575f5ffd5b8451600481106143da575f5ffd5b93506143e86020860161438e565b92506143f6604086016143a3565b91506060850151613d8181613959565b80356020831015612ea8575f19602084900360031b1b1692915050565b5f5f8335601e19843603018112614438575f5ffd5b83016020810192503590506001600160401b03811115614456575f5ffd5b803603821315613a9e575f5ffd5b604081525f61447760408301858761421c565b82810360208401526144898485614423565b6040835261449b60408401828461421c565b9150506144ab6020860186614423565b83830360208501526144be83828461421c565b9998505050505050505050565b5f6001600160401b038211156144e3576144e3613d8c565b5060051b60200190565b6003811061396d575f5ffd5b5f60208284031215614509575f5ffd5b81516001600160401b0381111561451e575f5ffd5b8201601f8101841361452e575f5ffd5b805161454161453c826144cb565b613ef4565b8082825260208201915060208360051b850101925086831115614562575f5ffd5b602084015b838110156146225780516001600160401b03811115614584575f5ffd5b85016080818a03601f19011215614599575f5ffd5b6145a1613e88565b60208201516145af816144ed565b815260408201516affffffffffffffffffffff811681146145ce575f5ffd5b602082015260608201516145e181613959565b604082015260808201516001600160401b038111156145fe575f5ffd5b61460d8b602083860101613f24565b60608301525084525060209283019201614567565b509695505050505050565b634e487b7160e01b5f52602160045260245ffd5b5f8235605e198336030181126142fd575f5ffd5b5f60208284031215614665575f5ffd5b8135612ebc816144ed565b66028a096a0a49d160cd1b8152818360078301375f9101600701908152919050565b65028a096a19d160d51b8152818360068301375f9101600601908152919050565b65028a096ab1d160d51b8152818360068301375f9101600601908152919050565b5f60ff821660ff81036146e9576146e9613da0565b60010192915050565b606081525f61470560608301868861421c565b828103602084015261471781866139a2565b91505082604083015295945050505050565b66028a096a0a49d160cd1b81525f612ebc600783018461436c565b65028a096a19d160d51b81525f612ebc600683018461436c565b65028a096ab1d160d51b81525f612ebc600683018461436c565b6affffffffffffffffffffff818116838216029081169081811461479e5761479e613da0565b5092915050565b5f602082840312156147b5575f5ffd5b815160068110612ebc575f5ffd5b5f602082840312156147d3575f5ffd5b81516001600160401b038111156147e8575f5ffd5b8201604081850312156147f9575f5ffd5b614801613eb0565b81516001600160401b03811115614816575f5ffd5b61482286828501613f24565b82525060208201516001600160401b0381111561483d575f5ffd5b80830192505084601f830112614851575f5ffd5b815161485f61453c826144cb565b8082825260208201915060208360051b860101925087831115614880575f5ffd5b602085015b838110156149515780516001600160401b038111156148a2575f5ffd5b860160a0818b03601f190112156148b7575f5ffd5b6148bf613ed2565b6148cb6020830161438e565b81526148d9604083016143a3565b60208201526060820151604082015260808201516001600160401b03811115614900575f5ffd5b61490f8c602083860101613f24565b60608301525060a08201516001600160401b0381111561492d575f5ffd5b61493c8c602083860101613f24565b60808301525084525060209283019201614885565b506020840152509095945050505050565b5f5f8335601e19843603018112614977575f5ffd5b8301803591506001600160401b03821115614990575f5ffd5b6020019150600581901b3603821315613a9e575f5ffd5b5f6149b2828661436c565b6101d160f51b8152838560028301375f930160020192835250909392505050565b608081525f6149e660808301878961421c565b85602084015282810360408401526149fe81866139a2565b9150508260608301529695505050505050565b5f614a1c828561436c565b6101d160f51b8152614a31600282018561436c565b95945050505050565b5f60208284031215614a4a575f5ffd5b5051919050565b606081525f614a6360608301866139a2565b602083019490945250901515604090910152919050565b5f5f5f5f60808587031215614a8d575f5ffd5b845160208601516040870151606088015192965090945092505f81900b8114613d81575f5ffd5b5f81614ac257614ac2613da0565b505f190190565b61010081525f614add61010083018a6139a2565b881515602084015287604084015286606084015285608084015282810360a0840152614b2481600e81526d4563686f4d61726b65744461746160901b602082015260400190565b60c0840195909552505060e001529594505050505056fea26469706673582212209b8baf72f27510b21011fcba0a311b4bca7eab9b383c47acd0f97555f23a7b1b64736f6c634300081e0033000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e031

Deployed Bytecode

0x608060405234801561000f575f5ffd5b5060043610610090575f3560e01c80635c60da1b116100635780635c60da1b1461014a5780639b5e6a8b1461015c578063a509f4d114610183578063cfce1a9914610196578063fe4d5536146101bd575f5ffd5b80630897d6f0146100945780630caba05e146100c457806317a68dd8146100e65780632c960900146100fb575b5f5ffd5b6100a76100a2366004611e68565b6101dd565b6040516001600160a01b0390911681526020015b60405180910390f35b6100d76100d2366004611fa0565b6111ac565b6040516100bb93929190611fb7565b6100f96100f4366004612014565b6114ce565b005b61010e610109366004612036565b611569565b6040805182516001600160401b031681526020808401516001600160a01b039081169183019190915292820151909216908201526060016100bb565b5f546100a7906001600160a01b031681565b6100a77f0000000000000000000000000dac89c5aa805ecd1967828331fb16576125c0ee81565b6100a7610191366004611fa0565b6115e5565b6100a77f000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e03181565b6101d06101cb366004611fa0565b61160d565b6040516100bb91906120a2565b5f6101e7856116a4565b6080820151602001516001600160801b0316156102105761020b82604001516116a4565b610217565b5f60408301525b60088310156102395760405163a9cac3f760e01b815260040160405180910390fd5b604083111561025b57604051638af6981560e01b815260040160405180910390fd5b8160600151515f036102805760405163d57e933160e01b815260040160405180910390fd5b5f6001600160a01b03166003858560405161029c9291906120b4565b908152604051908190036020019020546001600160a01b03600160401b909104161461031c578383600386866040516102d69291906120b4565b908152604051908190036020018120546307bbbbbf60e21b82526103139392916001600160a01b03600160401b90920491909116906004016120eb565b60405180910390fd5b815142111561033e57604051634ef659b360e11b815260040160405180910390fd5b815160208301511161036357604051634d9b1b8b60e01b815260040160405180910390fd5b7f000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e0316001600160a01b031663bec4d54e6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103bf573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103e39190612116565b68ffffffffffffffffff16158015906104955750815160208301516104089190612152565b7f000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e0316001600160a01b031663bec4d54e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610464573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104889190612116565b68ffffffffffffffffff16105b15610541577f000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e0316001600160a01b031663bec4d54e6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104f6573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061051a9190612116565b604051637d1cd12360e11b815268ffffffffffffffffff9091166004820152602401610313565b608082015151610550906116ce565b6080820151602001516001600160801b03161580159061059157508160800151602001516001600160801b03168260800151604001516001600160801b0316105b156105af576040516308cd810560e01b815260040160405180910390fd5b8160c00151515f036105d45760405163656f350160e01b815260040160405180910390fd5b6040805160018082528183019092525f918291606091839190602080830190803683370190505090505b8560c00151518310156108c4575f8660c00151848151811061062257610622612179565b60200260200101515f0151600281111561063e5761063e612165565b0361066a5760405180604001604052806007815260200166028a096a0a49d160cd1b81525091506106eb565b60018660c00151848151811061068257610682612179565b60200260200101515f0151600281111561069e5761069e612165565b036106c95760405180604001604052806006815260200165028a096a19d160d51b81525091506106eb565b60405180604001604052806006815260200165028a096ab1d160d51b81525091505b818660c00151848151811061070257610702612179565b60200260200101516060015160405160200161071f9291906121a4565b60408051601f198184030181529082905261073c916020016121b8565b60405160208183030381529060405280519060200120815f8151811061076457610764612179565b6020026020010181815250507f000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e0316001600160a01b031663e6e2be168760c0015185815181106107b5576107b5612179565b602002602001015160400151836040518363ffffffff1660e01b81526004016107df9291906121c3565b602060405180830381865afa1580156107fa573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061081e9190612218565b61087d57818660c00151848151811061083957610839612179565b6020026020010151606001516040516020016108569291906121a4565b60408051601f1981840301815290829052633ddcaa5960e11b8252610313916004016120a2565b8560c00151838151811061089357610893612179565b6020026020010151602001516affffffffffffffffffffff16846108b79190612237565b93506001909201916105fe565b836064146108e85760405163ecf0160d60e01b815260048101859052602401610313565b5f92505f93508560a0015160200151515f03610917576040516379a7f53760e01b815260040160405180910390fd5b5f8660a0015160200151516001600160401b03811115610939576109396119db565b604051908082528060200260200182016040528015610962578160200160208202803683370190505b5090505b8660a001516020015151841015610b9f578660a0015160200151848151811061099157610991612179565b60200260200101515f015160ff16856109aa9190612237565b94508660a001515f01518760a001516020015185815181106109ce576109ce612179565b6020026020010151606001516040516020016109eb92919061224a565b60408051601f1981840301815290829052610a08916020016121b8565b60405160208183030381529060405280519060200120818581518110610a3057610a30612179565b6020026020010181815250505f7f000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e0316001600160a01b031663b6001e245f848881518110610a7f57610a7f612179565b60200260200101516040518363ffffffff1660e01b8152600401610ab89291906001600160a01b03929092168252602082015260400190565b608060405180830381865afa158015610ad3573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610af79190612273565b505060ff16915050805f03610b695760a08801518051602090910151805187908110610b2557610b25612179565b602002602001015160600151604051602001610b4292919061224a565b60408051601f198184030181529082905263ba7fb2ed60e01b8252610313916004016120a2565b808860a00151602001518681518110610b8457610b84612179565b60209081029190910101516040015250600190930192610966565b84606414610bc357604051630d738e1160e31b815260048101869052602401610313565b6040516373715f0b60e11b81526001600160a01b037f000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e031169063e6e2be1690610c11905f9085906004016121c3565b602060405180830381865afa158015610c2c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c509190612218565b610c6d57604051630361692160e51b815260040160405180910390fd5b610d5f5f5f9054906101000a90046001600160a01b03167f000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e031308d8b604001518e8e604051610cbc9291906120b4565b60405180910390205f1c8d5f01518e60200151604051602001610d339796959493929190606097881b6bffffffffffffffffffffffff19908116825296881b8716601482015294871b861660288601529290951b909316603c83015260508201929092526070810192909252609082015260b00190565b6040516020818303038152906040528b8b604051610d529291906120b4565b604051809103902061177e565b95505f620186a07f000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e0316001600160a01b0316638ddc321f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610dc2573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610de691906122d2565b62ffffff16896080015160400151610dfe91906122f4565b610e089190612331565b9050808860800151604001818151610e20919061235e565b6001600160801b03169052506060880151608089015160a08a015160c08b0151604051631d00132960e31b81526001600160a01b038c169463e800994894610e6e9491939092600401612520565b5f604051808303815f87803b158015610e85575f5ffd5b505af1158015610e97573d5f5f3e3d5ffd5b505050506080880151516001600160a01b03166323b872dd33610eb8611793565b6040516001600160e01b031960e085901b1681526001600160a01b039283166004820152911660248201526001600160801b03841660448201526064016020604051808303815f875af1158015610f11573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f359190612218565b506080880151805160409182015191516323b872dd60e01b81523360048201526001600160a01b038a811660248301526001600160801b0390931660448201529116906323b872dd906064016020604051808303815f875af1158015610f9d573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fc19190612218565b50604080516060810182526001600160401b03421681526001600160a01b03808a1660208301525f5416818301529051600390611001908d908d906120b4565b908152604080516020928190038301812084518154948601516001600160401b039091166001600160e01b031990951694909417600160401b6001600160a01b03958616021781559390910151600190930180546001600160a01b031916939092169290921790558a908a906002905f9061107f90859085906120b4565b604051809103902081526020019081526020015f2091826110a19291906125f4565b506001805480820182555f9182527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60180546001600160a01b0319166001600160a01b038a81169182179092559154604080516304cbb47f60e01b8152905191831693928f1692917f95f1e03f5a5037130736d042aaa0cf4d85d403655b81b55612c57126f636fa92918f918f9185916304cbb47f916004808201926020929091908290030181865afa15801561115a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061117e91906126ad565b6001805490508f6040516111969594939291906126c4565b60405180910390a4505050505050949350505050565b6001545f908190606090156114c757600154600b11156112a7576001546001600160401b038111156111e0576111e06119db565b604051908082528060200260200182016040528015611209578160200160208202803683370190505b5090505f5b60018054829061121f908390612152565b6112299190612152565b8154811061123957611239612179565b905f5260205f20015f9054906101000a90046001600160a01b031682828151811061126657611266612179565b6001600160a01b03909216602092830291909101909101526001805461128c9190612152565b811461129a5760010161120e565b60018093509350506114c7565b835f036112b357600193505b6001546112c290600a906127ad565b91505f6112d083600a6127c0565b6001546112dd9190612152565b6001549091506112ee84600a6127c0565b101561130257826112fe816127d7565b9350505b8285111561130e578294505b8493505f5f8660010361133d57600180546113299190612152565b9150611336600a83612152565b90506113fa565b84870361136757821561135a57611355600184612152565b611360565b60099150815b91506113fa565b825f03611374575f61137f565b61137f600184612152565b6113898887612152565b61139490600a6127c0565b61139e9190612237565b6113a89083612237565b9150825f036113b7575f6113c2565b6113c2600184612152565b60016113ce8988612152565b6113d89190612152565b6113e390600a6127c0565b6113ed9190612237565b6113f79082612237565b90505b80611406836001612237565b6114109190612152565b6001600160401b03811115611427576114276119db565b604051908082528060200260200182016040528015611450578160200160208202803683370190505b5093505f5b6001838154811061146857611468612179565b905f5260205f20015f9054906101000a90046001600160a01b031685828151811061149557611495612179565b6001600160a01b03909216602092830291909101909101528282146114c2575f1990920191600101611455565b505050505b9193909250565b6114d6611793565b6001600160a01b0316336001600160a01b031614611507576040516315cead3960e31b815260040160405180910390fd5b611510816116a4565b5f80546040516001600160a01b03808516939216917fcfbf4028add9318bbf716f08c348595afb063b0e9feed1f86d33681a4b3ed4d391a35f80546001600160a01b0319166001600160a01b0392909216919091179055565b604080516060810182525f8082526020820181905281830152905160039061159490859085906120b4565b90815260408051918290036020908101832060608401835280546001600160401b03811685526001600160a01b03600160401b90910481169285019290925260010154169082015290505b92915050565b600181815481106115f4575f80fd5b5f918252602090912001546001600160a01b0316905081565b60026020525f90815260409020805461162590612570565b80601f016020809104026020016040519081016040528092919081815260200182805461165190612570565b801561169c5780601f106116735761010080835404028352916020019161169c565b820191905f5260205f20905b81548152906001019060200180831161167f57829003601f168201915b505050505081565b6001600160a01b0381166116cb5760405163140efc4d60e21b815260040160405180910390fd5b50565b6040516338b317cf60e21b81526001600160a01b0382811660048301527f000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e031169063e2cc5f3c90602401602060405180830381865afa158015611732573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117569190612218565b6116cb5760405163c1c1013b60e01b81526001600160a01b0382166004820152602401610313565b5f61178b8484845f611819565b949350505050565b5f7f000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e0316001600160a01b031663420f68616040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117f0573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061181491906127ef565b905090565b8251604051613d6160f01b8152603a820160f090811b6002838101919091526680600b3d3981f360c81b600484015264363d3d376160d81b600b8401528301901b6010820181905268603836393d3d3d366160b81b6012830152601b82015262013d7360e81b601d820152606086901b6020808301919091526e5af43d82803e903d91603657fd5bf360881b60348301525f929160458301918390604383019089015b602083106118db5780518252601f1990920191602091820191016118bc565b5f6001846020036101000a0319905080825116835283830192508660f01b83526f67363d3d37363d34f03d5260086018f35f52896010805ff5806119265763ebfef1885f526004601cfd5b8060145261d6945f5260016034536001600160a01b036017601e201698505f5f88888d855af115893b15171561196357638f86d2f15f526004601cfd5b5050505050505050949350505050565b6001600160a01b03811681146116cb575f5ffd5b803561199281611973565b919050565b5f5f83601f8401126119a7575f5ffd5b5081356001600160401b038111156119bd575f5ffd5b6020830191508360208285010111156119d4575f5ffd5b9250929050565b634e487b7160e01b5f52604160045260245ffd5b604080519081016001600160401b0381118282101715611a1157611a116119db565b60405290565b60405160a081016001600160401b0381118282101715611a1157611a116119db565b604051608081016001600160401b0381118282101715611a1157611a116119db565b60405160e081016001600160401b0381118282101715611a1157611a116119db565b604051601f8201601f191681016001600160401b0381118282101715611aa557611aa56119db565b604052919050565b5f82601f830112611abc575f5ffd5b81356001600160401b03811115611ad557611ad56119db565b611ae8601f8201601f1916602001611a7d565b818152846020838601011115611afc575f5ffd5b816020850160208301375f918101602001919091529392505050565b80356001600160801b0381168114611992575f5ffd5b5f60608284031215611b3e575f5ffd5b604051606081016001600160401b0381118282101715611b6057611b606119db565b6040529050808235611b7181611973565b8152611b7f60208401611b18565b6020820152611b9060408401611b18565b60408201525092915050565b5f6001600160401b03821115611bb457611bb46119db565b5060051b60200190565b60ff811681146116cb575f5ffd5b6001600160f81b03811681146116cb575f5ffd5b5f60408284031215611bf0575f5ffd5b611bf86119ef565b905081356001600160401b03811115611c0f575f5ffd5b611c1b84828501611aad565b82525060208201356001600160401b03811115611c36575f5ffd5b8201601f81018413611c46575f5ffd5b8035611c59611c5482611b9c565b611a7d565b8082825260208201915060208360051b850101925086831115611c7a575f5ffd5b602084015b83811015611d4f5780356001600160401b03811115611c9c575f5ffd5b850160a0818a03601f19011215611cb1575f5ffd5b611cb9611a17565b6020820135611cc781611bbe565b81526040820135611cd781611bcc565b60208201526060820135604082015260808201356001600160401b03811115611cfe575f5ffd5b611d0d8b602083860101611aad565b60608301525060a08201356001600160401b03811115611d2b575f5ffd5b611d3a8b602083860101611aad565b60808301525084525060209283019201611c7f565b5060208501525091949350505050565b5f82601f830112611d6e575f5ffd5b8135611d7c611c5482611b9c565b8082825260208201915060208360051b860101925085831115611d9d575f5ffd5b602085015b83811015611e5e5780356001600160401b03811115611dbf575f5ffd5b86016080818903601f19011215611dd4575f5ffd5b611ddc611a39565b602082013560038110611ded575f5ffd5b815260408201356affffffffffffffffffffff81168114611e0c575f5ffd5b6020820152611e1d60608301611987565b604082015260808201356001600160401b03811115611e3a575f5ffd5b611e498a602083860101611aad565b60608301525084525060209283019201611da2565b5095945050505050565b5f5f5f5f60608587031215611e7b575f5ffd5b8435611e8681611973565b935060208501356001600160401b03811115611ea0575f5ffd5b611eac87828801611997565b90945092505060408501356001600160401b03811115611eca575f5ffd5b85016101208188031215611edc575f5ffd5b611ee4611a5b565b8135815260208083013590820152611efe60408301611987565b604082015260608201356001600160401b03811115611f1b575f5ffd5b611f2789828501611aad565b606083015250611f3a8860808401611b2e565b608082015260e08201356001600160401b03811115611f57575f5ffd5b611f6389828501611be0565b60a0830152506101008201356001600160401b03811115611f82575f5ffd5b611f8e89828501611d5f565b60c08301525094979396509194505050565b5f60208284031215611fb0575f5ffd5b5035919050565b5f60608201858352846020840152606060408401528084518083526080850191506020860192505f5b818110156120075783516001600160a01b0316835260209384019390920191600101611fe0565b5090979650505050505050565b5f60208284031215612024575f5ffd5b813561202f81611973565b9392505050565b5f5f60208385031215612047575f5ffd5b82356001600160401b0381111561205c575f5ffd5b61206885828601611997565b90969095509350505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f61202f6020830184612074565b818382375f9101908152919050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b604081525f6120fe6040830185876120c3565b905060018060a01b0383166020830152949350505050565b5f60208284031215612126575f5ffd5b815168ffffffffffffffffff8116811461202f575f5ffd5b634e487b7160e01b5f52601160045260245ffd5b818103818111156115df576115df61213e565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f81518060208401855e5f93019283525090919050565b5f61178b6121b2838661218d565b8461218d565b5f61202f828461218d565b6001600160a01b03831681526040602080830182905283519183018290525f91908401906060840190835b8181101561220c5783518352602093840193909201916001016121ee565b50909695505050505050565b5f60208284031215612228575f5ffd5b8151801515811461202f575f5ffd5b808201808211156115df576115df61213e565b5f612255828561218d565b6101d160f51b815261226a600282018561218d565b95945050505050565b5f5f5f5f60808587031215612286575f5ffd5b845160048110612294575f5ffd5b60208601519094506122a581611bbe565b60408601519093506122b681611bcc565b60608601519092506122c781611973565b939692955090935050565b5f602082840312156122e2575f5ffd5b815162ffffff8116811461202f575f5ffd5b6001600160801b0381811683821602908116908181146123165761231661213e565b5092915050565b634e487b7160e01b5f52601260045260245ffd5b5f6001600160801b038316806123495761234961231d565b806001600160801b0384160491505092915050565b6001600160801b0382811682821603908111156115df576115df61213e565b80516001600160a01b031682526020808201516001600160801b039081169184019190915260409182015116910152565b5f8151604084526123c26040850182612074565b60208481015186830387830152805180845292935081019181840191600582901b8501015f5b8281101561246957601f19868303018452845160ff815116835260018060f81b03602082015116602084015260408101516040840152606081015160a0606085015261243760a0850182612074565b90506080820151915083810360808501526124528183612074565b6020978801979690960195935050506001016123e8565b50979650505050505050565b5f82825180855260208501945060208160051b830101602085015f5b8381101561220c57848303601f1901885281518051600381106124c257634e487b7160e01b5f52602160045260245ffd5b84526020818101516affffffffffffffffffffff16908501526040808201516001600160a01b03169085015260609081015160809185018290529061250990850182612074565b6020998a0199909450929092019150600101612491565b60c081525f61253260c0830187612074565b61253f602084018761237d565b828103608084015261255181866123ae565b905082810360a08401526125658185612475565b979650505050505050565b600181811c9082168061258457607f821691505b6020821081036125a257634e487b7160e01b5f52602260045260245ffd5b50919050565b601f8211156125ef57805f5260205f20601f840160051c810160208510156125cd5750805b601f840160051c820191505b818110156125ec575f81556001016125d9565b50505b505050565b6001600160401b0383111561260b5761260b6119db565b61261f836126198354612570565b836125a8565b5f601f841160018114612650575f85156126395750838201355b5f19600387901b1c1916600186901b1783556125ec565b5f83815260208120601f198716915b8281101561267f578685013582556020948501946001909201910161265f565b508682101561269b575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b5f602082840312156126bd575f5ffd5b5051919050565b60a081525f6126d760a0830187896120c3565b856020840152828103806040850152600e82526d4563686f4d61726b65744461746160901b602083015285606085015260408101608085015250835160408201526020840151606082015260018060a01b036040850151166080820152606084015161012060a083015261274f610160830182612074565b9050608085015161276360c084018261237d565b5060a0850151828203603f190161012084015261278082826123ae565b91505060c0850151603f198383030161014084015261279f8282612475565b9a9950505050505050505050565b5f826127bb576127bb61231d565b500490565b80820281158282048414176115df576115df61213e565b5f600182016127e8576127e861213e565b5060010190565b5f602082840312156127ff575f5ffd5b815161202f8161197356fea2646970667358221220322ce205386d4bf53fbedf82ee35313fb501b6116510630b14d572520a19de4264736f6c634300081e0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e031

-----Decoded View---------------
Arg [0] : echoAdministration (address): 0x787FB122fC8a4B183e42e92DD48D1b52F707e031

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 000000000000000000000000787fb122fc8a4b183e42e92dd48d1b52f707e031


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.