ETH Price: $3,198.24 (+1.35%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Transfer217162202025-01-27 13:40:3514 hrs ago1737985235IN
Frankencoin: FPS Token
0 ETH0.0008948210.43983764
Redeem217091352025-01-26 13:57:5938 hrs ago1737899879IN
Frankencoin: FPS Token
0 ETH0.0003344.13567428
Invest217090862025-01-26 13:48:1138 hrs ago1737899291IN
Frankencoin: FPS Token
0 ETH0.000317063.79924657
Invest217090762025-01-26 13:46:1138 hrs ago1737899171IN
Frankencoin: FPS Token
0 ETH0.000680935.78691312
Invest217085632025-01-26 12:02:4740 hrs ago1737892967IN
Frankencoin: FPS Token
0 ETH0.000505474.47752723
Invest216968152025-01-24 20:41:113 days ago1737751271IN
Frankencoin: FPS Token
0 ETH0.0012341910.48881949
Transfer216928312025-01-24 7:21:113 days ago1737703271IN
Frankencoin: FPS Token
0 ETH0.000253634.92259937
Transfer216896302025-01-23 20:37:354 days ago1737664655IN
Frankencoin: FPS Token
0 ETH0.0024728748.00484105
Transfer216888912025-01-23 18:09:354 days ago1737655775IN
Frankencoin: FPS Token
0 ETH0.0006593712.80023143
Invest216884402025-01-23 16:38:594 days ago1737650339IN
Frankencoin: FPS Token
0 ETH0.0018324816.23387832
Invest216884302025-01-23 16:36:594 days ago1737650219IN
Frankencoin: FPS Token
0 ETH0.0016757214.84522869
Invest216881592025-01-23 15:42:354 days ago1737646955IN
Frankencoin: FPS Token
0 ETH0.001237915.72856101
Invest216864502025-01-23 9:59:234 days ago1737626363IN
Frankencoin: FPS Token
0 ETH0.000546626.5479328
Approve216674652025-01-20 18:22:117 days ago1737397331IN
Frankencoin: FPS Token
0 ETH0.0015873834.32841945
Transfer216674602025-01-20 18:21:117 days ago1737397271IN
Frankencoin: FPS Token
0 ETH0.0034729640.51280956
Transfer216670382025-01-20 16:56:357 days ago1737392195IN
Frankencoin: FPS Token
0 ETH0.0036181242.20620642
Transfer216668342025-01-20 16:15:237 days ago1737389723IN
Frankencoin: FPS Token
0 ETH0.0073328990.6
Transfer216666732025-01-20 15:43:117 days ago1737387791IN
Frankencoin: FPS Token
0 ETH0.0015979934.2
Transfer216644002025-01-20 8:06:357 days ago1737360395IN
Frankencoin: FPS Token
0 ETH0.0020723940.22119377
Invest216606912025-01-19 19:42:118 days ago1737315731IN
Frankencoin: FPS Token
0 ETH0.0020996926.69060497
Invest216540212025-01-18 21:20:239 days ago1737235223IN
Frankencoin: FPS Token
0 ETH0.0020849818.46882853
Redeem216510352025-01-18 11:20:359 days ago1737199235IN
Frankencoin: FPS Token
0 ETH0.0009735612.05469989
Invest216461272025-01-17 18:53:1110 days ago1737139991IN
Frankencoin: FPS Token
0 ETH0.000931119.720221
Invest216457752025-01-17 17:42:4710 days ago1737135767IN
Frankencoin: FPS Token
0 ETH0.0017661515.50615881
Transfer216453912025-01-17 16:25:3510 days ago1737131135IN
Frankencoin: FPS Token
0 ETH0.0017321320.2085667
View all transactions

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block
From
To
184515182023-10-28 21:57:23457 days ago1698530243  Contract Creation0 ETH
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
Equity

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 200 runs

Other Settings:
paris EvmVersion
File 1 of 9 : Equity.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./Frankencoin.sol";
import "./utils/MathUtil.sol";
import "./interface/IReserve.sol";
import "./interface/IERC677Receiver.sol";

/**
 * @title Equity
 * @notice If the Frankencoin system was a bank, this contract would represent the equity on its balance sheet.
 * Like with a corporation, the owners of the equity capital are the shareholders, or in this case the holders
 * of Frankencoin Pool Shares (FPS) tokens. Anyone can mint additional FPS tokens by adding Frankencoins to the
 * reserve pool. Also, FPS tokens can be redeemed for Frankencoins again after a minimum holding period.
 * Furthermore, the FPS shares come with some voting power. Anyone that held at least 3% of the holding-period-
 * weighted reserve pool shares gains veto power and can veto new proposals.
 */
contract Equity is ERC20PermitLight, MathUtil, IReserve {
    /**
     * The VALUATION_FACTOR determines the market cap of the reserve pool shares relative to the equity reserves.
     * The following always holds: Market Cap = Valuation Factor * Equity Reserve = Price * Supply
     *
     * In the absence of profits and losses, the variables grow as follows when FPS tokens are minted:
     *
     * |   Reserve     |   Market Cap  |     Price     |     Supply   |
     * |          1000 |          3000 |             3 |         1000 |
     * |       1000000 |       3000000 |           300 |        10000 |
     * |    1000000000 |    3000000000 |         30000 |       100000 |
     * | 1000000000000 | 3000000000000 |       3000000 |      1000000 |
     *
     * I.e., the supply is proporational to the cubic root of the reserve and the price is proportional to the
     * squared cubic root. When profits accumulate or losses materialize, the reserve, the market cap,
     * and the price are adjusted proportionally, with the supply staying constant. In the absence of an extreme
     * inflation of the Swiss franc, it is unlikely that there will ever be more than ten million FPS.
     */
    uint32 public constant VALUATION_FACTOR = 3;

    uint256 private constant MINIMUM_EQUITY = 1000 * ONE_DEC18;

    /**
     * @notice The quorum in basis points. 100 is 1%.
     */
    uint32 private constant QUORUM = 200;

    /**
     * @notice The number of digits to store the average holding time of share tokens.
     */
    uint8 private constant TIME_RESOLUTION_BITS = 20;

    /**
     * @notice The minimum holding duration. You are not allowed to redeem your pool shares if you held them
     * for less than the minimum holding duration at average. For example, if you have two pool shares on your
     * address, one acquired 5 days ago and one acquired 105 days ago, you cannot redeem them as the average
     * holding duration of your shares is only 55 days < 90 days.
     */
    uint256 public constant MIN_HOLDING_DURATION = 90 days << TIME_RESOLUTION_BITS; // Set to 5 for local testing

    Frankencoin public immutable zchf;

    /**
     * @dev To track the total number of votes we need to know the number of votes at the anchor time and when the
     * anchor time was. This is (hopefully) stored in one 256 bit slot, with the anchor time taking 64 Bits and
     * the total vote count 192 Bits. Given the sub-second resolution of 20 Bits, the implicit assumption is
     * that the timestamp can always be stored in 44 Bits (i.e. it does not exceed half a million years). Further,
     * given 18 decimals (about 60 Bits), this implies that the total supply cannot exceed
     *   192 - 60 - 44 - 20 = 68 Bits
     * Here, we are also save, as 68 Bits would imply more than a trillion outstanding shares. In fact,
     * a limit of about 2**36 shares (that's about 2**96 Bits when taking into account the decimals) is imposed
     * when minting. This means that the maximum supply is billions shares, which is could only be reached in
     * a scenario with hyper inflation, in which case the stablecoin is worthless anyway.
     */
    uint192 private totalVotesAtAnchor; // Total number of votes at the anchor time, see comment on the um
    uint64 private totalVotesAnchorTime; // 44 Bit for the time stamp, 20 Bit sub-second time resolution

    /**
     * @notice Keeping track on who delegated votes to whom.
     * Note that delegation does not mean you cannot vote / veto any more, it just means that the delegate can
     * benefit from your votes when invoking a veto. Circular delegations are valid, do not help when voting.
     */
    mapping(address owner => address delegate) public delegates;

    /**
     * @notice A time stamp in the past such that: votes = balance * (time passed since anchor was set)
     */
    mapping(address owner => uint64 timestamp) private voteAnchor; // 44 bits for time stamp, 20 subsecond resolution

    event Delegation(address indexed from, address indexed to); // indicates a delegation
    event Trade(address who, int amount, uint totPrice, uint newprice); // amount pos or neg for mint or redemption

    constructor(Frankencoin zchf_) ERC20(18) {
        zchf = zchf_;
    }

    function name() external pure override returns (string memory) {
        return "Frankencoin Pool Share";
    }

    function symbol() external pure override returns (string memory) {
        return "FPS";
    }

    /**
     * @notice Returns the price of one FPS in ZCHF with 18 decimals precision.
     */
    function price() public view returns (uint256) {
        uint256 equity = zchf.equity();
        if (equity == 0 || totalSupply() == 0) {
            return ONE_DEC18; // initial price is 1000 ZCHF for the first 1000 FPS
        } else {
            return (VALUATION_FACTOR * zchf.equity() * ONE_DEC18) / totalSupply();
        }
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
        super._beforeTokenTransfer(from, to, amount);
        if (amount > 0) {
            // No need to adjust the sender votes. When they send out 10% of their shares, they also lose 10% of
            // their votes so everything falls nicely into place. Recipient votes should stay the same, but grow
            // faster in the future, requiring an adjustment of the anchor.
            uint256 roundingLoss = _adjustRecipientVoteAnchor(to, amount);
            // The total also must be adjusted and kept accurate by taking into account the rounding error.
            _adjustTotalVotes(from, amount, roundingLoss);
        }
    }

    /**
     * @notice Returns whether the given address is allowed to redeem FPS, which is the
     * case after their average holding duration is larger than the required minimum.
     */
    function canRedeem(address owner) public view returns (bool) {
        return _anchorTime() - voteAnchor[owner] >= MIN_HOLDING_DURATION;
    }

    /**
     * @notice Decrease the total votes anchor when tokens lose their voting power due to being moved
     * @param from      sender
     * @param amount    amount to be sent
     */
    function _adjustTotalVotes(address from, uint256 amount, uint256 roundingLoss) internal {
        uint64 time = _anchorTime();
        uint256 lostVotes = from == address(0x0) ? 0 : (time - voteAnchor[from]) * amount;
        totalVotesAtAnchor = uint192(totalVotes() - roundingLoss - lostVotes);
        totalVotesAnchorTime = time;
    }

    /**
     * @notice the vote anchor of the recipient is moved forward such that the number of calculated
     * votes does not change despite the higher balance.
     * @param to        receiver address
     * @param amount    amount to be received
     * @return the number of votes lost due to rounding errors
     */
    function _adjustRecipientVoteAnchor(address to, uint256 amount) internal returns (uint256) {
        if (to != address(0x0)) {
            uint256 recipientVotes = votes(to); // for example 21 if 7 shares were held for 3 seconds
            uint256 newbalance = balanceOf(to) + amount; // for example 11 if 4 shares are added
            // new example anchor is only 21 / 11 = 1 second in the past
            voteAnchor[to] = uint64(_anchorTime() - recipientVotes / newbalance);
            return recipientVotes % newbalance; // we have lost 21 % 11 = 10 votes
        } else {
            // optimization for burn, vote anchor of null address does not matter
            return 0;
        }
    }

    /**
     * @notice Time stamp with some additional bits for higher resolution.
     */
    function _anchorTime() internal view returns (uint64) {
        return uint64(block.timestamp << TIME_RESOLUTION_BITS);
    }

    /**
     * @notice The relative voting power of the address.
     * @return A percentage with 1e18 being 100%
     */
    function relativeVotes(address holder) external view returns (uint256) {
        return (ONE_DEC18 * votes(holder)) / totalVotes();
    }

    /**
     * @notice The votes of the holder, excluding votes from delegates.
     */
    function votes(address holder) public view returns (uint256) {
        return balanceOf(holder) * (_anchorTime() - voteAnchor[holder]);
    }

    /**
     * @notice How long the holder already held onto their average FPS in seconds.
     */
    function holdingDuration(address holder) public view returns (uint256) {
        return (_anchorTime() - voteAnchor[holder]) >> TIME_RESOLUTION_BITS;
    }

    /**
     * @notice Total number of votes in the system.
     */
    function totalVotes() public view returns (uint256) {
        return totalVotesAtAnchor + totalSupply() * (_anchorTime() - totalVotesAnchorTime);
    }

    /**
     * @notice The number of votes the sender commands when taking the support of the helpers into account.
     * @param sender    The address whose total voting power is of interest
     * @param helpers   An incrementally sorted list of helpers without duplicates and without the sender.
     *                  The call fails if the list contains an address that does not delegate to sender.
     *                  For indirect delegates, i.e. a -> b -> c, both a and b must be included for both to count.
     * @return          The total number of votes of sender at the current point in time.
     */
    function votesDelegated(address sender, address[] calldata helpers) public view returns (uint256) {
        uint256 _votes = votes(sender);
        require(_checkDuplicatesAndSorted(helpers));
        for (uint i = 0; i < helpers.length; i++) {
            address current = helpers[i];
            require(current != sender);
            require(_canVoteFor(sender, current));
            _votes += votes(current);
        }
        return _votes;
    }

    function _checkDuplicatesAndSorted(address[] calldata helpers) internal pure returns (bool ok) {
        if (helpers.length <= 1) {
            return true;
        } else {
            address prevAddress = helpers[0];
            for (uint i = 1; i < helpers.length; i++) {
                if (helpers[i] <= prevAddress) {
                    return false;
                }
                prevAddress = helpers[i];
            }
            return true;
        }
    }

    /**
     * @notice Checks whether the sender address is qualified given a list of helpers that delegated their votes
     * directly or indirectly to the sender. It is the responsiblity of the caller to figure out whether
     * helpes are necessary and to identify them by scanning the blockchain for Delegation events.
     */
    function checkQualified(address sender, address[] calldata helpers) public view override {
        uint256 _votes = votesDelegated(sender, helpers);
        if (_votes * 10000 < QUORUM * totalVotes()) revert NotQualified();
    }

    error NotQualified();

    /**
     * @notice Increases the voting power of the delegate by your number of votes without taking away any voting power
     * from the sender.
     */
    function delegateVoteTo(address delegate) external {
        delegates[msg.sender] = delegate;
        emit Delegation(msg.sender, delegate);
    }

    function _canVoteFor(address delegate, address owner) internal view returns (bool) {
        if (owner == delegate) {
            return true;
        } else if (owner == address(0x0)) {
            return false;
        } else {
            return _canVoteFor(delegate, delegates[owner]);
        }
    }

    /**
     * @notice Since quorum is rather low, it is important to have a way to prevent malicious minority holders
     * from blocking the whole system. This method provides a way for the good guys to team up and destroy
     * the bad guy's votes (at the cost of also reducing their own votes). This mechanism potentially
     * gives full control over the system to whoever has 51% of the votes.
     *
     * Since this is a rather aggressive measure, delegation is not supported. Every holder must call this
     * method on their own.
     * @param targets   The target addresses to remove votes from
     * @param votesToDestroy    The maximum number of votes the caller is willing to sacrifice
     */
    function kamikaze(address[] calldata targets, uint256 votesToDestroy) external {
        uint256 budget = _reduceVotes(msg.sender, votesToDestroy);
        uint256 destroyedVotes = 0;
        for (uint256 i = 0; i < targets.length && destroyedVotes < budget; i++) {
            destroyedVotes += _reduceVotes(targets[i], budget - destroyedVotes);
        }
        require(destroyedVotes > 0); // sanity check
        totalVotesAtAnchor = uint192(totalVotes() - destroyedVotes - budget);
        totalVotesAnchorTime = _anchorTime();
    }

    function _reduceVotes(address target, uint256 amount) internal returns (uint256) {
        uint256 votesBefore = votes(target);
        if (amount >= votesBefore) {
            amount = votesBefore;
            voteAnchor[target] = _anchorTime();
            return votesBefore;
        } else {
            voteAnchor[target] = uint64(_anchorTime() - (votesBefore - amount) / balanceOf(target));
            return votesBefore - votes(target);
        }
    }

    /**
     * @notice Call this method to obtain newly minted pool shares in exchange for Frankencoins.
     * No allowance required (i.e. it is hardcoded in the Frankencoin token contract).
     * Make sure to invest at least 10e-12 * market cap to avoid rounding losses.
     *
     * @dev If equity is close to zero or negative, you need to send enough ZCHF to bring equity back to 1000 ZCHF.
     *
     * @param amount            Frankencoins to invest
     * @param expectedShares    Minimum amount of expected shares for frontrunning protection
     */
    function invest(uint256 amount, uint256 expectedShares) external returns (uint256) {
        zchf.transferFrom(msg.sender, address(this), amount);
        uint256 equity = zchf.equity();
        require(equity >= MINIMUM_EQUITY, "insuf equity"); // ensures that the initial deposit is at least 1000 ZCHF

        uint256 shares = _calculateShares(equity <= amount ? 0 : equity - amount, amount);
        require(shares >= expectedShares);
        _mint(msg.sender, shares);
        emit Trade(msg.sender, int(shares), amount, price());

        // limit the total supply to a reasonable amount to guard against overflows with price and vote calculations
        // the 36 bits are 68 bits for magnitude and 60 bits for precision, as calculated in an above comment
        require(totalSupply() <= type(uint96).max, "total supply exceeded");
        return shares;
    }

    /**
     * @notice Calculate shares received when investing Frankencoins
     * @param investment    ZCHF to be invested
     * @return shares to be received in return
     */
    function calculateShares(uint256 investment) external view returns (uint256) {
        return _calculateShares(zchf.equity(), investment);
    }

    function _calculateShares(uint256 capitalBefore, uint256 investment) internal view returns (uint256) {
        uint256 totalShares = totalSupply();
        uint256 investmentExFees = (investment * 997) / 1000; // remove 0.3% fee
        // Assign 1000 FPS for the initial deposit, calculate the amount otherwise
        uint256 newTotalShares = capitalBefore < MINIMUM_EQUITY || totalShares == 0
            ? totalShares + 1000 * ONE_DEC18
            : _mulD18(totalShares, _cubicRoot(_divD18(capitalBefore + investmentExFees, capitalBefore)));
        return newTotalShares - totalShares;
    }

    /**
     * @notice Redeem the given amount of shares owned by the sender and transfer the proceeds to the target.
     * @return The amount of ZCHF transferred to the target
     */
    function redeem(address target, uint256 shares) external returns (uint256) {
        return _redeemFrom(msg.sender, target, shares);
    }

    /**
     * @notice Like redeem(...), but with an extra parameter to protect against frontrunning.
     * @param expectedProceeds  The minimum acceptable redemption proceeds.
     */
    function redeemExpected(address target, uint256 shares, uint256 expectedProceeds) external returns (uint256) {
        uint256 proceeds = _redeemFrom(msg.sender, target, shares);
        require(proceeds >= expectedProceeds);
        return proceeds;
    }

    /**
     * @notice Redeem FPS based on an allowance from the owner to the caller.
     * See also redeemExpected(...).
     */
    function redeemFrom(
        address owner,
        address target,
        uint256 shares,
        uint256 expectedProceeds
    ) external returns (uint256) {
        _useAllowance(owner, msg.sender, shares);
        uint256 proceeds = _redeemFrom(owner, target, shares);
        require(proceeds >= expectedProceeds);
        return proceeds;
    }

    function _redeemFrom(address owner, address target, uint256 shares) internal returns (uint256) {
        require(canRedeem(owner));
        uint256 proceeds = calculateProceeds(shares);
        _burn(owner, shares);
        zchf.transfer(target, proceeds);
        emit Trade(owner, -int(shares), proceeds, price());
        return proceeds;
    }

    /**
     * @notice Calculate ZCHF received when depositing shares
     * @param shares number of shares we want to exchange for ZCHF,
     *               in dec18 format
     * @return amount of ZCHF received for the shares
     */
    function calculateProceeds(uint256 shares) public view returns (uint256) {
        uint256 totalShares = totalSupply();
        require(shares + ONE_DEC18 < totalShares, "too many shares"); // make sure there is always at least one share
        uint256 capital = zchf.equity();
        uint256 reductionAfterFees = (shares * 997) / 1000;
        uint256 newCapital = _mulD18(capital, _power3(_divD18(totalShares - reductionAfterFees, totalShares)));
        return capital - newCapital;
    }

    /**
     * @notice If there is less than 1000 ZCHF in equity left (maybe even negative), the system is at risk
     * and we should allow qualified FPS holders to restructure the system.
     *
     * Example: there was a devastating loss and equity stands at -1'000'000. Most shareholders have lost hope in the
     * Frankencoin system except for a group of small FPS holders who still believes in it and is willing to provide
     * 2'000'000 ZCHF to save it. These brave souls are essentially donating 1'000'000 to the minter reserve and it
     * would be wrong to force them to share the other million with the passive FPS holders. Instead, they will get
     * the possibility to bootstrap the system again owning 100% of all FPS shares.
     *
     * @param helpers          A list of addresses that delegate to the caller in incremental order
     * @param addressesToWipe  A list of addresses whose FPS will be burned to zero
     */
    function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) external {
        require(zchf.equity() < MINIMUM_EQUITY);
        checkQualified(msg.sender, helpers);
        for (uint256 i = 0; i < addressesToWipe.length; i++) {
            address current = addressesToWipe[i];
            _burn(current, balanceOf(current));
        }
    }
}

