updated open issues

updated open issues

pragma solidity ^0.4.10;
import './BancorEventsDispatcher.sol';
import './TokenChangerInterface.sol';
import './SmartTokenInterface.sol';
import './SafeMath.sol';

/*
    Open issues:
    - all values are placeholders, need to update them with real values
    - verify ERC20 token addresses, transferFrom (must return a boolean flag) and update them with the correct values
    - might move all the ERC20 tokens to a different contract or do it manually to lower the gas cost and make the crowdsale changer more generic
*/

// interfaces

contract EtherToken {
    function deposit() public payable returns (bool success);
    function transfer(address _to, uint256 _value) public returns (bool success);
}

/*
    Crowdsale Changer v0.1

    The crowdsale version of the token changer, allows buying the smart token with ether/other ERC20 tokens
*/
contract CrowdsaleChanger is BancorEventsDispatcher, TokenChangerInterface, SafeMath {
    struct ERC20TokenData {
        uint256 valueN;     // 1 smallest unit in wei (numerator)
        uint256 valueD;     // 1 smallest unit in wei (denominator)
        uint16 limit;       // maximum contribution in percentage out of the total ETH raised so far. 1-1000 (10 == 1%), divided by 1000, 0 to disable
        bool isEnabled;     // is purchase of the smart token enabled with the ERC20 token, can be set by the owner
        bool isSet;         // used to tell if the mapping element is defined
    }

    uint256 public constant DURATION = 30 days;                     // crowdsale duration
    uint256 public constant ETHER_CAP = 1000000 ether;              // maximum ether contribution
    uint256 public constant BITCOIN_SUISSE_ETHER_CAP = 20000 ether; // maximum bitcoin suisse ether contribution
    uint256 public constant INITIAL_PRICE_N = 1;                    // initial price in wei (numerator)
    uint256 public constant INITIAL_PRICE_D = 100;                  // initial price in wei (denominator)
    uint8 public constant RESERVE_RATIO = 21;                       // constant reserve ratio for the new token, used to calculate the current price
    uint8 public constant BENEFICIARY_PERCENTAGE = 30;              // percentage out of the total supply that should be issued to the beneficiary

    // phases
    uint256 public constant PHASE1_MIN_CONTRIBUTION = 0 ether;
    uint256 public constant PHASE2_MIN_CONTRIBUTION = 100000 ether;
    uint256 public constant PHASE3_MIN_CONTRIBUTION = 300000 ether;
    // % of contribution allocated to the reserve
    uint8 public constant PHASE1_RESERVE_ALLOCATION = 30;
    uint8 public constant PHASE2_RESERVE_ALLOCATION = 50;
    uint8 public constant PHASE3_RESERVE_ALLOCATION = 70;

    string public version = '0.1';
    string public changerType = 'crowdsale';

    uint256 public startTime = 0;                           // crowdsale start time (in seconds)
    uint256 public endTime = 0;                             // crowdsale end time (in seconds)
    uint256 public etherContributed = 0;                    // ether contributed so far
    uint256 public tokenReserveBalance = 0;                 // amount of the ether contributed so far that gets into the smart token's reserve, used to calculate the current price
    address public etherToken = 0x0;                        // ether token contract address
    address public beneficiary = 0x0;                       // address to receive all contributed ether
    address public bitcoinSuisse = 0x0;                     // bitcoin suisse address
    SmartTokenInterface public token;                       // smart token governed by the changer
    bool public isActive = false;                           // true if the crowdsale functionality can be used, false if not
    address[] public acceptedTokens;                        // ERC20 standard token addresses
    mapping (address => ERC20TokenData) public tokenData;   // ERC20 token addresses -> ERC20 token data

    // events, can be used to listen to the contract directly, as opposed to through the events contract
    event Change(address indexed _fromToken, address indexed _toToken, address indexed _trader, uint256 _amount, uint256 _return);

    /*
        _token          smart token governed by the changer
        _etherToken     ether token contract address
        _startTime      crowdsale start time
        _beneficiary    address to receive all contributed ether
        _bitcoinSuisse  bitcoin suisse address
        _events         optional, address of a bancor events contract
    */
    function CrowdsaleChanger(address _token, address _etherToken, uint256 _startTime, address _beneficiary, address _bitcoinSuisse, address _events)
        BancorEventsDispatcher(_events)
        validAddress(_token)
        validAddress(_etherToken)
        validAddress(_beneficiary)
        validAddress(_bitcoinSuisse)
        earlierThan(_startTime)
    {
        token = SmartTokenInterface(_token);
        etherToken = _etherToken;
        startTime = _startTime;
        endTime = startTime + DURATION;
        beneficiary = _beneficiary;
        bitcoinSuisse = _bitcoinSuisse;

        addERC20Token(_etherToken, 0, 1, 1); // Ether

        addERC20Token(0xa74476443119A942dE498590Fe1f2454d7D4aC0d, 20, 1, 1); // Golem
        addERC20Token(0x48c80F1f4D53D5951e5D5438B54Cba84f29F32a5, 20, 1, 1); // Augur

        addERC20Token(0x6810e776880C02933D47DB1b9fc05908e5386b96, 10, 1, 1); // Gnosis
        addERC20Token(0xaeC2E87E0A235266D9C5ADc9DEb4b2E29b54D009, 10, 1, 1); // SingularDTV
        addERC20Token(0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A, 10, 1, 1); // DigixDAO

        addERC20Token(0x4993CB95c7443bdC06155c5f5688Be9D8f6999a5, 5, 1, 1); // ROUND
        addERC20Token(0x607F4C5BB672230e8672085532f7e901544a7375, 5, 1, 1); // iEx.ec
        addERC20Token(0x888666CA69E0f178DED6D75b5726Cee99A87D698, 5, 1, 1); // ICONOMI
        addERC20Token(0xAf30D2a7E90d7DC361c8C4585e9BB7D2F6f15bc7, 5, 1, 1); // FirstBlood
        addERC20Token(0xBEB9eF514a379B997e0798FDcC901Ee474B6D9A1, 5, 1, 1); // Melon
        addERC20Token(0x667088b212ce3d06a1b553a7221E1fD19000d9aF, 5, 1, 1); // Wings
    }

    // validates an address - currently only checks that it isn't null
    modifier validAddress(address _address) {
        require(_address != 0x0);
        _;
    }

    // validates an ERC20 token address - verifies that the address belongs to one of the ERC20 tokens
    modifier validERC20Token(address _address) {
        require(tokenData[_address].isSet);
        _;
    }

    // validates a token address - verifies that the address belongs to one of the changable tokens
    modifier validToken(address _address) {
        require(_address == address(token) || tokenData[_address].isSet);
        _;
    }

    // validates ERC20 token limit
    modifier validERC20TokenLimit(uint16 _limit) {
        require(_limit <= 1000);
        _;
    }

    // ensures that the contract is active
    modifier active() {
        assert(isActive);
        _;
    }

    // ensures that the contract is not active
    modifier inactive() {
        assert(!isActive);
        _;
    }

    // ensures that it's earlier than the given time
    modifier earlierThan(uint256 _time) {
        assert(now < _time);
        _;
    }

    // ensures that it's later than the given time
    modifier laterThan(uint256 _time) {
        assert(now > _time);
        _;
    }

    // ensures that we didn't reach the ether cap
    modifier etherCapNotReached() {
        assert(etherContributed < ETHER_CAP);
        _;
    }

    // ensures that the sender is bitcoin suisse
    modifier bitcoinSuisseOnly() {
        assert(msg.sender == bitcoinSuisse);
        _;
    }

    // ensures that we didn't reach the ether cap
    modifier bitcoinSuisseEtherCapNotReached() {
        assert(etherContributed < BITCOIN_SUISSE_ETHER_CAP);
        _;
    }

    /*
        returns the number of changeable tokens supported by the contract
        note that the number of changable tokens is the number of ERC20 tokens plus the smart token
    */
    function changeableTokenCount() public constant returns (uint16 count) {
        return uint16(acceptedTokens.length + 1);
    }

    /*
        given a changable token index, returns the changable token contract address
    */
    function changeableToken(uint16 _tokenIndex) public constant returns (address tokenAddress) {
        if (_tokenIndex == 0)
            return token;
        return acceptedTokens[_tokenIndex - 1];
    }

    /*
        defines a new ERC20 token
        can only be called by the changer owner while the changer is inactive

        _token      address of the ERC20 token
        _limit      maximum contribution in percentage out of the total ETH raised so far. 1-1000 (10 == 1%), divided by 1000
        _valueN     1 smallest unit in wei (numerator)
        _valueD     1 smallest unit in wei (denominator)
    */
    function addERC20Token(address _token, uint16 _limit, uint256 _valueN, uint256 _valueD)
        public
        ownerOnly
        inactive
        validAddress(_token)
        validERC20TokenLimit(_limit)
        returns (bool success)
    {
        require(_token != address(this) && _token != address(token) && !tokenData[_token].isSet && _valueN != 0 && _valueD != 0); // validate input

        tokenData[_token].limit = _limit;
        tokenData[_token].valueN = _valueN;
        tokenData[_token].valueD = _valueD;
        tokenData[_token].isEnabled = true;
        tokenData[_token].isSet = true;
        acceptedTokens.push(_token);
        return true;
    }

    /*
        updates one of the ERC20 tokens
        can only be called by the changer owner

        _erc20Token     address of the ERC20 token
        _limit          maximum contribution in percentage out of the total ETH raised so far. 1-1000 (10 == 1%), divided by 1000
        _valueN         1 smallest unit in wei (numerator)
        _valueD         1 smallest unit in wei (denominator)
    */
    function updateERC20Token(address _erc20Token, uint16 _limit, uint256 _valueN, uint256 _valueD)
        public
        ownerOnly
        validERC20Token(_erc20Token)
        validERC20TokenLimit(_limit)
        returns (bool success)
    {
        require(_valueN != 0 && _valueD != 0); // validate input
        ERC20TokenData data = tokenData[_erc20Token];
        data.limit = _limit;
        data.valueN = _valueN;
        data.valueD = _valueD;
        return true;
    }

    /*
        disables purchasing with the given ERC20 token in case the token got compromised
        can only be called by the changer owner

        _erc20Token     ERC20 token contract address
        _disable        true to disable the token, false to re-enable it
    */
    function disableERC20Token(address _erc20Token, bool _disable)
        public
        ownerOnly
        validERC20Token(_erc20Token)
    {
        tokenData[_erc20Token].isEnabled = !_disable;
    }

    /*
        withdraws tokens from one of the ERC20 tokens and sends them to an account
        can only be called by the changer owner
        this is a mechanism to allow the owner to return tokens that were sent directly to this contract by mistake

        _erc20Token     ERC20 token contract address
        _to             account to receive the new amount
        _amount         amount to withdraw (in the ERC20 token)
    */
    function withdraw(address _erc20Token, address _to, uint256 _amount)
        public
        ownerOnly
        validERC20Token(_erc20Token)
        validAddress(_to)
        returns (bool success)
    {
        require(_to != address(this) && _to != address(token) && _amount != 0); // validate input

        ERC20TokenInterface erc20Token = ERC20TokenInterface(_erc20Token);
        assert(erc20Token.transfer(_to, _amount));
        return true;
    }

    /*
        sets the smart token's changer address to a different one instead of the current contract address
        can only be called by the owner
        the changer can be set to null to transfer ownership from the changer to the original smart token's owner

        _changer    new changer contract address (can also be set to 0x0 to remove the current changer)
    */
    function setTokenChanger(address _changer)
        public
        ownerOnly
        validAddress(_changer)
        returns (bool success)
    {
        require(_changer != address(this) && _changer != address(token)); // validate input
        return token.setChanger(_changer);
    }

    /*
        activates/deactivates the contract logic
        can only be called by the owner
        activating the change logic also disables transfers in the smart token
        this can also be used as an emergency stop

        _activate   true to activate the contract, false to deactivate it
    */
    function activate(bool _activate) public ownerOnly returns (bool success)
    {
        assert(!_activate || acceptedTokens.length > 0); // validate state
        if (_activate)
            token.disableTransfers(true);

        isActive = _activate;
        return true;
    }

    /*
        returns the expected return for changing a specific amount of _fromToken to _toToken

        _fromToken  token to change from
        _toToken    token to change to
        _amount     amount to change, in fromToken
    */
    function getReturn(address _fromToken, address _toToken, uint256 _amount) public constant returns (uint256 amount) {
        require(_toToken == address(token)); // validate input
        return getPurchaseReturn(_fromToken, _amount);
    }

    /*
        returns the expected return for buying the token for an ERC20 token

        _erc20Token     ERC20 token contract address
        _depositAmount  amount to deposit (in the ERC20 token)
    */
    function getPurchaseReturn(address _erc20Token, uint256 _depositAmount)
        public
        constant
        active
        etherCapNotReached
        validERC20Token(_erc20Token)
        returns (uint256 amount)
    {
        ERC20TokenData data = tokenData[_erc20Token];
        require(data.isEnabled && _depositAmount != 0); // validate input

        uint256 depositEthValue = safeMul(_depositAmount, data.valueN) / data.valueD;
        if (depositEthValue == 0)
            return 0;

        // check ether cap
        require(safeAdd(etherContributed, depositEthValue) <= ETHER_CAP);

        // check limit
        if (data.limit != 0) {
            ERC20TokenInterface erc20Token = ERC20TokenInterface(_erc20Token);
            uint256 balance = erc20Token.balanceOf(beneficiary);
            uint256 balanceEthValue = safeMul(balance, data.valueN) / data.valueD;  // ether value of the ERC20 token beneficiary balance
            uint256 limit = safeMul(etherContributed, data.limit) / 1000; // current limit of the ERC20 token
            require(safeAdd(balanceEthValue, depositEthValue) <= limit);
        }

        // first contribution can't use the simplified bancor formula, so using the predefined initial price instead
        if (tokenReserveBalance == 0 || token.totalSupply() == 0)
            return safeMul(depositEthValue, INITIAL_PRICE_D) / INITIAL_PRICE_N;

        // using the simplified bancor formula -
        // Price = Reserve / (Supply * CRR)
        uint256 temp = safeMul(depositEthValue, token.totalSupply());
        temp = safeMul(temp, RESERVE_RATIO);
        return temp / 100 / tokenReserveBalance;
    }

    /*
        changes a specific amount of _fromToken to _toToken

        _fromToken  token to change from
        _toToken    token to change to
        _amount     amount to change, in fromToken
        _minReturn  if the change results in an amount smaller than the minimum return, it is cancelled
    */
    function change(address _fromToken, address _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256 amount) {
        require(_toToken == address(token)); // validate input
        return buyERC20(_fromToken, _amount, _minReturn);
    }

    /*
        buys the token with one of the ERC20 tokens
        requires the called to approve and allowance for the crowdsale contract

        _erc20Token     ERC20 token contract address
        _depositAmount  amount to deposit (in the ERC20 token)
        _minReturn      if the change results in an amount smaller than the minimum return, it is cancelled
    */
    function buyERC20(address _erc20Token, uint256 _depositAmount, uint256 _minReturn)
        public
        laterThan(startTime)
        earlierThan(endTime)
        returns (uint256 amount)
    {
        amount = getPurchaseReturn(_erc20Token, _depositAmount);
        assert(amount != 0 && amount >= _minReturn); // ensure the trade gives something in return and meets the minimum requested amount

        ERC20TokenInterface erc20Token = ERC20TokenInterface(_erc20Token);
        assert(erc20Token.transferFrom(msg.sender, beneficiary, _depositAmount)); // transfer _depositAmount funds from the caller in the ERC20 token

        ERC20TokenData data = tokenData[_erc20Token];
        uint256 depositEthValue = safeMul(_depositAmount, data.valueN) / data.valueD;
        handleContribution(msg.sender, depositEthValue, amount);
        dispatchChange(_erc20Token, token, msg.sender, _depositAmount, amount);
        return amount;
    }

    /*
        buys the token with ETH
    */
    function buyETH()
        public
        payable
        laterThan(startTime)
        earlierThan(endTime)
        returns (uint256 amount)
    {
        amount = handleETHDeposit(msg.sender, msg.value);
        dispatchChange(etherToken, token, msg.sender, msg.value, amount);
        return amount;
    }

    /*
        buys the token with BTCs (Bitcoin Suisse only)
        can only be called before the crowdsale started

        _contributor    account that should receive the new tokens
    */
    function buyBitcoinSuisse(address _contributor)
        public
        payable
        bitcoinSuisseOnly
        bitcoinSuisseEtherCapNotReached
        earlierThan(startTime)
        returns (uint256 amount)
    {
        require(safeAdd(etherContributed, msg.value) <= BITCOIN_SUISSE_ETHER_CAP); // validate input
        amount = handleETHDeposit(_contributor, msg.value);
        dispatchChange(etherToken, token, msg.sender, msg.value, amount);
        return amount;
    }

    /*
        handles direct ETH deposits (as opposed to ERC20 contributions)

        _contributor    account that should receive the new tokens
        _depositAmount  amount contributed by the account, in wei
    */
    function handleETHDeposit(address _contributor, uint256 _depositAmount) private returns (uint256 amount) {
        require(_depositAmount > 0); // validate input
        amount = getPurchaseReturn(etherToken, _depositAmount);
        assert(amount != 0); // ensure the trade gives something in return

        EtherToken ethToken = EtherToken(etherToken);
        assert(ethToken.deposit.value(_depositAmount)()); // transfer the ether to the ether contract
        assert(ethToken.transfer(beneficiary, _depositAmount)); // transfer the ther to the beneficiary account
        handleContribution(_contributor, _depositAmount, amount);
        return amount;
    }

    /*
        handles the generic part of the contribution - regardless of the type of contribution
        assumes that the contribution was already added to the beneficiary account in the different tokens
        updates the reserve balance, total contributed amount and issues new tokens to the contributor and to the beneficiary

        _contributor        account that should the new tokens
        _depositEthValue    amount contributed by the account, in wei
        _return             amount to be issued to the contributor, in the smart token
    */
    function handleContribution(address _contributor, uint256 _depositEthValue, uint256 _return) private {
        // update the reserve balance
        uint8 reserveAllocationPercentage;
        if (etherContributed >= PHASE3_MIN_CONTRIBUTION)
            reserveAllocationPercentage = PHASE3_RESERVE_ALLOCATION;
        else if (etherContributed >= PHASE2_MIN_CONTRIBUTION)
            reserveAllocationPercentage = PHASE2_RESERVE_ALLOCATION;
        else if (etherContributed >= PHASE1_MIN_CONTRIBUTION)
            reserveAllocationPercentage = PHASE1_RESERVE_ALLOCATION;

        uint256 addToReserve = safeMul(_depositEthValue, reserveAllocationPercentage) / 100;
        tokenReserveBalance = safeAdd(tokenReserveBalance, addToReserve);

        // update the total contribution amount
        etherContributed = safeAdd(etherContributed, _depositEthValue);
        // issue new funds to the contributor in the smart token
        assert(token.issue(_contributor, _return));

        // issued tokens to the beneficiary
        uint256 amount = safeMul(100, _return) / BENEFICIARY_PERCENTAGE;
        if (amount == 0)
            return;

        assert(token.issue(beneficiary, amount));
    }

    // utility

    function dispatchChange(address _fromToken, address _toToken, address _trader, uint256 _amount, uint256 _return) private {
        Change(_fromToken, _toToken, _trader, _amount, _return);

        if (address(events) != 0x0)
            events.tokenChange(_fromToken, _toToken, _trader, _amount, _return);
    }

    // fallback
    function() payable {
        buyETH();
    }
}

