ETH Price: $2,445.56 (-1.60%)

Token

Decentralized Universal Basic Income (DUBI)
 

Overview

Max Total Supply

654,806.584971570290661129 DUBI

Holders

830

Market

Onchain Market Cap

$0.00

Circulating Supply Market Cap

-

Other Info

Token Contract (WITH 18 Decimals)

Balance
58.597286427258955683 DUBI

Value
$0.00
0x3b4da358199060bcc5a527ab60099fb6a908aae9
Loading...
Loading
Loading...
Loading
Loading...
Loading

OVERVIEW

PRPS is a not-for-profit project. Its creator distributed the tokens free of charge towards a community that supports philanthropic endeavors. Various supporters have since developed initiatives around it such as the well known DEX DubiEx and the popular iOS/Android game Clash of Streamers.

# Exchange Pair Price  24H Volume % Volume
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.

Contract Source Code Verified (Exact Match)

Contract Name:
Dubi

Compiler Version
v0.6.12+commit.27d51765

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 19 : Dubi.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "./ERC20.sol";
import "./Purpose.sol";

contract Dubi is ERC20 {
    Purpose private immutable _prps;

    constructor(
        uint256 initialSupply,
        address optIn,
        address purpose,
        address hodl,
        address externalAddress1,
        address externalAddress2,
        address externalAddress3
    )
        public
        ERC20(
            "Decentralized Universal Basic Income",
            "DUBI",
            optIn,
            hodl,
            externalAddress1,
            externalAddress2,
            externalAddress3
        )
    {
        _mintInitialSupply(msg.sender, initialSupply);

        _prps = Purpose(purpose);
    }

    function hodlMint(address to, uint256 amount) public {
        require(msg.sender == _hodlAddress, "DUBI-2");
        _mint(to, amount);
    }

    function purposeMint(address to, uint256 amount) public {
        require(msg.sender == address(_prps), "DUBI-3");
        _mint(to, amount);
    }

    function _callerIsDeployTimeKnownContract()
        internal
        override
        view
        returns (bool)
    {
        if (msg.sender == address(_prps)) {
            return true;
        }

        return super._callerIsDeployTimeKnownContract();
    }

    //---------------------------------------------------------------
    // Fuel
    //---------------------------------------------------------------

    /**
     * @dev Burns `fuel` from `from`. Can only be called by one of the deploy-time known contracts.
     */
    function burnFuel(address from, TokenFuel memory fuel) public override {
        require(_callerIsDeployTimeKnownContract(), "DUBI-1");
        _burnFuel(from, fuel);
    }

    function _burnFuel(address from, TokenFuel memory fuel) private {
        require(fuel.amount <= MAX_BOOSTER_FUEL, "DUBI-5");
        require(from != address(0) && from != msg.sender, "DUBI-6");

        if (fuel.tokenAlias == TOKEN_FUEL_ALIAS_DUBI) {
            // Burn fuel from DUBI
            UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);
            require(unpacked.balance >= fuel.amount, "DUBI-7");
            unpacked.balance -= fuel.amount;
            _packedData[from] = _packUnpackedData(unpacked);
            return;
        }

        revert("DUBI-8");
    }

    /**
     *@dev Burn the fuel of a `boostedSend`
     */
    function _burnBoostedSendFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal override returns (FuelBurn memory) {
        FuelBurn memory fuelBurn;

        if (fuel.dubi > 0) {
            require(fuel.dubi <= MAX_BOOSTER_FUEL, "DUBI-5");

            // From uses his own DUBI to fuel the boost
            require(unpacked.balance >= fuelBurn.amount, "DUBI-7");
            unpacked.balance -= fuel.dubi;

            fuelBurn.amount = fuel.dubi;
            fuelBurn.fuelType = FuelType.DUBI;

            return fuelBurn;
        }

        // If the fuel is PRPS, then we have to reach out to the PRPS contract.
        if (fuel.unlockedPrps > 0) {
            // Reverts if the requested amount cannot be burned
            _prps.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_UNLOCKED_PRPS,
                    amount: fuel.unlockedPrps
                })
            );

            fuelBurn.amount = fuel.unlockedPrps;
            fuelBurn.fuelType = FuelType.UNLOCKED_PRPS;
            return fuelBurn;
        }

        if (fuel.lockedPrps > 0) {
            // Reverts if the requested amount cannot be burned
            _prps.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_LOCKED_PRPS,
                    amount: fuel.lockedPrps
                })
            );

            fuelBurn.amount = fuel.lockedPrps;
            fuelBurn.fuelType = FuelType.LOCKED_PRPS;
            return fuelBurn;
        }

        // No fuel at all
        return fuelBurn;
    }

    /**
     *@dev Burn the fuel of a `boostedBurn`
     */
    function _burnBoostedBurnFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal override returns (FuelBurn memory) {
        FuelBurn memory fuelBurn;

        // If the fuel is DUBI, then we can remove it directly
        if (fuel.dubi > 0) {
            require(fuel.dubi <= MAX_BOOSTER_FUEL, "DUBI-5");

            require(unpacked.balance >= fuel.dubi, "DUBI-7");
            unpacked.balance -= fuel.dubi;

            fuelBurn.amount = fuel.dubi;
            fuelBurn.fuelType = FuelType.DUBI;

            return fuelBurn;
        }

        // If the fuel is PRPS, then we have to reach out to the PRPS contract.
        if (fuel.unlockedPrps > 0) {
            // Reverts if the requested amount cannot be burned
            _prps.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_UNLOCKED_PRPS,
                    amount: fuel.unlockedPrps
                })
            );

            fuelBurn.amount = fuel.unlockedPrps;
            fuelBurn.fuelType = FuelType.UNLOCKED_PRPS;

            return fuelBurn;
        }

        if (fuel.lockedPrps > 0) {
            // Reverts if the requested amount cannot be burned
            _prps.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_LOCKED_PRPS,
                    amount: fuel.lockedPrps
                })
            );

            // No direct fuel, but we still return a indirect fuel so that it can be added
            // to the burn event.
            fuelBurn.amount = fuel.lockedPrps;
            fuelBurn.fuelType = FuelType.LOCKED_PRPS;
            return fuelBurn;
        }

        // DUBI has no intrinsic fuel
        if (fuel.intrinsicFuel > 0) {
            revert("DUBI-8");
        }

        // No fuel at all
        return fuelBurn;
    }

    //---------------------------------------------------------------
    // Pending ops
    //---------------------------------------------------------------
    function _getHasherContracts()
        internal
        override
        returns (address[] memory)
    {
        address[] memory hashers = new address[](5);
        hashers[0] = address(this);
        hashers[1] = address(_prps);
        hashers[2] = _hodlAddress;
        hashers[3] = _externalAddress1;
        hashers[4] = _externalAddress2;

        return hashers;
    }
}

File 2 of 19 : ERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/introspection/IERC1820Registry.sol";
import "./IBoostableERC20.sol";
import "./BoostableERC20.sol";

/**
 * @dev This is a heavily modified fork of @openzeppelin/contracts/token/ERC20/ERC20.sol (3.1.0)
 */
abstract contract ERC20 is IERC20, IBoostableERC20, BoostableERC20, Ownable {
    using SafeMath for uint256;

    // NOTE: In contrary to the Transfer event, the Burned event always
    // emits the amount including the burned fuel if any.
    // The amount is stored in the lower 96 bits of `amountAndFuel`,
    // followed by 3 bits to encode the type of fuel used and finally
    // another 96 bits for the fuel amount.
    //
    // 0         96        99                 195             256
    //   amount    fuelType      fuelAmount         padding
    //
    event Burned(uint256 amountAndFuel, bytes data);

    enum FuelType {NONE, UNLOCKED_PRPS, LOCKED_PRPS, DUBI, AUTO_MINTED_DUBI}

    struct FuelBurn {
        FuelType fuelType;
        uint96 amount;
    }

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    address internal immutable _hodlAddress;

    address internal immutable _externalAddress1;
    address internal immutable _externalAddress2;
    address internal immutable _externalAddress3;

    IERC1820Registry internal constant _ERC1820_REGISTRY = IERC1820Registry(
        0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
    );

    // Mapping of address to packed data.
    // For efficiency reasons the token balance is a packed uint96 alongside
    // other data. The packed data has the following layout:
    //
    //   MSB                      uint256                      LSB
    //      uint64 nonce | uint96 hodlBalance | uint96 balance
    //
    // balance: the balance of a token holder that can be transferred freely
    // hodlBalance: the balance of a token holder that is hodled
    // nonce: a sequential number used for booster replay protection
    //
    // Only PRPS utilizes `hodlBalance`. For DUBI it is always 0.
    //
    mapping(address => uint256) internal _packedData;

    struct UnpackedData {
        uint96 balance;
        uint96 hodlBalance;
        uint64 nonce;
    }

    function _unpackPackedData(uint256 packedData)
        internal
        pure
        returns (UnpackedData memory)
    {
        UnpackedData memory unpacked;

        // 1) Read balance from the first 96 bits
        unpacked.balance = uint96(packedData);

        // 2) Read hodlBalance from the next 96 bits
        unpacked.hodlBalance = uint96(packedData >> 96);

        // 3) Read nonce from the next 64 bits
        unpacked.nonce = uint64(packedData >> (96 + 96));

        return unpacked;
    }

    function _packUnpackedData(UnpackedData memory unpacked)
        internal
        pure
        returns (uint256)
    {
        uint256 packedData;

        // 1) Write balance to the first 96 bits
        packedData |= unpacked.balance;

        // 2) Write hodlBalance to the the next 96 bits
        packedData |= uint256(unpacked.hodlBalance) << 96;

        // 3) Write nonce to the next 64 bits
        packedData |= uint256(unpacked.nonce) << (96 + 96);

        return packedData;
    }

    // ERC20-allowances
    mapping(address => mapping(address => uint256)) private _allowances;

    //---------------------------------------------------------------
    // Pending state for non-boosted operations while opted-in
    //---------------------------------------------------------------
    uint8 internal constant OP_TYPE_SEND = BOOST_TAG_SEND;
    uint8 internal constant OP_TYPE_BURN = BOOST_TAG_BURN;

    struct PendingTransfer {
        // NOTE: For efficiency reasons balances are stored in a uint96 which is sufficient
        // since we only use 18 decimals.
        //
        // Two amounts are associated with a pending transfer, to allow deriving contracts
        // to store extra information.
        //
        // E.g. PRPS makes use of this by encoding the pending locked PRPS in the
        // `occupiedAmount` field.
        //
        address spender;
        uint96 transferAmount;
        address to;
        uint96 occupiedAmount;
        bytes data;
    }

    // A mapping of hash(user, opId) to pending transfers. Pending burns are also considered regular transfers.
    mapping(bytes32 => PendingTransfer) private _pendingTransfers;

    //---------------------------------------------------------------

    constructor(
        string memory name,
        string memory symbol,
        address optIn,
        address hodl,
        address externalAddress1,
        address externalAddress2,
        address externalAddress3
    ) public Ownable() BoostableERC20(optIn) {
        _name = name;
        _symbol = symbol;

        _hodlAddress = hodl;
        _externalAddress1 = externalAddress1;
        _externalAddress2 = externalAddress2;
        _externalAddress3 = externalAddress3;

        // register interfaces
        _ERC1820_REGISTRY.setInterfaceImplementer(
            address(this),
            keccak256("BoostableERC20Token"),
            address(this)
        );
        _ERC1820_REGISTRY.setInterfaceImplementer(
            address(this),
            keccak256("ERC20Token"),
            address(this)
        );
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals.
     */
    function decimals() public pure returns (uint8) {
        return 18;
    }

    /**
     * @dev Returns the current nonce of `account`
     */
    function getNonce(address account) external override view returns (uint64) {
        UnpackedData memory unpacked = _unpackPackedData(_packedData[account]);
        return unpacked.nonce;
    }

    /**
     * @dev Returns the total supply
     */
    function totalSupply()
        external
        override(IBoostableERC20, IERC20)
        view
        returns (uint256)
    {
        return _totalSupply;
    }

    /**
     * @dev Returns the amount of tokens owned by an account (`tokenHolder`).
     */
    function balanceOf(address tokenHolder)
        public
        override(IBoostableERC20, IERC20)
        view
        returns (uint256)
    {
        // Return the balance of the holder that is not hodled (i.e. first 96 bits of the packeData)
        return uint96(_packedData[tokenHolder]);
    }

    /**
     * @dev Returns the unpacked data struct of `tokenHolder`
     */
    function unpackedDataOf(address tokenHolder)
        public
        view
        returns (UnpackedData memory)
    {
        return _unpackPackedData(_packedData[tokenHolder]);
    }

    /**
     * @dev Mints `amount` new tokens for `to`.
     *
     * To make things more efficient, the total supply is optionally packed into the passed
     * amount where the first 96 bits are used for the actual amount and the following 96 bits
     * for the total supply.
     *
     */
    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }

    function _mintInitialSupply(address to, uint256 amount) internal {
        // _mint does not update the totalSupply by default, unless the second 96 bits
        // passed are non-zero - in which case the non-zero value becomes the new total supply.
        // So in order to get the correct initial supply, we have to mirror the lower 96 bits
        // to the following 96 bits.
        amount = amount | (amount << 96);
        _mint(to, amount);
    }

    function _mint(address to, uint256 amount) internal {
        require(to != address(0), "ERC20-1");

        // The actual amount to mint (=lower 96 bits)
        uint96 amountToMint = uint96(amount);

        // The new total supply, which may be 0 in which case no update is performed.
        uint96 updatedTotalSupply = uint96(amount >> 96);

        // Update state variables
        if (updatedTotalSupply > 0) {
            _totalSupply = updatedTotalSupply;
        }

        // Update packed data and check for uint96 overflow
        UnpackedData memory unpacked = _unpackPackedData(_packedData[to]);
        uint96 updatedBalance = unpacked.balance + amountToMint;

        // The overflow check also takes the hodlBalance into account
        require(
            updatedBalance + unpacked.hodlBalance >= unpacked.balance,
            "ERC20-2"
        );

        unpacked.balance = updatedBalance;
        _packedData[to] = _packUnpackedData(unpacked);

        emit Transfer(address(0), to, amountToMint);
    }

    /**
     * @dev Transfer `amount` from msg.sender to `recipient`
     */
    function transfer(address recipient, uint256 amount)
        public
        override(IBoostableERC20, IERC20)
        returns (bool)
    {
        _assertSenderRecipient(msg.sender, recipient);

        // Never create a pending transfer if msg.sender is a deploy-time known contract
        if (!_callerIsDeployTimeKnownContract()) {
            // Create pending transfer if sender is opted-in and the permaboost is active
            address from = msg.sender;
            IOptIn.OptInStatus memory optInStatus = getOptInStatus(from);
            if (optInStatus.isOptedIn && optInStatus.permaBoostActive) {
                _createPendingTransfer({
                    opType: OP_TYPE_SEND,
                    spender: msg.sender,
                    from: msg.sender,
                    to: recipient,
                    amount: amount,
                    data: "",
                    optInStatus: optInStatus
                });

                return true;
            }
        }

        _move({from: msg.sender, to: recipient, amount: amount});

        return true;
    }

    /**
     * @dev Burns `amount` of msg.sender.
     *
     * Also emits a {IERC20-Transfer} event for ERC20 compatibility.
     */
    function burn(uint256 amount, bytes memory data) public {
        // Create pending burn if sender is opted-in and the permaboost is active
        IOptIn.OptInStatus memory optInStatus = getOptInStatus(msg.sender);
        if (optInStatus.isOptedIn && optInStatus.permaBoostActive) {
            _createPendingTransfer({
                opType: OP_TYPE_BURN,
                spender: msg.sender,
                from: msg.sender,
                to: address(0),
                amount: amount,
                data: data,
                optInStatus: optInStatus
            });

            return;
        }

        _burn({
            from: msg.sender,
            amount: amount,
            data: data,
            incrementNonce: false
        });
    }

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient`.
     *
     * Can only be used by deploy-time known contracts.
     *
     * IBoostableERC20 extension
     */
    function boostedTransferFrom(
        address sender,
        address recipient,
        uint256 amount,
        bytes calldata data
    ) public override returns (bool) {
        _assertSenderRecipient(sender, recipient);

        IOptIn.OptInStatus memory optInStatus = getOptInStatus(sender);

        // Only transfer if `sender` is a deploy-time known contract, otherwise
        // revert.
        require(
            _isDeployTimeKnownContractAndCanTransfer(
                sender,
                recipient,
                amount,
                optInStatus,
                data
            ),
            "ERC20-17"
        );

        _move({from: sender, to: recipient, amount: amount});
        return true;
    }

    function _isDeployTimeKnownContractAndCanTransfer(
        address sender,
        address recipient,
        uint256 amount,
        IOptIn.OptInStatus memory optInStatus,
        bytes memory data
    ) private view returns (bool) {
        // If the caller not a deploy-time known contract, the transfer is not allowed
        if (!_callerIsDeployTimeKnownContract()) {
            return false;
        }

        if (msg.sender != _externalAddress3) {
            return true;
        }

        // _externalAddress3 passes a flag via `data` that indicates whether it is a boosted transaction
        // or not.
        uint8 isBoostedBits;
        assembly {
            // Load flag using a 1-byte offset, because `mload` always reads
            // 32-bytes at once and the first 32 bytes of `data` contain it's length.
            isBoostedBits := mload(add(data, 0x01))
        }

        // Reading into a 'bool' directly doesn't work for some reason
        if (isBoostedBits & 1 == 1) {
            return true;
        }

        //  If the latter, then _externalAddress3 can only transfer the funds if either:
        // - the permaboost is not active
        // - `sender` is not opted-in to begin with
        //
        // If `sender` is opted-in and the permaboost is active, _externalAddress3 cannot
        // take funds, except when boosted. Here the booster trusts _externalAddress3, since it already
        // verifies that `sender` provided a valid signature.
        //
        // This is special to _externalAddress3, other deploy-time known contracts do not make use of `data`.
        if (optInStatus.permaBoostActive && optInStatus.isOptedIn) {
            return false;
        }

        return true;
    }

    /**
     * @dev Verify the booster payload against the nonce that is stored in the packed data of an account.
     * The increment happens outside of this function, when the balance is updated.
     */
    function _verifyNonce(BoosterPayload memory payload, uint64 currentNonce)
        internal
        pure
    {
        require(currentNonce == payload.nonce - 1, "ERC20-5");
    }

    //---------------------------------------------------------------
    // Boosted functions
    //---------------------------------------------------------------

    /**
     * @dev Perform multiple `boostedSend` calls in a single transaction.
     *
     * NOTE: Booster extension
     */
    function boostedSendBatch(
        BoostedSend[] memory sends,
        Signature[] memory signatures
    ) external {
        require(
            sends.length > 0 && sends.length == signatures.length,
            "ERC20-6"
        );

        for (uint256 i = 0; i < sends.length; i++) {
            boostedSend(sends[i], signatures[i]);
        }
    }

    /**
     * @dev Perform multiple `boostedBurn` calls in a single transaction.
     *
     * NOTE: Booster extension
     */
    function boostedBurnBatch(
        BoostedBurn[] memory burns,
        Signature[] memory signatures
    ) external {
        require(
            burns.length > 0 && burns.length == signatures.length,
            "ERC20-6"
        );

        for (uint256 i = 0; i < burns.length; i++) {
            boostedBurn(burns[i], signatures[i]);
        }
    }

    /**
     * @dev Send `amount` tokens from `sender` to recipient`.
     * The `sender` must be opted-in and the `msg.sender` must be a trusted booster.
     *
     * NOTE: Booster extension
     */
    function boostedSend(BoostedSend memory send, Signature memory signature)
        public
    {
        address from = send.sender;
        address to = send.recipient;

        UnpackedData memory unpackedFrom = _unpackPackedData(_packedData[from]);
        UnpackedData memory unpackedTo = _unpackPackedData(_packedData[to]);

        // We verify the nonce separately, since it's stored next to the balance
        _verifyNonce(send.boosterPayload, unpackedFrom.nonce);

        _verifyBoostWithoutNonce(
            send.sender,
            hashBoostedSend(send, msg.sender),
            send.boosterPayload,
            signature
        );

        FuelBurn memory fuelBurn = _burnBoostedSendFuel(
            from,
            send.fuel,
            unpackedFrom
        );

        _moveUnpacked({
            from: send.sender,
            unpackedFrom: unpackedFrom,
            to: send.recipient,
            unpackedTo: unpackedTo,
            amount: send.amount,
            fuelBurn: fuelBurn,
            incrementNonce: true
        });
    }

    /**
     * @dev Burn the fuel of a `boostedSend`. Returns a `FuelBurn` struct containing information about the burn.
     */
    function _burnBoostedSendFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal virtual returns (FuelBurn memory);

    /**
     * @dev Burn `amount` tokens from `account`.
     * The `account` must be opted-in and the `msg.sender` must be a trusted booster.
     *
     * NOTE: Booster extension
     */
    function boostedBurn(
        BoostedBurn memory message,
        // A signature, that is compared against the function payload and only accepted if signed by 'sender'
        Signature memory signature
    ) public {
        address from = message.account;
        UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);

        // We verify the nonce separately, since it's stored next to the balance
        _verifyNonce(message.boosterPayload, unpacked.nonce);

        _verifyBoostWithoutNonce(
            message.account,
            hashBoostedBurn(message, msg.sender),
            message.boosterPayload,
            signature
        );

        FuelBurn memory fuelBurn = _burnBoostedBurnFuel(
            from,
            message.fuel,
            unpacked
        );

        _burnUnpacked({
            from: message.account,
            unpacked: unpacked,
            amount: message.amount,
            data: message.data,
            incrementNonce: true,
            fuelBurn: fuelBurn
        });
    }

    /**
     * @dev Burn the fuel of a `boostedSend`. Returns a `FuelBurn` struct containing information about the burn.
     */
    function _burnBoostedBurnFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal virtual returns (FuelBurn memory);

    function burnFuel(address from, TokenFuel memory fuel)
        external
        virtual
        override
    {}

    //---------------------------------------------------------------

    /**
     * @dev Get the allowance of `spender` for `holder`
     */
    function allowance(address holder, address spender)
        public
        override(IBoostableERC20, IERC20)
        view
        returns (uint256)
    {
        return _allowances[holder][spender];
    }

    /**
     * @dev Increase the allowance of `spender` by `value` for msg.sender
     */
    function approve(address spender, uint256 value)
        public
        override(IBoostableERC20, IERC20)
        returns (bool)
    {
        address holder = msg.sender;
        _assertSenderRecipient(holder, spender);
        _approve(holder, spender, value);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue)
        public
        virtual
        returns (bool)
    {
        _assertSenderRecipient(msg.sender, spender);
        _approve(
            msg.sender,
            spender,
            _allowances[msg.sender][spender].add(addedValue)
        );
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue)
        public
        virtual
        returns (bool)
    {
        _assertSenderRecipient(msg.sender, spender);
        _approve(
            msg.sender,
            spender,
            _allowances[msg.sender][spender].sub(subtractedValue, "ERC20-18")
        );
        return true;
    }

    /**
     * @dev Transfer `amount` from `holder` to `recipient`.
     *
     * `msg.sender` requires an allowance >= `amount` of `holder`.
     */
    function transferFrom(
        address holder,
        address recipient,
        uint256 amount
    ) public override(IBoostableERC20, IERC20) returns (bool) {
        _assertSenderRecipient(holder, recipient);

        address spender = msg.sender;

        // Create pending transfer if the token holder is opted-in and the permaboost is active
        IOptIn.OptInStatus memory optInStatus = getOptInStatus(holder);
        if (optInStatus.isOptedIn && optInStatus.permaBoostActive) {
            // Ignore allowances if holder is opted-in
            require(holder == spender, "ERC20-7");

            _createPendingTransfer({
                opType: OP_TYPE_SEND,
                spender: spender,
                from: holder,
                to: recipient,
                amount: amount,
                data: "",
                optInStatus: optInStatus
            });

            return true;
        }

        // Not opted-in, but we still need to check approval of the given spender

        _approve(
            holder,
            spender,
            _allowances[holder][spender].sub(amount, "ERC20-4")
        );

        _move({from: holder, to: recipient, amount: amount});

        return true;
    }

    /**
     * @dev Burn tokens
     * @param from address token holder address
     * @param amount uint256 amount of tokens to burn
     * @param data bytes extra information provided by the token holder
     * @param incrementNonce whether to increment the nonce or not - only true for boosted burns
     */
    function _burn(
        address from,
        uint256 amount,
        bytes memory data,
        bool incrementNonce
    ) internal virtual {
        require(from != address(0), "ERC20-8");

        UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);

        // Empty fuel burn
        FuelBurn memory fuelBurn;

        _burnUnpacked({
            from: from,
            unpacked: unpacked,
            amount: amount,
            data: data,
            incrementNonce: incrementNonce,
            fuelBurn: fuelBurn
        });
    }

    function _burnUnpacked(
        address from,
        UnpackedData memory unpacked,
        uint256 amount,
        bytes memory data,
        bool incrementNonce,
        FuelBurn memory fuelBurn
    ) internal {
        // _beforeBurn allows deriving contracts to run additional logic and affect the amount
        // that is actually getting burned. E.g. when burning PRPS, a portion of it might be taken
        // from the `hodlBalance`. Thus the returned `burnAmount` overrides `amount` and will be
        // subtracted from the actual `balance`.

        uint96 actualBurnAmount = _beforeBurn({
            from: from,
            unpacked: unpacked,
            transferAmount: uint96(amount),
            occupiedAmount: 0,
            createdAt: uint32(block.timestamp),
            fuelBurn: fuelBurn,
            finalizing: false
        });

        // Update to new balance

        if (incrementNonce) {
            // The nonce uses 64 bits, so a overflow is pretty much impossible
            // via increments of 1.
            unpacked.nonce++;
        }

        if (actualBurnAmount > 0) {
            require(unpacked.balance >= actualBurnAmount, "ERC20-9");
            unpacked.balance -= actualBurnAmount;
        }

        // Update packed data by writing to storage
        _packedData[from] = _packUnpackedData(unpacked);

        // Total supply can be updated in batches elsewhere, shaving off another >5k gas.
        // _totalSupply = _totalSupply.sub(amount);

        // The `Burned` event is emitted with the total amount that got burned.
        // Furthermore, the fuel used is encoded in the upper bits.
        uint256 amountAndFuel;

        // Set first 96 bits to amount
        amountAndFuel |= uint96(amount);

        // Set next 3 bits to fuel type
        uint8 fuelType = uint8(fuelBurn.fuelType);
        amountAndFuel |= uint256(fuelType) << 96;

        // Set next 96 bits to fuel amount
        amountAndFuel |= uint256(fuelBurn.amount) << (96 + 3);

        emit Burned(amountAndFuel, data);

        // We emit a transfer event with the actual burn amount excluding burned `hodlBalance`.
        emit Transfer(from, address(0), actualBurnAmount);
    }

    /**
     * @dev Allow deriving contracts to prepare a burn. By default it behaves like an identity function
     * and just returns the amount passed in.
     */
    function _beforeBurn(
        address from,
        UnpackedData memory unpacked,
        uint96 transferAmount,
        uint96 occupiedAmount,
        uint32 createdAt,
        FuelBurn memory fuelBurn,
        bool finalizing
    ) internal virtual returns (uint96) {
        return transferAmount;
    }

    function _move(
        address from,
        address to,
        uint256 amount
    ) internal {
        UnpackedData memory unpackedFrom = _unpackPackedData(_packedData[from]);
        UnpackedData memory unpackedTo = _unpackPackedData(_packedData[to]);

        // Empty fuel burn
        FuelBurn memory fuelBurn;

        _moveUnpacked({
            from: from,
            unpackedFrom: unpackedFrom,
            to: to,
            unpackedTo: unpackedTo,
            amount: amount,
            incrementNonce: false,
            fuelBurn: fuelBurn
        });
    }

    function _moveUnpacked(
        address from,
        UnpackedData memory unpackedFrom,
        address to,
        UnpackedData memory unpackedTo,
        uint256 amount,
        bool incrementNonce,
        FuelBurn memory fuelBurn
    ) internal {
        require(from != to, "ERC20-19");

        // Increment nonce of sender if it's a boosted send
        if (incrementNonce) {
            // The nonce uses 64 bits, so a overflow is pretty much impossible
            // via increments of 1.
            unpackedFrom.nonce++;
        }

        // Check if sender has enough tokens
        uint96 transferAmount = uint96(amount);
        require(unpackedFrom.balance >= transferAmount, "ERC20-10");

        // Subtract transfer amount from sender balance
        unpackedFrom.balance -= transferAmount;

        // Check that recipient balance doesn't overflow
        uint96 updatedRecipientBalance = unpackedTo.balance + transferAmount;
        require(updatedRecipientBalance >= unpackedTo.balance, "ERC20-12");
        unpackedTo.balance = updatedRecipientBalance;

        _packedData[from] = _packUnpackedData(unpackedFrom);
        _packedData[to] = _packUnpackedData(unpackedTo);

        // The transfer amount does not include any used fuel
        emit Transfer(from, to, transferAmount);
    }

    /**
     * @dev See {ERC20-_approve}.
     */
    function _approve(
        address holder,
        address spender,
        uint256 value
    ) internal {
        _allowances[holder][spender] = value;
        emit Approval(holder, spender, value);
    }

    function _assertSenderRecipient(address sender, address recipient)
        private
        pure
    {
        require(sender != address(0) && recipient != address(0), "ERC20-13");
    }

    /**
     * @dev Checks whether msg.sender is a deploy-time known contract or not.
     */
    function _callerIsDeployTimeKnownContract()
        internal
        virtual
        view
        returns (bool)
    {
        if (msg.sender == _hodlAddress) {
            return true;
        }

        if (msg.sender == _externalAddress1) {
            return true;
        }

        if (msg.sender == _externalAddress2) {
            return true;
        }

        if (msg.sender == _externalAddress3) {
            return true;
        }

        return false;
    }

    //---------------------------------------------------------------
    // Pending ops
    //---------------------------------------------------------------

    /**
     * @dev Create a pending transfer
     */
    function _createPendingTransfer(
        uint8 opType,
        address spender,
        address from,
        address to,
        uint256 amount,
        bytes memory data,
        IOptIn.OptInStatus memory optInStatus
    ) private {
        OpHandle memory opHandle = _createNewOpHandle(
            optInStatus,
            from,
            opType
        );

        PendingTransfer memory pendingTransfer = _createPendingTransferInternal(
            opHandle,
            spender,
            from,
            to,
            amount,
            data
        );

        _pendingTransfers[_getOpKey(from, opHandle.opId)] = pendingTransfer;

        // Emit PendingOp event
        emit PendingOp(from, opHandle.opId, opHandle.opType);
    }

    /**
     * @dev Create a pending transfer by moving the funds of `spender` to this contract.
     * Deriving contracts may override this function.
     */
    function _createPendingTransferInternal(
        OpHandle memory opHandle,
        address spender,
        address from,
        address to,
        uint256 amount,
        bytes memory data
    ) internal virtual returns (PendingTransfer memory) {
        // Move funds into this contract

        // Reverts if `from` has less than `amount` tokens.
        _move({from: from, to: address(this), amount: amount});

        // Create op
        PendingTransfer memory pendingTransfer = PendingTransfer({
            transferAmount: uint96(amount),
            spender: spender,
            occupiedAmount: 0,
            to: to,
            data: data
        });

        return pendingTransfer;
    }

    /**
     * @dev Finalize a pending op
     */
    function finalizePendingOp(address user, OpHandle memory opHandle) public {
        uint8 opType = opHandle.opType;

        // Assert that the caller (msg.sender) is allowed to finalize the given op
        uint32 createdAt = uint32(_assertCanFinalize(user, opHandle));

        // Reverts if opId doesn't exist
        PendingTransfer storage pendingTransfer = _safeGetPendingTransfer(
            user,
            opHandle.opId
        );

        // Cleanup
        // NOTE: We do not delete the pending transfer struct, because it only makes it
        // more expensive since we already hit the gas refund limit.
        //
        // delete _pendingTransfers[_getOpKey(user, opHandle.opId)];
        //
        // The difference is ~13k gas.
        //
        // Deleting the op handle is enough to invalidate an opId forever:
        _deleteOpHandle(user, opHandle);

        // Call op type specific finalize
        if (opType == OP_TYPE_SEND) {
            _finalizeTransferOp(pendingTransfer, user, createdAt);
        } else if (opType == OP_TYPE_BURN) {
            _finalizePendingBurn(pendingTransfer, user, createdAt);
        } else {
            revert("ERC20-15");
        }

        // Emit event
        emit FinalizedOp(user, opHandle.opId, opType);
    }

    /**
     * @dev Finalize a pending transfer
     */
    function _finalizeTransferOp(
        PendingTransfer storage pendingTransfer,
        address from,
        uint32 createdAt
    ) private {
        address to = pendingTransfer.to;

        uint96 transferAmount = pendingTransfer.transferAmount;

        address _this = address(this);
        UnpackedData memory unpackedThis = _unpackPackedData(
            _packedData[_this]
        );
        UnpackedData memory unpackedTo = _unpackPackedData(_packedData[to]);

        // Check that sender balance does not overflow
        require(unpackedThis.balance >= transferAmount, "ERC20-2");
        unpackedThis.balance -= transferAmount;

        // Check that recipient doesn't overflow
        uint96 updatedBalanceRecipient = unpackedTo.balance + transferAmount;
        require(updatedBalanceRecipient >= unpackedTo.balance, "ERC20-2");

        unpackedTo.balance = updatedBalanceRecipient;

        _packedData[_this] = _packUnpackedData(unpackedThis);
        _packedData[to] = _packUnpackedData(unpackedTo);

        // Transfer event is emitted with original sender
        emit Transfer(from, to, transferAmount);
    }

    /**
     * @dev Finalize a pending burn
     */
    function _finalizePendingBurn(
        PendingTransfer storage pendingTransfer,
        address from,
        uint32 createdAt
    ) private {
        uint96 transferAmount = pendingTransfer.transferAmount;

        // We pass the packedData of `from` to `_beforeBurn`, because it PRPS needs to update
        // the `hodlBalance` which is NOT on the contract's own packedData.
        UnpackedData memory unpackedFrom = _unpackPackedData(_packedData[from]);

        // Empty fuel burn
        FuelBurn memory fuelBurn;

        uint96 burnAmountExcludingLockedPrps = _beforeBurn({
            from: from,
            unpacked: unpackedFrom,
            transferAmount: transferAmount,
            occupiedAmount: pendingTransfer.occupiedAmount,
            createdAt: createdAt,
            fuelBurn: fuelBurn,
            finalizing: true
        });

        // Update to new balance
        // NOTE: We change the balance of this contract, because that's where
        // the pending PRPS went to.
        address _this = address(this);
        UnpackedData memory unpackedOfContract = _unpackPackedData(
            _packedData[_this]
        );
        require(
            unpackedOfContract.balance >= burnAmountExcludingLockedPrps,
            "ERC20-2"
        );

        unpackedOfContract.balance -= burnAmountExcludingLockedPrps;
        _packedData[_this] = _packUnpackedData(unpackedOfContract);
        _packedData[from] = _packUnpackedData(unpackedFrom);

        // Furthermore, total supply can be updated elsewhere, shaving off another >5k gas.
        // _totalSupply = _totalSupply.sub(amount);

        // Emit events using the same `transferAmount` instead of what `_beforeBurn`
        // returned which is only used for updating the balance correctly.
        emit Burned(transferAmount, pendingTransfer.data);
        emit Transfer(from, address(0), transferAmount);
    }

    /**
     * @dev Revert a pending operation.
     *
     * Only the opted-in booster can revert a transaction if it provides a signed and still valid booster message
     * from the original sender.
     */
    function revertPendingOp(
        address user,
        OpHandle memory opHandle,
        bytes memory boosterMessage,
        Signature memory signature
    ) public {
        // Prepare revert, including permission check and prevents reentrancy for same opHandle.
        _prepareOpRevert({
            user: user,
            opHandle: opHandle,
            boosterMessage: boosterMessage,
            signature: signature
        });

        // Now perform the actual revert of the pending op
        _revertPendingOp(user, opHandle.opType, opHandle.opId);
    }

    /**
     * @dev Revert a pending transfer
     */
    function _revertPendingOp(
        address user,
        uint8 opType,
        uint64 opId
    ) private {
        PendingTransfer storage pendingTransfer = _safeGetPendingTransfer(
            user,
            opId
        );

        uint96 transferAmount = pendingTransfer.transferAmount;
        uint96 occupiedAmount = pendingTransfer.occupiedAmount;

        // Move funds from this contract back to the original sender. Transfers and burns
        // are reverted the same way. We only transfer back the `transferAmount` - that is the amount
        // that actually got moved into this contract. The occupied amount is released during `onRevertPendingOp`
        // by the deriving contract.
        _move({from: address(this), to: user, amount: transferAmount});

        // Call hook to allow deriving contracts to perform additional cleanup
        _onRevertPendingOp(user, opType, opId, transferAmount, occupiedAmount);

        // NOTE: we do not clean up the ops mapping, because we already hit the
        // gas refund limit.
        // delete _pendingTransfers[_getOpKey(user, opHandle.opId)];

        // Emit event
        emit RevertedOp(user, opId, opType);
    }

    /**
     * @dev Hook that is called during revert of a pending transfer.
     * Allows deriving contracts to perform additional cleanup.
     */
    function _onRevertPendingOp(
        address user,
        uint8 opType,
        uint64 opId,
        uint96 transferAmount,
        uint96 occupiedAmount
    ) internal virtual {}

    /**
     * @dev Safely get a pending transfer. Reverts if it doesn't exist.
     */
    function _safeGetPendingTransfer(address user, uint64 opId)
        private
        view
        returns (PendingTransfer storage)
    {
        PendingTransfer storage pendingTransfer = _pendingTransfers[_getOpKey(
            user,
            opId
        )];

        require(pendingTransfer.spender != address(0), "ERC20-16");

        return pendingTransfer;
    }
}