File 2 of 9 : Frankencoin.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./utils/ERC20PermitLight.sol";
import "./Equity.sol";
import "./interface/IReserve.sol";
import "./interface/IFrankencoin.sol";

/**
 * @title FrankenCoin
 * @notice The Frankencoin (ZCHF) is an ERC-20 token that is designed to track the value of the Swiss franc.
 * It is not upgradable, but open to arbitrary minting plugins. These are automatically accepted if none of the
 * qualified pool share holders casts a veto, leading to a flexible but conservative governance.
 */
contract Frankencoin is ERC20PermitLight, IFrankencoin {
    /**
     * @notice Minimal fee and application period when suggesting a new minter.
     */
    uint256 public constant MIN_FEE = 1000 * (10 ** 18);
    uint256 public immutable MIN_APPLICATION_PERIOD; // for example 10 days

    /**
     * @notice The contract that holds the reserve.
     */
    IReserve public immutable override reserve;

    /**
     * @notice How much of the reserve belongs to the minters. Everything else belongs to the pool share holders.
     * Stored with 6 additional digits of accuracy so no rounding is necessary when dealing with parts per
     * million (ppm) in reserve calculations.
     */
    uint256 private minterReserveE6;

    /**
     * @notice Map of minters to approval time stamps. If the time stamp is in the past, the minter contract is allowed
     * to mint Frankencoins.
     */
    mapping(address minter => uint256 validityStart) public minters;

    /**
     * @notice List of positions that are allowed to mint and the minter that registered them.
     */
    mapping(address position => address registeringMinter) public positions;

    event MinterApplied(address indexed minter, uint256 applicationPeriod, uint256 applicationFee, string message);
    event MinterDenied(address indexed minter, string message);
    event Loss(address indexed reportingMinter, uint256 amount);
    event Profit(address indexed reportingMinter, uint256 amount);

    error PeriodTooShort();
    error FeeTooLow();
    error AlreadyRegistered();
    error NotMinter();
    error TooLate();

    modifier minterOnly() {
        if (!isMinter(msg.sender) && !isMinter(positions[msg.sender])) revert NotMinter();
        _;
    }

    /**
     * @notice Initiates the Frankencoin with the provided minimum application period for new plugins
     * in seconds, for example 10 days, i.e. 3600*24*10 = 864000
     */
    constructor(uint256 _minApplicationPeriod) ERC20(18) {
        MIN_APPLICATION_PERIOD = _minApplicationPeriod;
        reserve = new Equity(this);
    }

    function name() external pure override returns (string memory) {
        return "Frankencoin";
    }

    function symbol() external pure override returns (string memory) {
        return "ZCHF";
    }

    function initialize(address _minter, string calldata _message) external {
        require(totalSupply() == 0 && reserve.totalSupply() == 0);
        minters[_minter] = block.timestamp;
        emit MinterApplied(_minter, 0, 0, _message);
    }

    /**
     * @notice Publicly accessible method to suggest a new way of minting Frankencoin.
     * @dev The caller has to pay an application fee that is irrevocably lost even if the new minter is vetoed.
     * The caller must assume that someone will veto the new minter unless there is broad consensus that the new minter
     * adds value to the Frankencoin system. Complex proposals should have application periods and applications fees
     * above the minimum. It is assumed that over time, informal ways to coordinate on new minters emerge. The message
     * parameter might be useful for initiating further communication. Maybe it contains a link to a website describing
     * the proposed minter.
     *
     * @param _minter              An address that is given the permission to mint Frankencoins
     * @param _applicationPeriod   The time others have to veto the suggestion, at least MIN_APPLICATION_PERIOD
     * @param _applicationFee      The fee paid by the caller, at least MIN_FEE
     * @param _message             An optional human readable message to everyone watching this contract
     */
    function suggestMinter(
        address _minter,
        uint256 _applicationPeriod,
        uint256 _applicationFee,
        string calldata _message
    ) external override {
        if (_applicationPeriod < MIN_APPLICATION_PERIOD) revert PeriodTooShort();
        if (_applicationFee < MIN_FEE) revert FeeTooLow();
        if (minters[_minter] != 0) revert AlreadyRegistered();
        _collectProfits(address(this), msg.sender, _applicationFee);
        minters[_minter] = block.timestamp + _applicationPeriod;
        emit MinterApplied(_minter, _applicationPeriod, _applicationFee, _message);
    }

    /**
     * @notice Make the system more user friendly by skipping the allowance in many cases.
     * @dev We trust minters and the positions they have created to mint and burn as they please, so
     * giving them arbitrary allowances does not pose an additional risk.
     */
    function _allowance(address owner, address spender) internal view override returns (uint256) {
        uint256 explicit = super._allowance(owner, spender);
        if (explicit > 0) {
            return explicit; // don't waste gas checking minter
        } else if (isMinter(spender) || isMinter(getPositionParent(spender)) || spender == address(reserve)) {
            return INFINITY;
        } else {
            return 0;
        }
    }

    /**
     * @notice The reserve provided by the owners of collateralized positions.
     * @dev The minter reserve can be used to cover losses after the equity holders have been wiped out.
     */
    function minterReserve() public view returns (uint256) {
        return minterReserveE6 / 1000000;
    }

    /**
     * @notice Allows minters to register collateralized debt positions, thereby giving them the ability to mint Frankencoins.
     * @dev It is assumed that the responsible minter that registers the position ensures that the position can be trusted.
     */
    function registerPosition(address _position) external override {
        if (!isMinter(msg.sender)) revert NotMinter();
        positions[_position] = msg.sender;
    }

    /**
     * @notice The amount of equity of the Frankencoin system in ZCHF, owned by the holders of Frankencoin Pool Shares.
     * @dev Note that the equity contract technically holds both the minter reserve as well as the equity, so the minter
     * reserve must be subtracted. All fees and other kind of income is added to the Equity contract and essentially
     * constitutes profits attributable to the pool share holders.
     */
    function equity() public view returns (uint256) {
        uint256 balance = balanceOf(address(reserve));
        uint256 minReserve = minterReserve();
        if (balance <= minReserve) {
            return 0;
        } else {
            return balance - minReserve;
        }
    }

    /**
     * @notice Qualified pool share holders can deny minters during the application period.
     * @dev Calling this function is relatively cheap thanks to the deletion of a storage slot.
     */
    function denyMinter(address _minter, address[] calldata _helpers, string calldata _message) external override {
        if (block.timestamp > minters[_minter]) revert TooLate();
        reserve.checkQualified(msg.sender, _helpers);
        delete minters[_minter];
        emit MinterDenied(_minter, _message);
    }

    /**
     * @notice Mints the provided amount of ZCHF to the target address, automatically forwarding
     * the minting fee and the reserve to the right place.
     */
    function mintWithReserve(
        address _target,
        uint256 _amount,
        uint32 _reservePPM,
        uint32 _feesPPM
    ) external override minterOnly {
        uint256 usableMint = (_amount * (1000_000 - _feesPPM - _reservePPM)) / 1000_000; // rounding down is fine
        _mint(_target, usableMint);
        _mint(address(reserve), _amount - usableMint); // rest goes to equity as reserves or as fees
        minterReserveE6 += _amount * _reservePPM;
        emit Profit(msg.sender, (_feesPPM * _amount) / 1000_000);
    }

    function mint(address _target, uint256 _amount) external override minterOnly {
        _mint(_target, _amount);
    }

    /**
     * Anyone is allowed to burn their ZCHF.
     */
    function burn(uint256 _amount) external {
        _burn(msg.sender, _amount);
    }

    /**
     * @notice Burn someone elses ZCHF.
     */
    function burnFrom(address _owner, uint256 _amount) external override minterOnly {
        _burn(_owner, _amount);
    }

    /**
     * @notice Burn that amount without reclaiming the reserve, but freeing it up and thereby essentially donating it to the
     * pool share holders. This can make sense in combination with 'coverLoss', i.e. when it is the pool share
     * holders that bear the risk and depending on the outcome they make a profit or a loss.
     *
     * Design rule: Minters calling this method are only allowed to so for tokens amounts they previously minted with
     * the same _reservePPM amount.
     *
     * For example, if someone minted 50 ZCHF earlier with a 20% reserve requirement (200000 ppm), they got 40 ZCHF
     * and paid 10 ZCHF into the reserve. Now they want to repay the debt by burning 50 ZCHF. When doing so using this
     * method, 50 ZCHF get burned and on top of that, 10 ZCHF previously assigned to the minter's reserved are
     * reassigned to the pool share holders.
     */
    function burnWithoutReserve(uint256 amount, uint32 reservePPM) public override minterOnly {
        _burn(msg.sender, amount);
        uint256 reserveReduction = amount * reservePPM;
        if (reserveReduction > minterReserveE6) {
            emit Profit(msg.sender, minterReserveE6 / 1000_000);
            minterReserveE6 = 0; // should never happen, but we want robust behavior in case it does
        } else {
            minterReserveE6 -= reserveReduction;
            emit Profit(msg.sender, reserveReduction / 1000_000);
        }
    }

    /**
     * @notice Burns the provided number of tokens plus whatever reserves are associated with that amount given the reserve
     * requirement. The caller is only allowed to use this method for tokens also minted through the caller with the
     * same _reservePPM amount.
     *
     * Example: the calling contract has previously minted 100 ZCHF with a reserve ratio of 20% (i.e. 200000 ppm).
     * Now they have 41 ZCHF that they do not need so they decide to repay that amount. Assuming the reserves are
     * only 90% covered, the call to burnWithReserve will burn the 41 plus 9 from the reserve, reducing the outstanding
     * 'debt' of the caller by 50 ZCHF in total. This total is returned by the method so the caller knows how much less
     * they owe.
     */
    function burnWithReserve(
        uint256 _amountExcludingReserve,
        uint32 _reservePPM
    ) external override minterOnly returns (uint256) {
        uint256 freedAmount = calculateFreedAmount(_amountExcludingReserve, _reservePPM); // 50 in the example
        minterReserveE6 -= freedAmount * _reservePPM; // reduce reserve requirements by original ratio
        _transfer(address(reserve), msg.sender, freedAmount - _amountExcludingReserve); // collect assigned reserve
        _burn(msg.sender, freedAmount); // burn the rest of the freed amount
        return freedAmount;
    }

    /**
     * @notice Burns the target amount taking the tokens to be burned from the payer and the payer's reserve.
     * Only use this method for tokens also minted by the caller with the same _reservePPM.
     *
     * Example: the calling contract has previously minted 100 ZCHF with a reserve ratio of 20% (i.e. 200000 ppm).
     * To burn half of that again, the minter calls burnFrom with a target amount of 50 ZCHF. Assuming that reserves
     * are only 90% covered, this call will deduct 41 ZCHF from the payer's balance and 9 from the reserve, while
     * reducing the minter reserve by 10.
     */
    function burnFromWithReserve(
        address payer,
        uint256 targetTotalBurnAmount,
        uint32 reservePPM
    ) external override minterOnly returns (uint256) {
        uint256 assigned = calculateAssignedReserve(targetTotalBurnAmount, reservePPM);
        _transfer(address(reserve), payer, assigned); // send reserve to owner
        _burn(payer, targetTotalBurnAmount); // and burn the full amount from the owner's address
        minterReserveE6 -= targetTotalBurnAmount * reservePPM; // reduce reserve requirements by original ratio
        return assigned;
    }

    /**
     * @notice Calculates the reserve attributable to someone who minted the given amount with the given reserve requirement.
     * Under normal circumstances, this is just the reserver requirement multiplied by the amount. However, after a
     * severe loss of capital that burned into the minter's reserve, this can also be less than that.
     */
    function calculateAssignedReserve(uint256 mintedAmount, uint32 _reservePPM) public view returns (uint256) {
        uint256 theoreticalReserve = (_reservePPM * mintedAmount) / 1000000;
        uint256 currentReserve = balanceOf(address(reserve));
        uint256 minterReserve_ = minterReserve();
        if (currentReserve < minterReserve_) {
            // not enough reserves, owner has to take a loss
            return (theoreticalReserve * currentReserve) / minterReserve_;
        } else {
            return theoreticalReserve;
        }
    }

    /**
     * @notice Calculate the amount that is freed when returning amountExcludingReserve given a reserve ratio of reservePPM,
     * taking into account potential losses. Example values in the comments.
     */
    function calculateFreedAmount(
        uint256 amountExcludingReserve /* 41 */,
        uint32 reservePPM /* 20% */
    ) public view returns (uint256) {
        uint256 currentReserve = balanceOf(address(reserve)); // 18, 10% below what we should have
        uint256 minterReserve_ = minterReserve(); // 20
        uint256 adjustedReservePPM = currentReserve < minterReserve_
            ? (reservePPM * currentReserve) / minterReserve_
            : reservePPM; // 18%
        return (1000000 * amountExcludingReserve) / (1000000 - adjustedReservePPM); // 41 / (1-18%) = 50
    }

    /**
     * @notice Notify the Frankencoin that a minter lost economic access to some coins. This does not mean that the coins are
     * literally lost. It just means that some ZCHF will likely never be repaid and that in order to bring the system
     * back into balance, the lost amount of ZCHF must be removed from the reserve instead.
     *
     * For example, if a minter printed 1 million ZCHF for a mortgage and the mortgage turned out to be unsound with
     * the house only yielding 800'000 in the subsequent auction, there is a loss of 200'000 that needs to be covered
     * by the reserve.
     */
    function coverLoss(address source, uint256 _amount) external override minterOnly {
        uint256 reserveLeft = balanceOf(address(reserve));
        if (reserveLeft >= _amount) {
            _transfer(address(reserve), source, _amount);
        } else {
            _transfer(address(reserve), source, reserveLeft);
            _mint(source, _amount - reserveLeft);
        }
        emit Loss(source, _amount);
    }

    function collectProfits(address source, uint256 _amount) external override minterOnly {
        _collectProfits(msg.sender, source, _amount);
    }

    function _collectProfits(address minter, address source, uint256 _amount) internal {
        _transfer(source, address(reserve), _amount);
        emit Profit(minter, _amount);
    }

    /**
     * @notice Returns true if the address is an approved minter.
     */
    function isMinter(address _minter) public view override returns (bool) {
        return minters[_minter] != 0 && block.timestamp >= minters[_minter];
    }

    /**
     * @notice Returns the address of the minter that created this position or null if the provided address is unknown.
     */
    function getPositionParent(address _position) public view override returns (address) {
        return positions[_position];
    }
}