pragma solidity ^0.4.10;
import './BancorEventsDispatcher.sol';
import './TokenChangerInterface.sol';
import './SmartTokenInterface.sol';
import './SafeMath.sol';

/*
    Open issues:
    - all values are placeholders, need to update them with real values
    - verify ERC20 token addresses, transferFrom (must return a boolean flag) and update them with the correct values
*/

// interfaces

contract EtherToken {
    function deposit() public payable returns (bool success);
    function transfer(address _to, uint256 _value) public returns (bool success);
}

/*
    Crowdsale Changer v0.1

    The crowdsale version of the token changer, allows buying the smart token with ether/other ERC20 tokens
*/
contract CrowdsaleChanger is BancorEventsDispatcher, TokenChangerInterface, SafeMath {
    struct ERC20TokenData {
        uint256 valueN;     // 1 smallest unit in wei (numerator)
        uint256 valueD;     // 1 smallest unit in wei (denominator)
        uint16 limit;       // maximum contribution in percentage out of the total ETH raised so far. 1-1000 (10 == 1%), divided by 1000, 0 to disable
        bool isEnabled;     // is purchase of the smart token enabled with the ERC20 token, can be set by the owner
        bool isSet;         // used to tell if the mapping element is defined
    }

    uint256 public constant DURATION = 30 days;                     // crowdsale duration
    uint256 public constant ETHER_CAP = 1000000 ether;              // maximum ether contribution
    uint256 public constant BITCOIN_SUISSE_ETHER_CAP = 20000 ether; // maximum bitcoin suisse ether contribution
    uint256 public constant INITIAL_PRICE_N = 1;                    // initial price in wei (numerator)
    uint256 public constant INITIAL_PRICE_D = 100;                  // initial price in wei (denominator)
    uint8 public constant RESERVE_RATIO = 21;                       // constant reserve ratio for the new token, used to calculate the current price
    uint8 public constant BENEFICIARY_PERCENTAGE = 30;              // percentage out of the total supply that should be issued to the beneficiary

    // phases
    uint256 public constant PHASE1_MIN_CONTRIBUTION = 0 ether;
    uint256 public constant PHASE2_MIN_CONTRIBUTION = 100000 ether;
    uint256 public constant PHASE3_MIN_CONTRIBUTION = 300000 ether;
    // % of contribution allocated to the reserve
    uint8 public constant PHASE1_RESERVE_ALLOCATION = 30;
    uint8 public constant PHASE2_RESERVE_ALLOCATION = 50;
    uint8 public constant PHASE3_RESERVE_ALLOCATION = 70;

    string public version = '0.1';
    string public changerType = 'crowdsale';

    uint256 public startTime = 0;                           // crowdsale start time (in seconds)
    uint256 public endTime = 0;                             // crowdsale end time (in seconds)
    uint256 public etherContributed = 0;                    // ether contributed so far
    uint256 public tokenReserveBalance = 0;                 // amount of the ether contributed so far that gets into the smart token's reserve, used to calculate the current price
    address public etherToken = 0x0;                        // ether token contract address
    address public beneficiary = 0x0;                       // address to receive all contributed ether
    address public bitcoinSuisse = 0x0;                     // bitcoin suisse address
    SmartTokenInterface public token;                       // smart token governed by the changer
    bool public isActive = false;                           // true if the crowdsale functionality can be used, false if not
    address[] public acceptedTokens;                        // ERC20 standard token addresses
    mapping (address => ERC20TokenData) public tokenData;   // ERC20 token addresses -> ERC20 token data

    // events, can be used to listen to the contract directly, as opposed to through the events contract
    event Change(address indexed _fromToken, address indexed _toToken, address indexed _trader, uint256 _amount, uint256 _return);

    /*
        _token          smart token governed by the changer
        _etherToken     ether token contract address
        _startTime      crowdsale start time
        _beneficiary    address to receive all contributed ether
        _bitcoinSuisse  bitcoin suisse address
        _events         optional, address of a bancor events contract
    */
    function CrowdsaleChanger(address _token, address _etherToken, uint256 _startTime, address _beneficiary, address _bitcoinSuisse, address _events)
        BancorEventsDispatcher(_events)
        validAddress(_token)
        validAddress(_etherToken)
        validAddress(_beneficiary)
        validAddress(_bitcoinSuisse)
        earlierThan(_startTime)
    {
        token = SmartTokenInterface(_token);
        etherToken = _etherToken;
        startTime = _startTime;
        endTime = startTime + DURATION;
        beneficiary = _beneficiary;
        bitcoinSuisse = _bitcoinSuisse;

        addERC20Token(_etherToken, 0, 1, 1); // Ether

        addERC20Token(0xa74476443119A942dE498590Fe1f2454d7D4aC0d, 20, 1, 1); // Golem
        addERC20Token(0x48c80F1f4D53D5951e5D5438B54Cba84f29F32a5, 20, 1, 1); // Augur

        addERC20Token(0x6810e776880C02933D47DB1b9fc05908e5386b96, 10, 1, 1); // Gnosis
        addERC20Token(0xaeC2E87E0A235266D9C5ADc9DEb4b2E29b54D009, 10, 1, 1); // SingularDTV
        addERC20Token(0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A, 10, 1, 1); // DigixDAO

        addERC20Token(0x4993CB95c7443bdC06155c5f5688Be9D8f6999a5, 5, 1, 1); // ROUND
        addERC20Token(0x607F4C5BB672230e8672085532f7e901544a7375, 5, 1, 1); // iEx.ec
        addERC20Token(0x888666CA69E0f178DED6D75b5726Cee99A87D698, 5, 1, 1); // ICONOMI
        addERC20Token(0xAf30D2a7E90d7DC361c8C4585e9BB7D2F6f15bc7, 5, 1, 1); // FirstBlood
        addERC20Token(0xBEB9eF514a379B997e0798FDcC901Ee474B6D9A1, 5, 1, 1); // Melon
        addERC20Token(0x667088b212ce3d06a1b553a7221E1fD19000d9aF, 5, 1, 1); // Wings
    }

    // validates an address - currently only checks that it isn't null
    modifier validAddress(address _address) {
        require(_address != 0x0);
        _;
    }

    // validates an ERC20 token address - verifies that the address belongs to one of the ERC20 tokens
    modifier validERC20Token(address _address) {
        require(tokenData[_address].isSet);
        _;
    }

    // validates a token address - verifies that the address belongs to one of the changable tokens
    modifier validToken(address _address) {
        require(_address == address(token) || tokenData[_address].isSet);
        _;
    }

    // validates ERC20 token limit
    modifier validERC20TokenLimit(uint16 _limit) {
        require(_limit <= 1000);
        _;
    }

    // ensures that the contract is active
    modifier active() {
        assert(isActive);
        _;
    }

    // ensures that the contract is not active
    modifier inactive() {
        assert(!isActive);
        _;
    }

    // ensures that it's earlier than the given time
    modifier earlierThan(uint256 _time) {
        assert(now < _time);
        _;
    }

    // ensures that it's later than the given time
    modifier laterThan(uint256 _time) {
        assert(now > _time);
        _;
    }

    // ensures that we didn't reach the ether cap
    modifier etherCapNotReached() {
        assert(etherContributed < ETHER_CAP);
        _;
    }

    // ensures that the sender is bitcoin suisse
    modifier bitcoinSuisseOnly() {
        assert(msg.sender == bitcoinSuisse);
        _;
    }

    // ensures that we didn't reach the ether cap
    modifier bitcoinSuisseEtherCapNotReached() {
        assert(etherContributed < BITCOIN_SUISSE_ETHER_CAP);
        _;
    }

    /*
        returns the number of changeable tokens supported by the contract
        note that the number of changable tokens is the number of ERC20 tokens plus the smart token
    */
    function changeableTokenCount() public constant returns (uint16 count) {
        return uint16(acceptedTokens.length + 1);
    }

    /*
        given a changable token index, returns the changable token contract address
    */
    function changeableToken(uint16 _tokenIndex) public constant returns (address tokenAddress) {
        if (_tokenIndex == 0)
            return token;
        return acceptedTokens[_tokenIndex - 1];
    }

    /*
        defines a new ERC20 token
        can only be called by the changer owner while the changer is inactive

        _token      address of the ERC20 token
        _limit      maximum contribution in percentage out of the total ETH raised so far. 1-1000 (10 == 1%), divided by 1000
        _valueN     1 smallest unit in wei (numerator)
        _valueD     1 smallest unit in wei (denominator)
    */
    function addERC20Token(address _token, uint16 _limit, uint256 _valueN, uint256 _valueD)
        public
        ownerOnly
        inactive
        validAddress(_token)
        validERC20TokenLimit(_limit)
        returns (bool success)
    {
        require(_token != address(this) && _token != address(token) && !tokenData[_token].isSet && _valueN != 0 && _valueD != 0); // validate input

        tokenData[_token].limit = _limit;
        tokenData[_token].valueN = _valueN;
        tokenData[_token].valueD = _valueD;
        tokenData[_token].isEnabled = true;
        tokenData[_token].isSet = true;
        acceptedTokens.push(_token);
        return true;
    }

    /*
        updates one of the ERC20 tokens
        can only be called by the changer owner

        _erc20Token     address of the ERC20 token
        _limit          maximum contribution in percentage out of the total ETH raised so far. 1-1000 (10 == 1%), divided by 1000
        _valueN         1 smallest unit in wei (numerator)
        _valueD         1 smallest unit in wei (denominator)
    */
    function updateERC20Token(address _erc20Token, uint16 _limit, uint256 _valueN, uint256 _valueD)
        public
        ownerOnly
        validERC20Token(_erc20Token)
        validERC20TokenLimit(_limit)
        returns (bool success)
    {
        require(_valueN != 0 && _valueD != 0); // validate input
        ERC20TokenData data = tokenData[_erc20Token];
        data.limit = _limit;
        data.valueN = _valueN;
        data.valueD = _valueD;
        return true;
    }

    /*
        disables purchasing with the given ERC20 token in case the token got compromised
        can only be called by the changer owner

        _erc20Token     ERC20 token contract address
        _disable        true to disable the token, false to re-enable it
    */
    function disableERC20Token(address _erc20Token, bool _disable)
        public
        ownerOnly
        validERC20Token(_erc20Token)
    {
        tokenData[_erc20Token].isEnabled = !_disable;
    }

    /*
        withdraws tokens from one of the ERC20 tokens and sends them to an account
        can only be called by the changer owner
        this is a mechanism to allow the owner to return tokens that were sent directly to this contract by mistake

        _erc20Token     ERC20 token contract address
        _to             account to receive the new amount
        _amount         amount to withdraw (in the ERC20 token)
    */
    function withdraw(address _erc20Token, address _to, uint256 _amount)
        public
        ownerOnly
        validERC20Token(_erc20Token)
        validAddress(_to)
        returns (bool success)
    {
        require(_to != address(this) && _to != address(token) && _amount != 0); // validate input

        ERC20TokenInterface erc20Token = ERC20TokenInterface(_erc20Token);
        assert(erc20Token.transfer(_to, _amount));
        return true;
    }

    /*
        sets the smart token's changer address to a different one instead of the current contract address
        can only be called by the owner
        the changer can be set to null to transfer ownership from the changer to the original smart token's owner

        _changer    new changer contract address (can also be set to 0x0 to remove the current changer)
    */
    function setTokenChanger(address _changer)
        public
        ownerOnly
        validAddress(_changer)
        returns (bool success)
    {
        require(_changer != address(this) && _changer != address(token)); // validate input
        return token.setChanger(_changer);
    }

    /*
        activates/deactivates the contract logic
        can only be called by the owner
        activating the change logic also disables transfers in the smart token
        this can also be used as an emergency stop

        _activate   true to activate the contract, false to deactivate it
    */
    function activate(bool _activate) public ownerOnly returns (bool success)
    {
        assert(!_activate || acceptedTokens.length > 0); // validate state
        if (_activate)
            token.disableTransfers(true);

        isActive = _activate;
        return true;
    }

    /*
        returns the expected return for changing a specific amount of _fromToken to _toToken

        _fromToken  token to change from
        _toToken    token to change to
        _amount     amount to change, in fromToken
    */
    function getReturn(address _fromToken, address _toToken, uint256 _amount) public constant returns (uint256 amount) {
        require(_toToken == address(token)); // validate input
        return getPurchaseReturn(_fromToken, _amount);
    }

    /*
        returns the expected return for buying the token for an ERC20 token

        _erc20Token     ERC20 token contract address
        _depositAmount  amount to deposit (in the ERC20 token)
    */
    function getPurchaseReturn(address _erc20Token, uint256 _depositAmount)
        public
        constant
        active
        etherCapNotReached
        validERC20Token(_erc20Token)
        returns (uint256 amount)
    {
        ERC20TokenData data = tokenData[_erc20Token];
        require(data.isEnabled && _depositAmount != 0); // validate input

        uint256 depositEthValue = safeMul(_depositAmount, data.valueN) / data.valueD;
        if (depositEthValue == 0)
            return 0;

        // check ether cap
        require(safeAdd(etherContributed, depositEthValue) <= ETHER_CAP);

        // check limit
        if (data.limit != 0) {
            ERC20TokenInterface erc20Token = ERC20TokenInterface(_erc20Token);
            uint256 balance = erc20Token.balanceOf(beneficiary);
            uint256 balanceEthValue = safeMul(balance, data.valueN) / data.valueD;  // ether value of the ERC20 token beneficiary balance
            uint256 limit = safeMul(etherContributed, data.limit) / 1000; // current limit of the ERC20 token
            require(safeAdd(balanceEthValue, depositEthValue) <= limit);
        }

        // first contribution can't use the simplified bancor formula, so using the predefined initial price instead
        if (tokenReserveBalance == 0 || token.totalSupply() == 0)
            return safeMul(depositEthValue, INITIAL_PRICE_D) / INITIAL_PRICE_N;

        // using the simplified bancor formula -
        // Price = Reserve / (Supply * CRR)
        uint256 temp = safeMul(depositEthValue, token.totalSupply());
        temp = safeMul(temp, RESERVE_RATIO);
        return temp / 100 / tokenReserveBalance;
    }

    /*
        changes a specific amount of _fromToken to _toToken

        _fromToken  token to change from
        _toToken    token to change to
        _amount     amount to change, in fromToken
        _minReturn  if the change results in an amount smaller than the minimum return, it is cancelled
    */
    function change(address _fromToken, address _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256 amount) {
        require(_toToken == address(token)); // validate input
        return buyERC20(_fromToken, _amount, _minReturn);
    }

    /*
        buys the token with one of the ERC20 tokens
        requires the called to approve and allowance for the crowdsale contract

        _erc20Token     ERC20 token contract address
        _depositAmount  amount to deposit (in the ERC20 token)
        _minReturn      if the change results in an amount smaller than the minimum return, it is cancelled
    */
    function buyERC20(address _erc20Token, uint256 _depositAmount, uint256 _minReturn)
        public
        laterThan(startTime)
        earlierThan(endTime)
        returns (uint256 amount)
    {
        amount = getPurchaseReturn(_erc20Token, _depositAmount);
        assert(amount != 0 && amount >= _minReturn); // ensure the trade gives something in return and meets the minimum requested amount

        ERC20TokenInterface erc20Token = ERC20TokenInterface(_erc20Token);
        assert(erc20Token.transferFrom(msg.sender, beneficiary, _depositAmount)); // transfer _depositAmount funds from the caller in the ERC20 token

        ERC20TokenData data = tokenData[_erc20Token];
        uint256 depositEthValue = safeMul(_depositAmount, data.valueN) / data.valueD;
        handleContribution(msg.sender, depositEthValue, amount);
        dispatchChange(_erc20Token, token, msg.sender, _depositAmount, amount);
        return amount;
    }

    /*
        buys the token with ETH
    */
    function buyETH()
        public
        payable
        laterThan(startTime)
        earlierThan(endTime)
        returns (uint256 amount)
    {
        amount = handleETHDeposit(msg.sender, msg.value);
        dispatchChange(etherToken, token, msg.sender, msg.value, amount);
        return amount;
    }

    /*
        buys the token with BTCs (Bitcoin Suisse only)
        can only be called before the crowdsale started

        _contributor    account that should receive the new tokens
    */
    function buyBitcoinSuisse(address _contributor)
        public
        payable
        bitcoinSuisseOnly
        bitcoinSuisseEtherCapNotReached
        earlierThan(startTime)
        returns (uint256 amount)
    {
        require(safeAdd(etherContributed, msg.value) <= BITCOIN_SUISSE_ETHER_CAP); // validate input
        amount = handleETHDeposit(_contributor, msg.value);
        dispatchChange(etherToken, token, msg.sender, msg.value, amount);
        return amount;
    }

    /*
        handles direct ETH deposits (as opposed to ERC20 contributions)

        _contributor    account that should receive the new tokens
        _depositAmount  amount contributed by the account, in wei
    */
    function handleETHDeposit(address _contributor, uint256 _depositAmount) private returns (uint256 amount) {
        require(_depositAmount > 0); // validate input
        amount = getPurchaseReturn(etherToken, _depositAmount);
        assert(amount != 0); // ensure the trade gives something in return

        EtherToken ethToken = EtherToken(etherToken);
        assert(ethToken.deposit.value(_depositAmount)()); // transfer the ether to the ether contract
        assert(ethToken.transfer(beneficiary, _depositAmount)); // transfer the ther to the beneficiary account
        handleContribution(_contributor, _depositAmount, amount);
        return amount;
    }

    /*
        handles the generic part of the contribution - regardless of the type of contribution
        assumes that the contribution was already added to the beneficiary account in the different tokens
        updates the reserve balance, total contributed amount and issues new tokens to the contributor and to the beneficiary

        _contributor        account that should the new tokens
        _depositEthValue    amount contributed by the account, in wei
        _return             amount to be issued to the contributor, in the smart token
    */
    function handleContribution(address _contributor, uint256 _depositEthValue, uint256 _return) private {
        // update the reserve balance
        uint8 reserveAllocationPercentage;
        if (etherContributed >= PHASE3_MIN_CONTRIBUTION)
            reserveAllocationPercentage = PHASE3_RESERVE_ALLOCATION;
        else if (etherContributed >= PHASE2_MIN_CONTRIBUTION)
            reserveAllocationPercentage = PHASE2_RESERVE_ALLOCATION;
        else if (etherContributed >= PHASE1_MIN_CONTRIBUTION)
            reserveAllocationPercentage = PHASE1_RESERVE_ALLOCATION;

        uint256 addToReserve = safeMul(_depositEthValue, reserveAllocationPercentage) / 100;
        tokenReserveBalance = safeAdd(tokenReserveBalance, addToReserve);

        // update the total contribution amount
        etherContributed = safeAdd(etherContributed, _depositEthValue);
        // issue new funds to the contributor in the smart token
        assert(token.issue(_contributor, _return));

        // issued tokens to the beneficiary
        uint256 amount = safeMul(100, _return) / BENEFICIARY_PERCENTAGE;
        if (amount == 0)
            return;

        assert(token.issue(beneficiary, amount));
    }

    // utility

    function dispatchChange(address _fromToken, address _toToken, address _trader, uint256 _amount, uint256 _return) private {
        Change(_fromToken, _toToken, _trader, _amount, _return);

        if (address(events) != 0x0)
            events.tokenChange(_fromToken, _toToken, _trader, _amount, _return);
    }

    // fallback
    function() payable {
        buyETH();
    }
}