File 3 of 19 : Purpose.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "./ERC20.sol";
import "./Dubi.sol";
import "./IHodl.sol";
import "./MintMath.sol";

contract Purpose is ERC20 {
    // The DUBI contract, required for auto-minting DUBI on burn.
    Dubi private immutable _dubi;

    // The HODL contract, required for burning locked PRPS.
    IHodl private immutable _hodl;

    modifier onlyHodl() {
        require(msg.sender == _hodlAddress, "PRPS-1");
        _;
    }

    constructor(
        uint256 initialSupply,
        address optIn,
        address dubi,
        address hodl,
        address externalAddress1,
        address externalAddress2,
        address externalAddress3
    )
        public
        ERC20(
            "Purpose",
            "PRPS",
            optIn,
            hodl,
            externalAddress1,
            externalAddress2,
            externalAddress3
        )
    {
        _dubi = Dubi(dubi);
        _hodl = IHodl(hodl);

        _mintInitialSupply(msg.sender, initialSupply);
    }

    /**
     * @dev Returns the address of the {HODL} contract used for burning locked PRPS.
     */
    function hodl() external view returns (address) {
        return address(_hodl);
    }

    /**
     * @dev Returns the hodl balance of the given `tokenHolder`
     */
    function hodlBalanceOf(address tokenHolder) public view returns (uint256) {
        // The hodl balance follows after the first 96 bits in the packed data.
        return uint96(_packedData[tokenHolder] >> 96);
    }

    /**
     * @dev Transfer `amount` PRPS from `from` to the Hodl contract.
     *
     * This can only be called by the Hodl contract.
     */
    function hodlTransfer(address from, uint96 amount) external onlyHodl {
        _move(from, address(_hodl), amount);
    }

    /**
     * @dev Increase the hodl balance of `account` by `hodlAmount`. This is
     * only used as part of the migration.
     */
    function migrateHodlBalance(address account, uint96 hodlAmount)
        external
        onlyHodl
    {
        UnpackedData memory unpacked = _unpackPackedData(_packedData[account]);

        unpacked.hodlBalance += hodlAmount;
        _packedData[account] = _packUnpackedData(unpacked);
    }

    /**
     * @dev Increase the hodl balance of `to` by moving `amount` PRPS from `from`'s balance.
     *
     * This can only be called by the Hodl contract.
     */
    function increaseHodlBalance(
        address from,
        address to,
        uint96 amount
    ) external onlyHodl {
        UnpackedData memory unpackedDataFrom = _unpackPackedData(
            _packedData[from]
        );
        UnpackedData memory unpackedDataTo;

        // We only need to unpack twice if from != to
        if (from != to) {
            unpackedDataTo = _unpackPackedData(_packedData[to]);
        } else {
            unpackedDataTo = unpackedDataFrom;
        }

        // `from` must have enough balance
        require(unpackedDataFrom.balance >= amount, "PRPS-3");

        // Subtract balance from `from`
        unpackedDataFrom.balance -= amount;
        // Add to `hodlBalance` from `to`
        unpackedDataTo.hodlBalance += amount;

        // We only need to pack twice if from != to
        if (from != to) {
            _packedData[to] = _packUnpackedData(unpackedDataTo);
        }

        _packedData[from] = _packUnpackedData(unpackedDataFrom);
    }

    /**
     * @dev Decrease the hodl balance of `from` by `hodlAmount` and increase
     * the regular balance by `refundAmount.
     *
     * `refundAmount` might be less than `hodlAmount`.
     *
     * E.g. when burning fuel in locked PRPS
     *
     * This can only be called by the Hodl contract.
     */
    function decreaseHodlBalance(
        address from,
        uint96 hodlAmount,
        uint96 refundAmount
    ) external onlyHodl {
        require(hodlAmount >= refundAmount, "PRPS-4");

        UnpackedData memory unpackedDataFrom = _unpackPackedData(
            _packedData[from]
        );

        // `from` must have enough balance
        require(unpackedDataFrom.hodlBalance >= hodlAmount, "PRPS-5");

        // Subtract amount from hodl balance
        unpackedDataFrom.hodlBalance -= hodlAmount;

        if (refundAmount > 0) {
            // Add amount to balance
            unpackedDataFrom.balance += refundAmount;
        }

        // Write to storage
        _packedData[from] = _packUnpackedData(unpackedDataFrom);
    }

    /**
     * @dev Revert the hodl balance change caused by `from` on `to`.
     *
     * E.g. when reverting a pending hodl.
     *
     * This can only be called by the Hodl contract.
     */
    function revertHodlBalance(
        address from,
        address to,
        uint96 amount
    ) external onlyHodl {
        UnpackedData memory unpackedDataFrom = _unpackPackedData(
            _packedData[from]
        );
        UnpackedData memory unpackedDataTo;

        // We only need to unpack twice if from != to
        if (from != to) {
            unpackedDataTo = _unpackPackedData(_packedData[to]);
        } else {
            unpackedDataTo = unpackedDataFrom;
        }

        // `to` must have enough hodl balance
        require(unpackedDataTo.hodlBalance >= amount, "PRPS-5");

        // Subtract hodl balance from `to`
        unpackedDataTo.hodlBalance -= amount;
        // Add to `balance` from `from`
        unpackedDataFrom.balance += amount;

        // We only need to pack twice if from != to
        if (from != to) {
            _packedData[to] = _packUnpackedData(unpackedDataTo);
        }

        _packedData[from] = _packUnpackedData(unpackedDataFrom);
    }

    /**
     * @dev Mint DUBI when burning PRPS
     * @param from address token holder address
     * @param transferAmount amount of tokens to burn
     * @param occupiedAmount amount of tokens that are occupied
     * @param createdAt equal to block.timestamp if not finalizing a pending op, otherwise
     * it corresponds to op.createdAt
     * @param finalizing boolean indicating whether this is a finalizing transaction or not. Changes
     * how the `amount` is interpreted.
     *
     * When burning PRPS, we first try to burn unlocked PRPS.
     * If burning an amount that exceeds the unlocked PRPS of `from`, we attempt to burn the
     * difference from locked PRPS.
     *
     * If the desired `amount` cannot be filled by taking locked and unlocked PRPS into account,
     * this function reverts.
     *
     * Burning locked PRPS means reducing the `hodlBalance` while burning unlocked PRPS means reducing
     * the regular `balance`.
     *
     * This function returns the actual unlocked PRPS that needs to be removed from `balance`.
     *
     */
    function _beforeBurn(
        address from,
        UnpackedData memory unpacked,
        uint96 transferAmount,
        uint96 occupiedAmount,
        uint32 createdAt,
        FuelBurn memory fuelBurn,
        bool finalizing
    ) internal override returns (uint96) {
        uint96 totalDubiToMint;
        uint96 lockedPrpsToBurn;
        uint96 burnableUnlockedPrps;

        // Depending on whether this is a finalizing burn or not,
        // the amount of locked/unlocked PRPS is determined differently.
        if (finalizing) {
            // For a finalizing burn, we use the occupied amount, since we already know how much
            // locked PRPS we are going to burn. This amount represents the `pendingLockedPrps`
            // on the hodl items.
            lockedPrpsToBurn = occupiedAmount;

            // Since `transferAmount` is the total amount of PRPS getting burned, we need to subtract
            // the `occupiedAmount` to get the actual amount of unlocked PRPS.

            // Sanity check
            assert(transferAmount >= occupiedAmount);
            transferAmount -= occupiedAmount;

            // Set the unlocked PRPS to burn to the updated `transferAmount`
            burnableUnlockedPrps = transferAmount;
        } else {
            // For a direct burn, we start off with the full amounts, since we don't know the exact
            // amounts initially.

            lockedPrpsToBurn = transferAmount;
            burnableUnlockedPrps = unpacked.balance;
        }

        // 1) Try to burn unlocked PRPS
        if (burnableUnlockedPrps > 0) {
            // Nice, we can burn unlocked PRPS

            // Catch underflow i.e. don't burn more than we need to
            if (burnableUnlockedPrps > transferAmount) {
                burnableUnlockedPrps = transferAmount;
            }

            // Calculate DUBI to mint based on unlocked PRPS we can burn
            totalDubiToMint = MintMath.calculateDubiToMintMax(
                burnableUnlockedPrps
            );

            // Subtract the amount of burned unlocked PRPS from the locked PRPS we
            // need to burn if this is NOT a finalizing burn, because in that case we
            // already have the exact amount locked PRPS we want to burn.
            if (!finalizing) {
                lockedPrpsToBurn -= burnableUnlockedPrps;
            }
        }

        // 2) Burn locked PRPS if there's not enough unlocked PRPS

        // Burn an additional amount of locked PRPS equal to the fuel if any
        if (fuelBurn.fuelType == FuelType.LOCKED_PRPS) {
            // The `burnFromLockedPrps` call will fail, if not enough PRPS can be burned.
            lockedPrpsToBurn += fuelBurn.amount;
        }

        if (lockedPrpsToBurn > 0) {
            uint96 dubiToMintFromLockedPrps = _burnFromLockedPrps({
                from: from,
                unpacked: unpacked,
                lockedPrpsToBurn: lockedPrpsToBurn,
                createdAt: createdAt,
                finalizing: finalizing
            });

            // We check 'greater than or equal' because it's possible to mint 0 new DUBI
            // e.g. when called right after a hodl where not enough time passed to generate new DUBI.
            uint96 dubiToMint = totalDubiToMint + dubiToMintFromLockedPrps;
            require(dubiToMint >= totalDubiToMint, "PRPS-6");

            totalDubiToMint = dubiToMint;
        } else {
            // Sanity check for finalizes that don't touch locked PRPS
            assert(occupiedAmount == 0);
        }

        // Burn minted DUBI equal to the fuel if any
        if (fuelBurn.fuelType == FuelType.AUTO_MINTED_DUBI) {
            require(totalDubiToMint >= fuelBurn.amount, "PRPS-7");
            totalDubiToMint -= fuelBurn.amount;
        }

        // Mint DUBI taking differences between burned locked/unlocked into account
        if (totalDubiToMint > 0) {
            _dubi.purposeMint(from, totalDubiToMint);
        }

        return burnableUnlockedPrps;
    }

    function _burnFromLockedPrps(
        address from,
        UnpackedData memory unpacked,
        uint96 lockedPrpsToBurn,
        uint32 createdAt,
        bool finalizing
    ) private returns (uint96) {
        // Reverts if the exact amount needed cannot be burned
        uint96 dubiToMintFromLockedPrps = _hodl.burnLockedPrps({
            from: from,
            amount: lockedPrpsToBurn,
            dubiMintTimestamp: createdAt,
            burnPendingLockedPrps: finalizing
        });

        require(unpacked.hodlBalance >= lockedPrpsToBurn, "PRPS-8");

        unpacked.hodlBalance -= lockedPrpsToBurn;

        return dubiToMintFromLockedPrps;
    }

    function _callerIsDeployTimeKnownContract()
        internal
        override
        view
        returns (bool)
    {
        if (msg.sender == address(_dubi)) {
            return true;
        }

        return super._callerIsDeployTimeKnownContract();
    }

    //---------------------------------------------------------------
    // Fuel
    //---------------------------------------------------------------

    /**
     * @dev Burns `fuel` from `from`. Can only be called by one of the deploy-time known contracts.
     */
    function burnFuel(address from, TokenFuel memory fuel) public override {
        require(_callerIsDeployTimeKnownContract(), "PRPS-2");
        _burnFuel(from, fuel);
    }

    function _burnFuel(address from, TokenFuel memory fuel) private {
        require(fuel.amount <= MAX_BOOSTER_FUEL, "PRPS-10");
        require(from != address(0) && from != msg.sender, "PRPS-11");

        if (fuel.tokenAlias == TOKEN_FUEL_ALIAS_UNLOCKED_PRPS) {
            // Burn fuel from unlocked PRPS
            UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);
            require(unpacked.balance >= fuel.amount, "PRPS-7");
            unpacked.balance -= fuel.amount;
            _packedData[from] = _packUnpackedData(unpacked);
            return;
        }

        if (fuel.tokenAlias == TOKEN_FUEL_ALIAS_LOCKED_PRPS) {
            // Burn fuel from locked PRPS
            UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);
            require(unpacked.hodlBalance >= fuel.amount, "PRPS-7");
            unpacked.hodlBalance -= fuel.amount;

            // We pass a mint timestamp, but that doesn't mean that DUBI is minted.
            // The returned DUBI that should be minted is ignored.
            // Reverts if not enough locked PRPS can be burned.
            _hodl.burnLockedPrps({
                from: from,
                amount: fuel.amount,
                dubiMintTimestamp: uint32(block.timestamp),
                burnPendingLockedPrps: false
            });

            _packedData[from] = _packUnpackedData(unpacked);
            return;
        }

        revert("PRPS-12");
    }

    /**
     *@dev Burn the fuel of a `boostedSend`
     */
    function _burnBoostedSendFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal override returns (FuelBurn memory) {
        FuelBurn memory fuelBurn;

        if (fuel.unlockedPrps > 0) {
            require(fuel.unlockedPrps <= MAX_BOOSTER_FUEL, "PRPS-10");

            require(unpacked.balance >= fuel.unlockedPrps, "PRPS-7");
            unpacked.balance -= fuel.unlockedPrps;

            fuelBurn.amount = fuel.unlockedPrps;
            fuelBurn.fuelType = FuelType.UNLOCKED_PRPS;
            return fuelBurn;
        }

        if (fuel.lockedPrps > 0) {
            require(fuel.lockedPrps <= MAX_BOOSTER_FUEL, "PRPS-10");

            // We pass a mint timestamp, but that doesn't mean that DUBI is minted.
            // The returned DUBI that should be minted is ignored.
            // Reverts if not enough locked PRPS can be burned.
            _hodl.burnLockedPrps({
                from: from,
                amount: fuel.lockedPrps,
                dubiMintTimestamp: uint32(block.timestamp),
                burnPendingLockedPrps: false
            });

            require(unpacked.hodlBalance >= fuel.lockedPrps, "PRPS-7");
            unpacked.hodlBalance -= fuel.lockedPrps;

            fuelBurn.amount = fuel.lockedPrps;
            fuelBurn.fuelType = FuelType.LOCKED_PRPS;
            return fuelBurn;
        }

        // If the fuel is DUBI, then we have to reach out to the DUBI contract.
        if (fuel.dubi > 0) {
            // Reverts if the requested amount cannot be burned
            _dubi.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_DUBI,
                    amount: fuel.dubi
                })
            );

            fuelBurn.amount = fuel.dubi;
            fuelBurn.fuelType = FuelType.DUBI;
            return fuelBurn;
        }

        return fuelBurn;
    }

    /**
     *@dev Burn the fuel of a `boostedBurn`
     */
    function _burnBoostedBurnFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal override returns (FuelBurn memory) {
        FuelBurn memory fuelBurn;

        if (fuel.unlockedPrps > 0) {
            require(fuel.unlockedPrps <= MAX_BOOSTER_FUEL, "PRPS-10");

            require(unpacked.balance >= fuel.unlockedPrps, "PRPS-7");
            unpacked.balance -= fuel.unlockedPrps;

            fuelBurn.amount = fuel.unlockedPrps;
            fuelBurn.fuelType = FuelType.UNLOCKED_PRPS;
            return fuelBurn;
        }

        if (fuel.lockedPrps > 0) {
            require(fuel.lockedPrps <= MAX_BOOSTER_FUEL, "PRPS-10");

            require(unpacked.hodlBalance >= fuel.lockedPrps, "PRPS-7");
            // Fuel is taken from hodl balance in _beforeBurn
            // unpacked.hodlBalance -= fuel.lockedPrps;

            fuelBurn.amount = fuel.lockedPrps;
            fuelBurn.fuelType = FuelType.LOCKED_PRPS;

            return fuelBurn;
        }

        if (fuel.intrinsicFuel > 0) {
            require(fuel.intrinsicFuel <= MAX_BOOSTER_FUEL, "PRPS-10");

            fuelBurn.amount = fuel.intrinsicFuel;
            fuelBurn.fuelType = FuelType.AUTO_MINTED_DUBI;

            return fuelBurn;
        }

        // If the fuel is DUBI, then we have to reach out to the DUBI contract.
        if (fuel.dubi > 0) {
            // Reverts if the requested amount cannot be burned
            _dubi.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_DUBI,
                    amount: fuel.dubi
                })
            );

            fuelBurn.amount = fuel.dubi;
            fuelBurn.fuelType = FuelType.DUBI;
            return fuelBurn;
        }

        // No fuel at all
        return fuelBurn;
    }

    //---------------------------------------------------------------
    // Pending ops
    //---------------------------------------------------------------

    function _getHasherContracts()
        internal
        override
        returns (address[] memory)
    {
        address[] memory hashers = new address[](5);
        hashers[0] = address(this);
        hashers[1] = address(_dubi);
        hashers[2] = _hodlAddress;
        hashers[3] = _externalAddress1;
        hashers[4] = _externalAddress2;

        return hashers;
    }

    /**
     * @dev Create a pending transfer by moving the funds of `spender` to this contract.
     * Special behavior applies to pending burns to account for locked PRPS.
     */
    function _createPendingTransferInternal(
        OpHandle memory opHandle,
        address spender,
        address from,
        address to,
        uint256 amount,
        bytes memory data
    ) internal override returns (PendingTransfer memory) {
        if (opHandle.opType != OP_TYPE_BURN) {
            return
                // Nothing special to do for non-burns so just call parent implementation
                super._createPendingTransferInternal(
                    opHandle,
                    spender,
                    from,
                    to,
                    amount,
                    data
                );
        }

        // When burning, we first use unlocked PRPS and match the remaining amount with locked PRPS from the Hodl contract.

        // Sanity check
        assert(amount < 2**96);
        uint96 transferAmount = uint96(amount);
        uint96 lockedPrpsAmount = transferAmount;

        UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);
        // First try to move as much unlocked PRPS as possible to the PRPS address
        uint96 unlockedPrpsToMove = transferAmount;
        if (unlockedPrpsToMove > unpacked.balance) {
            unlockedPrpsToMove = unpacked.balance;
        }

        // Update the locked PRPS we have to use
        lockedPrpsAmount -= unlockedPrpsToMove;

        if (unlockedPrpsToMove > 0) {
            _move({from: from, to: address(this), amount: unlockedPrpsToMove});
        }

        // If we still need locked PRPS, call into the Hodl contract.
        // This will also take pending hodls into account, if `from` has
        // some.
        if (lockedPrpsAmount > 0) {
            // Reverts if not the exact amount can be set to pending
            _hodl.setLockedPrpsToPending(from, lockedPrpsAmount);
        }

        // Create pending transfer
        return
            PendingTransfer({
                spender: spender,
                transferAmount: transferAmount,
                to: to,
                occupiedAmount: lockedPrpsAmount,
                data: data
            });
    }

    /**
     * @dev Hook that is called during revert of a pending op.
     * Reverts any changes to locked PRPS when 'opType' is burn.
     */
    function _onRevertPendingOp(
        address user,
        uint8 opType,
        uint64 opId,
        uint96 transferAmount,
        uint96 occupiedAmount
    ) internal override {
        if (opType != OP_TYPE_BURN) {
            return;
        }

        // Extract the pending locked PRPS from the amount.
        if (occupiedAmount > 0) {
            _hodl.revertLockedPrpsSetToPending(user, occupiedAmount);
        }
    }

    //---------------------------------------------------------------
    // Shared pending ops for Hodl
    //---------------------------------------------------------------

    /**
     * @dev Creates a new opHandle with the given type for `user`. Hodl and Prps share the same
     * opCounter to enforce a consistent order in which pending ops are finalized/reverted
     * across contracts. This function can only be called by Hodl.
     */
    function createNewOpHandleShared(
        IOptIn.OptInStatus memory optInStatus,
        address user,
        uint8 opType
    ) public onlyHodl returns (OpHandle memory) {
        return _createNewOpHandle(optInStatus, user, opType);
    }

    /**
     * @dev Delete the op handle with the given `opId` from `user`. Hodl and Prps share the same
     * opCounter to enforce a consistent order in which pending ops are finalized/reverted
     * across contracts. This function can only be called by Hodl.
     */
    function deleteOpHandleShared(address user, OpHandle memory opHandle)
        public
        onlyHodl
        returns (bool)
    {
        _deleteOpHandle(user, opHandle);
        return true;
    }

    /**
     * @dev Get the next op id for `user`. Hodl and Prps share the same
     * opCounter to enforce a consistent order in which pending ops are finalized/reverted
     * across contracts. This function can only be called by Hodl.
     */
    function assertFinalizeFIFOShared(address user, uint64 opId)
        public
        onlyHodl
        returns (bool)
    {
        _assertFinalizeFIFO(user, opId);
        return true;
    }

    /**
     * @dev Get the next op id for `user`. Hodl and Prps share the same
     * opCounter to enforce a consistent order in which pending ops are finalized/reverted
     * across contracts. This function can only be called by Hodl.
     */
    function assertRevertLIFOShared(address user, uint64 opId)
        public
        onlyHodl
        returns (bool)
    {
        _assertRevertLIFO(user, opId);
        return true;
    }
}