File 3 of 9 : IERC20.sol
/**
 * SPDX-License-Identifier: MIT
 *
 * Copyright (c) 2016-2019 zOS Global Limited
 *
 */
pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see `ERC20Detailed`.
 */

interface IERC20 {
    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    function decimals() external view returns (uint8);

    /**
     * @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 always true. Throws error on failure.
     *
     * 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 can change 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.
     *
     * > 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 always true. Throws error on failure.
     *
     * 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 4 of 9 : IERC677Receiver.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC677Receiver {
    
    function onTokenTransfer(address from, uint256 amount, bytes calldata data) external returns (bool);

}

File 5 of 9 : IFrankencoin.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./IReserve.sol";

interface IFrankencoin is IERC20 {
    function suggestMinter(address _minter, uint256 _applicationPeriod, uint256 _applicationFee, string calldata _message) external;

    function registerPosition(address position) external;

    function denyMinter(address minter, address[] calldata helpers, string calldata message) external;

    function reserve() external view returns (IReserve);

    function minterReserve() external view returns (uint256);

    function calculateAssignedReserve(uint256 mintedAmount, uint32 _reservePPM) external view returns (uint256);

    function equity() external view returns (uint256);

    function isMinter(address minter) external view returns (bool);

    function getPositionParent(address position) external view returns (address);

    function mint(address target, uint256 amount) external;

    function mintWithReserve(address target, uint256 amount, uint32 reservePPM, uint32 feePPM) external;

    function burnFrom(address target, uint256 amount) external;

    function burnWithoutReserve(uint256 amountIncludingReserve, uint32 reservePPM) external;

    function burnFromWithReserve(address payer, uint256 targetTotalBurnAmount, uint32 _reservePPM) external returns (uint256);

    function burnWithReserve(uint256 amountExcludingReserve, uint32 reservePPM) external returns (uint256);

    function coverLoss(address source, uint256 amount) external;

    function collectProfits(address source, uint256 _amount) external;
}

File 6 of 9 : IReserve.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IERC20.sol";

interface IReserve is IERC20 {
   function invest(uint256 amount, uint256 expected) external returns (uint256);
   function checkQualified(address sender, address[] calldata helpers) external view;
}

File 7 of 9 : ERC20.sol
// SPDX-License-Identifier: MIT
// Copied and adjusted from OpenZeppelin
// Adjustments:
// - modifications to support ERC-677
// - removed require messages to save space
// - removed unnecessary require statements
// - removed GSN Context
// - upgraded to 0.8 to drop SafeMath
// - let name() and symbol() be implemented by subclass
// - infinite allowance support, with 2^255 and above considered infinite

pragma solidity ^0.8.0;

import "../interface/IERC20.sol";
import "../interface/IERC677Receiver.sol";

/**
 * @dev Implementation of the `IERC20` interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using `_mint`.
 * For a generic mechanism see `ERC20Mintable`.
 *
 * *For a detailed writeup see our guide [How to implement supply
 * mechanisms](https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226).*
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an `Approval` event is emitted on calls to `transferFrom`.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 */

abstract contract ERC20 is IERC20 {
    mapping(address account => uint256 balance) private _balances;

    mapping(address account => mapping(address spender => uint256 allowance)) private _allowances;

    uint256 internal constant INFINITY = (1 << 255);

    uint256 private _totalSupply;

    uint8 public immutable override decimals;

    // Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4139/files#diff-fa792f7d08644eebc519dac2c29b00a54afc4c6a76b9ef3bba56c8401fe674f6
    // Indicates an error related to the current balance of a sender. Used in transfers.
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
    // Indicates a failure with the spender’s allowance. Used in transfers.
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    constructor(uint8 _decimals) {
        decimals = _decimals;
    }

    /**
     * @dev See `IERC20.totalSupply`.
     */
    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See `IERC20.balanceOf`.
     */
    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See `IERC20.transfer`.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }

    /**
     * @dev See `IERC20.allowance`.
     */
    function allowance(address owner, address spender) external view override returns (uint256) {
        return _allowance(owner, spender);
    }

    function _allowance(address owner, address spender) internal view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See `IERC20.approve`.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) external override returns (bool) {
        _approve(msg.sender, spender, value);
        return true;
    }

    /**
     * @dev See `IERC20.transferFrom`.
     *
     * Emits an `Approval` event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of `ERC20`;
     *
     * Requirements:
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `value`.
     * - the caller must have allowance for `sender`'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) {
        _transfer(sender, recipient, amount);
        _useAllowance(sender, msg.sender, amount);
        return true;
    }

    function _useAllowance(address owner, address spender, uint256 amount) internal {
        uint256 currentAllowance = _allowance(owner, spender);
        if (currentAllowance < INFINITY) {
            // Only decrease the allowance if it was not set to 'infinite'
            // Documented in github.com/aktionariat/contracts/blob/master/doc/infiniteallowance.md
            if (currentAllowance < amount) revert ERC20InsufficientAllowance(owner, currentAllowance, amount);
            _approve(owner, spender, currentAllowance - amount);
        }
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to `transfer`, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a `Transfer` event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(recipient != address(0));

        _beforeTokenTransfer(sender, recipient, amount);
        if (_balances[sender] < amount) revert ERC20InsufficientBalance(sender, _balances[sender], amount);
        _balances[sender] -= amount;
        _balances[recipient] += amount;
        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a `Transfer` event with `from` set to the zero address.
     *
     * Requirements
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address recipient, uint256 amount) internal virtual {
        require(recipient != address(0));

        _beforeTokenTransfer(address(0), recipient, amount);

        _totalSupply += amount;
        _balances[recipient] += amount;
        emit Transfer(address(0), recipient, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a `Transfer` event with `to` set to the zero address.
     */
    function _burn(address account, uint256 amount) internal virtual {
        _beforeTokenTransfer(account, address(0), amount);

        _totalSupply -= amount;
        _balances[account] -= amount;
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * This is internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an `Approval` event.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _allowances[owner][spender] = value;
        emit Approval(owner, spender, value);
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be to transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}