Simplified decode functions

Simplified decode functions

Instead of modifying the Participant struct in place it returns the
necessary values.

pragma solidity ^0.4.0;

import "./Token.sol";

library NettingChannelLibrary {
    struct Lock
    {
        uint64 expiration;
        uint amount;
        bytes32 hashlock;
    }

    struct Participant
    {
        address node_address;
        uint256 balance;
        uint256 netted;
        uint256 transferred_amount;
        uint64 nonce;
        bytes32 locksroot;
        Lock[] unlocked;
    }

    struct Data {
        uint settle_timeout;
        uint opened;
        uint closed;
        uint settled;
        address closing_address;
        Token token;
        Participant[2] participants;
        mapping(address => uint8) participant_index;
        mapping(bytes32 => bool) locks;
        bool updated;
    }

    modifier notSettledButClosed(Data storage self) {
        if (self.settled > 0 || self.closed == 0)
            throw;
        _;
    }

    modifier stillTimeout(Data storage self) {
        if (self.closed + self.settle_timeout < block.number)
            throw;
        _;
    }

    modifier timeoutOver(Data storage self) {
        if (self.closed + self.settle_timeout > block.number)
            throw;
        _;
    }

    modifier channelSettled(Data storage self) {
        if (self.settled == 0)
            throw;
        _;
    }

    function isValidNonce(Data storage self, uint64 nonce)
        private
        returns (bool)
    {
        return (
            nonce >= self.opened * (2 ** 32) &&
            nonce < (self.opened + 1) * (2 ** 32)
        );
    }

    /// @notice Deposit amount to channel.
    /// @dev Deposit an amount to the channel. At least one of the participants
    /// must deposit before the channel is opened.
    /// @param caller_address The address of the invoker of the function
    /// @param channel_address The address of the channel
    /// @param amount The amount to be deposited to the address
    /// @return Success if the transfer was successful
    /// @return The new balance of the invoker
    function deposit(
        Data storage self,
        address caller_address,
        address channel_address,
        uint256 amount)
        returns (bool success, uint256 balance)
    {

        if (self.closed != 0) {
            throw;
        }

        if (self.token.balanceOf(caller_address) < amount) {
            throw;
        }

        uint8 index = index_or_throw(self, caller_address);

        Participant storage participant = self.participants[index];

        success = self.token.transferFrom(
            caller_address,
            channel_address,
            amount
        );

        if (success == true) {
            balance = participant.balance;
            balance += amount;
            participant.balance = balance;

            if (self.opened == 0) {
                self.opened = block.number;
            }

            return (true, balance);
        }

        return (false, 0);
    }

    /// @notice Get the partner or other participant of the channel
    /// @dev Get the other participating party of the channel
    /// @return The partner of the calling party
    function partner(Data storage self, address one_address) constant returns (address) {
        Participant[2] storage participants = self.participants;
        Participant storage node1 = participants[0];
        Participant storage node2 = participants[1];

        if (one_address == node1.node_address) {
            return node2.node_address;
        }

        if (one_address == node2.node_address) {
            return node1.node_address;
        }

        return 0x0;
    }

    function addressAndBalance(Data storage self)
        constant
        returns(
        address participant1,
        uint balance1,
        address participant2,
        uint balance2)
    {
        Participant[2] participants = self.participants;
        Participant node1 = participants[0];
        Participant node2 = participants[1];

        // return by name
        participant1 = node1.node_address;
        balance1 = node1.balance;
        participant2 = node2.node_address;
        balance2 = node2.balance;
    }

    /// @notice Close a channel between two parties that was used bidirectionally
    /// @param caller_address The address of the participant closing the channel
    /// @param their_transfer The latest known transfer of the other participant
    ///                       to the channel. Can also be empty, in which case
    ///                       we are attempting to close a channel without any
    ///                       transfers.
    function close(
        Data storage self,
        address caller_address,
        bytes their_transfer)
    {
        uint closer_index;
        uint counterparty_index;
        bytes memory transfer_raw;
        uint64 nonce;
        address transfer_address;
        bytes32 locksroot;
        uint256 transferred_amount;

        // close can be called only once
        if (self.closed > 0) {
            throw;
        }

        // Only a participant can call close
        closer_index = index_or_throw(self, caller_address);

        self.closing_address = caller_address;
        self.closed = block.number;

        // Only the closing party can provide a transfer from the counterparty,
        // and only when this function is called, i.e. this value can not be
        // updated afterwards.

        // An empty value means that the closer never received a transfer, or
        // he is intentionally not providing the latest transfer, in which case
        // the closing party is going to lose the tokens that were transferred
        // to him.
        if (their_transfer.length != 0) {
            (transfer_raw, transfer_address) = getTransferRawAddress(their_transfer);
            counterparty_index = index_or_throw(self, transfer_address);

            // only a message from the counter party is valid
            if (closer_index == counterparty_index) {
                throw;
            }

            // update the structure of the counterparty with its data provided
            // by the closing node
            Participant storage counterparty = self.participants[counterparty_index];

            (nonce, locksroot, transferred_amount) = decodeTransfer(transfer_raw);

            // only accept messages with a valid nonce
            if (!isValidNonce(self, nonce)) {
                throw;
            }

            counterparty.nonce = nonce;
            counterparty.locksroot = locksroot;
            counterparty.transferred_amount = transferred_amount;
        }

    }

    /// @notice Updates counter party transfer after closing.
    /// @param caller_address The counterparty to the channel. The participant
    ///                       that did not close the channel.
    /// @param their_transfer The transfer the counterparty believes is the
    ///                       valid state for the first participant.
    function updateTransfer(
        Data storage self,
        address caller_address,
        bytes their_transfer
    )
        notSettledButClosed(self)
        stillTimeout(self)
    {
        address transfer_address;
        bytes32 locksroot;
        bytes memory transfer_raw;
        uint256 transferred_amount;
        uint64 nonce;
        uint8 caller_index;
        uint8 closer_index;

        // updateTransfer can be called by the counter party only once
        if (self.updated) {
            throw;
        }
        self.updated = true;

        // Only a participant can call updateTransfer (#293 for third parties)
        caller_index = index_or_throw(self, caller_address);

        // The closer is not allowed to call updateTransfer
        if (self.closing_address == caller_address) {
            throw;
        }

        (transfer_raw, transfer_address) = getTransferRawAddress(their_transfer);

        // Counter party can only update the closer transfer
        if (transfer_address != self.closing_address) {
            throw;
        }

        // Update the structure of the closer with its data provided by the
        // counterparty
        closer_index = 1 - caller_index;

        (nonce, locksroot, transferred_amount) = decodeTransfer(transfer_raw);

        // only accept messages with a valid nonce
        if (!isValidNonce(self, nonce)) {
            throw;
        }

        self.participants[closer_index].nonce = nonce;
        self.participants[closer_index].locksroot = locksroot;
        self.participants[closer_index].transferred_amount = transferred_amount;
    }

    /// @notice Unlock a locked transfer
    /// @dev Unlock a locked transfer
    /// @param caller_address The calling address
    /// @param locked_encoded The lock
    /// @param merkle_proof The merkle proof
    /// @param secret The secret
    function unlock(
        Data storage self,
        address caller_address,
        bytes locked_encoded,
        bytes merkle_proof,
        bytes32 secret)
        notSettledButClosed(self)
    {
        uint amount;
        uint partner_id;
        uint64 expiration;
        bytes32 el;
        bytes32 h;
        bytes32 hashlock;

        (expiration, amount, hashlock) = decodeLock(locked_encoded);

        if (self.locks[hashlock]) {
            throw;
        }

        if (expiration < block.number) {
            throw;
        }

        if (hashlock != sha3(secret)) {
            throw;
        }

        //Check if caller_address is a participant and select her partner
        uint8 index = 1 - index_or_throw(self, caller_address);

        Participant storage counterparty = self.participants[index];
        if (counterparty.nonce == 0) {
            throw;
        }

        h = computeMerkleRoot(locked_encoded, merkle_proof);

        if (counterparty.locksroot != h) {
            throw;
        }

        counterparty.unlocked.push(Lock(expiration, amount, hashlock));
        self.locks[hashlock] = true;
    }

    function computeMerkleRoot(bytes lock, bytes merkle_proof)
        private
        constant
        returns (bytes32)
    {
        if (merkle_proof.length % 32 != 0) {
            throw;
        }

        uint i;
        bytes32 h;
        bytes32 el;

        h = sha3(lock);
        for (i = 32; i <= merkle_proof.length; i += 32) {
            assembly {
                el := mload(add(merkle_proof, i))
            }

            if (h < el) {
                h = sha3(h, el);
            } else {
                h = sha3(el, h);
            }
        }

        return h;
    }

    /// @notice Settles the balance between the two parties
    /// @dev Settles the balances of the two parties fo the channel
    /// @return The participants with netted balances
    function settle(Data storage self, address caller_address)
        notSettledButClosed(self)
        timeoutOver(self)
    {
        uint total_deposit;
        uint k;

        Participant[2] storage participants = self.participants;
        Participant storage node1 = participants[0];
        Participant storage node2 = participants[1];

        node1.netted = node1.balance + node2.transferred_amount - node1.transferred_amount;
        node2.netted = node2.balance + node1.transferred_amount - node2.transferred_amount;

        for (k = 0; k < node1.unlocked.length; k++) {
            node1.netted -= node1.unlocked[k].amount;
            node2.netted += node1.unlocked[k].amount;
        }

        for (k = 0; k < node2.unlocked.length; k++) {
            node2.netted -= node2.unlocked[k].amount;
            node1.netted += node2.unlocked[k].amount;
        }

        self.settled = block.number;
        total_deposit = node1.balance + node2.balance;

        Participant memory closing_party;
        Participant memory other_party;

        if (node1.node_address == self.closing_address) {
            closing_party = node1;
            other_party = node2;
        } else {
            closing_party = node2;
            other_party = node1;
        }

        // first pay out to the party that did not close the channel
        uint amount = total_deposit < other_party.netted
            ? total_deposit
            : other_party.netted;
        if (amount > 0) {
            if (!self.token.transfer(other_party.node_address, amount)) {
                throw;
            }
        }
        // then payout whatever can be paid out to the closing party
        amount = total_deposit - other_party.netted;
        if (amount > 0) {
            if (!self.token.transfer(closing_party.node_address, amount)) {
                throw;
            }
        }

        kill(self);
    }

    function getTransferRawAddress(bytes memory signed_transfer) private returns (bytes memory, address) {
        uint signature_start;
        uint length;
        bytes memory signature;
        bytes memory transfer_raw;
        bytes32 transfer_hash;
        address transfer_address;

        length = signed_transfer.length;
        signature_start = length - 65;
        signature = slice(signed_transfer, signature_start, length);
        transfer_raw = slice(signed_transfer, 0, signature_start);

        transfer_hash = sha3(transfer_raw);
        var (r, s, v) = signatureSplit(signature);
        transfer_address = ecrecover(transfer_hash, v, r, s);

        return (transfer_raw, transfer_address);
    }

    // NOTES:
    //
    // - The EVM is a big-endian, byte addressing machine, with 32bytes/256bits
    //   words.
    // - The Ethereum Contract ABI specifies that variable length types have a
    //   32bytes prefix to define the variable size.
    // - Solidity has additional data types that are narrower than 32bytes
    //   (e.g. uint128 uses a half word).
    // - Solidity uses the *least-significant* bits of the word to store the
    //   values of a narrower type.
    //
    // GENERAL APPROACH:
    //
    // Add to the message pointer the number of bytes required to move the
    // address so that the target data is at the end of the 32bytes word.
    //
    // EXAMPLE:
    //
    // To decode the cmdid, consider this initial state:
    //
    //
    //     v- pointer word start
    //     [ 32 bytes length prefix ][ cmdid ] ----
    //                              ^- pointer word end
    //
    //
    // Because the cmdid has 1 byte length the type uint8 is used, the decoder
    // needs to move the pointer so the cmdid is at the end of the pointer
    // word.
    //
    //
    //             v- pointer word start [moved 1byte ahead]
    //     [ 32 bytes length prefix ][ cmdid ] ----
    //                                       ^- pointer word end
    //
    //
    // Now the data of the cmdid can be loaded to the uint8 variable.
    //
    // REFERENCES:
    // - https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
    // - http://solidity.readthedocs.io/en/develop/assembly.html

    function decodeTransfer(bytes transfer_raw)
        private
        returns (uint64 nonce, bytes32 locksroot, uint256 transferred_amount)
    {
        uint cmdid = uint(transfer_raw[0]);

        if (cmdid == 5) {
            return decodeDirectTransfer(transfer_raw);
        } else if (cmdid == 7) {
            return decodeMediatedTransfer(transfer_raw);
        } else if (cmdid == 8) {
            return decodeRefundTransfer(transfer_raw);
        }

        throw;
    }

    function decodeDirectTransfer(bytes memory message)
        private
        returns (uint64 nonce, bytes32 locksroot, uint256 transferred_amount)
    {
        // size of the raw message without the signature
        if (message.length != 124) {
            throw;
        }

        // Message format:
        // [0:1] cmdid
        // [1:4] pad
        // [4:12] nonce
        // [12:20] identifier
        // [20:40] token
        // [40:60] recipient
        // [60:92] transferred_amount
        // [92:124] optional_locksroot
        assembly {
            nonce := mload(add(message, 12))
            transferred_amount := mload(add(message, 92))
            locksroot := mload(add(message, 124))
        }
    }

    function decodeMediatedTransfer(bytes memory message)
        private
        returns (uint64 nonce, bytes32 locksroot, uint256 transferred_amount)
    {
        // size of the raw message without the signature
        if (message.length != 268) {
            throw;
        }

        // Message format:
        // [0:1] cmdid
        // [1:4] pad
        // [4:12] nonce
        // [12:20] identifier
        // [20:28] expiration
        // [28:48] token
        // [48:68] recipient
        // [68:88] target
        // [88:108] initiator
        // [108:140] locksroot
        // [140:172] hashlock
        // [172:204] transferred_amoun
        // [204:236] amount
        // [236:268] fee
        assembly {
            nonce := mload(add(message, 12))
            locksroot := mload(add(message, 140))
            transferred_amount := mload(add(message, 204))
        }
    }

    function decodeRefundTransfer(bytes memory message)
        private
        returns (uint64 nonce, bytes32 locksroot, uint256 transferred_amount)
    {
        // size of the raw message without the signature
        if (message.length != 196) {
            throw;
        }

        // Message format:
        // [0:1] cmdid
        // [1:4] pad
        // [4:12] nonce
        // [12:20] identifier
        // [20:28] expiration
        // [28:48] token
        // [48:68] recipient
        // [68:100] locksroot
        // [100:132] transferred_amount
        // [132:164] amount
        // [164:196] hashlock
        assembly {
            nonce := mload(add(message, 12))
            locksroot := mload(add(message, 100))
            transferred_amount := mload(add(message, 132))
        }
    }

    function decodeLock(bytes lock) private returns (uint64 expiration, uint amount, bytes32 hashlock) {
        if (lock.length != 72) {
            throw;
        }

        // Lock format:
        // [0:8] expiration
        // [8:40] amount
        // [40:72] hashlock
        assembly {
            expiration := mload(add(lock, 8))
            amount := mload(add(lock, 40))
            hashlock := mload(add(lock, 72))
        }
    }

    // Get nonce from a message
    function getNonce(bytes message) private returns (uint64 nonce) {
        // don't care about length of message since nonce is always at a fixed position
        assembly {
            nonce := mload(add(message, 12))
        }
    }

    function signatureSplit(bytes signature) private returns (bytes32 r, bytes32 s, uint8 v) {
        // The signature format is a compact form of:
        //   {bytes32 r}{bytes32 s}{uint8 v}
        // Compact means, uint8 is not padded to 32 bytes.
        assembly {
            r := mload(add(signature, 32))
            s := mload(add(signature, 64))
            // Here we are loading the last 32 bytes, including 31 bytes
            // of 's'. There is no 'mload8' to do this.
            //
            // 'byte' is not working due to the Solidity parser, so lets
            // use the second best option, 'and'
            v := and(mload(add(signature, 65)), 1)
        }
        // old geth sends a `v` value of [0,1], while the new, in line with the YP sends [27,28]
        if(v < 27) v += 27;
    }

    function slice(bytes a, uint start, uint end) private returns (bytes n) {
        if (a.length < end) {
            throw;
        }
        if (start < 0) {
            throw;
        }

        n = new bytes(end - start);
        for (uint i = start; i < end; i++) { //python style slice
            n[i - start] = a[i];
        }
    }

    function index_or_throw(Data storage self, address caller_address) private returns (uint8) {
        uint8 n;
        // Return index of participant, or throw
        n = self.participant_index[caller_address];
        if (n == 0) {
            throw;
        }
        return n - 1;
    }

    function kill(Data storage self) channelSettled(self) {
        selfdestruct(0x00000000000000000000);
    }
}