File 4 of 19 : IERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

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

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

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

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

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

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

File 5 of 19 : SafeMath.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

File 6 of 19 : Ownable.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

import "../GSN/Context.sol";
/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () internal {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(_owner == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

File 7 of 19 : IERC1820Registry.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Interface of the global ERC1820 Registry, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register
 * implementers for interfaces in this registry, as well as query support.
 *
 * Implementers may be shared by multiple accounts, and can also implement more
 * than a single interface for each account. Contracts can implement interfaces
 * for themselves, but externally-owned accounts (EOA) must delegate this to a
 * contract.
 *
 * {IERC165} interfaces can also be queried via the registry.
 *
 * For an in-depth explanation and source code analysis, see the EIP text.
 */
interface IERC1820Registry {
    /**
     * @dev Sets `newManager` as the manager for `account`. A manager of an
     * account is able to set interface implementers for it.
     *
     * By default, each account is its own manager. Passing a value of `0x0` in
     * `newManager` will reset the manager to this initial state.
     *
     * Emits a {ManagerChanged} event.
     *
     * Requirements:
     *
     * - the caller must be the current manager for `account`.
     */
    function setManager(address account, address newManager) external;

    /**
     * @dev Returns the manager for `account`.
     *
     * See {setManager}.
     */
    function getManager(address account) external view returns (address);

    /**
     * @dev Sets the `implementer` contract as ``account``'s implementer for
     * `interfaceHash`.
     *
     * `account` being the zero address is an alias for the caller's address.
     * The zero address can also be used in `implementer` to remove an old one.
     *
     * See {interfaceHash} to learn how these are created.
     *
     * Emits an {InterfaceImplementerSet} event.
     *
     * Requirements:
     *
     * - the caller must be the current manager for `account`.
     * - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not
     * end in 28 zeroes).
     * - `implementer` must implement {IERC1820Implementer} and return true when
     * queried for support, unless `implementer` is the caller. See
     * {IERC1820Implementer-canImplementInterfaceForAddress}.
     */
    function setInterfaceImplementer(address account, bytes32 interfaceHash, address implementer) external;

    /**
     * @dev Returns the implementer of `interfaceHash` for `account`. If no such
     * implementer is registered, returns the zero address.
     *
     * If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28
     * zeroes), `account` will be queried for support of it.
     *
     * `account` being the zero address is an alias for the caller's address.
     */
    function getInterfaceImplementer(address account, bytes32 interfaceHash) external view returns (address);

    /**
     * @dev Returns the interface hash for an `interfaceName`, as defined in the
     * corresponding
     * https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP].
     */
    function interfaceHash(string calldata interfaceName) external pure returns (bytes32);

    /**
     *  @notice Updates the cache with whether the contract implements an ERC165 interface or not.
     *  @param account Address of the contract for which to update the cache.
     *  @param interfaceId ERC165 interface for which to update the cache.
     */
    function updateERC165Cache(address account, bytes4 interfaceId) external;

    /**
     *  @notice Checks whether a contract implements an ERC165 interface or not.
     *  If the result is not cached a direct lookup on the contract address is performed.
     *  If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling
     *  {updateERC165Cache} with the contract address.
     *  @param account Address of the contract to check.
     *  @param interfaceId ERC165 interface to check.
     *  @return True if `account` implements `interfaceId`, false otherwise.
     */
    function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool);

    /**
     *  @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache.
     *  @param account Address of the contract to check.
     *  @param interfaceId ERC165 interface to check.
     *  @return True if `account` implements `interfaceId`, false otherwise.
     */
    function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);

    event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer);

    event ManagerChanged(address indexed account, address indexed newManager);
}

File 8 of 19 : IBoostableERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

// Token agnostic fuel struct that is passed around when the fuel is burned by a different (token) contract.
// The contract has to explicitely support the desired token that should be burned.
struct TokenFuel {
    // A token alias that must be understood by the target contract
    uint8 tokenAlias;
    uint96 amount;
}

/**
 * @dev Extends the interface of the ERC20 standard as defined in the EIP with
 * `boostedTransferFrom` to perform transfers without having to rely on an allowance.
 */