File 8 of 9 : ERC20PermitLight.sol
// SPDX-License-Identifier: MIT
// Copied from https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol
// and modified it.

pragma solidity ^0.8.0;

import "./ERC20.sol";

abstract contract ERC20PermitLight is ERC20 {
    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(address account => uint256 nonce) public nonces;

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        unchecked {
            // unchecked to save a little gas with the nonce increment...
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
                                bytes32(0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
            _approve(recoveredAddress, spender, value);
        }
    }

    function DOMAIN_SEPARATOR() public view returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    //keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
                    bytes32(0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218),
                    block.chainid,
                    address(this)
                )
            );
    }
}

File 9 of 9 : MathUtil.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title Functions for share valuation
 */
contract MathUtil {
    uint256 internal constant ONE_DEC18 = 10 ** 18;

    // Let's go for 12 digits of precision (18-6)
    uint256 internal constant THRESH_DEC18 = 10 ** 6;

    /**
     * @notice Cubic root with Halley approximation
     *         Number 1e18 decimal
     * @param _v     number for which we calculate x**(1/3)
     * @return returns _v**(1/3)
     */
    function _cubicRoot(uint256 _v) internal pure returns (uint256) {
        // Good first guess for _v slightly above 1.0, which is often the case in the Frankencoin system
        uint256 x = _v > ONE_DEC18 && _v < 10 ** 19 ? (_v - ONE_DEC18) / 3 + ONE_DEC18 : ONE_DEC18;
        uint256 diff;
        do {
            uint256 powX3 = _mulD18(_mulD18(x, x), x);
            uint256 xnew = x * (powX3 + 2 * _v) / (2 * powX3 + _v);
            diff = xnew > x ? xnew - x : x - xnew;
            x = xnew;
        } while (diff > THRESH_DEC18);
        return x;
    }

    function _mulD18(uint256 _a, uint256 _b) internal pure returns (uint256) {
        return (_a * _b) / ONE_DEC18;
    }

    function _divD18(uint256 _a, uint256 _b) internal pure returns (uint256) {
        return (_a * ONE_DEC18) / _b;
    }

    function _power3(uint256 _x) internal pure returns (uint256) {
        return _mulD18(_mulD18(_x, _x), _x);
    }

    function _min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "paris",
  "metadata": {
    "useLiteralContent": true
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"contract Frankencoin","name":"zchf_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientBalance","type":"error"},{"inputs":[],"name":"NotQualified","type":"error"},{"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":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Delegation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"who","type":"address"},{"indexed":false,"internalType":"int256","name":"amount","type":"int256"},{"indexed":false,"internalType":"uint256","name":"totPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newprice","type":"uint256"}],"name":"Trade","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":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_HOLDING_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VALUATION_FACTOR","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","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":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"calculateProceeds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"investment","type":"uint256"}],"name":"calculateShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"canRedeem","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address[]","name":"helpers","type":"address[]"}],"name":"checkQualified","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegate","type":"address"}],"name":"delegateVoteTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"delegates","outputs":[{"internalType":"address","name":"delegate","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"holder","type":"address"}],"name":"holdingDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"expectedShares","type":"uint256"}],"name":"invest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"targets","type":"address[]"},{"internalType":"uint256","name":"votesToDestroy","type":"uint256"}],"name":"kamikaze","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"price","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"uint256","name":"expectedProceeds","type":"uint256"}],"name":"redeemExpected","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"uint256","name":"expectedProceeds","type":"uint256"}],"name":"redeemFrom","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"holder","type":"address"}],"name":"relativeVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"helpers","type":"address[]"},{"internalType":"address[]","name":"addressesToWipe","type":"address[]"}],"name":"restructureCapTable","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalVotes","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":"sender","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":"holder","type":"address"}],"name":"votes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address[]","name":"helpers","type":"address[]"}],"name":"votesDelegated","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"zchf","outputs":[{"internalType":"contract Frankencoin","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