pragma solidity ^0.4.0;

import "./Token.sol";

library NettingChannelLibrary {
    struct Lock
    {
        uint64 expiration;
        uint amount;
        bytes32 hashlock;
    }

    struct Participant
    {
        address node_address;
        uint256 balance;
        uint256 netted;
        uint256 transferred_amount;
        uint64 nonce;
        bytes32 locksroot;
        Lock[] unlocked;
    }

    struct Data {
        uint settle_timeout;
        uint opened;
        uint closed;
        uint settled;
        address closing_address;
        Token token;
        Participant[2] participants;
        mapping(address => uint8) participant_index;
        mapping(bytes32 => bool) locks;
        bool updated;
    }

    modifier notSettledButClosed(Data storage self) {
        if (self.settled > 0 || self.closed == 0)
            throw;
        _;
    }

    modifier stillTimeout(Data storage self) {
        if (self.closed + self.settle_timeout < block.number)
            throw;
        _;
    }

    modifier timeoutOver(Data storage self) {
        if (self.closed + self.settle_timeout > block.number)
            throw;
        _;
    }

    modifier isCounterParty(Data storage self, address caller) {
        if (caller == self.closing_address) {
            throw;
        }
        address participant0 = self.participants[0].node_address;
        address participant1 = self.participants[1].node_address;
        if (caller != participant0 && caller != participant1) {
            throw;
        }
        _;
    }

    modifier inNonceRange(Data storage self, bytes message) {
        uint64 nonce;
        nonce = getNonce(message);
        if (nonce < self.opened * (2 ** 32) || nonce >= (self.opened + 1) * (2 ** 32))
            throw;
        _;
    }

    modifier channelSettled(Data storage self) {
        if (self.settled == 0)
            throw;
        _;
    }

    modifier notYetUpdated(Data storage self) {
        if (self.updated)
            throw;
        _;
    }

    /// @notice Deposit amount to channel.
    /// @dev Deposit an amount to the channel. At least one of the participants
    /// must deposit before the channel is opened.
    /// @param caller_address The address of the invoker of the function
    /// @param channel_address The address of the channel
    /// @param amount The amount to be deposited to the address
    /// @return Success if the transfer was successful
    /// @return The new balance of the invoker
    function deposit(
        Data storage self,
        address caller_address,
        address channel_address,
        uint256 amount)
        returns (bool success, uint256 balance)
    {

        if (self.closed != 0) {
            throw;
        }

        if (self.token.balanceOf(caller_address) < amount) {
            throw;
        }

        uint8 index = index_or_throw(self, caller_address);

        Participant storage participant = self.participants[index];

        success = self.token.transferFrom(
            caller_address,
            channel_address,
            amount
        );

        if (success == true) {
            balance = participant.balance;
            balance += amount;
            participant.balance = balance;

            if (self.opened == 0) {
                self.opened = block.number;
            }

            return (true, balance);
        }

        return (false, 0);
    }

    /// @notice Get the partner or other participant of the channel
    /// @dev Get the other participating party of the channel
    /// @return The partner of the calling party
    function partner(Data storage self, address one_address) constant returns (address) {
        Participant[2] storage participants = self.participants;
        Participant storage node1 = participants[0];
        Participant storage node2 = participants[1];

        if (one_address == node1.node_address) {
            return node2.node_address;
        }

        if (one_address == node2.node_address) {
            return node1.node_address;
        }

        return 0x0;
    }

    function addressAndBalance(Data storage self)
        constant
        returns(
        address participant1,
        uint balance1,
        address participant2,
        uint balance2)
    {
        Participant[2] participants = self.participants;
        Participant node1 = participants[0];
        Participant node2 = participants[1];

        // return by name
        participant1 = node1.node_address;
        balance1 = node1.balance;
        participant2 = node2.node_address;
        balance2 = node2.balance;
    }

    /// @notice Close a channel between two parties that was used bidirectionally
    /// @param caller_address The address of the participant closing the channel
    /// @param their_transfer The latest known transfer of the other participant
    ///                       to the channel. Can also be empty, in which case
    ///                       we are attempting to close a channel without any
    ///                       transfers.
    function close(
        Data storage self,
        address caller_address,
        bytes their_transfer)
    {
        uint closer_index;
        uint counterparty_index;
        bytes memory transfer_raw;
        uint64 nonce;
        address transfer_address;

        // Already closed
        if (self.closed > 0) {
            throw;
        }

        // Only a participant can call close
        closer_index = index_or_throw(self, caller_address);

        self.closing_address = caller_address;
        self.closed = block.number;

        // Only the closing party can provide a transfer from the counterparty,
        // and only when this function is called, i.e. this value can not be
        // updated afterwards.

        // An empty value means that the closer never received a transfer, or
        // he is intentionally not providing the latest transfer, in which case
        // the closing party is going to lose the tokens that were transferred
        // to him.
        if (their_transfer.length != 0) {

            // only accept messages with a valid nonce
            nonce = getNonce(their_transfer);
            if (nonce < self.opened * (2 ** 32) || nonce >= (self.opened + 1) * (2 ** 32)) {
                throw;
            }

            (transfer_raw, transfer_address) = getTransferRawAddress(their_transfer);
            counterparty_index = index_or_throw(self, transfer_address);

            // only a message from the counter party is valid
            if (closer_index == counterparty_index) {
                throw;
            }

            // update the structure of the counterparty with its data provided
            // by the closing node
            Participant storage counterparty = self.participants[counterparty_index];
            decodeAndAssign(counterparty, transfer_raw);
        }

    }

    function processTransfer(Data storage self, Participant storage node1, Participant storage node2, bytes transfer)
        inNonceRange(self, transfer)
        internal
        returns (address)
    {
        bytes memory transfer_raw;
        address transfer_address;

        if (transfer.length <= 65) {
            throw;
        }

        (transfer_raw, transfer_address) = getTransferRawAddress(transfer);
        if (node1.node_address == transfer_address) {
            Participant storage sender = node1;
        } else if (node2.node_address == transfer_address) {
            sender = node2;
        } else {
            throw;
        }

        decodeAndAssign(sender, transfer_raw);

        return sender.node_address;
    }

    /// @notice Updates (disputes) the state after closing.
    /// @param caller_address The counterparty to the channel. The participant
    ///                       that did not close the channel.
    /// @param their_transfer The transfer the counterparty believes is the
    ///                        valid state for the first participant.
    function updateTransfer(
        Data storage self,
        address caller_address,
        bytes their_transfer
    )
        notSettledButClosed(self)
        stillTimeout(self)
        isCounterParty(self, caller_address)
        notYetUpdated(self)
    {
        Participant[2] storage participants = self.participants;
        Participant storage node1 = participants[0];
        Participant storage node2 = participants[1];

        processTransfer(self, node1, node2, their_transfer);
        self.updated = true;
    }

    /// @notice Unlock a locked transfer
    /// @dev Unlock a locked transfer
    /// @param caller_address The calling address
    /// @param locked_encoded The lock
    /// @param merkle_proof The merkle proof
    /// @param secret The secret
    function unlock(
        Data storage self,
        address caller_address,
        bytes locked_encoded,
        bytes merkle_proof,
        bytes32 secret)
        notSettledButClosed(self)
    {
        uint amount;
        uint partner_id;
        uint64 expiration;
        bytes32 el;
        bytes32 h;
        bytes32 hashlock;

        (expiration, amount, hashlock) = decodeLock(locked_encoded);

        if (self.locks[hashlock]) {
            throw;
        }

        if (expiration < block.number) {
            throw;
        }

        if (hashlock != sha3(secret)) {
            throw;
        }

        //Check if caller_address is a participant and select her partner
        uint8 index = 1 - index_or_throw(self, caller_address);

        Participant storage counterparty = self.participants[index];
        if (counterparty.nonce == 0) {
            throw;
        }

        h = computeMerkleRoot(locked_encoded, merkle_proof);

        if (counterparty.locksroot != h) {
            throw;
        }

        counterparty.unlocked.push(Lock(expiration, amount, hashlock));
        self.locks[hashlock] = true;
    }

    function computeMerkleRoot(bytes lock, bytes merkle_proof)
        private
        constant
        returns (bytes32)
    {
        if (merkle_proof.length % 32 != 0) {
            throw;
        }

        uint i;
        bytes32 h;
        bytes32 el;

        h = sha3(lock);
        for (i = 32; i <= merkle_proof.length; i += 32) {
            assembly {
                el := mload(add(merkle_proof, i))
            }

            if (h < el) {
                h = sha3(h, el);
            } else {
                h = sha3(el, h);
            }
        }

        return h;
    }

    /// @notice Settles the balance between the two parties
    /// @dev Settles the balances of the two parties fo the channel
    /// @return The participants with netted balances
    function settle(Data storage self, address caller_address)
        notSettledButClosed(self)
        timeoutOver(self)
    {
        uint total_deposit;
        uint k;

        Participant[2] storage participants = self.participants;
        Participant storage node1 = participants[0];
        Participant storage node2 = participants[1];

        node1.netted = node1.balance + node2.transferred_amount - node1.transferred_amount;
        node2.netted = node2.balance + node1.transferred_amount - node2.transferred_amount;

        for (k = 0; k < node1.unlocked.length; k++) {
            node1.netted -= node1.unlocked[k].amount;
            node2.netted += node1.unlocked[k].amount;
        }

        for (k = 0; k < node2.unlocked.length; k++) {
            node2.netted -= node2.unlocked[k].amount;
            node1.netted += node2.unlocked[k].amount;
        }

        self.settled = block.number;
        total_deposit = node1.balance + node2.balance;

        Participant memory closing_party;
        Participant memory other_party;

        if (node1.node_address == self.closing_address) {
            closing_party = node1;
            other_party = node2;
        } else {
            closing_party = node2;
            other_party = node1;
        }

        // first pay out to the party that did not close the channel
        uint amount = total_deposit < other_party.netted
            ? total_deposit
            : other_party.netted;
        if (amount > 0) {
            if (!self.token.transfer(other_party.node_address, amount)) {
                throw;
            }
        }
        // then payout whatever can be paid out to the closing party
        amount = total_deposit - other_party.netted;
        if (amount > 0) {
            if (!self.token.transfer(closing_party.node_address, amount)) {
                throw;
            }
        }

        kill(self);
    }

    function getTransferRawAddress(bytes memory signed_transfer) private returns (bytes memory, address) {
        uint signature_start;
        uint length;
        bytes memory signature;
        bytes memory transfer_raw;
        bytes32 transfer_hash;
        address transfer_address;

        length = signed_transfer.length;
        signature_start = length - 65;
        signature = slice(signed_transfer, signature_start, length);
        transfer_raw = slice(signed_transfer, 0, signature_start);

        transfer_hash = sha3(transfer_raw);
        var (r, s, v) = signatureSplit(signature);
        transfer_address = ecrecover(transfer_hash, v, r, s);

        return (transfer_raw, transfer_address);
    }

    function decodeAndAssign(Participant storage sender, bytes transfer_raw) private {
        // all checks must be done by now
        uint cmdid;

        cmdid = uint(transfer_raw[0]);

        if (cmdid == 5) {
            assignDirectTransfer(sender, transfer_raw);
        } else if (cmdid == 7) {
            assignMediatedTransfer(sender, transfer_raw);
        } else if (cmdid == 8) {
            assignRefundTransfer(sender, transfer_raw);
        } else {
            throw;
        }
    }

    // NOTES:
    //
    // - The EVM is a big-endian, byte addressing machine, with 32bytes/256bits
    //   words.
    // - The Ethereum Contract ABI specifies that variable length types have a
    //   32bytes prefix to define the variable size.
    // - Solidity has additional data types that are narrower than 32bytes
    //   (e.g. uint128 uses a half word).
    // - Solidity uses the *least-significant* bits of the word to store the
    //   values of a narrower type.
    //
    // GENERAL APPROACH:
    //
    // Add to the message pointer the number of bytes required to move the
    // address so that the target data is at the end of the 32bytes word.
    //
    // EXAMPLE:
    //
    // To decode the cmdid, consider this initial state:
    //
    //
    //     v- pointer word start
    //     [ 32 bytes length prefix ][ cmdid ] ----
    //                              ^- pointer word end
    //
    //
    // Because the cmdid has 1 byte length the type uint8 is used, the decoder
    // needs to move the pointer so the cmdid is at the end of the pointer
    // word.
    //
    //
    //             v- pointer word start [moved 1byte ahead]
    //     [ 32 bytes length prefix ][ cmdid ] ----
    //                                       ^- pointer word end
    //
    //
    // Now the data of the cmdid can be loaded to the uint8 variable.
    //
    // REFERENCES:
    // - https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
    // - http://solidity.readthedocs.io/en/develop/assembly.html

    // TODO: use sstore instead of these temporaries

    function assignDirectTransfer(Participant storage participant, bytes memory message) private {
        if (message.length != 124) { // size of the raw message without the signature
            throw;
        }

        uint64 nonce;
        uint256 transferred_amount;
        bytes32 locksroot;

        assembly {
                                                          // [0:1] cmdid
                                                          // [1:4] pad
            nonce := mload(add(message, 12))              // [4:12] nonce
                                                          // [12:20] identifier
                                                          // [20:40] token
                                                          // [40:60] recipient
            transferred_amount := mload(add(message, 92)) // [60:92] transferred_amount
            locksroot := mload(add(message, 124))         // [92:124] optional_locksroot
        }

        participant.nonce = nonce;
        participant.transferred_amount = transferred_amount;
        participant.locksroot = locksroot;
    }

    function assignMediatedTransfer(Participant storage participant, bytes memory message) private {
        if (message.length != 268) { // size of the raw message without the signature
            throw;
        }

        uint64 nonce;
        bytes32 locksroot;
        uint256 transferred_amount;

        assembly {
                                                           // [0:1] cmdid
                                                           // [1:4] pad
            nonce := mload(add(message, 12))               // [4:12] nonce
                                                           // [12:20] identifier
                                                           // [20:28] expiration
                                                           // [28:48] token
                                                           // [48:68] recipient
                                                           // [68:88] target
                                                           // [88:108] initiator
            locksroot := mload(add(message, 140))          // [108:140] locksroot
                                                           // [140:172] hashlock
            transferred_amount := mload(add(message, 204)) // [172:204] transferred_amoun
                                                           // [204:236] amount
                                                           // [236:268] fee
        }

        participant.nonce = nonce;
        participant.locksroot = locksroot;
        participant.transferred_amount = transferred_amount;
    }

    function assignRefundTransfer(Participant storage participant, bytes memory message) private {
        if (message.length != 196) { // size of the raw message without the signature
            throw;
        }

        uint64 nonce;
        bytes32 locksroot;
        uint256 transferred_amount;

        assembly {
                                                            // [0:1] cmdid
                                                            // [1:4] pad
            nonce := mload(add(message, 12))                // [4:12] nonce
                                                            // [12:20] identifier
                                                            // [20:28] expiration
                                                            // [28:48] token
                                                            // [48:68] recipient
            locksroot := mload(add(message, 100))           // [68:100] locksroot
            transferred_amount := mload(add(message, 132))  // [100:132] transferred_amount
                                                            // [132:164] amount
                                                            // [164:196] hashlock
        }

        participant.nonce = nonce;
        participant.locksroot = locksroot;
        participant.transferred_amount = transferred_amount;
    }

    function decodeLock(bytes lock) private returns (uint64 expiration, uint amount, bytes32 hashlock) {
        if (lock.length != 72) {
            throw;
        }

        assembly {
            expiration := mload(add(lock, 8))   // [0:8] expiration
            amount := mload(add(lock, 40))      // [8:40] amont
            hashlock := mload(add(lock, 72))    // [40:72] hashlock

        }
    }

    // Get nonce from a message
    function getNonce(bytes message) private returns (uint64 nonce) {
        // don't care about length of message since nonce is always at a fixed position
        assembly {
            nonce := mload(add(message, 12))
        }
    }

    function signatureSplit(bytes signature) private returns (bytes32 r, bytes32 s, uint8 v) {
        // The signature format is a compact form of:
        //   {bytes32 r}{bytes32 s}{uint8 v}
        // Compact means, uint8 is not padded to 32 bytes.
        assembly {
            r := mload(add(signature, 32))
            s := mload(add(signature, 64))
            // Here we are loading the last 32 bytes, including 31 bytes
            // of 's'. There is no 'mload8' to do this.
            //
            // 'byte' is not working due to the Solidity parser, so lets
            // use the second best option, 'and'
            v := and(mload(add(signature, 65)), 1)
        }
        // old geth sends a `v` value of [0,1], while the new, in line with the YP sends [27,28]
        if(v < 27) v += 27;
    }

    function slice(bytes a, uint start, uint end) private returns (bytes n) {
        if (a.length < end) {
            throw;
        }
        if (start < 0) {
            throw;
        }

        n = new bytes(end - start);
        for (uint i = start; i < end; i++) { //python style slice
            n[i - start] = a[i];
        }
    }

    function index_or_throw(Data storage self, address caller_address) private returns (uint8) {
        uint8 n;
        // Return index of participant, or throw
        n = self.participant_index[caller_address];
        if (n == 0) {
            throw;
        }
        return n - 1;
    }

    function kill(Data storage self) channelSettled(self) {
        selfdestruct(0x00000000000000000000);
    }
}