interface IBoostableERC20 {
    // ERC20
    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    function transfer(address recipient, uint256 amount)
        external
        returns (bool);

    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );

    // Extension

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient`.
     *
     * If the caller is known by the callee, then the implementation should skip approval checks.
     * Also accepts a data payload, similar to ERC721's `safeTransferFrom` to pass arbitrary data.
     *
     */
    function boostedTransferFrom(
        address sender,
        address recipient,
        uint256 amount,
        bytes calldata data
    ) external returns (bool);

    /**
     * @dev Burns `fuel` from `from`.
     */
    function burnFuel(address from, TokenFuel memory fuel) external;
}

File 9 of 19 : BoostableERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "./Boostable.sol";
import "./BoostableLib.sol";

/**
 * @dev EIP712 boostable primitives related to ERC20 for the Purpose domain
 */
abstract contract BoostableERC20 is Boostable {
    /**
     * @dev A struct representing the payload of the ERC20 `boostedSend` function.
     */
    struct BoostedSend {
        uint8 tag;
        address sender;
        address recipient;
        uint256 amount;
        bytes data;
        BoosterFuel fuel;
        BoosterPayload boosterPayload;
    }

    /**
     * @dev A struct representing the payload of the ERC20 `boostedBurn` function.
     */
    struct BoostedBurn {
        uint8 tag;
        address account;
        uint256 amount;
        bytes data;
        BoosterFuel fuel;
        BoosterPayload boosterPayload;
    }

    uint8 internal constant BOOST_TAG_SEND = 0;
    uint8 internal constant BOOST_TAG_BURN = 1;

    bytes32 internal constant BOOSTED_SEND_TYPEHASH = keccak256(
        "BoostedSend(uint8 tag,address sender,address recipient,uint256 amount,bytes data,BoosterFuel fuel,BoosterPayload boosterPayload)BoosterFuel(uint96 dubi,uint96 unlockedPrps,uint96 lockedPrps,uint96 intrinsicFuel)BoosterPayload(address booster,uint64 timestamp,uint64 nonce,bool isLegacySignature)"
    );

    bytes32 internal constant BOOSTED_BURN_TYPEHASH = keccak256(
        "BoostedBurn(uint8 tag,address account,uint256 amount,bytes data,BoosterFuel fuel,BoosterPayload boosterPayload)BoosterFuel(uint96 dubi,uint96 unlockedPrps,uint96 lockedPrps,uint96 intrinsicFuel)BoosterPayload(address booster,uint64 timestamp,uint64 nonce,bool isLegacySignature)"
    );

    constructor(address optIn) public Boostable(optIn) {}

    /**
     * @dev Returns the hash of `boostedSend`.
     */
    function hashBoostedSend(BoostedSend memory send, address booster)
        internal
        view
        returns (bytes32)
    {
        return
            BoostableLib.hashWithDomainSeparator(
                _DOMAIN_SEPARATOR,
                keccak256(
                    abi.encode(
                        BOOSTED_SEND_TYPEHASH,
                        BOOST_TAG_SEND,
                        send.sender,
                        send.recipient,
                        send.amount,
                        keccak256(send.data),
                        BoostableLib.hashBoosterFuel(send.fuel),
                        BoostableLib.hashBoosterPayload(
                            send.boosterPayload,
                            booster
                        )
                    )
                )
            );
    }

    /**
     * @dev Returns the hash of `boostedBurn`.
     */
    function hashBoostedBurn(BoostedBurn memory burn, address booster)
        internal
        view
        returns (bytes32)
    {
        return
            BoostableLib.hashWithDomainSeparator(
                _DOMAIN_SEPARATOR,
                keccak256(
                    abi.encode(
                        BOOSTED_BURN_TYPEHASH,
                        BOOST_TAG_BURN,
                        burn.account,
                        burn.amount,
                        keccak256(burn.data),
                        BoostableLib.hashBoosterFuel(burn.fuel),
                        BoostableLib.hashBoosterPayload(
                            burn.boosterPayload,
                            booster
                        )
                    )
                )
            );
    }

    /**
     * @dev Tries to interpret the given boosterMessage and
     * return it's hash plus creation timestamp.
     */
    function decodeAndHashBoosterMessage(
        address targetBooster,
        bytes memory boosterMessage
    ) external override view returns (bytes32, uint64) {
        require(boosterMessage.length > 0, "PB-7");

        uint8 tag = _readBoosterTag(boosterMessage);
        if (tag == BOOST_TAG_SEND) {
            BoostedSend memory send = abi.decode(boosterMessage, (BoostedSend));
            return (
                hashBoostedSend(send, targetBooster),
                send.boosterPayload.timestamp
            );
        }

        if (tag == BOOST_TAG_BURN) {
            BoostedBurn memory burn = abi.decode(boosterMessage, (BoostedBurn));
            return (
                hashBoostedBurn(burn, targetBooster),
                burn.boosterPayload.timestamp
            );
        }

        // Unknown tag, so just return an empty result
        return ("", 0);
    }
}

File 10 of 19 : Context.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

File 11 of 19 : Boostable.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "./ProtectedBoostable.sol";

/**
 * @dev Purpose Boostable primitives using the EIP712 standard
 */
abstract contract Boostable is ProtectedBoostable {
    // "Purpose", "Dubi" and "Hodl" are all under the "Purpose" umbrella
    constructor(address optIn)
        public
        ProtectedBoostable(
            optIn,
            keccak256(
                abi.encode(
                    EIP712_DOMAIN_TYPEHASH,
                    keccak256("Purpose"),
                    keccak256("1"),
                    _getChainId(),
                    address(this)
                )
            )
        )
    {}

    // Fuel alias constants - used when fuel is burned from external contract calls
    uint8 internal constant TOKEN_FUEL_ALIAS_UNLOCKED_PRPS = 0;
    uint8 internal constant TOKEN_FUEL_ALIAS_LOCKED_PRPS = 1;
    uint8 internal constant TOKEN_FUEL_ALIAS_DUBI = 2;
}

File 12 of 19 : BoostableLib.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

struct BoosterFuel {
    uint96 dubi;
    uint96 unlockedPrps;
    uint96 lockedPrps;
    uint96 intrinsicFuel;
}

struct BoosterPayload {
    address booster;
    uint64 timestamp;
    uint64 nonce;
    // Fallback for 'personal_sign' when e.g. using hardware wallets that don't support
    // EIP712 signing (yet).
    bool isLegacySignature;
}

// Library for Boostable hash functions that are completely inlined.
library BoostableLib {
    bytes32 private constant BOOSTER_PAYLOAD_TYPEHASH = keccak256(
        "BoosterPayload(address booster,uint64 timestamp,uint64 nonce,bool isLegacySignature)"
    );

    bytes32 internal constant BOOSTER_FUEL_TYPEHASH = keccak256(
        "BoosterFuel(uint96 dubi,uint96 unlockedPrps,uint96 lockedPrps,uint96 intrinsicFuel)"
    );

    /**
     * @dev Returns the hash of the packed DOMAIN_SEPARATOR and `messageHash` and is used for verifying
     * a signature.
     */
    function hashWithDomainSeparator(
        bytes32 domainSeparator,
        bytes32 messageHash
    ) internal pure returns (bytes32) {
        return
            keccak256(
                abi.encodePacked("\x19\x01", domainSeparator, messageHash)
            );
    }

    /**
     * @dev Returns the hash of `payload` using the provided booster (i.e. `msg.sender`).
     */
    function hashBoosterPayload(BoosterPayload memory payload, address booster)
        internal
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encode(
                    BOOSTER_PAYLOAD_TYPEHASH,
                    booster,
                    payload.timestamp,
                    payload.nonce,
                    payload.isLegacySignature
                )
            );
    }

    function hashBoosterFuel(BoosterFuel memory fuel)
        internal
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encode(
                    BOOSTER_FUEL_TYPEHASH,
                    fuel.dubi,
                    fuel.unlockedPrps,
                    fuel.lockedPrps,
                    fuel.intrinsicFuel
                )
            );
    }

    /**
     * @dev Returns the tag found in the given `boosterMessage`.
     */
    function _readBoosterTag(bytes memory boosterMessage)
        internal
        pure
        returns (uint8)
    {
        // The tag is either the 32th byte or the 64th byte depending on whether
        // the booster message contains dynamic bytes or not.
        //
        // If it contains a dynamic byte array, then the first word points to the first
        // data location.
        //
        // Therefore, we read the 32th byte and check if it's >= 32 and if so,
        // simply read the (32 + first word)th byte to get the tag.
        //
        // This imposes a limit on the number of tags we can support (<32), but
        // given that it is very unlikely for so many tags to exist it is fine.
        //
        // Read the 32th byte to get the tag, because it is a uint8 padded to 32 bytes.
        // i.e.
        // -----------------------------------------------------------------v
        // 0x0000000000000000000000000000000000000000000000000000000000000001
        //   ...
        //
        uint8 tag = uint8(boosterMessage[31]);
        if (tag >= 32) {
            // Read the (32 + tag) byte. E.g. if tag is 32, then we read the 64th:
            // --------------------------------------------------------------------
            // 0x0000000000000000000000000000000000000000000000000000000000000020 |
            //   0000000000000000000000000000000000000000000000000000000000000001 <
            //   ...
            //
            tag = uint8(boosterMessage[31 + tag]);
        }

        return tag;
    }
}

File 13 of 19 : ProtectedBoostable.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "./EIP712Boostable.sol";
import "./IOptIn.sol";
import "./ProtectedBoostableLib.sol";

abstract contract ProtectedBoostable is EIP712Boostable {
    //---------------------------------------------------------------
    // State for non-boosted operations while opted-in and the OPT_IN permaboost is active
    //---------------------------------------------------------------

    uint256 private constant MAX_PENDING_OPS = 25;

    // A mapping of account to an opCounter.
    mapping(address => OpCounter) internal _opCounters;

    // A mapping of account to an array containing all it's pending ops.
    mapping(address => OpHandle[]) internal _pendingOpsByAddress;

    // A mapping of keccak256(address,opId) to a struct holding metadata like the associated user account and creation timestamp.
    mapping(bytes32 => OpMetadata) internal _opMetadata;

    // Event that is emitted whenever a pending op is created
    // NOTE: returning an OpHandle in the event flattens it into an array for some reason
    // i.e. emit PendingOp(0x123.., OpHandle(1, 0)) => { from: 0x123, opHandle: ['1', '0']}
    event PendingOp(address from, uint64 opId, uint8 opType);
    // Event that is emitted whenever a pending op is finalized
    event FinalizedOp(address from, uint64 opId, uint8 opType);
    // Event that is emitted whenever a pending op is reverted
    event RevertedOp(address from, uint64 opId, uint8 opType);

    constructor(address optIn, bytes32 domainSeparator)
        public
        EIP712Boostable(optIn, domainSeparator)
    {}

    //---------------------------------------------------------------
    // Pending ops
    //---------------------------------------------------------------

    /**
     * @dev Returns the metadata of an op. Returns a zero struct if it doesn't exist.
     */
    function getOpMetadata(address user, uint64 opId)
        public
        virtual
        view
        returns (OpMetadata memory)
    {
        return _opMetadata[_getOpKey(user, opId)];
    }

    /**
     * @dev Returns the metadata of an op. Returns a zero struct if it doesn't exist.
     */
    function getOpCounter(address user)
        public
        virtual
        view
        returns (OpCounter memory)
    {
        return _opCounters[user];
    }

    /**
     * @dev Returns the metadata of an op. Reverts if it doesn't exist or
     * the opType mismatches.
     */
    function safeGetOpMetadata(address user, OpHandle memory opHandle)
        public
        virtual
        view
        returns (OpMetadata memory)
    {
        OpMetadata storage metadata = _opMetadata[_getOpKey(
            user,
            opHandle.opId
        )];

        // If 'createdAt' is zero, then it's non-existent for us
        require(metadata.createdAt > 0, "PB-1");
        require(metadata.opType == opHandle.opType, "PB-2");

        return metadata;
    }

    /**
     * @dev Get the next op id for `user`
     */
    function _getNextOpId(address user) internal returns (uint64) {
        OpCounter storage counter = _opCounters[user];
        // NOTE: we always increase by 1, so it cannot overflow as long as this
        // is the only place increasing the counter.
        uint64 nextOpId = counter.value + 1;

        // This also updates the nextFinalize/Revert values
        if (counter.nextFinalize == 0) {
            // Only gets updated if currently pointing to "nothing", because FIFO
            counter.nextFinalize = nextOpId;
        }

        // nextRevert is always updated to the new opId, because LIFO
        counter.nextRevert = nextOpId;
        counter.value = nextOpId;

        // NOTE: It is safe to downcast to uint64 since it's practically impossible to overflow.
        return nextOpId;
    }

    /**
     * @dev Creates a new opHandle with the given type for `user`.
     */
    function _createNewOpHandle(
        IOptIn.OptInStatus memory optInStatus,
        address user,
        uint8 opType
    ) internal virtual returns (OpHandle memory) {
        uint64 nextOpId = _getNextOpId(user);
        OpHandle memory opHandle = OpHandle({opId: nextOpId, opType: opType});

        // NOTE: we have a hard limit of 25 pending OPs and revert if that
        // limit is exceeded.
        require(_pendingOpsByAddress[user].length < MAX_PENDING_OPS, "PB-3");

        address booster = optInStatus.optedInTo;

        _pendingOpsByAddress[user].push(opHandle);
        _opMetadata[_getOpKey(user, nextOpId)] = OpMetadata({
            createdAt: uint64(block.timestamp),
            booster: booster,
            opType: opType
        });

        return opHandle;
    }

    /**
     * @dev Delete the given `opHandle` from `user`.
     */
    function _deleteOpHandle(address user, OpHandle memory opHandle)
        internal
        virtual
    {
        OpHandle[] storage _opHandles = _pendingOpsByAddress[user];
        OpCounter storage opCounter = _opCounters[user];

        ProtectedBoostableLib.deleteOpHandle(
            user,
            opHandle,
            _opHandles,
            opCounter,
            _opMetadata
        );
    }

    /**
     * @dev Assert that the caller is allowed to finalize a pending op.
     *
     * Returns the user and createdAt timestamp of the op on success in order to
     * save some gas by minimizing redundant look-ups.
     */
    function _assertCanFinalize(address user, OpHandle memory opHandle)
        internal
        returns (uint64)
    {
        OpMetadata memory metadata = safeGetOpMetadata(user, opHandle);

        uint64 createdAt = metadata.createdAt;

        // First check if the user is still opted-in. If not, then anyone
        // can finalize since it is no longer associated with the original booster.
        IOptIn.OptInStatus memory optInStatus = getOptInStatus(user);
        if (!optInStatus.isOptedIn) {
            return createdAt;
        }

        // Revert if not FIFO order
        _assertFinalizeFIFO(user, opHandle.opId);

        return ProtectedBoostableLib.assertCanFinalize(metadata, optInStatus);
    }

    /**
     * @dev Asserts that the caller (msg.sender) is allowed to revert a pending operation.
     * The caller must be opted-in by user and provide a valid signature from the user
     * that hasn't expired yet.
     */
    function _assertCanRevert(
        address user,
        OpHandle memory opHandle,
        uint64 opTimestamp,
        bytes memory boosterMessage,
        Signature memory signature
    ) internal {
        // Revert if not LIFO order
        _assertRevertLIFO(user, opHandle.opId);

        IOptIn.OptInStatus memory optInStatus = getOptInStatus(user);

        require(
            optInStatus.isOptedIn && msg.sender == optInStatus.optedInTo,
            "PB-6"
        );

        // In order to verify the boosterMessage, we need the hash and timestamp of when it
        // was signed. To interpret the boosterMessage, consult all available hasher contracts and
        // take the first non-zero result.
        address[] memory hasherContracts = _getHasherContracts();

        // Call external library function, which performs the actual assertion. The reason
        // why it is not inlined, is that the need to reduce bytecode size.
        ProtectedBoostableLib.verifySignatureForRevert(
            user,
            opTimestamp,
            optInStatus,
            boosterMessage,
            hasherContracts,
            signature
        );
    }

    function _getHasherContracts() internal virtual returns (address[] memory);

    /**
     * @dev Asserts that the given opId is the next to be finalized for `user`.
     */
    function _assertFinalizeFIFO(address user, uint64 opId) internal virtual {
        OpCounter storage counter = _opCounters[user];
        require(counter.nextFinalize == opId, "PB-9");
    }

    /**
     * @dev Asserts that the given opId is the next to be reverted for `user`.
     */
    function _assertRevertLIFO(address user, uint64 opId) internal virtual {
        OpCounter storage counter = _opCounters[user];
        require(counter.nextRevert == opId, "PB-10");
    }

    /**
     * @dev Prepare an op revert.
     * - Asserts that the caller is allowed to revert the given op
     * - Deletes the op handle to minimize risks of reentrancy
     */
    function _prepareOpRevert(
        address user,
        OpHandle memory opHandle,
        bytes memory boosterMessage,
        Signature memory signature
    ) internal {
        OpMetadata memory metadata = safeGetOpMetadata(user, opHandle);

        _assertCanRevert(
            user,
            opHandle,
            metadata.createdAt,
            boosterMessage,
            signature
        );

        // Delete opHandle, which prevents reentrancy since `safeGetOpMetadata`
        // will fail afterwards.
        _deleteOpHandle(user, opHandle);
    }

    /**
     * @dev Returns the hash of (user, opId) which is used as a look-up
     * key in the `_opMetadata` mapping.
     */
    function _getOpKey(address user, uint64 opId)
        internal
        pure
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(user, opId));
    }

    /**
     * @dev Deriving contracts can override this function to accept a boosterMessage for a given booster and
     * interpret it into a hash and timestamp.
     */
    function decodeAndHashBoosterMessage(
        address targetBooster,
        bytes memory boosterMessage
    ) external virtual view returns (bytes32, uint64) {}

    /**
     * @dev Returns the tag found in the given `boosterMesasge`.
     */
    function _readBoosterTag(bytes memory boosterMessage)
        internal
        pure
        returns (uint8)
    {
        // The tag is either the 32th byte or the 64th byte depending on whether
        // the booster message contains dynamic bytes or not.
        //
        // If it contains a dynamic byte array, then the first word points to the first
        // data location.
        //
        // Therefore, we read the 32th byte and check if it's >= 32 and if so,
        // simply read the (32 + first word)th byte to get the tag.
        //
        // This imposes a limit on the number of tags we can support (<32), but
        // given that it is very unlikely for so many tags to exist it is fine.
        //
        // Read the 32th byte to get the tag, because it is a uint8 padded to 32 bytes.
        // i.e.
        // -----------------------------------------------------------------v
        // 0x0000000000000000000000000000000000000000000000000000000000000001
        //   ...
        //
        uint8 tag = uint8(boosterMessage[31]);
        if (tag >= 32) {
            // Read the (32 + tag) byte. E.g. if tag is 32, then we read the 64th:
            // --------------------------------------------------------------------
            // 0x0000000000000000000000000000000000000000000000000000000000000020 |
            //   0000000000000000000000000000000000000000000000000000000000000001 <
            //   ...
            //
            tag = uint8(boosterMessage[31 + tag]);
        }

        return tag;
    }
}

File 14 of 19 : EIP712Boostable.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/cryptography/ECDSA.sol";
import "./IOptIn.sol";
import "./BoostableLib.sol";
import "./IBoostableERC20.sol";

/**
 * @dev Boostable base contract
 *
 * All deriving contracts are expected to implement EIP712 for the message signing.
 *
 */
abstract contract EIP712Boostable {
    using ECDSA for bytes32;

    // solhint-disable-next-line var-name-mixedcase
    IOptIn internal immutable _OPT_IN;
    // solhint-disable-next-line var-name-mixedcase
    bytes32 internal immutable _DOMAIN_SEPARATOR;

    bytes32 internal constant EIP712_DOMAIN_TYPEHASH = keccak256(
        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
    );

    bytes32 private constant BOOSTER_PAYLOAD_TYPEHASH = keccak256(
        "BoosterPayload(address booster,uint64 timestamp,uint64 nonce,bool isLegacySignature)"
    );

    bytes32 internal constant BOOSTER_FUEL_TYPEHASH = keccak256(
        "BoosterFuel(uint96 dubi,uint96 unlockedPrps,uint96 lockedPrps,uint96 intrinsicFuel)"
    );

    // The boost fuel is capped to 10 of the respective token that will be used for payment.
    uint96 internal constant MAX_BOOSTER_FUEL = 10 ether;

    // A magic booster permission prefix
    bytes6 private constant MAGIC_BOOSTER_PERMISSION_PREFIX = "BOOST-";

    constructor(address optIn, bytes32 domainSeparator) public {
        _OPT_IN = IOptIn(optIn);
        _DOMAIN_SEPARATOR = domainSeparator;
    }

    // A mapping of mappings to keep track of used nonces by address to
    // protect against replays. Each 'Boostable' contract maintains it's own
    // state for nonces.
    mapping(address => uint64) private _nonces;

    //---------------------------------------------------------------

    function getNonce(address account) external virtual view returns (uint64) {
        return _nonces[account];
    }

    function getOptInStatus(address account)
        internal
        view
        returns (IOptIn.OptInStatus memory)
    {
        return _OPT_IN.getOptInStatus(account);
    }

    /**
     * @dev Called by every 'boosted'-function to ensure that `msg.sender` (i.e. a booster) is
     * allowed to perform the call for `from` (the origin) by verifying that `messageHash`
     * has been signed by `from`. Additionally, `from` provides a nonce to prevent
     * replays. Boosts cannot be verified out of order.
     *
     * @param from the address that the boost is made for
     * @param messageHash the reconstructed message hash based on the function input
     * @param payload the booster payload
     * @param signature the signature of `from`
     */
    function verifyBoost(
        address from,
        bytes32 messageHash,
        BoosterPayload memory payload,
        Signature memory signature
    ) internal {
        uint64 currentNonce = _nonces[from];
        require(currentNonce == payload.nonce - 1, "AB-1");

        _nonces[from] = currentNonce + 1;

        _verifyBoostWithoutNonce(from, messageHash, payload, signature);
    }

    /**
     * @dev Verify a boost without verifying the nonce.
     */
    function _verifyBoostWithoutNonce(
        address from,
        bytes32 messageHash,
        BoosterPayload memory payload,
        Signature memory signature
    ) internal view {
        // The sender must be the booster specified in the payload
        require(msg.sender == payload.booster, "AB-2");

        (bool isOptedInToSender, uint256 optOutPeriod) = _OPT_IN.isOptedInBy(
            msg.sender,
            from
        );

        // `from` must be opted-in to booster
        require(isOptedInToSender, "AB-3");

        // The given timestamp must not be greater than `block.timestamp + 1 hour`
        // and at most `optOutPeriod(booster)` seconds old.
        uint64 _now = uint64(block.timestamp);
        uint64 _optOutPeriod = uint64(optOutPeriod);

        bool notTooFarInFuture = payload.timestamp <= _now + 1 hours;
        bool belowMaxAge = true;

        // Calculate the absolute difference. Because of the small tolerance, `payload.timestamp`
        // may be greater than `_now`:
        if (payload.timestamp <= _now) {
            belowMaxAge = _now - payload.timestamp <= _optOutPeriod;
        }

        // Signature must not be expired
        require(notTooFarInFuture && belowMaxAge, "AB-4");

        // NOTE: Currently, hardware wallets (e.g. Ledger, Trezor) do not support EIP712 signing (specifically `signTypedData_v4`).
        // However, a user can still sign the EIP712 hash with the caveat that it's signed using `personal_sign` which prepends
        // the prefix '"\x19Ethereum Signed Message:\n" + len(message)'.
        //
        // To still support that, we add the prefix to the hash if `isLegacySignature` is true.
        if (payload.isLegacySignature) {
            messageHash = messageHash.toEthSignedMessageHash();
        }

        // Valid, if the recovered address from `messageHash` with the given `signature` matches `from`.

        address signer = ecrecover(
            messageHash,
            signature.v,
            signature.r,
            signature.s
        );

        if (!payload.isLegacySignature && signer != from) {
            // As a last resort we try anyway, in case the caller simply forgot the `isLegacySignature` flag.
            signer = ecrecover(
                messageHash.toEthSignedMessageHash(),
                signature.v,
                signature.r,
                signature.s
            );
        }

        require(from == signer, "AB-5");
    }

    /**
     * @dev Returns the hash of `payload` using the provided booster (i.e. `msg.sender`).
     */
    function hashBoosterPayload(BoosterPayload memory payload, address booster)
        internal
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encode(
                    BOOSTER_PAYLOAD_TYPEHASH,
                    booster,
                    payload.timestamp,
                    payload.nonce
                )
            );
    }

    function _getChainId() internal pure returns (uint256) {
        uint256 chainId;
        assembly {
            chainId := chainid()
        }
        return chainId;
    }
}

File 15 of 19 : IOptIn.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

struct Signature {
    bytes32 r;
    bytes32 s;
    uint8 v;
}

interface IOptIn {
    struct OptInStatus {
        bool isOptedIn;
        bool permaBoostActive;
        address optedInTo;
        uint32 optOutPeriod;
    }

    function getOptInStatusPair(address accountA, address accountB)
        external
        view
        returns (OptInStatus memory, OptInStatus memory);

    function getOptInStatus(address account)
        external
        view
        returns (OptInStatus memory);

    function isOptedInBy(address _sender, address _account)
        external
        view
        returns (bool, uint256);
}

File 16 of 19 : ProtectedBoostableLib.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/cryptography/ECDSA.sol";
import "./IOptIn.sol";

struct OpHandle {
    uint8 opType;
    uint64 opId;
}

struct OpMetadata {
    uint8 opType; // the operation type
    uint64 createdAt; // the creation timestamp of an op
    address booster; // the booster at the time of when the op has been created
}

struct OpCounter {
    // The current value of the counter
    uint64 value;
    // Contains the opId that is to be finalized next - i.e. FIFO order
    uint64 nextFinalize;
    // Contains the opId that is to be reverted next - i.e. LIFO order
    uint64 nextRevert;
}

// Library containing public functions for pending ops - those will never be inlined
// to reduce the bytecode size of individual contracts.
library ProtectedBoostableLib {
    using ECDSA for bytes32;

    function deleteOpHandle(
        address user,
        OpHandle memory opHandle,
        OpHandle[] storage opHandles,
        OpCounter storage opCounter,
        mapping(bytes32 => OpMetadata) storage opMetadata
    ) public {
        uint256 length = opHandles.length;
        assert(length > 0);

        uint64 minOpId; // becomes next LIFO
        uint64 maxOpId; // becomes next FIFO

        // Pending ops are capped to MAX_PENDING_OPS. We always perform
        // MIN(length, MAX_PENDING_OPS) look-ups to do a "swap-and-pop" and
        // for updating the opCounter LIFO/FIFO pointers.
        for (uint256 i = 0; i < length; i++) {
            uint64 currOpId = opHandles[i].opId;
            if (currOpId == opHandle.opId) {
                // Overwrite item at i with last
                opHandles[i] = opHandles[length - 1];

                // Continue, to ignore this opId when updating
                // minOpId and maxOpId.
                continue;
            }

            // Update minOpId
            if (minOpId == 0 || currOpId < minOpId) {
                minOpId = currOpId;
            }

            // Update maxOpId
            if (currOpId > maxOpId) {
                maxOpId = currOpId;
            }
        }

        // Might be 0 when everything got finalized/reverted
        opCounter.nextFinalize = minOpId;
        // Might be 0 when everything got finalized/reverted
        opCounter.nextRevert = maxOpId;

        // Remove the last item
        opHandles.pop();

        // Remove metadata
        delete opMetadata[_getOpKey(user, opHandle.opId)];
    }

    function assertCanFinalize(
        OpMetadata memory metadata,
        IOptIn.OptInStatus memory optInStatus
    ) public view returns (uint64) {
        // Now there are three valid scenarios remaining:
        //
        // - msg.sender is the original booster
        // - op is expired
        // - getBoosterAddress returns a different booster than the original booster
        //
        // In the second and third case, anyone can call finalize.
        address originalBooster = metadata.booster;

        if (originalBooster == msg.sender) {
            return metadata.createdAt; // First case
        }

        address currentBooster = optInStatus.optedInTo;
        uint256 optOutPeriod = optInStatus.optOutPeriod;

        bool isExpired = block.timestamp >= metadata.createdAt + optOutPeriod;
        if (isExpired) {
            return metadata.createdAt; // Second case
        }

        if (currentBooster != originalBooster) {
            return metadata.createdAt; // Third case
        }

        revert("PB-4");
    }

    function verifySignatureForRevert(
        address user,
        uint64 opTimestamp,
        IOptIn.OptInStatus memory optInStatus,
        bytes memory boosterMessage,
        address[] memory hasherContracts,
        Signature memory signature
    ) public {
        require(hasherContracts.length > 0, "PB-12");

        // Result of hasher contract call
        uint64 signedAt;
        bytes32 boosterHash;
        bool signatureVerified;

        for (uint256 i = 0; i < hasherContracts.length; i++) {
            // Call into the hasher contract and take the first non-zero result.
            // The contract must implement the following function:
            //
            // decodeAndHashBoosterMessage(
            //     address targetBooster,
            //     bytes memory boosterMessage
            // )
            //
            // If it doesn't, then the call will fail (success=false) and we try the next one.
            // If it succeeds (success = true), then we try to decode the result.
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, bytes memory result) = address(hasherContracts[i])
                .call(
                // keccak256("decodeAndHashBoosterMessage(address,bytes)")
                abi.encodeWithSelector(
                    0xaf6eec54,
                    msg.sender, /* msg.sender becomes the target booster */
                    boosterMessage
                )
            );

            if (!success) {
                continue;
            }

            // The result is exactly 2 words long = 512 bits = 64 bytes
            // 32 bytes for the expected message hash
            // 8 bytes (padded to 32 bytes) for the expected timestamp
            if (result.length != 64) {
                continue;
            }

            // NOTE: A contract with malintent could return any hash that we would
            // try to recover against. But there is no harm done in doing so since
            // the user must have signed it.
            //
            // However, it might return an unrelated timestamp, that the user hasn't
            // signed - so it could prolong the expiry of a signature which is a valid
            // concern whose risk we minimize by using also the op timestamp which guarantees
            // that a signature eventually expires.

            // Decode and recover signer
            (boosterHash, signedAt) = abi.decode(result, (bytes32, uint64));
            address signer = ecrecover(
                boosterHash,
                signature.v,
                signature.r,
                signature.s
            );

            if (user != signer) {
                // NOTE: Currently, hardware wallets (e.g. Ledger, Trezor) do not support EIP712 signing (specifically `signTypedData_v4`).
                // However, a user can still sign the EIP712 hash with the caveat that it's signed using `personal_sign` which prepends
                // the prefix '"\x19Ethereum Signed Message:\n" + len(message)'.
                //
                // To still support that, we also add the prefix and try to use the recovered address instead:
                signer = ecrecover(
                    boosterHash.toEthSignedMessageHash(),
                    signature.v,
                    signature.r,
                    signature.s
                );
            }

            // If we recovered `user` from the signature, then we have a valid signature.
            if (user == signer) {
                signatureVerified = true;
                break;
            }

            // Keep trying
        }

        // Revert if signature couldn't be verified with any of the returned hashes
        require(signatureVerified, "PB-8");

        // Lastly, the current time must not be older than:
        // MIN(opTimestamp, signedAt) + optOutPeriod * 3
        uint64 _now = uint64(block.timestamp);
        // The maximum age is equal to whichever is lowest:
        //      opTimestamp + optOutPeriod * 3
        //      signedAt + optOutPeriod * 3
        uint64 maximumAge;
        if (opTimestamp > signedAt) {
            maximumAge = signedAt + uint64(optInStatus.optOutPeriod * 3);
        } else {
            maximumAge = opTimestamp + uint64(optInStatus.optOutPeriod * 3);
        }

        require(_now <= maximumAge, "PB-11");
    }

    function _getOpKey(address user, uint64 opId)
        internal
        pure
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(user, opId));
    }
}

File 17 of 19 : ECDSA.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        // Check the signature length
        if (signature.length != 65) {
            revert("ECDSA: invalid signature length");
        }

        // Divide the signature in r, s and v variables
        bytes32 r;
        bytes32 s;
        uint8 v;

        // ecrecover takes the signature parameters, and the only way to get them
        // currently is to use assembly.
        // solhint-disable-next-line no-inline-assembly
        assembly {
            r := mload(add(signature, 0x20))
            s := mload(add(signature, 0x40))
            v := byte(0, mload(add(signature, 0x60)))
        }

        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            revert("ECDSA: invalid signature 's' value");
        }

        if (v != 27 && v != 28) {
            revert("ECDSA: invalid signature 'v' value");
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        require(signer != address(0), "ECDSA: invalid signature");

        return signer;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * replicates the behavior of the
     * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
     * JSON-RPC method.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }
}

File 18 of 19 : IHodl.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IHodl {
    /**
     * @dev Lock the given amount of PRPS for the specified period (or infinitely)
     * for DUBI.
     */
    function hodl(
        uint24 id,
        uint96 amountPrps,
        uint16 duration,
        address dubiBeneficiary,
        address prpsBeneficiary
    ) external;

    /**
     * @dev Release a hodl of `prpsBeneficiary` with the given `creator` and `id`.
     */
    function release(
        uint24 id,
        address prpsBeneficiary,
        address creator
    ) external;

    /**
     * @dev Withdraw can be used to withdraw DUBI from infinitely locked PRPS.
     * The amount of DUBI withdrawn depends on the time passed since the last withdrawal.
     */
    function withdraw(
        uint24 id,
        address prpsBeneficiary,
        address creator
    ) external;

    /**
     * @dev Burn `amount` of `from`'s locked and/or pending PRPS.
     *
     * This function is supposed to be only called by the PRPS contract.
     *
     * Returns the amount of DUBI that needs to be minted.
     */
    function burnLockedPrps(
        address from,
        uint96 amount,
        uint32 dubiMintTimestamp,
        bool burnPendingLockedPrps
    ) external returns (uint96);

    /**
     * @dev Set `amount` of `from`'s locked PRPS to pending.
     *
     * This function is supposed to be only called by the PRPS contract.
     *
     * Returns the amount of locked PRPS that could be set to pending.
     */
    function setLockedPrpsToPending(address from, uint96 amount) external;

    /**
     * @dev Revert `amount` of `from`'s pending locked PRPS to not pending.
     *
     * This function is supposed to be only called by the PRPS contract and returns
     */
    function revertLockedPrpsSetToPending(address account, uint96 amount)
        external;
}

File 19 of 19 : MintMath.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

// NOTE: we ignore leap-seconds etc.
library MintMath {
    // The maximum number of seconds per month (365 * 24 * 60 * 60 / 12)
    uint32 public constant SECONDS_PER_MONTH = 2628000;
    // The maximum number of days PRPS can be finitely locked for
    uint16 public constant MAX_FINITE_LOCK_DURATION_DAYS = 365;
    // The maximum number of seconds PRPS can be finitely locked for
    uint32 public constant MAX_FINITE_LOCK_DURATION_SECONDS = uint32(
        MAX_FINITE_LOCK_DURATION_DAYS
    ) *
        24 *
        60 *
        60;

    /**
     * @dev Calculates the DUBI to mint based on the given amount of PRPS and duration in days.
     * NOTE: We trust the caller to ensure that the duration between 1 and 365.
     */
    function calculateDubiToMintByDays(
        uint256 amountPrps,
        uint16 durationInDays
    ) internal pure returns (uint96) {
        uint32 durationInSeconds = uint32(durationInDays) * 24 * 60 * 60;
        return calculateDubiToMintBySeconds(amountPrps, durationInSeconds);
    }

    /**
     * @dev Calculates the DUBI to mint based on the given amount of PRPS and duration in seconds.
     */
    function calculateDubiToMintBySeconds(
        uint256 amountPrps,
        uint32 durationInSeconds
    ) internal pure returns (uint96) {
        // NOTE: We do not use safe math for efficiency reasons

        uint256 _percentage = percentage(
            durationInSeconds,
            MAX_FINITE_LOCK_DURATION_SECONDS,
            18 // precision in WEI, 10^18
        ) * 4; // A full lock grants 4%, so multiply by 4.

        // Multiply PRPS by the percentage and then divide by the precision (=10^8)
        // from the previous step
        uint256 _dubiToMint = (amountPrps * _percentage) / (1 ether * 100); // multiply by 100, because we deal with percentages

        // Assert that the calculated DUBI never overflows uint96
        assert(_dubiToMint < 2**96);

        return uint96(_dubiToMint);
    }

    function calculateDubiToMintMax(uint96 amount)
        internal
        pure
        returns (uint96)
    {
        return
            calculateDubiToMintBySeconds(
                amount,
                MAX_FINITE_LOCK_DURATION_SECONDS
            );
    }

    function calculateMintDuration(uint32 _now, uint32 lastWithdrawal)
        internal
        pure
        returns (uint32)
    {
        require(lastWithdrawal > 0 && lastWithdrawal <= _now, "MINT-1");

        // NOTE: we don't use any safe math here for efficiency reasons. The assert above
        // is already a pretty good guarantee that nothing goes wrong. Also, all numbers involved
        // are very well smaller than uint256 in the first place.
        uint256 _elapsedTotal = _now - lastWithdrawal;
        uint256 _proRatedYears = _elapsedTotal / SECONDS_PER_MONTH / 12;
        uint256 _elapsedInYear = _elapsedTotal %
            MAX_FINITE_LOCK_DURATION_SECONDS;

        //
        // Examples (using months instead of seconds):
        // calculation formula: (monthsSinceWithdrawal % 12) + (_proRatedYears * 12)

        // 1) Burn after 11 months since last withdrawal (number of years = 11 / 12 + 1 = 1)
        // => (11 % 12) + (years * 12) => 23 months worth of DUBI
        // => 23 months

        // 1) Burn after 4 months since last withdrawal (number of years = 4 / 12 + 1 = 1)
        // => (4 % 12) + (years * 12) => 16 months worth of DUBI
        // => 16 months

        // 2) Burn 0 months after withdrawal after 4 months (number of years = 0 / 12 + 1 = 1):
        // => (0 % 12) + (years * 12) => 12 months worth of DUBI (+ 4 months worth of withdrawn DUBI)
        // => 16 months

        // 3) Burn after 36 months since last withdrawal (number of years = 36 / 12 + 1 = 4)
        // => (36 % 12) + (years * 12) => 48 months worth of DUBI
        // => 48 months

        // 4) Burn 1 month after withdrawal after 35 months (number of years = 1 / 12 + 1 = 1):
        // => (1 % 12) + (years * 12) => 12 month worth of DUBI (+ 35 months worth of withdrawn DUBI)
        // => 47 months
        uint32 _mintDuration = uint32(
            _elapsedInYear + _proRatedYears * MAX_FINITE_LOCK_DURATION_SECONDS
        );

        return _mintDuration;
    }

    function percentage(
        uint256 numerator,
        uint256 denominator,
        uint256 precision
    ) internal pure returns (uint256) {
        return
            ((numerator * (uint256(10)**(precision + 1))) / denominator + 5) /
            uint256(10);
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "abi"
      ]
    }
  },
  "libraries": {
    "contracts/ProtectedBoostableLib.sol": {
      "ProtectedBoostableLib": "0x85ae45a05971170b70744292e2f051c0c49cf909"
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"uint256","name":"initialSupply","type":"uint256"},{"internalType":"address","name":"optIn","type":"address"},{"internalType":"address","name":"purpose","type":"address"},{"internalType":"address","name":"hodl","type":"address"},{"internalType":"address","name":"externalAddress1","type":"address"},{"internalType":"address","name":"externalAddress2","type":"address"},{"internalType":"address","name":"externalAddress3","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amountAndFuel","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Burned","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint64","name":"opId","type":"uint64"},{"indexed":false,"internalType":"uint8","name":"opType","type":"uint8"}],"name":"FinalizedOp","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint64","name":"opId","type":"uint64"},{"indexed":false,"internalType":"uint8","name":"opType","type":"uint8"}],"name":"PendingOp","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint64","name":"opId","type":"uint64"},{"indexed":false,"internalType":"uint8","name":"opType","type":"uint8"}],"name":"RevertedOp","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"holder","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenHolder","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"tag","type":"uint8"},{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"components":[{"internalType":"uint96","name":"dubi","type":"uint96"},{"internalType":"uint96","name":"unlockedPrps","type":"uint96"},{"internalType":"uint96","name":"lockedPrps","type":"uint96"},{"internalType":"uint96","name":"intrinsicFuel","type":"uint96"}],"internalType":"struct BoosterFuel","name":"fuel","type":"tuple"},{"components":[{"internalType":"address","name":"booster","type":"address"},{"internalType":"uint64","name":"timestamp","type":"uint64"},{"internalType":"uint64","name":"nonce","type":"uint64"},{"internalType":"bool","name":"isLegacySignature","type":"bool"}],"internalType":"struct BoosterPayload","name":"boosterPayload","type":"tuple"}],"internalType":"struct BoostableERC20.BoostedBurn","name":"message","type":"tuple"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"internalType":"struct Signature","name":"signature","type":"tuple"}],"name":"boostedBurn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"tag","type":"uint8"},{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"components":[{"internalType":"uint96","name":"dubi","type":"uint96"},{"internalType":"uint96","name":"unlockedPrps","type":"uint96"},{"internalType":"uint96","name":"lockedPrps","type":"uint96"},{"internalType":"uint96","name":"intrinsicFuel","type":"uint96"}],"internalType":"struct BoosterFuel","name":"fuel","type":"tuple"},{"components":[{"internalType":"address","name":"booster","type":"address"},{"internalType":"uint64","name":"timestamp","type":"uint64"},{"internalType":"uint64","name":"nonce","type":"uint64"},{"internalType":"bool","name":"isLegacySignature","type":"bool"}],"internalType":"struct BoosterPayload","name":"boosterPayload","type":"tuple"}],"internalType":"struct BoostableERC20.BoostedBurn[]","name":"burns","type":"tuple[]"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"internalType":"struct Signature[]","name":"signatures","type":"tuple[]"}],"name":"boostedBurnBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"tag","type":"uint8"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"components":[{"internalType":"uint96","name":"dubi","type":"uint96"},{"internalType":"uint96","name":"unlockedPrps","type":"uint96"},{"internalType":"uint96","name":"lockedPrps","type":"uint96"},{"internalType":"uint96","name":"intrinsicFuel","type":"uint96"}],"internalType":"struct BoosterFuel","name":"fuel","type":"tuple"},{"components":[{"internalType":"address","name":"booster","type":"address"},{"internalType":"uint64","name":"timestamp","type":"uint64"},{"internalType":"uint64","name":"nonce","type":"uint64"},{"internalType":"bool","name":"isLegacySignature","type":"bool"}],"internalType":"struct BoosterPayload","name":"boosterPayload","type":"tuple"}],"internalType":"struct BoostableERC20.BoostedSend","name":"send","type":"tuple"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"internalType":"struct Signature","name":"signature","type":"tuple"}],"name":"boostedSend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"tag","type":"uint8"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"components":[{"internalType":"uint96","name":"dubi","type":"uint96"},{"internalType":"uint96","name":"unlockedPrps","type":"uint96"},{"internalType":"uint96","name":"lockedPrps","type":"uint96"},{"internalType":"uint96","name":"intrinsicFuel","type":"uint96"}],"internalType":"struct BoosterFuel","name":"fuel","type":"tuple"},{"components":[{"internalType":"address","name":"booster","type":"address"},{"internalType":"uint64","name":"timestamp","type":"uint64"},{"internalType":"uint64","name":"nonce","type":"uint64"},{"internalType":"bool","name":"isLegacySignature","type":"bool"}],"internalType":"struct BoosterPayload","name":"boosterPayload","type":"tuple"}],"internalType":"struct BoostableERC20.BoostedSend[]","name":"sends","type":"tuple[]"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"internalType":"struct Signature[]","name":"signatures","type":"tuple[]"}],"name":"boostedSendBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"boostedTransferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"components":[{"internalType":"uint8","name":"tokenAlias","type":"uint8"},{"internalType":"uint96","name":"amount","type":"uint96"}],"internalType":"struct TokenFuel","name":"fuel","type":"tuple"}],"name":"burnFuel","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"targetBooster","type":"address"},{"internalType":"bytes","name":"boosterMessage","type":"bytes"}],"name":"decodeAndHashBoosterMessage","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"components":[{"internalType":"uint8","name":"opType","type":"uint8"},{"internalType":"uint64","name":"opId","type":"uint64"}],"internalType":"struct OpHandle","name":"opHandle","type":"tuple"}],"name":"finalizePendingOp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getOpCounter","outputs":[{"components":[{"internalType":"uint64","name":"value","type":"uint64"},{"internalType":"uint64","name":"nextFinalize","type":"uint64"},{"internalType":"uint64","name":"nextRevert","type":"uint64"}],"internalType":"struct OpCounter","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint64","name":"opId","type":"uint64"}],"name":"getOpMetadata","outputs":[{"components":[{"internalType":"uint8","name":"opType","type":"uint8"},{"internalType":"uint64","name":"createdAt","type":"uint64"},{"internalType":"address","name":"booster","type":"address"}],"internalType":"struct OpMetadata","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"hodlMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"purposeMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"components":[{"internalType":"uint8","name":"opType","type":"uint8"},{"internalType":"uint64","name":"opId","type":"uint64"}],"internalType":"struct OpHandle","name":"opHandle","type":"tuple"},{"internalType":"bytes","name":"boosterMessage","type":"bytes"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"internalType":"struct Signature","name":"signature","type":"tuple"}],"name":"revertPendingOp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"components":[{"internalType":"uint8","name":"opType","type":"uint8"},{"internalType":"uint64","name":"opId","type":"uint64"}],"internalType":"struct OpHandle","name":"opHandle","type":"tuple"}],"name":"safeGetOpMetadata","outputs":[{"components":[{"internalType":"uint8","name":"opType","type":"uint8"},{"internalType":"uint64","name":"createdAt","type":"uint64"},{"internalType":"address","name":"booster","type":"address"}],"internalType":"struct OpMetadata","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"holder","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenHolder","type":"address"}],"name":"unpackedDataOf","outputs":[{"components":[{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint96","name":"hodlBalance","type":"uint96"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"internalType":"struct ERC20.UnpackedData","name":"","type":"tuple"}],"stateMutability":"view","type":"function"}]

6101606040523480156200001257600080fd5b506040516200510e3803806200510e8339810160408190526200003591620005c5565b604051806060016040528060248152602001620050ea602491396040805180820190915260048152634455424960e01b602082015287868686868480807f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f7fc941133d32664f2f3a7ee899586c7b1f0212a1ab605ead437725dcc4c5b199f97fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6620000df62000332565b30604051602001620000f695949392919062000686565b60408051808303601f19018152919052805160209091012060609190911b6001600160601b03191660805260a05250600090506200013362000336565b600480546001600160a01b0319166001600160a01b038316908117909155604051919250906000907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a3508651620001969060069060208a019062000509565b508551620001ac90600790602089019062000509565b506001600160601b0319606085811b821660c05284811b821660e05283811b82166101005282901b16610120526040516329965a1d60e01b8152731820a4b7618bde71dce8cdc73aab6c95905fad24906329965a1d90620002369030907f51d0ab336ae4fc621cb076e5c123b2236d97b9709e1ff2e304c6d727b35c6e4b90829060040162000663565b600060405180830381600087803b1580156200025157600080fd5b505af115801562000266573d6000803e3d6000fd5b50506040516329965a1d60e01b8152731820a4b7618bde71dce8cdc73aab6c95905fad2492506329965a1d9150620002c79030907faea199e31a596269b42cdafd93407f14436db6e4cad65417994c2eb37381e05a90829060040162000663565b600060405180830381600087803b158015620002e257600080fd5b505af1158015620002f7573d6000803e3d6000fd5b50505050505050505050506200031433886200033a60201b60201c565b5050505060601b6001600160601b0319166101405250620007219050565b4690565b3390565b606081901b176200034c828262000350565b5050565b6001600160a01b038216620003825760405162461bcd60e51b81526004016200037990620006d3565b60405180910390fd5b80606081901c6001600160601b03811615620003a6576001600160601b0381166005555b620003b06200058e565b6001600160a01b038516600090815260086020526040902054620003d4906200048c565b80516020820151919250808501916001600160601b039182169083019091161015620004145760405162461bcd60e51b81526004016200037990620006b2565b6001600160601b03811682526200042b82620004c7565b6001600160a01b0387166000818152600860205260408082209390935591519091907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906200047c908890620006f4565b60405180910390a3505050505050565b620004966200058e565b620004a06200058e565b6001600160601b038381168252606084901c16602082015260c09290921c60408301525090565b805160208201516040909201516001600160601b0390911660609290921b600160601b600160c01b03169190911760c09190911b6001600160c01b0319161790565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200054c57805160ff19168380011785556200057c565b828001600101855582156200057c579182015b828111156200057c5782518255916020019190600101906200055f565b506200058a929150620005ae565b5090565b604080516060810182526000808252602082018190529181019190915290565b5b808211156200058a5760008155600101620005af565b600080600080600080600060e0888a031215620005e0578283fd5b875196506020880151620005f48162000708565b6040890151909650620006078162000708565b60608901519095506200061a8162000708565b60808901519094506200062d8162000708565b60a0890151909350620006408162000708565b60c0890151909250620006538162000708565b8091505092959891949750929550565b6001600160a01b0393841681526020810192909252909116604082015260600190565b9485526020850193909352604084019190915260608301526001600160a01b0316608082015260a00190565b60208082526007908201526622a9219918169960c91b604082015260600190565b60208082526007908201526645524332302d3160c81b604082015260600190565b6001600160601b0391909116815260200190565b6001600160a01b03811681146200071e57600080fd5b50565b60805160601c60a05160c05160601c60e05160601c6101005160601c6101205160601c6101405160601c614922620007c860003980610f2452806113b152806121675280612231528061269a52806127175280612f5e5250806112c7528061299b52508061296252806130485250806129295280612ffa52508061096252806128f05280612fac525080611d2e528061247b5250806112215280611e0052506149226000f3fe608060405234801561001057600080fd5b50600436106101f05760003560e01c8063688d872c1161010f578063a9059cbb116100a2578063dc3b52d711610071578063dc3b52d714610455578063dd62ed3e14610468578063f2fde38b1461047b578063fe9d93031461048e576101f0565b8063a9059cbb146103fb578063af6eec541461040e578063b44be68c1461042f578063b55ff88414610442576101f0565b80638da5cb5b116100de5780638da5cb5b146103ab57806395d89b41146103c05780639cc0b8b3146103c8578063a457c2d7146103e8576101f0565b8063688d872c1461036a57806370a082311461037d578063715018a6146103905780637675dda414610398576101f0565b80632d0335ab1161018757806340c10f191161015657806340c10f191461031e5780634598980b14610331578063470e5a92146103445780634cc9512314610357576101f0565b80632d0335ab146102b65780632ec4ac0a146102d6578063313ce567146102f6578063395093511461030b576101f0565b806318160ddd116101c357806318160ddd1461025b57806323b872dd14610270578063240fa6de14610283578063252b2d20146102a3576101f0565b806306fdde03146101f5578063095ea7b31461021357806309ee40a01461023357806310b4a23d14610246575b600080fd5b6101fd6104a1565b60405161020a9190614182565b60405180910390f35b610226610221366004613969565b610538565b60405161020a919061405c565b610226610241366004613774565b61055c565b6102596102543660046138ff565b6105fd565b005b61026361062f565b60405161020a91906146d7565b61022661027e366004613734565b610635565b61029661029136600461385a565b61073b565b60405161020a9190614676565b6102966102b1366004613994565b6107fe565b6102c96102c43660046136e0565b610868565b60405161020a91906146f9565b6102e96102e43660046136e0565b61089f565b60405161020a919061469f565b6102fe6108c9565b60405161020a919061470d565b610226610319366004613969565b6108ce565b61025961032c366004613969565b610918565b61025961033f366004613969565b610957565b61025961035236600461385a565b61099f565b610259610365366004613a67565b610a58565b610259610378366004613bd4565b610acf565b61026361038b3660046136e0565b610b6a565b610259610b8e565b6102596103a63660046139c1565b610c0d565b6103b3610c7f565b60405161020a9190613edf565b6101fd610c8e565b6103db6103d63660046136e0565b610cef565b60405161020a9190614647565b6102266103f6366004613969565b610d4e565b610226610409366004613969565b610dae565b61042161041c36600461380d565b610e27565b60405161020a92919061409e565b61025961043d36600461388f565b610ef4565b610259610450366004613969565b610f19565b610259610463366004613ce7565b610f61565b6102636104763660046136fc565b611030565b6102596104893660046136e0565b61105b565b61025961049c366004613d8a565b611112565b60068054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561052d5780601f106105025761010080835404028352916020019161052d565b820191906000526020600020905b81548152906001019060200180831161051057829003601f168201915b505050505090505b90565b600033610545818561115e565b61055081858561119a565b60019150505b92915050565b6000610568868661115e565b610570613091565b61057987611202565b90506105be8787878488888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506112a692505050565b6105e35760405162461bcd60e51b81526004016105da9061438e565b60405180910390fd5b6105ee87878761132c565b60019150505b95945050505050565b6106056113a4565b6106215760405162461bcd60e51b81526004016105da906141f6565b61062b82826113ec565b5050565b60055490565b6000610641848461115e565b3361064a613091565b61065386611202565b80519091508015610665575080602001515b156106c557816001600160a01b0316866001600160a01b03161461069b5760405162461bcd60e51b81526004016105da90614313565b6106ba600083888888604051806020016040528060008152508761152d565b600192505050610734565b6040805180820182526007815266115490cc8c0b4d60ca1b6020808301919091526001600160a01b03808a16600090815260098352848120918716815291529190912054610722918891859161071d91908990611644565b61119a565b61072d86868661132c565b6001925050505b9392505050565b6107436130b8565b600060036000610757868660200151611670565b81526020810191909152604001600020805490915061010090046001600160401b03166107965760405162461bcd60e51b81526004016105da906141d8565b8251815460ff9081169116146107be5760405162461bcd60e51b81526004016105da90614216565b60408051606081018252915460ff8116835261010081046001600160401b03166020840152600160481b90046001600160a01b0316908201529392505050565b6108066130b8565b600360006108148585611670565b815260208082019290925260409081016000208151606081018352905460ff8116825261010081046001600160401b031693820193909352600160481b9092046001600160a01b0316908201529392505050565b60006108726130b8565b6001600160a01b038316600090815260086020526040902054610894906116a3565b604001519392505050565b6108a76130b8565b6001600160a01b038216600090815260086020526040902054610556906116a3565b601290565b60006108da338461115e565b3360008181526009602090815260408083206001600160a01b038816845290915290205461090f9190859061071d90866116da565b50600192915050565b6109206116ff565b6004546001600160a01b0390811691161461094d5760405162461bcd60e51b81526004016105da906144d4565b61062b8282611703565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461094d5760405162461bcd60e51b81526004016105da906143f0565b805160006109ad8484611815565b905060006109bf8585602001516118f6565b90506109cb8585611941565b60ff83166109e3576109de8186846119dd565b610a12565b60ff8316600114156109fa576109de818684611b55565b60405162461bcd60e51b81526004016105da906143ce565b7ffd1941e7a2d0c6f1cc53e8a73873275917cb8a1cf357e47f86f6e681e5d0f0f185856020015185604051610a499392919061402f565b60405180910390a15050505050565b60008251118015610a6a575080518251145b610a865760405162461bcd60e51b81526004016105da906141b7565b60005b8251811015610aca57610ac2838281518110610aa157fe5b6020026020010151838381518110610ab557fe5b6020026020010151610f61565b600101610a89565b505050565b6020820151610adc6130b8565b6001600160a01b038216600090815260086020526040902054610afe906116a3565b9050610b128460a001518260400151611cef565b610b2f8460200151610b248633611d27565b8660a0015186611dd2565b610b376130d8565b610b4683866080015184612066565b9050610b63856020015183876040015188606001516001866122e6565b5050505050565b6001600160a01b03166000908152600860205260409020546001600160601b031690565b610b966116ff565b6004546001600160a01b03908116911614610bc35760405162461bcd60e51b81526004016105da906144d4565b6004546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600480546001600160a01b0319169055565b60008251118015610c1f575080518251145b610c3b5760405162461bcd60e51b81526004016105da906141b7565b60005b8251811015610aca57610c77838281518110610c5657fe5b6020026020010151838381518110610c6a57fe5b6020026020010151610acf565b600101610c3e565b6004546001600160a01b031690565b60078054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561052d5780601f106105025761010080835404028352916020019161052d565b610cf76130b8565b506001600160a01b0316600090815260016020908152604091829020825160608101845290546001600160401b038082168352600160401b8204811693830193909352600160801b90049091169181019190915290565b6000610d5a338461115e565b604080518082018252600881526708aa48664605a62760c31b602080830191909152336000818152600983528481206001600160a01b03891682529092529290205461090f9291869161071d918790611644565b6000610dba338461115e565b610dc26113a4565b610e1c5733610dcf613091565b610dd882611202565b80519091508015610dea575080602001515b15610e1957610e0e600033338888604051806020016040528060008152508761152d565b600192505050610556565b50505b61090f33848461132c565b6000806000835111610e4b5760405162461bcd60e51b81526004016105da90614352565b6000610e5684612430565b905060ff8116610e9c57610e686130ef565b84806020019051810190610e7c9190613c17565b9050610e888187612474565b8160c0015160200151935093505050610eed565b60ff811660011415610ee457610eb061314c565b84806020019051810190610ec49190613b15565b9050610ed08187611d27565b8160a0015160200151935093505050610eed565b60008092509250505b9250929050565b610f008484848461250a565b610f138484600001518560200151612539565b50505050565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461094d5760405162461bcd60e51b81526004016105da906145c8565b60208201516040830151610f736130b8565b6001600160a01b038316600090815260086020526040902054610f95906116a3565b9050610f9f6130b8565b6001600160a01b038316600090815260086020526040902054610fc1906116a3565b9050610fd58660c001518360400151611cef565b610ff28660200151610fe78833612474565b8860c0015188611dd2565b610ffa6130d8565b611009858860a00151856125c3565b90506110278760200151848960400151858b6060015160018761274c565b50505050505050565b6001600160a01b03918216600090815260096020908152604080832093909416825291909152205490565b6110636116ff565b6004546001600160a01b039081169116146110905760405162461bcd60e51b81526004016105da906144d4565b6001600160a01b0381166110b65760405162461bcd60e51b81526004016105da90614254565b6004546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600480546001600160a01b0319166001600160a01b0392909216919091179055565b61111a613091565b61112333611202565b80519091508015611135575080602001515b156111515761114b60013333600087878761152d565b5061062b565b610aca338484600061287b565b6001600160a01b0382161580159061117e57506001600160a01b03811615155b61062b5760405162461bcd60e51b81526004016105da90614494565b6001600160a01b0380841660008181526009602090815260408083209487168084529490915290819020849055517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906111f59085906146d7565b60405180910390a3505050565b61120a613091565b604051630f425aed60e11b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690631e84b5da90611256908590600401613edf565b60806040518083038186803b15801561126e57600080fd5b505afa158015611282573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105569190613d1a565b60006112b06113a4565b6112bc575060006105f4565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146112f4575060016105f4565b60018281015190818116141561130e5760019150506105f4565b8360200151801561131d575083515b156105ee5760009150506105f4565b6113346130b8565b6001600160a01b038416600090815260086020526040902054611356906116a3565b90506113606130b8565b6001600160a01b038416600090815260086020526040902054611382906116a3565b905061138c6130d8565b61139c868487858860008761274c565b505050505050565b6000336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156113df57506001610535565b6113e76128e3565b905090565b678ac7230489e800006001600160601b031681602001516001600160601b0316111561142a5760405162461bcd60e51b81526004016105da906145e8565b6001600160a01b0382161580159061144b57506001600160a01b0382163314155b6114675760405162461bcd60e51b81526004016105da906145a8565b805160ff16600214156115155761147c6130b8565b6001600160a01b03831660009081526008602052604090205461149e906116a3565b905081602001516001600160601b031681600001516001600160601b031610156114da5760405162461bcd60e51b81526004016105da90614234565b60208201518151036001600160601b031681526114f6816129cf565b6001600160a01b0384166000908152600860205260409020555061062b565b60405162461bcd60e51b81526004016105da906142f3565b6115356130d8565b61154082878a612a16565b905061154a613187565b611558828989898989612bb4565b905080600a600061156d8a8660200151611670565b8152602080820192909252604090810160002083518154858501516001600160601b03908116600160a01b9081026001600160a01b039485166001600160a01b03199485161785161785559487015160018501805460608a01519093169096029084169190921617909116179091556080830151805191926115f7926002850192909101906131b7565b505050602082015182516040517fa824f616acaccd5102c745c13e260dc1ca704e95425ebc56e73a6e495d5a3d8992611631928b9261402f565b60405180910390a1505050505050505050565b600081848411156116685760405162461bcd60e51b81526004016105da9190614182565b505050900390565b60008282604051602001611685929190613e64565b60405160208183030381529060405280519060200120905092915050565b6116ab6130b8565b6116b36130b8565b6001600160601b038381168252606084901c16602082015260c09290921c60408301525090565b6000828201838110156107345760405162461bcd60e51b81526004016105da906142bc565b3390565b6001600160a01b0382166117295760405162461bcd60e51b81526004016105da90614608565b80606081901c6001600160601b0381161561174c576001600160601b0381166005555b6117546130b8565b6001600160a01b038516600090815260086020526040902054611776906116a3565b80516020820151919250808501916001600160601b0391821690830190911610156117b35760405162461bcd60e51b81526004016105da90614509565b6001600160601b03811682526117c8826129cf565b6001600160a01b0387166000818152600860205260408082209390935591519091906000805160206148cd8339815191529061180590889061471b565b60405180910390a3505050505050565b600061181f6130b8565b611829848461073b565b6020810151909150611839613091565b61184286611202565b8051909150611855575091506105569050565b611863868660200151612c18565b60405163c2288c7160e01b81527385ae45a05971170b70744292e2f051c0c49cf9099063c2288c719061189c9086908590600401614684565b60206040518083038186803b1580156118b457600080fd5b505af41580156118c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ec9190613db8565b9695505050505050565b600080600a60006119078686611670565b8152602081019190915260400160002080549091506001600160a01b03166107345760405162461bcd60e51b81526004016105da90614472565b6001600160a01b0382166000908152600260209081526040808320600190925291829020915163c112c77160e01b81529091907385ae45a05971170b70744292e2f051c0c49cf9099063c112c771906119a7908790879087908790600390600401613f0d565b60006040518083038186803b1580156119bf57600080fd5b505af41580156119d3573d6000803e3d6000fd5b5050505050505050565b600183015483546001600160a01b0390911690600160a01b90046001600160601b031630611a096130b8565b6001600160a01b038216600090815260086020526040902054611a2b906116a3565b9050611a356130b8565b6001600160a01b038516600090815260086020526040902054611a57906116a3565b9050836001600160601b031682600001516001600160601b03161015611a8f5760405162461bcd60e51b81526004016105da90614509565b81516001600160601b0390859003811683528151808601919081169082161015611acb5760405162461bcd60e51b81526004016105da90614509565b6001600160601b0381168252611ae0836129cf565b6001600160a01b038516600090815260086020526040902055611b02826129cf565b6001600160a01b0380881660008181526008602052604090819020939093559151908a16906000805160206148cd83398151915290611b4290899061471b565b60405180910390a3505050505050505050565b8254600160a01b90046001600160601b0316611b6f6130b8565b6001600160a01b038416600090815260086020526040902054611b91906116a3565b9050611b9b6130d8565b6000611bc48684868a60010160149054906101000a90046001600160601b031689876001612c63565b905030611bcf6130b8565b6001600160a01b038216600090815260086020526040902054611bf1906116a3565b9050826001600160601b031681600001516001600160601b03161015611c295760405162461bcd60e51b81526004016105da90614509565b80518390036001600160601b03168152611c42816129cf565b6001600160a01b038316600090815260086020526040902055611c64856129cf565b6001600160a01b0389166000908152600860205260409081902091909155517fe7b1342ce7f88416536f0a97fd9274421e3718dd094f96e9cefec28f6d7002c190611cb590889060028d019061472f565b60405180910390a160006001600160a01b0316886001600160a01b03166000805160206148cd83398151915288604051611b42919061471b565b60018260400151036001600160401b0316816001600160401b03161461062b5760405162461bcd60e51b81526004016105da90614587565b60006107347f00000000000000000000000000000000000000000000000000000000000000007f72079e1ca444dd2cbc0e28bb80962e9f4fbafb3705580b6b9f66186befba0395600186602001518760400151886060015180519060200120611d938a60800151612c6e565b611da18b60a001518b612cd6565b604051602001611db797969594939291906140fa565b60405160208183030381529060405280519060200120612d1d565b81516001600160a01b03163314611dfb5760405162461bcd60e51b81526004016105da906143b0565b6000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316630296287733886040518363ffffffff1660e01b8152600401611e4c929190613ef3565b604080518083038186803b158015611e6357600080fd5b505afa158015611e77573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e9b9190613ae8565b9150915081611ebc5760405162461bcd60e51b81526004016105da90614370565b6020840151429082906001600160401b03610e10840181169181169182111591600191851610611f0557826001600160401b0316886020015185036001600160401b0316111590505b818015611f0f5750805b611f2b5760405162461bcd60e51b81526004016105da9061454b565b876060015115611f4157611f3e89612d32565b98505b600060018a89604001518a600001518b6020015160405160008152602001604052604051611f729493929190614136565b6020604051602081039080840390855afa158015611f94573d6000803e3d6000fd5b5050506020604051035190508860600151158015611fc457508a6001600160a01b0316816001600160a01b031614155b15612028576001611fd48b612d32565b6040808b01518b516020808e015184516000815290910193849052611ff99493614136565b6020604051602081039080840390855afa15801561201b573d6000803e3d6000fd5b5050506020604051035190505b806001600160a01b03168b6001600160a01b0316146120595760405162461bcd60e51b81526004016105da906144b6565b5050505050505050505050565b61206e6130d8565b6120766130d8565b83516001600160601b03161561211e578351678ac7230489e800006001600160601b0390911611156120ba5760405162461bcd60e51b81526004016105da906145e8565b835183516001600160601b03918216911610156120e95760405162461bcd60e51b81526004016105da90614234565b83518351036001600160601b03908116845284511660208201528060035b9081600481111561211457fe5b9052509050610734565b60208401516001600160601b0316156121e957604080518082018252600081526020868101516001600160601b03169082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916310b4a23d9161219c918991600401613f53565b600060405180830381600087803b1580156121b657600080fd5b505af11580156121ca573d6000803e3d6000fd5b5050506020808601516001600160601b03169083015250806001612107565b60408401516001600160601b0316156122b35760408051808201825260018152858201516001600160601b0316602082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916310b4a23d91612266918991600401613f53565b600060405180830381600087803b15801561228057600080fd5b505af1158015612294573d6000803e3d6000fd5b50505060408501516001600160601b0316602083015250806002612107565b60608401516001600160601b0316156122de5760405162461bcd60e51b81526004016105da906142f3565b949350505050565b60006122f9878787600042876000612c63565b90508215612316576040860180516001016001600160401b031690525b6001600160601b038116156123635785516001600160601b03808316911610156123525760405162461bcd60e51b81526004016105da9061452a565b85518190036001600160601b031686525b61236c866129cf565b6001600160a01b03881660009081526008602052604081209190915582516001600160601b038716919060048111156123a157fe5b905060608160ff16901b82179150606384602001516001600160601b0316901b821791507fe7b1342ce7f88416536f0a97fd9274421e3718dd094f96e9cefec28f6d7002c182876040516123f69291906146e0565b60405180910390a160006001600160a01b0316896001600160a01b03166000805160206148cd83398151915285604051611b42919061471b565b60008082601f8151811061244057fe5b0160209081015160f81c91508110610556578281601f0160ff168151811061246457fe5b016020015160f81c905092915050565b60006107347f00000000000000000000000000000000000000000000000000000000000000007f7d1602bfd7297f6720514f0c3dabe77313ce0d25cd5085644e3fcdc5635df66060008660200151876040015188606001518960800151805190602001206124e58b60a00151612c6e565b6124f38c60c001518c612cd6565b604051602001611db79897969594939291906140b5565b6125126130b8565b61251c858561073b565b905061252f858583602001518686612d45565b610b638585611941565b600061254584836118f6565b805460018201549192506001600160601b03600160a01b91829004811692919091041661257330878461132c565b6125808686868585610b63565b7f74c334759f9bfb4a4342d46ed6f4dfb2a3c6b7fe1c7c2288988e8b44bc281b8a8685876040516125b39392919061402f565b60405180910390a1505050505050565b6125cb6130d8565b6125d36130d8565b83516001600160601b031615612651578351678ac7230489e800006001600160601b0390911611156126175760405162461bcd60e51b81526004016105da906145e8565b80602001516001600160601b031683600001516001600160601b031610156120e95760405162461bcd60e51b81526004016105da90614234565b60208401516001600160601b0316156126cf57604080518082018252600081526020868101516001600160601b03169082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916310b4a23d9161219c918991600401613f53565b60408401516001600160601b0316156122de5760408051808201825260018152858201516001600160601b0316602082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916310b4a23d91612266918991600401613f53565b846001600160a01b0316876001600160a01b0316141561277e5760405162461bcd60e51b81526004016105da9061429a565b8115612799576040860180516001016001600160401b031690525b855183906001600160601b03808316911610156127c85760405162461bcd60e51b81526004016105da90614195565b86516001600160601b03908290038116885285518083019190811690821610156128045760405162461bcd60e51b81526004016105da90614450565b6001600160601b0381168652612819886129cf565b6001600160a01b038a1660009081526008602052604090205561283b866129cf565b6001600160a01b0380891660008181526008602052604090819020939093559151908b16906000805160206148cd83398151915290611b4290869061471b565b6001600160a01b0384166128a15760405162461bcd60e51b81526004016105da90614410565b6128a96130b8565b6001600160a01b0385166000908152600860205260409020546128cb906116a3565b90506128d56130d8565b61139c8683878787866122e6565b6000336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016141561291e57506001610535565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016141561295757506001610535565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016141561299057506001610535565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156129c957506001610535565b50600090565b805160208201516040909201516001600160601b0390911660609290921b6bffffffffffffffffffffffff60601b169190911760c09190911b6001600160c01b0319161790565b612a1e6130d8565b6000612a2984612e20565b9050612a336130d8565b5060408051808201825260ff851681526001600160401b0383166020808301919091526001600160a01b038716600090815260029091529190912054601911612a8e5760405162461bcd60e51b81526004016105da90614334565b6040868101516001600160a01b0387811660009081526002602090815284822080546001810182559083528183208751910180548884015160ff1990911660ff9384161768ffffffffffffffff0019166101006001600160401b0392831602179091558651606081018852918b168252421691810191909152918316938201939093529091600390612b208987611670565b815260208082019290925260409081016000208351815493850151949092015160ff1990931660ff9092169190911768ffffffffffffffff0019166101006001600160401b0390941693909302929092177fffffff0000000000000000000000000000000000000000ffffffffffffffffff16600160481b6001600160a01b03909216919091021790555095945050505050565b612bbc613187565b612bc785308561132c565b612bcf613187565b50506040805160a0810182526001600160a01b0380881682526001600160601b038516602083015285169181019190915260006060820152608081018290529695505050505050565b6001600160a01b038216600090815260016020526040902080546001600160401b03838116600160401b9092041614610aca5760405162461bcd60e51b81526004016105da90614569565b509295945050505050565b60007ff1f7d09b7527273cfd8370620354f0cd9a62e7bd3efaafdeca15664a7b3e045c8260000151836020015184604001518560600151604051602001612cb9959493929190614154565b604051602081830303815290604052805190602001209050919050565b60007ff45b7e63030d0a046d85957a672f9a0806d76fff4cc32355d75f64eb6e83d00882846020015185604001518660600151604051602001611685959493929190614067565b60008282604051602001611685929190613ec4565b600081604051602001612cb99190613e93565b612d53858560200151612ebd565b612d5b613091565b612d6486611202565b80519091508015612d8a575080604001516001600160a01b0316336001600160a01b0316145b612da65760405162461bcd60e51b81526004016105da90614629565b6060612db0612f08565b60405163e17e413160e01b81529091507385ae45a05971170b70744292e2f051c0c49cf9099063e17e413190612df4908a90899087908a9088908b90600401613f85565b60006040518083038186803b158015612e0c57600080fd5b505af4158015612059573d6000803e3d6000fd5b6001600160a01b03811660009081526001602081905260408220805490916001600160401b0380831690910191600160401b900416612e825781546fffffffffffffffff00000000000000001916600160401b6001600160401b038316021782555b815467ffffffffffffffff60801b1916600160801b6001600160401b0383169081029190911767ffffffffffffffff19161790915592915050565b6001600160a01b038216600090815260016020526040902080546001600160401b03838116600160801b9092041614610aca5760405162461bcd60e51b81526004016105da90614431565b60408051600580825260c082019092526060918291906020820160a0803683370190505090503081600081518110612f3c57fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f000000000000000000000000000000000000000000000000000000000000000081600181518110612f8a57fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f000000000000000000000000000000000000000000000000000000000000000081600281518110612fd857fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f00000000000000000000000000000000000000000000000000000000000000008160038151811061302657fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f00000000000000000000000000000000000000000000000000000000000000008160048151811061307457fe5b6001600160a01b0390921660209283029190910190910152905090565b60408051608081018252600080825260208201819052918101829052606081019190915290565b604080516060810182526000808252602082018190529181019190915290565b604080518082019091526000808252602082015290565b6040518060e00160405280600060ff16815260200160006001600160a01b0316815260200160006001600160a01b03168152602001600081526020016060815260200161313a613091565b8152602001613147613091565b905290565b6040518060c00160405280600060ff16815260200160006001600160a01b03168152602001600081526020016060815260200161313a613091565b6040805160a081018252600080825260208201819052918101829052606080820192909252608081019190915290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106131f857805160ff1916838001178555613225565b82800160010185558215613225579182015b8281111561322557825182559160200191906001019061320a565b50613231929150613235565b5090565b5b808211156132315760008155600101613236565b80356105568161486d565b80516105568161486d565b600082601f830112613270578081fd5b813561328361327e826147f3565b6147cd565b81815291506020808301908481016060808502870183018810156132a657600080fd5b60005b858110156132cd576132bb8984613686565b855293830193918101916001016132a9565b50505050505092915050565b600082601f8301126132e9578081fd5b81356132f761327e82614812565b915080825283602082850101111561330e57600080fd5b8060208401602084013760009082016020015292915050565b600082601f830112613337578081fd5b815161334561327e82614812565b915080825283602082850101111561335c57600080fd5b61336d816020840160208601614841565b5092915050565b60006101808284031215613386578081fd5b61339060c06147cd565b9050813561339d816148a8565b815260208201356133ad8161486d565b60208201526040828101359082015260608201356001600160401b038111156133d557600080fd5b6133e1848285016132d9565b6060830152506133f483608084016134c0565b6080820152613407836101008401613587565b60a082015292915050565b60006101a08284031215613424578081fd5b61342e60e06147cd565b905061343a83836136ca565b8152613449836020840161324a565b602082015261345b836040840161324a565b60408201526060820135606082015260808201356001600160401b0381111561348357600080fd5b61348f848285016132d9565b6080830152506134a28360a084016134c0565b60a08201526134b5836101208401613587565b60c082015292915050565b6000608082840312156134d1578081fd5b6134db60806147cd565b905081356134e8816148b7565b815260208201356134f8816148b7565b6020820152604082013561350b816148b7565b6040820152606082013561351e816148b7565b606082015292915050565b60006080828403121561353a578081fd5b61354460806147cd565b90508151613551816148b7565b81526020820151613561816148b7565b60208201526040820151613574816148b7565b6040820152606082015161351e816148b7565b600060808284031215613598578081fd5b6135a260806147cd565b905081356135af8161486d565b815260208201356135bf81614893565b602082015260408201356135d281614893565b6040820152606082013561351e81614885565b6000608082840312156135f6578081fd5b61360060806147cd565b9050815161360d8161486d565b8152602082015161361d81614893565b6020820152604082015161363081614893565b6040820152606082015161351e81614885565b600060408284031215613654578081fd5b61365e60406147cd565b9050813561366b816148a8565b8152602082013561367b81614893565b602082015292915050565b600060608284031215613697578081fd5b6136a160606147cd565b9050813581526020820135602082015260408201356136bf816148a8565b604082015292915050565b8035610556816148a8565b8051610556816148a8565b6000602082840312156136f1578081fd5b81356107348161486d565b6000806040838503121561370e578081fd5b82356137198161486d565b915060208301356137298161486d565b809150509250929050565b600080600060608486031215613748578081fd5b83356137538161486d565b925060208401356137638161486d565b929592945050506040919091013590565b60008060008060006080868803121561378b578283fd5b85356137968161486d565b945060208601356137a68161486d565b93506040860135925060608601356001600160401b03808211156137c8578283fd5b818801915088601f8301126137db578283fd5b8135818111156137e9578384fd5b8960208285010111156137fa578384fd5b9699959850939650602001949392505050565b6000806040838503121561381f578182fd5b823561382a8161486d565b915060208301356001600160401b03811115613844578182fd5b613850858286016132d9565b9150509250929050565b6000806060838503121561386c578182fd5b82356138778161486d565b91506138868460208501613643565b90509250929050565b60008060008060e085870312156138a4578182fd5b84356138af8161486d565b93506138be8660208701613643565b925060608501356001600160401b038111156138d8578283fd5b6138e4878288016132d9565b9250506138f48660808701613686565b905092959194509250565b6000808284036060811215613912578283fd5b833561391d8161486d565b92506040601f1982011215613930578182fd5b5061393b60406147cd565b6020840135613949816148a8565b81526040840135613959816148b7565b6020820152919491935090915050565b6000806040838503121561397b578182fd5b82356139868161486d565b946020939093013593505050565b600080604083850312156139a6578182fd5b82356139b18161486d565b9150602083013561372981614893565b600080604083850312156139d3578182fd5b82356001600160401b03808211156139e9578384fd5b818501915085601f8301126139fc578384fd5b8135613a0a61327e826147f3565b81815260208082019190858101885b85811015613a4257613a308c8484358b0101613374565b85529382019390820190600101613a19565b50919750880135945050505080821115613a5a578283fd5b5061385085828601613260565b60008060408385031215613a79578182fd5b82356001600160401b0380821115613a8f578384fd5b818501915085601f830112613aa2578384fd5b8135613ab061327e826147f3565b81815260208082019190858101885b85811015613a4257613ad68c8484358b0101613412565b85529382019390820190600101613abf565b60008060408385031215613afa578182fd5b8251613b0581614885565b6020939093015192949293505050565b600060208284031215613b26578081fd5b81516001600160401b0380821115613b3c578283fd5b908301906101808286031215613b50578283fd5b613b5a60c06147cd565b8251613b65816148a8565b8152613b748660208501613255565b602082015260408301516040820152606083015182811115613b94578485fd5b613ba087828601613327565b606083015250613bb38660808501613529565b6080820152613bc68661010085016135e5565b60a082015295945050505050565b60008060808385031215613be6578182fd5b82356001600160401b03811115613bfb578283fd5b613c0785828601613374565b9250506138868460208501613686565b600060208284031215613c28578081fd5b81516001600160401b0380821115613c3e578283fd5b908301906101a08286031215613c52578283fd5b613c5c60e06147cd565b613c6686846136d5565b8152613c758660208501613255565b6020820152613c878660408501613255565b604082015260608301516060820152608083015182811115613ca7578485fd5b613cb387828601613327565b608083015250613cc68660a08501613529565b60a0820152613cd98661012085016135e5565b60c082015295945050505050565b60008060808385031215613cf9578182fd5b82356001600160401b03811115613d0e578283fd5b613c0785828601613412565b600060808284031215613d2b578081fd5b613d3560806147cd565b8251613d4081614885565b81526020830151613d5081614885565b60208201526040830151613d638161486d565b6040820152606083015163ffffffff81168114613d7e578283fd5b60608201529392505050565b60008060408385031215613d9c578182fd5b8235915060208301356001600160401b03811115613844578182fd5b600060208284031215613dc9578081fd5b815161073481614893565b60008151808452613dec816020860160208601614841565b601f01601f19169290920160200192915050565b805160ff1682526020808201516001600160401b0316908301526040908101516001600160a01b0316910152565b8051151582526020808201511515908301526040808201516001600160a01b03169083015260609081015163ffffffff16910152565b60609290921b6bffffffffffffffffffffffff1916825260c01b6001600160c01b0319166014820152601c0190565b7f19457468657265756d205369676e6564204d6573736167653a0a3332000000008152601c810191909152603c0190565b61190160f01b81526002810192909252602282015260420190565b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b03959095168552835160ff16602080870191909152909301516001600160401b031660408501526060840191909152608083015260a082015260c00190565b6001600160a01b03929092168252805160ff1660208084019190915201516001600160601b0316604082015260600190565b600061016060018060a01b03808a16845260206001600160401b038a1681860152613fb3604086018a613e2e565b8260c0860152613fc583860189613dd4565b85810360e08701528751808252828901945090820190855b81811015613ffb578551851683529483019491830191600101613fdd565b50508094505085516101008601528086015161012086015250505060ff604084015116610140830152979650505050505050565b6001600160a01b039390931683526001600160401b0391909116602083015260ff16604082015260600190565b901515815260200190565b9485526001600160a01b039390931660208501526001600160401b0391821660408501521660608301521515608082015260a00190565b9182526001600160401b0316602082015260400190565b97885260ff9690961660208801526001600160a01b039485166040880152929093166060860152608085015260a084019190915260c083015260e08201526101000190565b96875260ff9590951660208701526001600160a01b039390931660408601526060850191909152608084015260a083015260c082015260e00190565b93845260ff9290921660208401526040830152606082015260800190565b9485526001600160601b03938416602086015291831660408501528216606084015216608082015260a00190565b6000602082526107346020830184613dd4565b602080825260089082015267045524332302d31360c41b604082015260600190565b60208082526007908201526622a9219918169b60c91b604082015260600190565b60208082526004908201526350422d3160e01b604082015260600190565b602080825260069082015265445542492d3160d01b604082015260600190565b6020808252600490820152632821169960e11b604082015260600190565b602080825260069082015265445542492d3760d01b604082015260600190565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b60208082526008908201526745524332302d313960c01b604082015260600190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b602080825260069082015265088aa84925a760d31b604082015260600190565b60208082526007908201526645524332302d3760c81b604082015260600190565b60208082526004908201526350422d3360e01b604082015260600190565b60208082526004908201526350422d3760e01b604082015260600190565b60208082526004908201526341422d3360e01b604082015260600190565b60208082526008908201526745524332302d313760c01b604082015260600190565b60208082526004908201526320a1169960e11b604082015260600190565b60208082526008908201526745524332302d313560c01b604082015260600190565b602080825260069082015265222aa124969960d11b604082015260600190565b60208082526007908201526608aa48664605a760cb1b604082015260600190565b602080825260059082015264050422d31360dc1b604082015260600190565b60208082526008908201526722a921991816989960c11b604082015260600190565b60208082526008908201526722a921991816989b60c11b604082015260600190565b60208082526008908201526745524332302d313360c01b604082015260600190565b60208082526004908201526341422d3560e01b604082015260600190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526007908201526622a9219918169960c91b604082015260600190565b60208082526007908201526645524332302d3960c81b604082015260600190565b60208082526004908201526310508b4d60e21b604082015260600190565b60208082526004908201526350422d3960e01b604082015260600190565b60208082526007908201526645524332302d3560c81b604082015260600190565b602080825260069082015265222aa124969b60d11b604082015260600190565b602080825260069082015265445542492d3360d01b604082015260600190565b602080825260069082015265445542492d3560d01b604082015260600190565b60208082526007908201526645524332302d3160c81b604082015260600190565b6020808252600490820152632821169b60e11b604082015260600190565b81516001600160401b039081168252602080840151821690830152604092830151169181019190915260600190565b606081016105568284613e00565b60e081016146928285613e00565b6107346060830184613e2e565b81516001600160601b039081168252602080840151909116908201526040918201516001600160401b03169181019190915260600190565b90815260200190565b6000838252604060208301526122de6040830184613dd4565b6001600160401b0391909116815260200190565b60ff91909116815260200190565b6001600160601b0391909116815260200190565b6000604082016001600160601b038516835260206040818501528285546001808216600081146147665760018114614784576147bf565b60028304607f16865260ff19831660608901526080880193506147bf565b6002830461479281886146d7565b61479b8b614835565b895b838110156147b65781548382015290850190880161479d565b91909101955050505b509198975050505050505050565b6040518181016001600160401b03811182821017156147eb57600080fd5b604052919050565b60006001600160401b03821115614808578081fd5b5060209081020190565b60006001600160401b03821115614827578081fd5b50601f01601f191660200190565b60009081526020902090565b60005b8381101561485c578181015183820152602001614844565b83811115610f135750506000910152565b6001600160a01b038116811461488257600080fd5b50565b801515811461488257600080fd5b6001600160401b038116811461488257600080fd5b60ff8116811461488257600080fd5b6001600160601b038116811461488257600080fdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220fead6c60ce95b63b6730927ed7e3e8d7e2d20fc92757ccec5ef000b845349d3e64736f6c634300060c0033446563656e7472616c697a656420556e6976657273616c20426173696320496e636f6d650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a916bc21d2429645585abede4ae00742a16dd1c6000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f12000000000000000000000000ac0122e9258a85ba5479db764dc8ef91cab08db00000000000000000000000003b4da358199060bcc5a527ab60099fb6a908aae9000000000000000000000000025dbd03ed18b4b8425af51b4d05f5b00e78208a000000000000000000000000e7cd2797ac6f08b5721c7e7fcc991251df5e3884

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101f05760003560e01c8063688d872c1161010f578063a9059cbb116100a2578063dc3b52d711610071578063dc3b52d714610455578063dd62ed3e14610468578063f2fde38b1461047b578063fe9d93031461048e576101f0565b8063a9059cbb146103fb578063af6eec541461040e578063b44be68c1461042f578063b55ff88414610442576101f0565b80638da5cb5b116100de5780638da5cb5b146103ab57806395d89b41146103c05780639cc0b8b3146103c8578063a457c2d7146103e8576101f0565b8063688d872c1461036a57806370a082311461037d578063715018a6146103905780637675dda414610398576101f0565b80632d0335ab1161018757806340c10f191161015657806340c10f191461031e5780634598980b14610331578063470e5a92146103445780634cc9512314610357576101f0565b80632d0335ab146102b65780632ec4ac0a146102d6578063313ce567146102f6578063395093511461030b576101f0565b806318160ddd116101c357806318160ddd1461025b57806323b872dd14610270578063240fa6de14610283578063252b2d20146102a3576101f0565b806306fdde03146101f5578063095ea7b31461021357806309ee40a01461023357806310b4a23d14610246575b600080fd5b6101fd6104a1565b60405161020a9190614182565b60405180910390f35b610226610221366004613969565b610538565b60405161020a919061405c565b610226610241366004613774565b61055c565b6102596102543660046138ff565b6105fd565b005b61026361062f565b60405161020a91906146d7565b61022661027e366004613734565b610635565b61029661029136600461385a565b61073b565b60405161020a9190614676565b6102966102b1366004613994565b6107fe565b6102c96102c43660046136e0565b610868565b60405161020a91906146f9565b6102e96102e43660046136e0565b61089f565b60405161020a919061469f565b6102fe6108c9565b60405161020a919061470d565b610226610319366004613969565b6108ce565b61025961032c366004613969565b610918565b61025961033f366004613969565b610957565b61025961035236600461385a565b61099f565b610259610365366004613a67565b610a58565b610259610378366004613bd4565b610acf565b61026361038b3660046136e0565b610b6a565b610259610b8e565b6102596103a63660046139c1565b610c0d565b6103b3610c7f565b60405161020a9190613edf565b6101fd610c8e565b6103db6103d63660046136e0565b610cef565b60405161020a9190614647565b6102266103f6366004613969565b610d4e565b610226610409366004613969565b610dae565b61042161041c36600461380d565b610e27565b60405161020a92919061409e565b61025961043d36600461388f565b610ef4565b610259610450366004613969565b610f19565b610259610463366004613ce7565b610f61565b6102636104763660046136fc565b611030565b6102596104893660046136e0565b61105b565b61025961049c366004613d8a565b611112565b60068054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561052d5780601f106105025761010080835404028352916020019161052d565b820191906000526020600020905b81548152906001019060200180831161051057829003601f168201915b505050505090505b90565b600033610545818561115e565b61055081858561119a565b60019150505b92915050565b6000610568868661115e565b610570613091565b61057987611202565b90506105be8787878488888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506112a692505050565b6105e35760405162461bcd60e51b81526004016105da9061438e565b60405180910390fd5b6105ee87878761132c565b60019150505b95945050505050565b6106056113a4565b6106215760405162461bcd60e51b81526004016105da906141f6565b61062b82826113ec565b5050565b60055490565b6000610641848461115e565b3361064a613091565b61065386611202565b80519091508015610665575080602001515b156106c557816001600160a01b0316866001600160a01b03161461069b5760405162461bcd60e51b81526004016105da90614313565b6106ba600083888888604051806020016040528060008152508761152d565b600192505050610734565b6040805180820182526007815266115490cc8c0b4d60ca1b6020808301919091526001600160a01b03808a16600090815260098352848120918716815291529190912054610722918891859161071d91908990611644565b61119a565b61072d86868661132c565b6001925050505b9392505050565b6107436130b8565b600060036000610757868660200151611670565b81526020810191909152604001600020805490915061010090046001600160401b03166107965760405162461bcd60e51b81526004016105da906141d8565b8251815460ff9081169116146107be5760405162461bcd60e51b81526004016105da90614216565b60408051606081018252915460ff8116835261010081046001600160401b03166020840152600160481b90046001600160a01b0316908201529392505050565b6108066130b8565b600360006108148585611670565b815260208082019290925260409081016000208151606081018352905460ff8116825261010081046001600160401b031693820193909352600160481b9092046001600160a01b0316908201529392505050565b60006108726130b8565b6001600160a01b038316600090815260086020526040902054610894906116a3565b604001519392505050565b6108a76130b8565b6001600160a01b038216600090815260086020526040902054610556906116a3565b601290565b60006108da338461115e565b3360008181526009602090815260408083206001600160a01b038816845290915290205461090f9190859061071d90866116da565b50600192915050565b6109206116ff565b6004546001600160a01b0390811691161461094d5760405162461bcd60e51b81526004016105da906144d4565b61062b8282611703565b336001600160a01b037f000000000000000000000000ac0122e9258a85ba5479db764dc8ef91cab08db0161461094d5760405162461bcd60e51b81526004016105da906143f0565b805160006109ad8484611815565b905060006109bf8585602001516118f6565b90506109cb8585611941565b60ff83166109e3576109de8186846119dd565b610a12565b60ff8316600114156109fa576109de818684611b55565b60405162461bcd60e51b81526004016105da906143ce565b7ffd1941e7a2d0c6f1cc53e8a73873275917cb8a1cf357e47f86f6e681e5d0f0f185856020015185604051610a499392919061402f565b60405180910390a15050505050565b60008251118015610a6a575080518251145b610a865760405162461bcd60e51b81526004016105da906141b7565b60005b8251811015610aca57610ac2838281518110610aa157fe5b6020026020010151838381518110610ab557fe5b6020026020010151610f61565b600101610a89565b505050565b6020820151610adc6130b8565b6001600160a01b038216600090815260086020526040902054610afe906116a3565b9050610b128460a001518260400151611cef565b610b2f8460200151610b248633611d27565b8660a0015186611dd2565b610b376130d8565b610b4683866080015184612066565b9050610b63856020015183876040015188606001516001866122e6565b5050505050565b6001600160a01b03166000908152600860205260409020546001600160601b031690565b610b966116ff565b6004546001600160a01b03908116911614610bc35760405162461bcd60e51b81526004016105da906144d4565b6004546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600480546001600160a01b0319169055565b60008251118015610c1f575080518251145b610c3b5760405162461bcd60e51b81526004016105da906141b7565b60005b8251811015610aca57610c77838281518110610c5657fe5b6020026020010151838381518110610c6a57fe5b6020026020010151610acf565b600101610c3e565b6004546001600160a01b031690565b60078054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561052d5780601f106105025761010080835404028352916020019161052d565b610cf76130b8565b506001600160a01b0316600090815260016020908152604091829020825160608101845290546001600160401b038082168352600160401b8204811693830193909352600160801b90049091169181019190915290565b6000610d5a338461115e565b604080518082018252600881526708aa48664605a62760c31b602080830191909152336000818152600983528481206001600160a01b03891682529092529290205461090f9291869161071d918790611644565b6000610dba338461115e565b610dc26113a4565b610e1c5733610dcf613091565b610dd882611202565b80519091508015610dea575080602001515b15610e1957610e0e600033338888604051806020016040528060008152508761152d565b600192505050610556565b50505b61090f33848461132c565b6000806000835111610e4b5760405162461bcd60e51b81526004016105da90614352565b6000610e5684612430565b905060ff8116610e9c57610e686130ef565b84806020019051810190610e7c9190613c17565b9050610e888187612474565b8160c0015160200151935093505050610eed565b60ff811660011415610ee457610eb061314c565b84806020019051810190610ec49190613b15565b9050610ed08187611d27565b8160a0015160200151935093505050610eed565b60008092509250505b9250929050565b610f008484848461250a565b610f138484600001518560200151612539565b50505050565b336001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f12161461094d5760405162461bcd60e51b81526004016105da906145c8565b60208201516040830151610f736130b8565b6001600160a01b038316600090815260086020526040902054610f95906116a3565b9050610f9f6130b8565b6001600160a01b038316600090815260086020526040902054610fc1906116a3565b9050610fd58660c001518360400151611cef565b610ff28660200151610fe78833612474565b8860c0015188611dd2565b610ffa6130d8565b611009858860a00151856125c3565b90506110278760200151848960400151858b6060015160018761274c565b50505050505050565b6001600160a01b03918216600090815260096020908152604080832093909416825291909152205490565b6110636116ff565b6004546001600160a01b039081169116146110905760405162461bcd60e51b81526004016105da906144d4565b6001600160a01b0381166110b65760405162461bcd60e51b81526004016105da90614254565b6004546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600480546001600160a01b0319166001600160a01b0392909216919091179055565b61111a613091565b61112333611202565b80519091508015611135575080602001515b156111515761114b60013333600087878761152d565b5061062b565b610aca338484600061287b565b6001600160a01b0382161580159061117e57506001600160a01b03811615155b61062b5760405162461bcd60e51b81526004016105da90614494565b6001600160a01b0380841660008181526009602090815260408083209487168084529490915290819020849055517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906111f59085906146d7565b60405180910390a3505050565b61120a613091565b604051630f425aed60e11b81526001600160a01b037f000000000000000000000000a916bc21d2429645585abede4ae00742a16dd1c61690631e84b5da90611256908590600401613edf565b60806040518083038186803b15801561126e57600080fd5b505afa158015611282573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105569190613d1a565b60006112b06113a4565b6112bc575060006105f4565b336001600160a01b037f000000000000000000000000e7cd2797ac6f08b5721c7e7fcc991251df5e388416146112f4575060016105f4565b60018281015190818116141561130e5760019150506105f4565b8360200151801561131d575083515b156105ee5760009150506105f4565b6113346130b8565b6001600160a01b038416600090815260086020526040902054611356906116a3565b90506113606130b8565b6001600160a01b038416600090815260086020526040902054611382906116a3565b905061138c6130d8565b61139c868487858860008761274c565b505050505050565b6000336001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f121614156113df57506001610535565b6113e76128e3565b905090565b678ac7230489e800006001600160601b031681602001516001600160601b0316111561142a5760405162461bcd60e51b81526004016105da906145e8565b6001600160a01b0382161580159061144b57506001600160a01b0382163314155b6114675760405162461bcd60e51b81526004016105da906145a8565b805160ff16600214156115155761147c6130b8565b6001600160a01b03831660009081526008602052604090205461149e906116a3565b905081602001516001600160601b031681600001516001600160601b031610156114da5760405162461bcd60e51b81526004016105da90614234565b60208201518151036001600160601b031681526114f6816129cf565b6001600160a01b0384166000908152600860205260409020555061062b565b60405162461bcd60e51b81526004016105da906142f3565b6115356130d8565b61154082878a612a16565b905061154a613187565b611558828989898989612bb4565b905080600a600061156d8a8660200151611670565b8152602080820192909252604090810160002083518154858501516001600160601b03908116600160a01b9081026001600160a01b039485166001600160a01b03199485161785161785559487015160018501805460608a01519093169096029084169190921617909116179091556080830151805191926115f7926002850192909101906131b7565b505050602082015182516040517fa824f616acaccd5102c745c13e260dc1ca704e95425ebc56e73a6e495d5a3d8992611631928b9261402f565b60405180910390a1505050505050505050565b600081848411156116685760405162461bcd60e51b81526004016105da9190614182565b505050900390565b60008282604051602001611685929190613e64565b60405160208183030381529060405280519060200120905092915050565b6116ab6130b8565b6116b36130b8565b6001600160601b038381168252606084901c16602082015260c09290921c60408301525090565b6000828201838110156107345760405162461bcd60e51b81526004016105da906142bc565b3390565b6001600160a01b0382166117295760405162461bcd60e51b81526004016105da90614608565b80606081901c6001600160601b0381161561174c576001600160601b0381166005555b6117546130b8565b6001600160a01b038516600090815260086020526040902054611776906116a3565b80516020820151919250808501916001600160601b0391821690830190911610156117b35760405162461bcd60e51b81526004016105da90614509565b6001600160601b03811682526117c8826129cf565b6001600160a01b0387166000818152600860205260408082209390935591519091906000805160206148cd8339815191529061180590889061471b565b60405180910390a3505050505050565b600061181f6130b8565b611829848461073b565b6020810151909150611839613091565b61184286611202565b8051909150611855575091506105569050565b611863868660200151612c18565b60405163c2288c7160e01b81527385ae45a05971170b70744292e2f051c0c49cf9099063c2288c719061189c9086908590600401614684565b60206040518083038186803b1580156118b457600080fd5b505af41580156118c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ec9190613db8565b9695505050505050565b600080600a60006119078686611670565b8152602081019190915260400160002080549091506001600160a01b03166107345760405162461bcd60e51b81526004016105da90614472565b6001600160a01b0382166000908152600260209081526040808320600190925291829020915163c112c77160e01b81529091907385ae45a05971170b70744292e2f051c0c49cf9099063c112c771906119a7908790879087908790600390600401613f0d565b60006040518083038186803b1580156119bf57600080fd5b505af41580156119d3573d6000803e3d6000fd5b5050505050505050565b600183015483546001600160a01b0390911690600160a01b90046001600160601b031630611a096130b8565b6001600160a01b038216600090815260086020526040902054611a2b906116a3565b9050611a356130b8565b6001600160a01b038516600090815260086020526040902054611a57906116a3565b9050836001600160601b031682600001516001600160601b03161015611a8f5760405162461bcd60e51b81526004016105da90614509565b81516001600160601b0390859003811683528151808601919081169082161015611acb5760405162461bcd60e51b81526004016105da90614509565b6001600160601b0381168252611ae0836129cf565b6001600160a01b038516600090815260086020526040902055611b02826129cf565b6001600160a01b0380881660008181526008602052604090819020939093559151908a16906000805160206148cd83398151915290611b4290899061471b565b60405180910390a3505050505050505050565b8254600160a01b90046001600160601b0316611b6f6130b8565b6001600160a01b038416600090815260086020526040902054611b91906116a3565b9050611b9b6130d8565b6000611bc48684868a60010160149054906101000a90046001600160601b031689876001612c63565b905030611bcf6130b8565b6001600160a01b038216600090815260086020526040902054611bf1906116a3565b9050826001600160601b031681600001516001600160601b03161015611c295760405162461bcd60e51b81526004016105da90614509565b80518390036001600160601b03168152611c42816129cf565b6001600160a01b038316600090815260086020526040902055611c64856129cf565b6001600160a01b0389166000908152600860205260409081902091909155517fe7b1342ce7f88416536f0a97fd9274421e3718dd094f96e9cefec28f6d7002c190611cb590889060028d019061472f565b60405180910390a160006001600160a01b0316886001600160a01b03166000805160206148cd83398151915288604051611b42919061471b565b60018260400151036001600160401b0316816001600160401b03161461062b5760405162461bcd60e51b81526004016105da90614587565b60006107347fcc8d9dbb126d24ffe7913ca013b4ed4cae09e128eedc4622e4be8004699cf3c27f72079e1ca444dd2cbc0e28bb80962e9f4fbafb3705580b6b9f66186befba0395600186602001518760400151886060015180519060200120611d938a60800151612c6e565b611da18b60a001518b612cd6565b604051602001611db797969594939291906140fa565b60405160208183030381529060405280519060200120612d1d565b81516001600160a01b03163314611dfb5760405162461bcd60e51b81526004016105da906143b0565b6000807f000000000000000000000000a916bc21d2429645585abede4ae00742a16dd1c66001600160a01b0316630296287733886040518363ffffffff1660e01b8152600401611e4c929190613ef3565b604080518083038186803b158015611e6357600080fd5b505afa158015611e77573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e9b9190613ae8565b9150915081611ebc5760405162461bcd60e51b81526004016105da90614370565b6020840151429082906001600160401b03610e10840181169181169182111591600191851610611f0557826001600160401b0316886020015185036001600160401b0316111590505b818015611f0f5750805b611f2b5760405162461bcd60e51b81526004016105da9061454b565b876060015115611f4157611f3e89612d32565b98505b600060018a89604001518a600001518b6020015160405160008152602001604052604051611f729493929190614136565b6020604051602081039080840390855afa158015611f94573d6000803e3d6000fd5b5050506020604051035190508860600151158015611fc457508a6001600160a01b0316816001600160a01b031614155b15612028576001611fd48b612d32565b6040808b01518b516020808e015184516000815290910193849052611ff99493614136565b6020604051602081039080840390855afa15801561201b573d6000803e3d6000fd5b5050506020604051035190505b806001600160a01b03168b6001600160a01b0316146120595760405162461bcd60e51b81526004016105da906144b6565b5050505050505050505050565b61206e6130d8565b6120766130d8565b83516001600160601b03161561211e578351678ac7230489e800006001600160601b0390911611156120ba5760405162461bcd60e51b81526004016105da906145e8565b835183516001600160601b03918216911610156120e95760405162461bcd60e51b81526004016105da90614234565b83518351036001600160601b03908116845284511660208201528060035b9081600481111561211457fe5b9052509050610734565b60208401516001600160601b0316156121e957604080518082018252600081526020868101516001600160601b03169082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f1216916310b4a23d9161219c918991600401613f53565b600060405180830381600087803b1580156121b657600080fd5b505af11580156121ca573d6000803e3d6000fd5b5050506020808601516001600160601b03169083015250806001612107565b60408401516001600160601b0316156122b35760408051808201825260018152858201516001600160601b0316602082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f1216916310b4a23d91612266918991600401613f53565b600060405180830381600087803b15801561228057600080fd5b505af1158015612294573d6000803e3d6000fd5b50505060408501516001600160601b0316602083015250806002612107565b60608401516001600160601b0316156122de5760405162461bcd60e51b81526004016105da906142f3565b949350505050565b60006122f9878787600042876000612c63565b90508215612316576040860180516001016001600160401b031690525b6001600160601b038116156123635785516001600160601b03808316911610156123525760405162461bcd60e51b81526004016105da9061452a565b85518190036001600160601b031686525b61236c866129cf565b6001600160a01b03881660009081526008602052604081209190915582516001600160601b038716919060048111156123a157fe5b905060608160ff16901b82179150606384602001516001600160601b0316901b821791507fe7b1342ce7f88416536f0a97fd9274421e3718dd094f96e9cefec28f6d7002c182876040516123f69291906146e0565b60405180910390a160006001600160a01b0316896001600160a01b03166000805160206148cd83398151915285604051611b42919061471b565b60008082601f8151811061244057fe5b0160209081015160f81c91508110610556578281601f0160ff168151811061246457fe5b016020015160f81c905092915050565b60006107347fcc8d9dbb126d24ffe7913ca013b4ed4cae09e128eedc4622e4be8004699cf3c27f7d1602bfd7297f6720514f0c3dabe77313ce0d25cd5085644e3fcdc5635df66060008660200151876040015188606001518960800151805190602001206124e58b60a00151612c6e565b6124f38c60c001518c612cd6565b604051602001611db79897969594939291906140b5565b6125126130b8565b61251c858561073b565b905061252f858583602001518686612d45565b610b638585611941565b600061254584836118f6565b805460018201549192506001600160601b03600160a01b91829004811692919091041661257330878461132c565b6125808686868585610b63565b7f74c334759f9bfb4a4342d46ed6f4dfb2a3c6b7fe1c7c2288988e8b44bc281b8a8685876040516125b39392919061402f565b60405180910390a1505050505050565b6125cb6130d8565b6125d36130d8565b83516001600160601b031615612651578351678ac7230489e800006001600160601b0390911611156126175760405162461bcd60e51b81526004016105da906145e8565b80602001516001600160601b031683600001516001600160601b031610156120e95760405162461bcd60e51b81526004016105da90614234565b60208401516001600160601b0316156126cf57604080518082018252600081526020868101516001600160601b03169082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f1216916310b4a23d9161219c918991600401613f53565b60408401516001600160601b0316156122de5760408051808201825260018152858201516001600160601b0316602082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f1216916310b4a23d91612266918991600401613f53565b846001600160a01b0316876001600160a01b0316141561277e5760405162461bcd60e51b81526004016105da9061429a565b8115612799576040860180516001016001600160401b031690525b855183906001600160601b03808316911610156127c85760405162461bcd60e51b81526004016105da90614195565b86516001600160601b03908290038116885285518083019190811690821610156128045760405162461bcd60e51b81526004016105da90614450565b6001600160601b0381168652612819886129cf565b6001600160a01b038a1660009081526008602052604090205561283b866129cf565b6001600160a01b0380891660008181526008602052604090819020939093559151908b16906000805160206148cd83398151915290611b4290869061471b565b6001600160a01b0384166128a15760405162461bcd60e51b81526004016105da90614410565b6128a96130b8565b6001600160a01b0385166000908152600860205260409020546128cb906116a3565b90506128d56130d8565b61139c8683878787866122e6565b6000336001600160a01b037f000000000000000000000000ac0122e9258a85ba5479db764dc8ef91cab08db016141561291e57506001610535565b336001600160a01b037f0000000000000000000000003b4da358199060bcc5a527ab60099fb6a908aae916141561295757506001610535565b336001600160a01b037f000000000000000000000000025dbd03ed18b4b8425af51b4d05f5b00e78208a16141561299057506001610535565b336001600160a01b037f000000000000000000000000e7cd2797ac6f08b5721c7e7fcc991251df5e38841614156129c957506001610535565b50600090565b805160208201516040909201516001600160601b0390911660609290921b6bffffffffffffffffffffffff60601b169190911760c09190911b6001600160c01b0319161790565b612a1e6130d8565b6000612a2984612e20565b9050612a336130d8565b5060408051808201825260ff851681526001600160401b0383166020808301919091526001600160a01b038716600090815260029091529190912054601911612a8e5760405162461bcd60e51b81526004016105da90614334565b6040868101516001600160a01b0387811660009081526002602090815284822080546001810182559083528183208751910180548884015160ff1990911660ff9384161768ffffffffffffffff0019166101006001600160401b0392831602179091558651606081018852918b168252421691810191909152918316938201939093529091600390612b208987611670565b815260208082019290925260409081016000208351815493850151949092015160ff1990931660ff9092169190911768ffffffffffffffff0019166101006001600160401b0390941693909302929092177fffffff0000000000000000000000000000000000000000ffffffffffffffffff16600160481b6001600160a01b03909216919091021790555095945050505050565b612bbc613187565b612bc785308561132c565b612bcf613187565b50506040805160a0810182526001600160a01b0380881682526001600160601b038516602083015285169181019190915260006060820152608081018290529695505050505050565b6001600160a01b038216600090815260016020526040902080546001600160401b03838116600160401b9092041614610aca5760405162461bcd60e51b81526004016105da90614569565b509295945050505050565b60007ff1f7d09b7527273cfd8370620354f0cd9a62e7bd3efaafdeca15664a7b3e045c8260000151836020015184604001518560600151604051602001612cb9959493929190614154565b604051602081830303815290604052805190602001209050919050565b60007ff45b7e63030d0a046d85957a672f9a0806d76fff4cc32355d75f64eb6e83d00882846020015185604001518660600151604051602001611685959493929190614067565b60008282604051602001611685929190613ec4565b600081604051602001612cb99190613e93565b612d53858560200151612ebd565b612d5b613091565b612d6486611202565b80519091508015612d8a575080604001516001600160a01b0316336001600160a01b0316145b612da65760405162461bcd60e51b81526004016105da90614629565b6060612db0612f08565b60405163e17e413160e01b81529091507385ae45a05971170b70744292e2f051c0c49cf9099063e17e413190612df4908a90899087908a9088908b90600401613f85565b60006040518083038186803b158015612e0c57600080fd5b505af4158015612059573d6000803e3d6000fd5b6001600160a01b03811660009081526001602081905260408220805490916001600160401b0380831690910191600160401b900416612e825781546fffffffffffffffff00000000000000001916600160401b6001600160401b038316021782555b815467ffffffffffffffff60801b1916600160801b6001600160401b0383169081029190911767ffffffffffffffff19161790915592915050565b6001600160a01b038216600090815260016020526040902080546001600160401b03838116600160801b9092041614610aca5760405162461bcd60e51b81526004016105da90614431565b60408051600580825260c082019092526060918291906020820160a0803683370190505090503081600081518110612f3c57fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f1281600181518110612f8a57fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f000000000000000000000000ac0122e9258a85ba5479db764dc8ef91cab08db081600281518110612fd857fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f0000000000000000000000003b4da358199060bcc5a527ab60099fb6a908aae98160038151811061302657fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f000000000000000000000000025dbd03ed18b4b8425af51b4d05f5b00e78208a8160048151811061307457fe5b6001600160a01b0390921660209283029190910190910152905090565b60408051608081018252600080825260208201819052918101829052606081019190915290565b604080516060810182526000808252602082018190529181019190915290565b604080518082019091526000808252602082015290565b6040518060e00160405280600060ff16815260200160006001600160a01b0316815260200160006001600160a01b03168152602001600081526020016060815260200161313a613091565b8152602001613147613091565b905290565b6040518060c00160405280600060ff16815260200160006001600160a01b03168152602001600081526020016060815260200161313a613091565b6040805160a081018252600080825260208201819052918101829052606080820192909252608081019190915290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106131f857805160ff1916838001178555613225565b82800160010185558215613225579182015b8281111561322557825182559160200191906001019061320a565b50613231929150613235565b5090565b5b808211156132315760008155600101613236565b80356105568161486d565b80516105568161486d565b600082601f830112613270578081fd5b813561328361327e826147f3565b6147cd565b81815291506020808301908481016060808502870183018810156132a657600080fd5b60005b858110156132cd576132bb8984613686565b855293830193918101916001016132a9565b50505050505092915050565b600082601f8301126132e9578081fd5b81356132f761327e82614812565b915080825283602082850101111561330e57600080fd5b8060208401602084013760009082016020015292915050565b600082601f830112613337578081fd5b815161334561327e82614812565b915080825283602082850101111561335c57600080fd5b61336d816020840160208601614841565b5092915050565b60006101808284031215613386578081fd5b61339060c06147cd565b9050813561339d816148a8565b815260208201356133ad8161486d565b60208201526040828101359082015260608201356001600160401b038111156133d557600080fd5b6133e1848285016132d9565b6060830152506133f483608084016134c0565b6080820152613407836101008401613587565b60a082015292915050565b60006101a08284031215613424578081fd5b61342e60e06147cd565b905061343a83836136ca565b8152613449836020840161324a565b602082015261345b836040840161324a565b60408201526060820135606082015260808201356001600160401b0381111561348357600080fd5b61348f848285016132d9565b6080830152506134a28360a084016134c0565b60a08201526134b5836101208401613587565b60c082015292915050565b6000608082840312156134d1578081fd5b6134db60806147cd565b905081356134e8816148b7565b815260208201356134f8816148b7565b6020820152604082013561350b816148b7565b6040820152606082013561351e816148b7565b606082015292915050565b60006080828403121561353a578081fd5b61354460806147cd565b90508151613551816148b7565b81526020820151613561816148b7565b60208201526040820151613574816148b7565b6040820152606082015161351e816148b7565b600060808284031215613598578081fd5b6135a260806147cd565b905081356135af8161486d565b815260208201356135bf81614893565b602082015260408201356135d281614893565b6040820152606082013561351e81614885565b6000608082840312156135f6578081fd5b61360060806147cd565b9050815161360d8161486d565b8152602082015161361d81614893565b6020820152604082015161363081614893565b6040820152606082015161351e81614885565b600060408284031215613654578081fd5b61365e60406147cd565b9050813561366b816148a8565b8152602082013561367b81614893565b602082015292915050565b600060608284031215613697578081fd5b6136a160606147cd565b9050813581526020820135602082015260408201356136bf816148a8565b604082015292915050565b8035610556816148a8565b8051610556816148a8565b6000602082840312156136f1578081fd5b81356107348161486d565b6000806040838503121561370e578081fd5b82356137198161486d565b915060208301356137298161486d565b809150509250929050565b600080600060608486031215613748578081fd5b83356137538161486d565b925060208401356137638161486d565b929592945050506040919091013590565b60008060008060006080868803121561378b578283fd5b85356137968161486d565b945060208601356137a68161486d565b93506040860135925060608601356001600160401b03808211156137c8578283fd5b818801915088601f8301126137db578283fd5b8135818111156137e9578384fd5b8960208285010111156137fa578384fd5b9699959850939650602001949392505050565b6000806040838503121561381f578182fd5b823561382a8161486d565b915060208301356001600160401b03811115613844578182fd5b613850858286016132d9565b9150509250929050565b6000806060838503121561386c578182fd5b82356138778161486d565b91506138868460208501613643565b90509250929050565b60008060008060e085870312156138a4578182fd5b84356138af8161486d565b93506138be8660208701613643565b925060608501356001600160401b038111156138d8578283fd5b6138e4878288016132d9565b9250506138f48660808701613686565b905092959194509250565b6000808284036060811215613912578283fd5b833561391d8161486d565b92506040601f1982011215613930578182fd5b5061393b60406147cd565b6020840135613949816148a8565b81526040840135613959816148b7565b6020820152919491935090915050565b6000806040838503121561397b578182fd5b82356139868161486d565b946020939093013593505050565b600080604083850312156139a6578182fd5b82356139b18161486d565b9150602083013561372981614893565b600080604083850312156139d3578182fd5b82356001600160401b03808211156139e9578384fd5b818501915085601f8301126139fc578384fd5b8135613a0a61327e826147f3565b81815260208082019190858101885b85811015613a4257613a308c8484358b0101613374565b85529382019390820190600101613a19565b50919750880135945050505080821115613a5a578283fd5b5061385085828601613260565b60008060408385031215613a79578182fd5b82356001600160401b0380821115613a8f578384fd5b818501915085601f830112613aa2578384fd5b8135613ab061327e826147f3565b81815260208082019190858101885b85811015613a4257613ad68c8484358b0101613412565b85529382019390820190600101613abf565b60008060408385031215613afa578182fd5b8251613b0581614885565b6020939093015192949293505050565b600060208284031215613b26578081fd5b81516001600160401b0380821115613b3c578283fd5b908301906101808286031215613b50578283fd5b613b5a60c06147cd565b8251613b65816148a8565b8152613b748660208501613255565b602082015260408301516040820152606083015182811115613b94578485fd5b613ba087828601613327565b606083015250613bb38660808501613529565b6080820152613bc68661010085016135e5565b60a082015295945050505050565b60008060808385031215613be6578182fd5b82356001600160401b03811115613bfb578283fd5b613c0785828601613374565b9250506138868460208501613686565b600060208284031215613c28578081fd5b81516001600160401b0380821115613c3e578283fd5b908301906101a08286031215613c52578283fd5b613c5c60e06147cd565b613c6686846136d5565b8152613c758660208501613255565b6020820152613c878660408501613255565b604082015260608301516060820152608083015182811115613ca7578485fd5b613cb387828601613327565b608083015250613cc68660a08501613529565b60a0820152613cd98661012085016135e5565b60c082015295945050505050565b60008060808385031215613cf9578182fd5b82356001600160401b03811115613d0e578283fd5b613c0785828601613412565b600060808284031215613d2b578081fd5b613d3560806147cd565b8251613d4081614885565b81526020830151613d5081614885565b60208201526040830151613d638161486d565b6040820152606083015163ffffffff81168114613d7e578283fd5b60608201529392505050565b60008060408385031215613d9c578182fd5b8235915060208301356001600160401b03811115613844578182fd5b600060208284031215613dc9578081fd5b815161073481614893565b60008151808452613dec816020860160208601614841565b601f01601f19169290920160200192915050565b805160ff1682526020808201516001600160401b0316908301526040908101516001600160a01b0316910152565b8051151582526020808201511515908301526040808201516001600160a01b03169083015260609081015163ffffffff16910152565b60609290921b6bffffffffffffffffffffffff1916825260c01b6001600160c01b0319166014820152601c0190565b7f19457468657265756d205369676e6564204d6573736167653a0a3332000000008152601c810191909152603c0190565b61190160f01b81526002810192909252602282015260420190565b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b03959095168552835160ff16602080870191909152909301516001600160401b031660408501526060840191909152608083015260a082015260c00190565b6001600160a01b03929092168252805160ff1660208084019190915201516001600160601b0316604082015260600190565b600061016060018060a01b03808a16845260206001600160401b038a1681860152613fb3604086018a613e2e565b8260c0860152613fc583860189613dd4565b85810360e08701528751808252828901945090820190855b81811015613ffb578551851683529483019491830191600101613fdd565b50508094505085516101008601528086015161012086015250505060ff604084015116610140830152979650505050505050565b6001600160a01b039390931683526001600160401b0391909116602083015260ff16604082015260600190565b901515815260200190565b9485526001600160a01b039390931660208501526001600160401b0391821660408501521660608301521515608082015260a00190565b9182526001600160401b0316602082015260400190565b97885260ff9690961660208801526001600160a01b039485166040880152929093166060860152608085015260a084019190915260c083015260e08201526101000190565b96875260ff9590951660208701526001600160a01b039390931660408601526060850191909152608084015260a083015260c082015260e00190565b93845260ff9290921660208401526040830152606082015260800190565b9485526001600160601b03938416602086015291831660408501528216606084015216608082015260a00190565b6000602082526107346020830184613dd4565b602080825260089082015267045524332302d31360c41b604082015260600190565b60208082526007908201526622a9219918169b60c91b604082015260600190565b60208082526004908201526350422d3160e01b604082015260600190565b602080825260069082015265445542492d3160d01b604082015260600190565b6020808252600490820152632821169960e11b604082015260600190565b602080825260069082015265445542492d3760d01b604082015260600190565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b60208082526008908201526745524332302d313960c01b604082015260600190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b602080825260069082015265088aa84925a760d31b604082015260600190565b60208082526007908201526645524332302d3760c81b604082015260600190565b60208082526004908201526350422d3360e01b604082015260600190565b60208082526004908201526350422d3760e01b604082015260600190565b60208082526004908201526341422d3360e01b604082015260600190565b60208082526008908201526745524332302d313760c01b604082015260600190565b60208082526004908201526320a1169960e11b604082015260600190565b60208082526008908201526745524332302d313560c01b604082015260600190565b602080825260069082015265222aa124969960d11b604082015260600190565b60208082526007908201526608aa48664605a760cb1b604082015260600190565b602080825260059082015264050422d31360dc1b604082015260600190565b60208082526008908201526722a921991816989960c11b604082015260600190565b60208082526008908201526722a921991816989b60c11b604082015260600190565b60208082526008908201526745524332302d313360c01b604082015260600190565b60208082526004908201526341422d3560e01b604082015260600190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526007908201526622a9219918169960c91b604082015260600190565b60208082526007908201526645524332302d3960c81b604082015260600190565b60208082526004908201526310508b4d60e21b604082015260600190565b60208082526004908201526350422d3960e01b604082015260600190565b60208082526007908201526645524332302d3560c81b604082015260600190565b602080825260069082015265222aa124969b60d11b604082015260600190565b602080825260069082015265445542492d3360d01b604082015260600190565b602080825260069082015265445542492d3560d01b604082015260600190565b60208082526007908201526645524332302d3160c81b604082015260600190565b6020808252600490820152632821169b60e11b604082015260600190565b81516001600160401b039081168252602080840151821690830152604092830151169181019190915260600190565b606081016105568284613e00565b60e081016146928285613e00565b6107346060830184613e2e565b81516001600160601b039081168252602080840151909116908201526040918201516001600160401b03169181019190915260600190565b90815260200190565b6000838252604060208301526122de6040830184613dd4565b6001600160401b0391909116815260200190565b60ff91909116815260200190565b6001600160601b0391909116815260200190565b6000604082016001600160601b038516835260206040818501528285546001808216600081146147665760018114614784576147bf565b60028304607f16865260ff19831660608901526080880193506147bf565b6002830461479281886146d7565b61479b8b614835565b895b838110156147b65781548382015290850190880161479d565b91909101955050505b509198975050505050505050565b6040518181016001600160401b03811182821017156147eb57600080fd5b604052919050565b60006001600160401b03821115614808578081fd5b5060209081020190565b60006001600160401b03821115614827578081fd5b50601f01601f191660200190565b60009081526020902090565b60005b8381101561485c578181015183820152602001614844565b83811115610f135750506000910152565b6001600160a01b038116811461488257600080fd5b50565b801515811461488257600080fd5b6001600160401b038116811461488257600080fd5b60ff8116811461488257600080fd5b6001600160601b038116811461488257600080fdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220fead6c60ce95b63b6730927ed7e3e8d7e2d20fc92757ccec5ef000b845349d3e64736f6c634300060c0033

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

0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a916bc21d2429645585abede4ae00742a16dd1c6000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f12000000000000000000000000ac0122e9258a85ba5479db764dc8ef91cab08db00000000000000000000000003b4da358199060bcc5a527ab60099fb6a908aae9000000000000000000000000025dbd03ed18b4b8425af51b4d05f5b00e78208a000000000000000000000000e7cd2797ac6f08b5721c7e7fcc991251df5e3884

-----Decoded View---------------
Arg [0] : initialSupply (uint256): 0
Arg [1] : optIn (address): 0xA916Bc21D2429645585aBeDe4aE00742A16DD1C6
Arg [2] : purpose (address): 0xb628Bc994e39CE264ECa6f6EE1620909816A9F12
Arg [3] : hodl (address): 0xaC0122e9258a85bA5479DB764DC8eF91caB08db0
Arg [4] : externalAddress1 (address): 0x3b4DA358199060BCc5A527Ab60099Fb6a908AaE9
Arg [5] : externalAddress2 (address): 0x025Dbd03ED18B4b8425af51b4d05F5b00E78208A
Arg [6] : externalAddress3 (address): 0xe7cD2797aC6F08b5721C7e7FCc991251Df5e3884

-----Encoded View---------------
7 Constructor Arguments found :
Arg [0] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [1] : 000000000000000000000000a916bc21d2429645585abede4ae00742a16dd1c6
Arg [2] : 000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f12
Arg [3] : 000000000000000000000000ac0122e9258a85ba5479db764dc8ef91cab08db0
Arg [4] : 0000000000000000000000003b4da358199060bcc5a527ab60099fb6a908aae9
Arg [5] : 000000000000000000000000025dbd03ed18b4b8425af51b4d05f5b00e78208a
Arg [6] : 000000000000000000000000e7cd2797ac6f08b5721c7e7fcc991251df5e3884


Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.