60c060405234801561001057600080fd5b50604051620022883803806200228883398101604081905261003191610047565b60126080526001600160a01b031660a052610077565b60006020828403121561005957600080fd5b81516001600160a01b038116811461007057600080fd5b9392505050565b60805160a0516121b3620000d5600039600081816104b2015281816107ca0152818161092001528181610aea01528181610b9b01528181610cb601528181610ff20152818161106c01526113740152600061030801526121b36000f3fe608060405234801561001057600080fd5b50600436106101fb5760003560e01c806370a082311161011a578063a9059cbb116100ad578063c9f72b671161007c578063c9f72b67146104d4578063d505accf146104e7578063d87aa643146104fa578063d8bff5a51461050d578063dd62ed3e1461052057600080fd5b8063a9059cbb1461047a578063ad08ce5b1461048d578063b0c2bf06146104a0578063c4d4803a146104ad57600080fd5b806391ac6f99116100e957806391ac6f991461042d57806395d89b41146104405780639823004f1461045f578063a035b1fe1461047257600080fd5b806370a08231146103be5780637ecebe00146103e7578063820710af1461040757806384a7aa0c1461041a57600080fd5b806323b872dd116101925780633644e515116101615780633644e5151461034f5780633ec1619414610357578063587cde1e1461036a5780635895b773146103ab57600080fd5b806323b872dd146102d3578063250f25f4146102e6578063313ce56714610303578063352e3a831461033c57600080fd5b8063151535b9116101ce578063151535b91461029057806318160ddd146102a35780631e9a6950146102ab5780632295abea146102be57600080fd5b806306fdde0314610200578063095ea7b3146102445780630d15fd77146102675780630e89c3701461027d575b600080fd5b6040805180820190915260168152754672616e6b656e636f696e20506f6f6c20536861726560501b60208201525b60405161023b9190611cb2565b60405180910390f35b610257610252366004611d1c565b610533565b604051901515815260200161023b565b61026f61054a565b60405190815260200161023b565b61026f61028b366004611d46565b6105a5565b61025761029e366004611d79565b6105ca565b60025461026f565b61026f6102b9366004611d1c565b610612565b6102d16102cc366004611ddf565b610626565b005b6102576102e1366004611e2a565b6106f5565b6102ee600381565b60405163ffffffff909116815260200161023b565b61032a7f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff909116815260200161023b565b6102d161034a366004611e66565b610717565b61026f61076a565b61026f610365366004611eb8565b6107c3565b610393610378366004611d79565b6005602052600090815260409020546001600160a01b031681565b6040516001600160a01b03909116815260200161023b565b61026f6103b9366004611e66565b610850565b61026f6103cc366004611d79565b6001600160a01b031660009081526020819052604090205490565b61026f6103f5366004611d79565b60036020526000908152604090205481565b6102d1610415366004611ed1565b61090a565b61026f610428366004611d79565b610a2d565b61026f61043b366004611f3c565b610a5c565b60408051808201909152600381526246505360e81b602082015261022e565b6102d161046d366004611d79565b610a8e565b61026f610ae5565b610257610488366004611d1c565b610c40565b61026f61049b366004611eb8565b610c4d565b61026f65076a7000000081565b6103937f000000000000000000000000000000000000000000000000000000000000000081565b61026f6104e2366004611d79565b610d91565b6102d16104f5366004611f7e565b610ddd565b61026f610508366004611ff1565b610fcd565b61026f61051b366004611d79565b611233565b61026f61052e366004612013565b611294565b60006105403384846112c1565b5060015b92915050565b600454600090600160c01b90046001600160401b03164260141b61056e919061205c565b6001600160401b031661058060025490565b61058a919061207c565b6004546105a091906001600160c01b0316612093565b905090565b6000806105b3338686611323565b9050828110156105c257600080fd5b949350505050565b6001600160a01b03811660009081526006602052604081205465076a7000000090610601906001600160401b03164260141b61205c565b6001600160401b0316101592915050565b600061061f338484611323565b9392505050565b6000610632338361144b565b90506000805b848110801561064657508282105b156106a257610684868683818110610660576106606120a6565b90506020020160208101906106759190611d79565b61067f84866120bc565b61144b565b61068e9083612093565b91508061069a816120cf565b915050610638565b50600081116106b057600080fd5b81816106ba61054a565b6106c491906120bc565b6106ce91906120bc565b6001600160c01b0316600160c01b4260141b6001600160401b031602176004555050505050565b600061070284848461153b565b61070d84338461165d565b5060019392505050565b6000610724848484610850565b905061072e61054a565b6107399060c861207c565b6107458261271061207c565b10156107645760405163bcfcdc1160e01b815260040160405180910390fd5b50505050565b604080517f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a794692186020820152469181019190915230606082015260009060800160405160208183030381529060405280519060200120905090565b60006105447f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610826573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061084a91906120e8565b836116dd565b60008061085c85611233565b9050610868848461177b565b61087157600080fd5b60005b83811015610901576000858583818110610890576108906120a6565b90506020020160208101906108a59190611d79565b9050866001600160a01b0316816001600160a01b0316036108c557600080fd5b6108cf8782611854565b6108d857600080fd5b6108e181611233565b6108eb9084612093565b92505080806108f9906120cf565b915050610874565b50949350505050565b61091e670de0b6b3a76400006103e861207c565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561097c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109a091906120e8565b106109aa57600080fd5b6109b5338585610717565b60005b81811015610a265760008383838181106109d4576109d46120a6565b90506020020160208101906109e99190611d79565b9050610a1381610a0e836001600160a01b031660009081526020819052604090205490565b6118ba565b5080610a1e816120cf565b9150506109b8565b5050505050565b6000610a3761054a565b610a4083611233565b610a5290670de0b6b3a764000061207c565b6105449190612117565b6000610a6985338561165d565b6000610a76868686611323565b905082811015610a8557600080fd5b95945050505050565b3360008181526005602052604080822080546001600160a01b0319166001600160a01b03861690811790915590519092917fd000f39f92c3ed77f890f16b6ced1555e0ab2cdf470522d2210de67d8c83d45b91a350565b6000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b46573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b6a91906120e8565b9050801580610b795750600254155b15610b8d57670de0b6b3a764000091505090565b600254670de0b6b3a76400007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bf7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c1b91906120e8565b610c2690600361207c565b610c30919061207c565b610c3a9190612117565b91505090565b600061054033848461153b565b600080610c5960025490565b905080610c6e670de0b6b3a764000085612093565b10610cb25760405162461bcd60e51b815260206004820152600f60248201526e746f6f206d616e792073686172657360881b60448201526064015b60405180910390fd5b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d12573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d3691906120e8565b905060006103e8610d49866103e561207c565b610d539190612117565b90506000610d7b83610d76610d71610d6b86896120bc565b88611950565b61196f565b611980565b9050610d8781846120bc565b9695505050505050565b6001600160a01b038116600090815260066020526040812054601490610dc2906001600160401b031642831b61205c565b6001600160401b0316901c6001600160401b03169050919050565b42841015610e2d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152606401610ca9565b60006001610e3961076a565b6001600160a01b038a811660008181526003602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610f45573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811615801590610f7b5750876001600160a01b0316816001600160a01b0316145b610fb85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610ca9565b610fc38188886112c1565b5050505050505050565b6040516323b872dd60e01b8152336004820152306024820152604481018390526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906323b872dd906064016020604051808303816000875af1158015611043573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611067919061212b565b5060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156110c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110ec91906120e8565b9050611102670de0b6b3a76400006103e861207c565b8110156111405760405162461bcd60e51b815260206004820152600c60248201526b696e7375662065717569747960a01b6044820152606401610ca9565b60006111658583111561115c5761115786846120bc565b61115f565b60005b866116dd565b90508381101561117457600080fd5b61117e3382611995565b7fd98fb7c2b7c7b545387da80b92c08bc5d2a4b922fb74851c3d27ee07ca897bdf3382876111aa610ae5565b604080516001600160a01b039095168552602085019390935291830152606082015260800160405180910390a16bffffffffffffffffffffffff6111ed60025490565b11156105c25760405162461bcd60e51b81526020600482015260156024820152741d1bdd185b081cdd5c1c1b1e48195e18d959591959605a1b6044820152606401610ca9565b6001600160a01b038116600090815260066020526040812054611262906001600160401b03164260141b61205c565b6001600160401b031661128a836001600160a01b031660009081526020819052604090205490565b610544919061207c565b6001600160a01b03808316600090815260016020908152604080832093851683529290529081205461061f565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b600061132e846105ca565b61133757600080fd5b600061134283610c4d565b905061134e85846118ba565b60405163a9059cbb60e01b81526001600160a01b038581166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af11580156113bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113e1919061212b565b507fd98fb7c2b7c7b545387da80b92c08bc5d2a4b922fb74851c3d27ee07ca897bdf8561140d8561214d565b83611416610ae5565b604080516001600160a01b039095168552602085019390935291830152606082015260800160405180910390a1949350505050565b60008061145784611233565b905080831061149f576001600160a01b0384166000908152600660205260409020805467ffffffffffffffff19164260141b6001600160401b03161790559150819050610544565b6001600160a01b0384166000908152602081905260409020546114c284836120bc565b6114cc9190612117565b6114e2906001600160401b034260141b166120bc565b6001600160a01b0385166000908152600660205260409020805467ffffffffffffffff19166001600160401b039290921691909117905561152284611233565b61152c90826120bc565b915050610544565b5092915050565b6001600160a01b03821661154e57600080fd5b611559838383611a36565b6001600160a01b0383166000908152602081905260409020548111156115bc576001600160a01b0383166000818152602081905260409081902054905163391434e360e21b81526004810192909252602482015260448101829052606401610ca9565b6001600160a01b038316600090815260208190526040812080548392906115e49084906120bc565b90915550506001600160a01b03821660009081526020819052604081208054839290611611908490612093565b92505081905550816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161131691815260200190565b6001600160a01b03838116600090815260016020908152604080832093861683529290522054600160ff1b81101561076457818110156116c957604051637dc7a0d960e11b81526001600160a01b03851660048201526024810182905260448101839052606401610ca9565b61076484846116d885856120bc565b6112c1565b6000806116e960025490565b905060006103e86116fc856103e561207c565b6117069190612117565b9050600061171e670de0b6b3a76400006103e861207c565b861080611729575082155b6117515761174c83610d76611747611741868b612093565b8a611950565b611a5a565b61176f565b611765670de0b6b3a76400006103e861207c565b61176f9084612093565b9050610d8783826120bc565b60006001821161178d57506001610544565b6000838360008181106117a2576117a26120a6565b90506020020160208101906117b79190611d79565b905060015b8381101561184957816001600160a01b03168585838181106117e0576117e06120a6565b90506020020160208101906117f59190611d79565b6001600160a01b03161161180e57600092505050610544565b848482818110611820576118206120a6565b90506020020160208101906118359190611d79565b915080611841816120cf565b9150506117bc565b506001915050610544565b6000826001600160a01b0316826001600160a01b03160361187757506001610544565b6001600160a01b03821661188d57506000610544565b6001600160a01b038083166000908152600560205260409020546118b391859116611854565b9050610544565b6118c682600083611a36565b80600260008282546118d891906120bc565b90915550506001600160a01b038216600090815260208190526040812080548392906119059084906120bc565b90915550506040518181526000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906020015b60405180910390a35050565b600081611965670de0b6b3a76400008561207c565b61061f9190612117565b600061054461197e8384611980565b835b6000670de0b6b3a7640000611965838561207c565b6001600160a01b0382166119a857600080fd5b6119b460008383611a36565b80600260008282546119c69190612093565b90915550506001600160a01b038216600090815260208190526040812080548392906119f3908490612093565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001611944565b8015611a55576000611a488383611b4c565b9050610764848383611c0a565b505050565b600080670de0b6b3a764000083118015611a7b5750678ac7230489e8000083105b611a8d57670de0b6b3a7640000611ab6565b670de0b6b3a76400006003611aa282866120bc565b611aac9190612117565b611ab69190612093565b905060005b6000611ad0611aca8485611980565b84611980565b9050600085611ae083600261207c565b611aea9190612093565b611af587600261207c565b611aff9084612093565b611b09908661207c565b611b139190612117565b9050838111611b2b57611b2681856120bc565b611b35565b611b3584826120bc565b909350915050620f42408111611abb575092915050565b60006001600160a01b03831615611c02576000611b6884611233565b9050600083611b8c866001600160a01b031660009081526020819052604090205490565b611b969190612093565b9050611ba28183612117565b611bb8906001600160401b034260141b166120bc565b6001600160a01b0386166000908152600660205260409020805467ffffffffffffffff19166001600160401b0392909216919091179055611bf98183612169565b92505050610544565b506000610544565b4260141b60006001600160a01b03851615611c65576001600160a01b0385166000908152600660205260409020548490611c4d906001600160401b03168461205c565b6001600160401b0316611c60919061207c565b611c68565b60005b90508083611c7461054a565b611c7e91906120bc565b611c8891906120bc565b6001600160401b03909216600160c01b026001600160c01b03929092169190911760045550505050565b600060208083528351808285015260005b81811015611cdf57858101830151858201604001528201611cc3565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114611d1757600080fd5b919050565b60008060408385031215611d2f57600080fd5b611d3883611d00565b946020939093013593505050565b600080600060608486031215611d5b57600080fd5b611d6484611d00565b95602085013595506040909401359392505050565b600060208284031215611d8b57600080fd5b61061f82611d00565b60008083601f840112611da657600080fd5b5081356001600160401b03811115611dbd57600080fd5b6020830191508360208260051b8501011115611dd857600080fd5b9250929050565b600080600060408486031215611df457600080fd5b83356001600160401b03811115611e0a57600080fd5b611e1686828701611d94565b909790965060209590950135949350505050565b600080600060608486031215611e3f57600080fd5b611e4884611d00565b9250611e5660208501611d00565b9150604084013590509250925092565b600080600060408486031215611e7b57600080fd5b611e8484611d00565b925060208401356001600160401b03811115611e9f57600080fd5b611eab86828701611d94565b9497909650939450505050565b600060208284031215611eca57600080fd5b5035919050565b60008060008060408587031215611ee757600080fd5b84356001600160401b0380821115611efe57600080fd5b611f0a88838901611d94565b90965094506020870135915080821115611f2357600080fd5b50611f3087828801611d94565b95989497509550505050565b60008060008060808587031215611f5257600080fd5b611f5b85611d00565b9350611f6960208601611d00565b93969395505050506040820135916060013590565b600080600080600080600060e0888a031215611f9957600080fd5b611fa288611d00565b9650611fb060208901611d00565b95506040880135945060608801359350608088013560ff81168114611fd457600080fd5b9699959850939692959460a0840135945060c09093013592915050565b6000806040838503121561200457600080fd5b50508035926020909101359150565b6000806040838503121561202657600080fd5b61202f83611d00565b915061203d60208401611d00565b90509250929050565b634e487b7160e01b600052601160045260246000fd5b6001600160401b0382811682821603908082111561153457611534612046565b808202811582820484141761054457610544612046565b8082018082111561054457610544612046565b634e487b7160e01b600052603260045260246000fd5b8181038181111561054457610544612046565b6000600182016120e1576120e1612046565b5060010190565b6000602082840312156120fa57600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b60008261212657612126612101565b500490565b60006020828403121561213d57600080fd5b8151801515811461061f57600080fd5b6000600160ff1b820161216257612162612046565b5060000390565b60008261217857612178612101565b50069056fea26469706673582212206818156b0f95da52359612333f8405c6c5bcd583951a291d1688c80aed4d6dd664736f6c63430008140033000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101fb5760003560e01c806370a082311161011a578063a9059cbb116100ad578063c9f72b671161007c578063c9f72b67146104d4578063d505accf146104e7578063d87aa643146104fa578063d8bff5a51461050d578063dd62ed3e1461052057600080fd5b8063a9059cbb1461047a578063ad08ce5b1461048d578063b0c2bf06146104a0578063c4d4803a146104ad57600080fd5b806391ac6f99116100e957806391ac6f991461042d57806395d89b41146104405780639823004f1461045f578063a035b1fe1461047257600080fd5b806370a08231146103be5780637ecebe00146103e7578063820710af1461040757806384a7aa0c1461041a57600080fd5b806323b872dd116101925780633644e515116101615780633644e5151461034f5780633ec1619414610357578063587cde1e1461036a5780635895b773146103ab57600080fd5b806323b872dd146102d3578063250f25f4146102e6578063313ce56714610303578063352e3a831461033c57600080fd5b8063151535b9116101ce578063151535b91461029057806318160ddd146102a35780631e9a6950146102ab5780632295abea146102be57600080fd5b806306fdde0314610200578063095ea7b3146102445780630d15fd77146102675780630e89c3701461027d575b600080fd5b6040805180820190915260168152754672616e6b656e636f696e20506f6f6c20536861726560501b60208201525b60405161023b9190611cb2565b60405180910390f35b610257610252366004611d1c565b610533565b604051901515815260200161023b565b61026f61054a565b60405190815260200161023b565b61026f61028b366004611d46565b6105a5565b61025761029e366004611d79565b6105ca565b60025461026f565b61026f6102b9366004611d1c565b610612565b6102d16102cc366004611ddf565b610626565b005b6102576102e1366004611e2a565b6106f5565b6102ee600381565b60405163ffffffff909116815260200161023b565b61032a7f000000000000000000000000000000000000000000000000000000000000001281565b60405160ff909116815260200161023b565b6102d161034a366004611e66565b610717565b61026f61076a565b61026f610365366004611eb8565b6107c3565b610393610378366004611d79565b6005602052600090815260409020546001600160a01b031681565b6040516001600160a01b03909116815260200161023b565b61026f6103b9366004611e66565b610850565b61026f6103cc366004611d79565b6001600160a01b031660009081526020819052604090205490565b61026f6103f5366004611d79565b60036020526000908152604090205481565b6102d1610415366004611ed1565b61090a565b61026f610428366004611d79565b610a2d565b61026f61043b366004611f3c565b610a5c565b60408051808201909152600381526246505360e81b602082015261022e565b6102d161046d366004611d79565b610a8e565b61026f610ae5565b610257610488366004611d1c565b610c40565b61026f61049b366004611eb8565b610c4d565b61026f65076a7000000081565b6103937f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb81565b61026f6104e2366004611d79565b610d91565b6102d16104f5366004611f7e565b610ddd565b61026f610508366004611ff1565b610fcd565b61026f61051b366004611d79565b611233565b61026f61052e366004612013565b611294565b60006105403384846112c1565b5060015b92915050565b600454600090600160c01b90046001600160401b03164260141b61056e919061205c565b6001600160401b031661058060025490565b61058a919061207c565b6004546105a091906001600160c01b0316612093565b905090565b6000806105b3338686611323565b9050828110156105c257600080fd5b949350505050565b6001600160a01b03811660009081526006602052604081205465076a7000000090610601906001600160401b03164260141b61205c565b6001600160401b0316101592915050565b600061061f338484611323565b9392505050565b6000610632338361144b565b90506000805b848110801561064657508282105b156106a257610684868683818110610660576106606120a6565b90506020020160208101906106759190611d79565b61067f84866120bc565b61144b565b61068e9083612093565b91508061069a816120cf565b915050610638565b50600081116106b057600080fd5b81816106ba61054a565b6106c491906120bc565b6106ce91906120bc565b6001600160c01b0316600160c01b4260141b6001600160401b031602176004555050505050565b600061070284848461153b565b61070d84338461165d565b5060019392505050565b6000610724848484610850565b905061072e61054a565b6107399060c861207c565b6107458261271061207c565b10156107645760405163bcfcdc1160e01b815260040160405180910390fd5b50505050565b604080517f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a794692186020820152469181019190915230606082015260009060800160405160208183030381529060405280519060200120905090565b60006105447f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb6001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610826573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061084a91906120e8565b836116dd565b60008061085c85611233565b9050610868848461177b565b61087157600080fd5b60005b83811015610901576000858583818110610890576108906120a6565b90506020020160208101906108a59190611d79565b9050866001600160a01b0316816001600160a01b0316036108c557600080fd5b6108cf8782611854565b6108d857600080fd5b6108e181611233565b6108eb9084612093565b92505080806108f9906120cf565b915050610874565b50949350505050565b61091e670de0b6b3a76400006103e861207c565b7f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb6001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561097c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109a091906120e8565b106109aa57600080fd5b6109b5338585610717565b60005b81811015610a265760008383838181106109d4576109d46120a6565b90506020020160208101906109e99190611d79565b9050610a1381610a0e836001600160a01b031660009081526020819052604090205490565b6118ba565b5080610a1e816120cf565b9150506109b8565b5050505050565b6000610a3761054a565b610a4083611233565b610a5290670de0b6b3a764000061207c565b6105449190612117565b6000610a6985338561165d565b6000610a76868686611323565b905082811015610a8557600080fd5b95945050505050565b3360008181526005602052604080822080546001600160a01b0319166001600160a01b03861690811790915590519092917fd000f39f92c3ed77f890f16b6ced1555e0ab2cdf470522d2210de67d8c83d45b91a350565b6000807f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb6001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b46573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b6a91906120e8565b9050801580610b795750600254155b15610b8d57670de0b6b3a764000091505090565b600254670de0b6b3a76400007f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb6001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bf7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c1b91906120e8565b610c2690600361207c565b610c30919061207c565b610c3a9190612117565b91505090565b600061054033848461153b565b600080610c5960025490565b905080610c6e670de0b6b3a764000085612093565b10610cb25760405162461bcd60e51b815260206004820152600f60248201526e746f6f206d616e792073686172657360881b60448201526064015b60405180910390fd5b60007f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb6001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d12573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d3691906120e8565b905060006103e8610d49866103e561207c565b610d539190612117565b90506000610d7b83610d76610d71610d6b86896120bc565b88611950565b61196f565b611980565b9050610d8781846120bc565b9695505050505050565b6001600160a01b038116600090815260066020526040812054601490610dc2906001600160401b031642831b61205c565b6001600160401b0316901c6001600160401b03169050919050565b42841015610e2d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152606401610ca9565b60006001610e3961076a565b6001600160a01b038a811660008181526003602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610f45573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811615801590610f7b5750876001600160a01b0316816001600160a01b0316145b610fb85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610ca9565b610fc38188886112c1565b5050505050505050565b6040516323b872dd60e01b8152336004820152306024820152604481018390526000907f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb6001600160a01b0316906323b872dd906064016020604051808303816000875af1158015611043573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611067919061212b565b5060007f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb6001600160a01b03166391a0ac6a6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156110c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110ec91906120e8565b9050611102670de0b6b3a76400006103e861207c565b8110156111405760405162461bcd60e51b815260206004820152600c60248201526b696e7375662065717569747960a01b6044820152606401610ca9565b60006111658583111561115c5761115786846120bc565b61115f565b60005b866116dd565b90508381101561117457600080fd5b61117e3382611995565b7fd98fb7c2b7c7b545387da80b92c08bc5d2a4b922fb74851c3d27ee07ca897bdf3382876111aa610ae5565b604080516001600160a01b039095168552602085019390935291830152606082015260800160405180910390a16bffffffffffffffffffffffff6111ed60025490565b11156105c25760405162461bcd60e51b81526020600482015260156024820152741d1bdd185b081cdd5c1c1b1e48195e18d959591959605a1b6044820152606401610ca9565b6001600160a01b038116600090815260066020526040812054611262906001600160401b03164260141b61205c565b6001600160401b031661128a836001600160a01b031660009081526020819052604090205490565b610544919061207c565b6001600160a01b03808316600090815260016020908152604080832093851683529290529081205461061f565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b600061132e846105ca565b61133757600080fd5b600061134283610c4d565b905061134e85846118ba565b60405163a9059cbb60e01b81526001600160a01b038581166004830152602482018390527f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb169063a9059cbb906044016020604051808303816000875af11580156113bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113e1919061212b565b507fd98fb7c2b7c7b545387da80b92c08bc5d2a4b922fb74851c3d27ee07ca897bdf8561140d8561214d565b83611416610ae5565b604080516001600160a01b039095168552602085019390935291830152606082015260800160405180910390a1949350505050565b60008061145784611233565b905080831061149f576001600160a01b0384166000908152600660205260409020805467ffffffffffffffff19164260141b6001600160401b03161790559150819050610544565b6001600160a01b0384166000908152602081905260409020546114c284836120bc565b6114cc9190612117565b6114e2906001600160401b034260141b166120bc565b6001600160a01b0385166000908152600660205260409020805467ffffffffffffffff19166001600160401b039290921691909117905561152284611233565b61152c90826120bc565b915050610544565b5092915050565b6001600160a01b03821661154e57600080fd5b611559838383611a36565b6001600160a01b0383166000908152602081905260409020548111156115bc576001600160a01b0383166000818152602081905260409081902054905163391434e360e21b81526004810192909252602482015260448101829052606401610ca9565b6001600160a01b038316600090815260208190526040812080548392906115e49084906120bc565b90915550506001600160a01b03821660009081526020819052604081208054839290611611908490612093565b92505081905550816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161131691815260200190565b6001600160a01b03838116600090815260016020908152604080832093861683529290522054600160ff1b81101561076457818110156116c957604051637dc7a0d960e11b81526001600160a01b03851660048201526024810182905260448101839052606401610ca9565b61076484846116d885856120bc565b6112c1565b6000806116e960025490565b905060006103e86116fc856103e561207c565b6117069190612117565b9050600061171e670de0b6b3a76400006103e861207c565b861080611729575082155b6117515761174c83610d76611747611741868b612093565b8a611950565b611a5a565b61176f565b611765670de0b6b3a76400006103e861207c565b61176f9084612093565b9050610d8783826120bc565b60006001821161178d57506001610544565b6000838360008181106117a2576117a26120a6565b90506020020160208101906117b79190611d79565b905060015b8381101561184957816001600160a01b03168585838181106117e0576117e06120a6565b90506020020160208101906117f59190611d79565b6001600160a01b03161161180e57600092505050610544565b848482818110611820576118206120a6565b90506020020160208101906118359190611d79565b915080611841816120cf565b9150506117bc565b506001915050610544565b6000826001600160a01b0316826001600160a01b03160361187757506001610544565b6001600160a01b03821661188d57506000610544565b6001600160a01b038083166000908152600560205260409020546118b391859116611854565b9050610544565b6118c682600083611a36565b80600260008282546118d891906120bc565b90915550506001600160a01b038216600090815260208190526040812080548392906119059084906120bc565b90915550506040518181526000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906020015b60405180910390a35050565b600081611965670de0b6b3a76400008561207c565b61061f9190612117565b600061054461197e8384611980565b835b6000670de0b6b3a7640000611965838561207c565b6001600160a01b0382166119a857600080fd5b6119b460008383611a36565b80600260008282546119c69190612093565b90915550506001600160a01b038216600090815260208190526040812080548392906119f3908490612093565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001611944565b8015611a55576000611a488383611b4c565b9050610764848383611c0a565b505050565b600080670de0b6b3a764000083118015611a7b5750678ac7230489e8000083105b611a8d57670de0b6b3a7640000611ab6565b670de0b6b3a76400006003611aa282866120bc565b611aac9190612117565b611ab69190612093565b905060005b6000611ad0611aca8485611980565b84611980565b9050600085611ae083600261207c565b611aea9190612093565b611af587600261207c565b611aff9084612093565b611b09908661207c565b611b139190612117565b9050838111611b2b57611b2681856120bc565b611b35565b611b3584826120bc565b909350915050620f42408111611abb575092915050565b60006001600160a01b03831615611c02576000611b6884611233565b9050600083611b8c866001600160a01b031660009081526020819052604090205490565b611b969190612093565b9050611ba28183612117565b611bb8906001600160401b034260141b166120bc565b6001600160a01b0386166000908152600660205260409020805467ffffffffffffffff19166001600160401b0392909216919091179055611bf98183612169565b92505050610544565b506000610544565b4260141b60006001600160a01b03851615611c65576001600160a01b0385166000908152600660205260409020548490611c4d906001600160401b03168461205c565b6001600160401b0316611c60919061207c565b611c68565b60005b90508083611c7461054a565b611c7e91906120bc565b611c8891906120bc565b6001600160401b03909216600160c01b026001600160c01b03929092169190911760045550505050565b600060208083528351808285015260005b81811015611cdf57858101830151858201604001528201611cc3565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114611d1757600080fd5b919050565b60008060408385031215611d2f57600080fd5b611d3883611d00565b946020939093013593505050565b600080600060608486031215611d5b57600080fd5b611d6484611d00565b95602085013595506040909401359392505050565b600060208284031215611d8b57600080fd5b61061f82611d00565b60008083601f840112611da657600080fd5b5081356001600160401b03811115611dbd57600080fd5b6020830191508360208260051b8501011115611dd857600080fd5b9250929050565b600080600060408486031215611df457600080fd5b83356001600160401b03811115611e0a57600080fd5b611e1686828701611d94565b909790965060209590950135949350505050565b600080600060608486031215611e3f57600080fd5b611e4884611d00565b9250611e5660208501611d00565b9150604084013590509250925092565b600080600060408486031215611e7b57600080fd5b611e8484611d00565b925060208401356001600160401b03811115611e9f57600080fd5b611eab86828701611d94565b9497909650939450505050565b600060208284031215611eca57600080fd5b5035919050565b60008060008060408587031215611ee757600080fd5b84356001600160401b0380821115611efe57600080fd5b611f0a88838901611d94565b90965094506020870135915080821115611f2357600080fd5b50611f3087828801611d94565b95989497509550505050565b60008060008060808587031215611f5257600080fd5b611f5b85611d00565b9350611f6960208601611d00565b93969395505050506040820135916060013590565b600080600080600080600060e0888a031215611f9957600080fd5b611fa288611d00565b9650611fb060208901611d00565b95506040880135945060608801359350608088013560ff81168114611fd457600080fd5b9699959850939692959460a0840135945060c09093013592915050565b6000806040838503121561200457600080fd5b50508035926020909101359150565b6000806040838503121561202657600080fd5b61202f83611d00565b915061203d60208401611d00565b90509250929050565b634e487b7160e01b600052601160045260246000fd5b6001600160401b0382811682821603908082111561153457611534612046565b808202811582820484141761054457610544612046565b8082018082111561054457610544612046565b634e487b7160e01b600052603260045260246000fd5b8181038181111561054457610544612046565b6000600182016120e1576120e1612046565b5060010190565b6000602082840312156120fa57600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b60008261212657612126612101565b500490565b60006020828403121561213d57600080fd5b8151801515811461061f57600080fd5b6000600160ff1b820161216257612162612046565b5060000390565b60008261217857612178612101565b50069056fea26469706673582212206818156b0f95da52359612333f8405c6c5bcd583951a291d1688c80aed4d6dd664736f6c63430008140033

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

000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb

-----Decoded View---------------
Arg [0] : zchf_ (address): 0xB58E61C3098d85632Df34EecfB899A1Ed80921cB

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

OVERVIEW

Frankencoin Pool Shares (FPS) are shares in the equity reserve pool of the Frankencoin system. Being an FPS holdes is similar to being a shareholder of a bank.

Validator Index Block Amount
View All Withdrawals

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

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