Fix comment in ERC721Consecutive (#3778)

Co-authored-by: Francisco <4223852eec80914c4c190cdc031e06592b151a67@gmail.com>

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../ERC721.sol";
import "../../../interfaces/IERC2309.sol";
import "../../../utils/Checkpoints.sol";
import "../../../utils/structs/BitMaps.sol";

/**
 * @dev Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in
 * https://eips.ethereum.org/EIPS/eip-2309[EIP-2309].
 *
 * This extension allows the minting of large batches of tokens, during contract construction only. For upgradeable
 * contracts this implies that batch minting is only available during proxy deployment, and not in subsequent upgrades.
 * These batches are limited to 5000 tokens at a time by default to accommodate off-chain indexers.
 *
 * Using this extension removes the ability to mint single tokens during contract construction. This ability is
 * regained after construction. During construction, only batch minting is allowed.
 *
 * IMPORTANT: This extension bypasses the hooks {_beforeTokenTransfer} and {_afterTokenTransfer} for tokens minted in
 * batch. When using this extension, you should consider the {_beforeConsecutiveTokenTransfer} and
 * {_afterConsecutiveTokenTransfer} hooks in addition to {_beforeTokenTransfer} and {_afterTokenTransfer}.
 *
 * IMPORTANT: When overriding {_afterTokenTransfer}, be careful about call ordering. {ownerOf} may return invalid
 * values during the {_afterTokenTransfer} execution if the super call is not called first. To be safe, execute the
 * super call before your custom logic.
 *
 * _Available since v4.8._
 */
abstract contract ERC721Consecutive is IERC2309, ERC721 {
    using BitMaps for BitMaps.BitMap;
    using Checkpoints for Checkpoints.Trace160;

    Checkpoints.Trace160 private _sequentialOwnership;
    BitMaps.BitMap private _sequentialBurn;

    /**
     * @dev Maximum size of a batch of consecutive tokens. This is designed to limit stress on off-chain indexing
     * services that have to record one entry per token, and have protections against "unreasonably large" batches of
     * tokens.
     *
     * NOTE: Overriding the default value of 5000 will not cause on-chain issues, but may result in the asset not being
     * correctly supported by off-chain indexing services (including marketplaces).
     */
    function _maxBatchSize() internal view virtual returns (uint96) {
        return 5000;
    }

    /**
     * @dev See {ERC721-_ownerOf}. Override that checks the sequential ownership structure for tokens that have
     * been minted as part of a batch, and not yet transferred.
     */
    function _ownerOf(uint256 tokenId) internal view virtual override returns (address) {
        address owner = super._ownerOf(tokenId);

        // If token is owned by the core, or beyond consecutive range, return base value
        if (owner != address(0) || tokenId > type(uint96).max) {
            return owner;
        }

        // Otherwise, check the token was not burned, and fetch ownership from the anchors
        // Note: no need for safe cast, we know that tokenId <= type(uint96).max
        return _sequentialBurn.get(tokenId) ? address(0) : address(_sequentialOwnership.lowerLookup(uint96(tokenId)));
    }

    /**
     * @dev Mint a batch of tokens of length `batchSize` for `to`. Returns the token id of the first token minted in the
     * batch; if `batchSize` is 0, returns the number of consecutive ids minted so far.
     *
     * Requirements:
     *
     * - `batchSize` must not be greater than {_maxBatchSize}.
     * - The function is called in the constructor of the contract (directly or indirectly).
     *
     * CAUTION: Does not emit a `Transfer` event. This is ERC721 compliant as long as it is done inside of the
     * constructor, which is enforced by this function.
     *
     * CAUTION: Does not invoke `onERC721Received` on the receiver.
     *
     * Emits a {IERC2309-ConsecutiveTransfer} event.
     */
    function _mintConsecutive(address to, uint96 batchSize) internal virtual returns (uint96) {
        uint96 first = _totalConsecutiveSupply();

        // minting a batch of size 0 is a no-op
        if (batchSize > 0) {
            require(!Address.isContract(address(this)), "ERC721Consecutive: batch minting restricted to constructor");
            require(to != address(0), "ERC721Consecutive: mint to the zero address");
            require(batchSize <= _maxBatchSize(), "ERC721Consecutive: batch too large");

            // hook before
            _beforeTokenTransfer(address(0), to, first, batchSize);

            // push an ownership checkpoint & emit event
            uint96 last = first + batchSize - 1;
            _sequentialOwnership.push(last, uint160(to));
            emit ConsecutiveTransfer(first, last, address(0), to);

            // hook after
            _afterTokenTransfer(address(0), to, first, batchSize);
        }

        return first;
    }

    /**
     * @dev See {ERC721-_mint}. Override version that restricts normal minting to after construction.
     *
     * Warning: Using {ERC721Consecutive} prevents using {_mint} during construction in favor of {_mintConsecutive}.
     * After construction, {_mintConsecutive} is no longer available and {_mint} becomes available.
     */
    function _mint(address to, uint256 tokenId) internal virtual override {
        require(Address.isContract(address(this)), "ERC721Consecutive: can't mint during construction");
        super._mint(to, tokenId);
    }

    /**
     * @dev See {ERC721-_afterTokenTransfer}. Burning of tokens that have been sequentially minted must be explicit.
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId,
        uint256 batchSize
    ) internal virtual override {
        if (
            to == address(0) && // if we burn
            firstTokenId < _totalConsecutiveSupply() && // and the tokenId was minted in a batch
            !_sequentialBurn.get(firstTokenId) // and the token was never marked as burnt
        ) {
            require(batchSize == 1, "ERC721Consecutive: batch burn not supported");
            _sequentialBurn.set(firstTokenId);
        }
        super._afterTokenTransfer(from, to, firstTokenId, batchSize);
    }

    function _totalConsecutiveSupply() private view returns (uint96) {
        (bool exists, uint96 latestId, ) = _sequentialOwnership.latestCheckpoint();
        return exists ? latestId + 1 : 0;
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../ERC721.sol";
import "../../../interfaces/IERC2309.sol";
import "../../../utils/Checkpoints.sol";
import "../../../utils/structs/BitMaps.sol";

/**
 * @dev Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in
 * https://eips.ethereum.org/EIPS/eip-2309[EIP-2309].
 *
 * This extension allows the minting of large batches of tokens, during contract construction only. For upgradeable
 * contracts this implies that batch minting is only available during proxy deployment, and not in subsequent upgrades.
 * These batches are limited to 5000 tokens at a time by default to accommodate off-chain indexers.
 *
 * Using this extension removes the ability to mint single tokens during contract construction. This ability is
 * regained after construction. During construction, only batch minting is allowed.
 *
 * IMPORTANT: This extension bypasses the hooks {_beforeTokenTransfer} and {_afterTokenTransfer} for tokens minted in
 * batch. When using this extension, you should consider the {_beforeConsecutiveTokenTransfer} and
 * {_afterConsecutiveTokenTransfer} hooks in addition to {_beforeTokenTransfer} and {_afterTokenTransfer}.
 *
 * IMPORTANT: When overriding {_afterTokenTransfer}, be careful about call ordering. {ownerOf} may return invalid
 * values during the {_afterTokenTransfer} execution if the super call is not called first. To be safe, execute the
 * super call before your custom logic.
 *
 * _Available since v4.8._
 */
abstract contract ERC721Consecutive is IERC2309, ERC721 {
    using BitMaps for BitMaps.BitMap;
    using Checkpoints for Checkpoints.Trace160;

    Checkpoints.Trace160 private _sequentialOwnership;
    BitMaps.BitMap private _sequentialBurn;

    /**
     * @dev Maximum size of a batch of consecutive tokens. This is designed to limit stress on off-chain indexing
     * services that have to record one entry per token, and have protections against "unreasonably large" batches of
     * tokens.
     *
     * NOTE: Overriding the default value of 5000 will not cause on-chain issues, but may result in the asset not being
     * correctly supported by off-chain indexing services (including marketplaces).
     */
    function _maxBatchSize() internal view virtual returns (uint96) {
        return 5000;
    }

    /**
     * @dev See {ERC721-_ownerOf}. Override that checks the sequential ownership structure for tokens that have
     * been minted as part of a batch, and not yet transferred.
     */
    function _ownerOf(uint256 tokenId) internal view virtual override returns (address) {
        address owner = super._ownerOf(tokenId);

        // If token is owned by the core, or beyond consecutive range, return base value
        if (owner != address(0) || tokenId > type(uint96).max) {
            return owner;
        }

        // Otherwise, check the token was not burned, and fetch ownership from the anchors
        // Note: no need for safe cast, we know that tokenId <= type(uint96).max
        return _sequentialBurn.get(tokenId) ? address(0) : address(_sequentialOwnership.lowerLookup(uint96(tokenId)));
    }

    /**
     * @dev Mint a batch of tokens of length `batchSize` for `to`. Returns the token id of the first token minted in the
     * batch; if `batchSize` is 0, returns the number of consecutive ids minted so far.
     *
     * Requirements:
     *
     * - `batchSize` must not be greater than {_maxBatchSize}.
     * - The function is called in the constructor of the contract (directly or indirectly).
     *
     * CAUTION: Does not emit a `Transfer` event. This is ERC721 compliant as long as it is done outside of the
     * constructor, which is enforced by this function.
     *
     * CAUTION: Does not invoke `onERC721Received` on the receiver.
     *
     * Emits a {IERC2309-ConsecutiveTransfer} event.
     */
    function _mintConsecutive(address to, uint96 batchSize) internal virtual returns (uint96) {
        uint96 first = _totalConsecutiveSupply();

        // minting a batch of size 0 is a no-op
        if (batchSize > 0) {
            require(!Address.isContract(address(this)), "ERC721Consecutive: batch minting restricted to constructor");
            require(to != address(0), "ERC721Consecutive: mint to the zero address");
            require(batchSize <= _maxBatchSize(), "ERC721Consecutive: batch too large");

            // hook before
            _beforeTokenTransfer(address(0), to, first, batchSize);

            // push an ownership checkpoint & emit event
            uint96 last = first + batchSize - 1;
            _sequentialOwnership.push(last, uint160(to));
            emit ConsecutiveTransfer(first, last, address(0), to);

            // hook after
            _afterTokenTransfer(address(0), to, first, batchSize);
        }

        return first;
    }

    /**
     * @dev See {ERC721-_mint}. Override version that restricts normal minting to after construction.
     *
     * Warning: Using {ERC721Consecutive} prevents using {_mint} during construction in favor of {_mintConsecutive}.
     * After construction, {_mintConsecutive} is no longer available and {_mint} becomes available.
     */
    function _mint(address to, uint256 tokenId) internal virtual override {
        require(Address.isContract(address(this)), "ERC721Consecutive: can't mint during construction");
        super._mint(to, tokenId);
    }

    /**
     * @dev See {ERC721-_afterTokenTransfer}. Burning of tokens that have been sequentially minted must be explicit.
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId,
        uint256 batchSize
    ) internal virtual override {
        if (
            to == address(0) && // if we burn
            firstTokenId < _totalConsecutiveSupply() && // and the tokenId was minted in a batch
            !_sequentialBurn.get(firstTokenId) // and the token was never marked as burnt
        ) {
            require(batchSize == 1, "ERC721Consecutive: batch burn not supported");
            _sequentialBurn.set(firstTokenId);
        }
        super._afterTokenTransfer(from, to, firstTokenId, batchSize);
    }

    function _totalConsecutiveSupply() private view returns (uint96) {
        (bool exists, uint96 latestId, ) = _sequentialOwnership.latestCheckpoint();
        return exists ? latestId + 1 : 0;
    }
}