pragma solidity ^0.5.0;

pragma experimental ABIEncoderV2;

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 */
interface IERC20 {
    function transfer(address to, uint256 value) external returns (bool);

    function approve(address spender, uint256 value) external returns (bool);

    function transferFrom(address from, address to, uint256 value) external returns (bool);

    function totalSupply() external view returns (uint256);

    function balanceOf(address who) external view returns (uint256);

    function allowance(address owner, address spender) external view returns (uint256);

    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(address indexed owner, address indexed spender, uint256 value);
}

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
     * account.
     */
    constructor () internal {
        _owner = msg.sender;
        emit OwnershipTransferred(address(0), _owner);
    }

    /**
     * @return the address of the owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(isOwner());
        _;
    }

    /**
     * @return true if `msg.sender` is the owner of the contract.
     */
    function isOwner() public view returns (bool) {
        return msg.sender == _owner;
    }

    /**
     * @dev Allows the current owner to relinquish control of the contract.
     * @notice Renouncing to ownership will leave the contract without an owner.
     * It will not be possible to call the functions with the `onlyOwner`
     * modifier anymore.
     */
    function renounceOwnership() public onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Allows the current owner to transfer control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     */
    function transferOwnership(address newOwner) public onlyOwner {
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     */
    function _transferOwnership(address newOwner) internal {
        require(newOwner != address(0));
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

/**
 * @title SafeMath
 * @dev Unsigned math operations with safety checks that revert on error
 */
library SafeMath {
    /**
    * @dev Multiplies two unsigned integers, reverts on overflow.
    */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b);

        return c;
    }

    /**
    * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
    */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
    * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
    */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a);
        uint256 c = a - b;

        return c;
    }

    /**
    * @dev Adds two unsigned integers, reverts on overflow.
    */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a);

        return c;
    }

    /**
    * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
    * reverts when dividing by zero.
    */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0);
        return a % b;
    }
}

/**
 * @title SignedSafeMath
 * @dev Signed math operations with safety checks that revert on error
 */
library SignedSafeMath {
    int256 constant private INT256_MIN = -2**255;

    /**
    * @dev Multiplies two signed integers, reverts on overflow.
    */
    function mul(int256 a, int256 b) internal pure returns (int256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        require(!(a == -1 && b == INT256_MIN)); // This is the only case of overflow not detected by the check below

        int256 c = a * b;
        require(c / a == b);

        return c;
    }

    /**
    * @dev Integer division of two signed integers truncating the quotient, reverts on division by zero.
    */
    function div(int256 a, int256 b) internal pure returns (int256) {
        require(b != 0); // Solidity only automatically asserts when dividing by 0
        require(!(b == -1 && a == INT256_MIN)); // This is the only case of overflow

        int256 c = a / b;

        return c;
    }

    /**
    * @dev Subtracts two signed integers, reverts on overflow.
    */
    function sub(int256 a, int256 b) internal pure returns (int256) {
        int256 c = a - b;
        require((b >= 0 && c <= a) || (b < 0 && c > a));

        return c;
    }

    /**
    * @dev Adds two signed integers, reverts on overflow.
    */
    function add(int256 a, int256 b) internal pure returns (int256) {
        int256 c = a + b;
        require((b >= 0 && c >= a) || (b < 0 && c < a));

        return c;
    }
}

/**
 * @title Standard ERC20 token
 *
 * @dev Implementation of the basic standard token.
 * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
 * Originally based on code by FirstBlood:
 * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
 *
 * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for
 * all accounts just by listening to said events. Note that this isn't required by the specification, and other
 * compliant implementations may not do it.
 */
contract ERC20 is IERC20 {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowed;

    uint256 private _totalSupply;

    /**
    * @dev Total number of tokens in existence
    */
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    /**
    * @dev Gets the balance of the specified address.
    * @param owner The address to query the balance of.
    * @return An uint256 representing the amount owned by the passed address.
    */
    function balanceOf(address owner) public view returns (uint256) {
        return _balances[owner];
    }

    /**
     * @dev Function to check the amount of tokens that an owner allowed to a spender.
     * @param owner address The address which owns the funds.
     * @param spender address The address which will spend the funds.
     * @return A uint256 specifying the amount of tokens still available for the spender.
     */
    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowed[owner][spender];
    }

    /**
    * @dev Transfer token for a specified address
    * @param to The address to transfer to.
    * @param value The amount to be transferred.
    */
    function transfer(address to, uint256 value) public returns (bool) {
        _transfer(msg.sender, to, value);
        return true;
    }

    /**
     * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
     * Beware that changing an allowance with this method brings the risk that someone may use both the old
     * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
     * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     */
    function approve(address spender, uint256 value) public returns (bool) {
        require(spender != address(0));

        _allowed[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }

    /**
     * @dev Transfer tokens from one address to another.
     * Note that while this function emits an Approval event, this is not required as per the specification,
     * and other compliant implementations may not emit the event.
     * @param from address The address which you want to send tokens from
     * @param to address The address which you want to transfer to
     * @param value uint256 the amount of tokens to be transferred
     */
    function transferFrom(address from, address to, uint256 value) public returns (bool) {
        _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value);
        _transfer(from, to, value);
        emit Approval(from, msg.sender, _allowed[from][msg.sender]);
        return true;
    }

    /**
     * @dev Increase the amount of tokens that an owner allowed to a spender.
     * approve should be called when allowed_[_spender] == 0. To increment
     * allowed value is better to use this function to avoid 2 calls (and wait until
     * the first transaction is mined)
     * From MonolithDAO Token.sol
     * Emits an Approval event.
     * @param spender The address which will spend the funds.
     * @param addedValue The amount of tokens to increase the allowance by.
     */
    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        require(spender != address(0));

        _allowed[msg.sender][spender] = _allowed[msg.sender][spender].add(addedValue);
        emit Approval(msg.sender, spender, _allowed[msg.sender][spender]);
        return true;
    }

    /**
     * @dev Decrease the amount of tokens that an owner allowed to a spender.
     * approve should be called when allowed_[_spender] == 0. To decrement
     * allowed value is better to use this function to avoid 2 calls (and wait until
     * the first transaction is mined)
     * From MonolithDAO Token.sol
     * Emits an Approval event.
     * @param spender The address which will spend the funds.
     * @param subtractedValue The amount of tokens to decrease the allowance by.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        require(spender != address(0));

        _allowed[msg.sender][spender] = _allowed[msg.sender][spender].sub(subtractedValue);
        emit Approval(msg.sender, spender, _allowed[msg.sender][spender]);
        return true;
    }

    /**
    * @dev Transfer token for a specified addresses
    * @param from The address to transfer from.
    * @param to The address to transfer to.
    * @param value The amount to be transferred.
    */
    function _transfer(address from, address to, uint256 value) internal {
        require(to != address(0));

        _balances[from] = _balances[from].sub(value);
        _balances[to] = _balances[to].add(value);
        emit Transfer(from, to, value);
    }

    /**
     * @dev Internal function that mints an amount of the token and assigns it to
     * an account. This encapsulates the modification of balances such that the
     * proper events are emitted.
     * @param account The account that will receive the created tokens.
     * @param value The amount that will be created.
     */
    function _mint(address account, uint256 value) internal {
        require(account != address(0));

        _totalSupply = _totalSupply.add(value);
        _balances[account] = _balances[account].add(value);
        emit Transfer(address(0), account, value);
    }

    /**
     * @dev Internal function that burns an amount of the token of a given
     * account.
     * @param account The account whose tokens will be burnt.
     * @param value The amount that will be burnt.
     */
    function _burn(address account, uint256 value) internal {
        require(account != address(0));

        _totalSupply = _totalSupply.sub(value);
        _balances[account] = _balances[account].sub(value);
        emit Transfer(account, address(0), value);
    }

    /**
     * @dev Internal function that burns an amount of the token of a given
     * account, deducting from the sender's allowance for said account. Uses the
     * internal burn function.
     * Emits an Approval event (reflecting the reduced allowance).
     * @param account The account whose tokens will be burnt.
     * @param value The amount that will be burnt.
     */
    function _burnFrom(address account, uint256 value) internal {
        _allowed[account][msg.sender] = _allowed[account][msg.sender].sub(value);
        _burn(account, value);
        emit Approval(account, msg.sender, _allowed[account][msg.sender]);
    }
}

// The functionality that all derivative contracts expose to the admin.
interface AdminInterface {
    // Initiates the shutdown process, in case of an emergency.
    function emergencyShutdown() external;

    // A core contract method called immediately before or after any financial transaction. It pays fees and moves money
    // between margin accounts to make sure they reflect the NAV of the contract.
    function remargin() external;
}

contract ExpandedIERC20 is IERC20 {
    // Burns a specific amount of tokens. Burns the sender's tokens, so it is safe to leave this method permissionless.
    function burn(uint value) external;

    // Mints tokens and adds them to the balance of the `to` address.
    // Note: this method should be permissioned to only allow designated parties to mint tokens.
    function mint(address to, uint value) external;
}

// This interface allows derivative contracts to pay Oracle fees for their use of the system.
interface StoreInterface {

    // Pays Oracle fees in ETH to the store. To be used by contracts whose margin currency is ETH.
    function payOracleFees() external payable;

    // Pays Oracle fees in the margin currency, erc20Address, to the store. To be used if the margin currency is an
    // ERC20 token rather than ETH. All approved tokens are transfered.
    function payOracleFeesErc20(address erc20Address) external; 

    // Computes the Oracle fees that a contract should pay for a period. `pfc` is the "profit from corruption", or the
    // maximum amount of margin currency that a token sponsor could extract from the contract through corrupting the
    // price feed in their favor.
    function computeOracleFees(uint startTime, uint endTime, uint pfc) external view returns (uint feeAmount);
}

interface ReturnCalculatorInterface {
    // Computes the return between oldPrice and newPrice.
    function computeReturn(int oldPrice, int newPrice) external view returns (int assetReturn);

    // Gets the effective leverage for the return calculator.
    // Note: if this parameter doesn't exist for this calculator, this method should return 1.
    function leverage() external view returns (int _leverage);
}

// This interface allows contracts to query unverified prices.
interface PriceFeedInterface {
    // Whether this PriceFeeds provides prices for the given identifier.
    function isIdentifierSupported(bytes32 identifier) external view returns (bool isSupported);

    // Gets the latest time-price pair at which a price was published. The transaction will revert if no prices have
    // been published for this identifier.
    function latestPrice(bytes32 identifier) external view returns (uint publishTime, int price);

    // An event fired when a price is published.
    event PriceUpdated(bytes32 indexed identifier, uint indexed time, int price);
}

contract AddressWhitelist is Ownable {
    enum Status { None, In, Out }
    mapping(address => Status) private whitelist;

    address[] private whitelistIndices;

    // Adds an address to the whitelist
    function addToWhitelist(address newElement) external onlyOwner {
        // Ignore if address is already included
        if (whitelist[newElement] == Status.In) {
            return;
        }

        // Only append new addresses to the array, never a duplicate
        if (whitelist[newElement] == Status.None) {
            whitelistIndices.push(newElement);
        }

        whitelist[newElement] = Status.In;

        emit AddToWhitelist(newElement);
    }

    // Removes an address from the whitelist.
    function removeFromWhitelist(address elementToRemove) external onlyOwner {
        if (whitelist[elementToRemove] != Status.Out) {
            whitelist[elementToRemove] = Status.Out;
            emit RemoveFromWhitelist(elementToRemove);
        }
    }

    // Checks whether an address is on the whitelist.
    function isOnWhitelist(address elementToCheck) external view returns (bool) {
        return whitelist[elementToCheck] == Status.In;
    }

    // Gets all addresses that are currently included in the whitelist
    // Note: This method skips over, but still iterates through addresses.
    // It is possible for this call to run out of gas if a large number of
    // addresses have been removed. To prevent this unlikely scenario, we can
    // modify the implementation so that when addresses are removed, the last addresses
    // in the array is moved to the empty index.
    function getWhitelist() external view returns (address[] memory activeWhitelist) {
        // Determine size of whitelist first
        uint activeCount = 0;
        for (uint i = 0; i < whitelistIndices.length; i++) {
            if (whitelist[whitelistIndices[i]] == Status.In) {
                activeCount++;
            }
        }

        // Populate whitelist
        activeWhitelist = new address[](activeCount);
        activeCount = 0;
        for (uint i = 0; i < whitelistIndices.length; i++) {
            address addr = whitelistIndices[i];
            if (whitelist[addr] == Status.In) {
                activeWhitelist[activeCount] = addr;
                activeCount++;
            }
        }
    }

    event AddToWhitelist(address indexed addedAddress);
    event RemoveFromWhitelist(address indexed removedAddress);
}

contract Withdrawable is Ownable {
    // Withdraws ETH from the contract.
    function withdraw(uint amount) external onlyOwner {
        msg.sender.transfer(amount);
    }

    // Withdraws ERC20 tokens from the contract.
    function withdrawErc20(address erc20Address, uint amount) external onlyOwner {
        IERC20 erc20 = IERC20(erc20Address);
        require(erc20.transfer(msg.sender, amount));
    }
}

// This interface allows contracts to query a verified, trusted price.
interface OracleInterface {
    // Requests the Oracle price for an identifier at a time. Returns the time at which a price will be available.
    // Returns 0 is the price is available now, and returns 2^256-1 if the price will never be available.  Reverts if
    // the Oracle doesn't support this identifier. Only contracts registered in the Registry are authorized to call this
    // method.
    function requestPrice(bytes32 identifier, uint time) external returns (uint expectedTime);

    // Checks whether a price has been resolved.
    function hasPrice(bytes32 identifier, uint time) external view returns (bool hasPriceAvailable);

    // Returns the Oracle price for identifier at a time. Reverts if the Oracle doesn't support this identifier or if
    // the Oracle doesn't have a price for this time. Only contracts registered in the Registry are authorized to call
    // this method.
    function getPrice(bytes32 identifier, uint time) external view returns (int price);

    // Returns whether the Oracle provides verified prices for the given identifier.
    function isIdentifierSupported(bytes32 identifier) external view returns (bool isSupported);

    // An event fired when a request for a (identifier, time) pair is made.
    event VerifiedPriceRequested(bytes32 indexed identifier, uint indexed time);

    // An event fired when a verified price is available for a (identifier, time) pair.
    event VerifiedPriceAvailable(bytes32 indexed identifier, uint indexed time, int price);
}

interface RegistryInterface {
    struct RegisteredDerivative {
        address derivativeAddress;
        address derivativeCreator;
    }

    // Registers a new derivative. Only authorized derivative creators can call this method.
    function registerDerivative(address[] calldata counterparties, address derivativeAddress) external;

    // Adds a new derivative creator to this list of authorized creators. Only the owner of this contract can call
    // this method.   
    function addDerivativeCreator(address derivativeCreator) external;

    // Removes a derivative creator to this list of authorized creators. Only the owner of this contract can call this
    // method.  
    function removeDerivativeCreator(address derivativeCreator) external;

    // Returns whether the derivative has been registered with the registry (and is therefore an authorized participant
    // in the UMA system).
    function isDerivativeRegistered(address derivative) external view returns (bool isRegistered);

    // Returns a list of all derivatives that are associated with a particular party.
    function getRegisteredDerivatives(address party) external view returns (RegisteredDerivative[] memory derivatives);

    // Returns all registered derivatives.
    function getAllRegisteredDerivatives() external view returns (RegisteredDerivative[] memory derivatives);

    // Returns whether an address is authorized to register new derivatives.
    function isDerivativeCreatorAuthorized(address derivativeCreator) external view returns (bool isAuthorized);
}

contract Registry is RegistryInterface, Withdrawable {

    using SafeMath for uint;

    // Array of all registeredDerivatives that are approved to use the UMA Oracle.
    RegisteredDerivative[] private registeredDerivatives;

    // This enum is required because a WasValid state is required to ensure that derivatives cannot be re-registered.
    enum PointerValidity {
        Invalid,
        Valid,
        WasValid
    }

    struct Pointer {
        PointerValidity valid;
        uint128 index;
    }

    // Maps from derivative address to a pointer that refers to that RegisteredDerivative in registeredDerivatives.
    mapping(address => Pointer) private derivativePointers;

    // Note: this must be stored outside of the RegisteredDerivative because mappings cannot be deleted and copied
    // like normal data. This could be stored in the Pointer struct, but storing it there would muddy the purpose
    // of the Pointer struct and break separation of concern between referential data and data.
    struct PartiesMap {
        mapping(address => bool) parties;
    }

    // Maps from derivative address to the set of parties that are involved in that derivative.
    mapping(address => PartiesMap) private derivativesToParties;

    // Maps from derivative creator address to whether that derivative creator has been approved to register contracts.
    mapping(address => bool) private derivativeCreators;

    modifier onlyApprovedDerivativeCreator {
        require(derivativeCreators[msg.sender]);
        _;
    }

    function registerDerivative(address[] calldata parties, address derivativeAddress)
        external
        onlyApprovedDerivativeCreator
    {
        // Create derivative pointer.
        Pointer storage pointer = derivativePointers[derivativeAddress];

        // Ensure that the pointer was not valid in the past (derivatives cannot be re-registered or double
        // registered).
        require(pointer.valid == PointerValidity.Invalid);
        pointer.valid = PointerValidity.Valid;

        registeredDerivatives.push(RegisteredDerivative(derivativeAddress, msg.sender));

        // No length check necessary because we should never hit (2^127 - 1) derivatives.
        pointer.index = uint128(registeredDerivatives.length.sub(1));

        // Set up PartiesMap for this derivative.
        PartiesMap storage partiesMap = derivativesToParties[derivativeAddress];
        for (uint i = 0; i < parties.length; i = i.add(1)) {
            partiesMap.parties[parties[i]] = true;
        }

        address[] memory partiesForEvent = parties;
        emit RegisterDerivative(derivativeAddress, partiesForEvent);
    }

    function addDerivativeCreator(address derivativeCreator) external onlyOwner {
        if (!derivativeCreators[derivativeCreator]) {
            derivativeCreators[derivativeCreator] = true;
            emit AddDerivativeCreator(derivativeCreator);
        }
    }

    function removeDerivativeCreator(address derivativeCreator) external onlyOwner {
        if (derivativeCreators[derivativeCreator]) {
            derivativeCreators[derivativeCreator] = false;
            emit RemoveDerivativeCreator(derivativeCreator);
        }
    }

    function isDerivativeRegistered(address derivative) external view returns (bool isRegistered) {
        return derivativePointers[derivative].valid == PointerValidity.Valid;
    }

    function getRegisteredDerivatives(address party) external view returns (RegisteredDerivative[] memory derivatives) {
        // This is not ideal - we must statically allocate memory arrays. To be safe, we make a temporary array as long
        // as registeredDerivatives. We populate it with any derivatives that involve the provided party. Then, we copy
        // the array over to the return array, which is allocated using the correct size. Note: this is done by double
        // copying each value rather than storing some referential info (like indices) in memory to reduce the number
        // of storage reads. This is because storage reads are far more expensive than extra memory space (~100:1).
        RegisteredDerivative[] memory tmpDerivativeArray = new RegisteredDerivative[](registeredDerivatives.length);
        uint outputIndex = 0;
        for (uint i = 0; i < registeredDerivatives.length; i = i.add(1)) {
            RegisteredDerivative storage derivative = registeredDerivatives[i];
            if (derivativesToParties[derivative.derivativeAddress].parties[party]) {
                // Copy selected derivative to the temporary array.
                tmpDerivativeArray[outputIndex] = derivative;
                outputIndex = outputIndex.add(1);
            }
        }

        // Copy the temp array to the return array that is set to the correct size.
        derivatives = new RegisteredDerivative[](outputIndex);
        for (uint j = 0; j < outputIndex; j = j.add(1)) {
            derivatives[j] = tmpDerivativeArray[j];
        }
    }

    function getAllRegisteredDerivatives() external view returns (RegisteredDerivative[] memory derivatives) {
        return registeredDerivatives;
    }

    function isDerivativeCreatorAuthorized(address derivativeCreator) external view returns (bool isAuthorized) {
        return derivativeCreators[derivativeCreator];
    }

    event RegisterDerivative(address indexed derivativeAddress, address[] parties);
    event AddDerivativeCreator(address indexed addedDerivativeCreator);
    event RemoveDerivativeCreator(address indexed removedDerivativeCreator);

}

contract Testable is Ownable {

    // Is the contract being run on the test network. Note: this variable should be set on construction and never
    // modified.
    bool public isTest;

    uint private currentTime;

    constructor(bool _isTest) internal {
        isTest = _isTest;
        if (_isTest) {
            currentTime = now; // solhint-disable-line not-rely-on-time
        }
    }

    modifier onlyIfTest {
        require(isTest);
        _;
    }

    function setCurrentTime(uint _time) external onlyOwner onlyIfTest {
        currentTime = _time;
    }

    function getCurrentTime() public view returns (uint) {
        if (isTest) {
            return currentTime;
        } else {
            return now; // solhint-disable-line not-rely-on-time
        }
    }
}

contract ContractCreator is Withdrawable {
    Registry internal registry;
    address internal oracleAddress;
    address internal storeAddress;
    address internal priceFeedAddress;

    constructor(address registryAddress, address _oracleAddress, address _storeAddress, address _priceFeedAddress)
        public
    {
        registry = Registry(registryAddress);
        oracleAddress = _oracleAddress;
        storeAddress = _storeAddress;
        priceFeedAddress = _priceFeedAddress;
    }

    function _registerContract(address[] memory parties, address contractToRegister) internal {
        registry.registerDerivative(parties, contractToRegister);
    }
}

library TokenizedDerivativeParams {
    enum ReturnType {
        Linear,
        Compound
    }

    struct ConstructorParams {
        address sponsor;
        address admin;
        address oracle;
        address store;
        address priceFeed;
        uint defaultPenalty; // Percentage of margin requirement * 10^18
        uint supportedMove; // Expected percentage move in the underlying price that the long is protected against.
        bytes32 product;
        uint fixedYearlyFee; // Percentage of nav * 10^18
        uint disputeDeposit; // Percentage of margin requirement * 10^18
        address returnCalculator;
        uint startingTokenPrice;
        uint expiry;
        address marginCurrency;
        uint withdrawLimit; // Percentage of derivativeStorage.shortBalance * 10^18
        ReturnType returnType;
        uint startingUnderlyingPrice;
        uint creationTime;
    }
}

// TokenizedDerivativeStorage: this library name is shortened due to it being used so often.
library TDS {
        enum State {
        // The contract is active, and tokens can be created and redeemed. Margin can be added and withdrawn (as long as
        // it exceeds required levels). Remargining is allowed. Created contracts immediately begin in this state.
        // Possible state transitions: Disputed, Expired, Defaulted.
        Live,

        // Disputed, Expired, Defaulted, and Emergency are Frozen states. In a Frozen state, the contract is frozen in
        // time awaiting a resolution by the Oracle. No tokens can be created or redeemed. Margin cannot be withdrawn.
        // The resolution of these states moves the contract to the Settled state. Remargining is not allowed.

        // The derivativeStorage.externalAddresses.sponsor has disputed the price feed output. If the dispute is valid (i.e., the NAV calculated from the
        // Oracle price differs from the NAV calculated from the price feed), the dispute fee is added to the short
        // account. Otherwise, the dispute fee is added to the long margin account.
        // Possible state transitions: Settled.
        Disputed,

        // Contract expiration has been reached.
        // Possible state transitions: Settled.
        Expired,

        // The short margin account is below its margin requirement. The derivativeStorage.externalAddresses.sponsor can choose to confirm the default and
        // move to Settle without waiting for the Oracle. Default penalties will be assessed when the contract moves to
        // Settled.
        // Possible state transitions: Settled.
        Defaulted,

        // UMA has manually triggered a shutdown of the account.
        // Possible state transitions: Settled.
        Emergency,

        // Token price is fixed. Tokens can be redeemed by anyone. All short margin can be withdrawn. Tokens can't be
        // created, and contract can't remargin.
        // Possible state transitions: None.
        Settled
    }

    // The state of the token at a particular time. The state gets updated on remargin.
    struct TokenState {
        int underlyingPrice;
        int tokenPrice;
        uint time;
    }

    // The information in the following struct is only valid if in the midst of a Dispute.
    struct Dispute {
        int disputedNav;
        uint deposit;
    }

    struct WithdrawThrottle {
        uint startTime;
        uint remainingWithdrawal;
    }

    struct FixedParameters {
        // Fixed contract parameters.
        uint defaultPenalty; // Percentage of margin requirement * 10^18
        uint supportedMove; // Expected percentage move that the long is protected against.
        uint disputeDeposit; // Percentage of margin requirement * 10^18
        uint fixedFeePerSecond; // Percentage of nav*10^18
        uint withdrawLimit; // Percentage of derivativeStorage.shortBalance*10^18
        bytes32 product;
        TokenizedDerivativeParams.ReturnType returnType;
        uint initialTokenUnderlyingRatio;
        uint creationTime;
        string symbol;
    }

    struct ExternalAddresses {
        // Other addresses/contracts
        address sponsor;
        address admin;
        address apDelegate;
        OracleInterface oracle;
        StoreInterface store;
        PriceFeedInterface priceFeed;
        ReturnCalculatorInterface returnCalculator;
        IERC20 marginCurrency;
    }

    struct Storage {
        FixedParameters fixedParameters;
        ExternalAddresses externalAddresses;

        // Balances
        int shortBalance;
        int longBalance;

        State state;
        uint endTime;

        // The NAV of the contract always reflects the transition from (`prev`, `current`).
        // In the case of a remargin, a `latest` price is retrieved from the price feed, and we shift `current` -> `prev`
        // and `latest` -> `current` (and then recompute).
        // In the case of a dispute, `current` might change (which is why we have to hold on to `prev`).
        TokenState referenceTokenState;
        TokenState currentTokenState;

        int nav;  // Net asset value is measured in Wei

        Dispute disputeInfo;

        // Only populated once the contract enters a frozen state.
        int defaultPenaltyAmount;

        WithdrawThrottle withdrawThrottle;
    }
}

library TokenizedDerivativeUtils {
    using TokenizedDerivativeUtils for TDS.Storage;
    using SafeMath for uint;
    using SignedSafeMath for int;

    uint private constant SECONDS_PER_DAY = 86400;
    uint private constant SECONDS_PER_YEAR = 31536000;
    uint private constant INT_MAX = 2**255 - 1;
    uint private constant UINT_FP_SCALING_FACTOR = 10**18;
    int private constant INT_FP_SCALING_FACTOR = 10**18;

    modifier onlySponsor(TDS.Storage storage s) {
        require(msg.sender == s.externalAddresses.sponsor);
        _;
    }

    modifier onlyAdmin(TDS.Storage storage s) {
        require(msg.sender == s.externalAddresses.admin);
        _;
    }

    modifier onlySponsorOrAdmin(TDS.Storage storage s) {
        require(msg.sender == s.externalAddresses.sponsor || msg.sender == s.externalAddresses.admin);
        _;
    }

    modifier onlySponsorOrApDelegate(TDS.Storage storage s) {
        require(msg.sender == s.externalAddresses.sponsor || msg.sender == s.externalAddresses.apDelegate);
        _;
    }

    // Contract initializer. Should only be called at construction.
    // Note: Must be a public function because structs cannot be passed as calldata (required data type for external
    // functions).
    function _initialize(
        TDS.Storage storage s, TokenizedDerivativeParams.ConstructorParams memory params, string memory symbol) public {

        s._setFixedParameters(params, symbol);
        s._setExternalAddresses(params);
        
        // Keep the starting token price relatively close to FP_SCALING_FACTOR to prevent users from unintentionally
        // creating rounding or overflow errors.
        require(params.startingTokenPrice >= UINT_FP_SCALING_FACTOR.div(10**9));
        require(params.startingTokenPrice <= UINT_FP_SCALING_FACTOR.mul(10**9));

        // TODO(mrice32): we should have an ideal start time rather than blindly polling.
        (uint latestTime, int latestUnderlyingPrice) = s.externalAddresses.priceFeed.latestPrice(s.fixedParameters.product);

        // If nonzero, take the user input as the starting price.
        if (params.startingUnderlyingPrice != 0) {
            latestUnderlyingPrice = _safeIntCast(params.startingUnderlyingPrice);
        }

        require(latestUnderlyingPrice > 0);
        require(latestTime != 0);

        // Keep the ratio in case it's needed for margin computation.
        s.fixedParameters.initialTokenUnderlyingRatio = params.startingTokenPrice.mul(UINT_FP_SCALING_FACTOR).div(_safeUintCast(latestUnderlyingPrice));
        require(s.fixedParameters.initialTokenUnderlyingRatio != 0);

        // Set end time to max value of uint to implement no expiry.
        if (params.expiry == 0) {
            s.endTime = ~uint(0);
        } else {
            require(params.expiry >= latestTime);
            s.endTime = params.expiry;
        }

        s.nav = s._computeInitialNav(latestUnderlyingPrice, latestTime, params.startingTokenPrice);

        s.state = TDS.State.Live;
    }

    function _depositAndCreateTokens(TDS.Storage storage s, uint marginForPurchase, uint tokensToPurchase) external onlySponsorOrApDelegate(s) {
        s._remarginInternal();

        int newTokenNav = _computeNavForTokens(s.currentTokenState.tokenPrice, tokensToPurchase);

        if (newTokenNav < 0) {
            newTokenNav = 0;
        }

        uint positiveTokenNav = _safeUintCast(newTokenNav);

        // Get any refund due to sending more margin than the argument indicated (should only be able to happen in the
        // ETH case).
        uint refund = s._pullSentMargin(marginForPurchase);

        // Subtract newTokenNav from amount sent.
        uint depositAmount = marginForPurchase.sub(positiveTokenNav);

        // Deposit additional margin into the short account.
        s._depositInternal(depositAmount);

        // The _createTokensInternal call returns any refund due to the amount sent being larger than the amount
        // required to purchase the tokens, so we add that to the running refund. This should be 0 in this case,
        // but we leave this here in case of some refund being generated due to rounding errors or any bugs to ensure
        // the sender never loses money.
        refund = refund.add(s._createTokensInternal(tokensToPurchase, positiveTokenNav));

        // Send the accumulated refund.
        s._sendMargin(refund);
    }

    function _redeemTokens(TDS.Storage storage s, uint tokensToRedeem) external {
        require(s.state == TDS.State.Live || s.state == TDS.State.Settled);
        require(tokensToRedeem > 0);

        if (s.state == TDS.State.Live) {
            require(msg.sender == s.externalAddresses.sponsor || msg.sender == s.externalAddresses.apDelegate);
            s._remarginInternal();
            require(s.state == TDS.State.Live);
        }

        ExpandedIERC20 thisErc20Token = ExpandedIERC20(address(this));

        uint initialSupply = _totalSupply();
        require(initialSupply > 0);

        _pullAuthorizedTokens(thisErc20Token, tokensToRedeem);
        thisErc20Token.burn(tokensToRedeem);
        emit TokensRedeemed(s.fixedParameters.symbol, tokensToRedeem);

        // Value of the tokens is just the percentage of all the tokens multiplied by the balance of the investor
        // margin account.
        uint tokenPercentage = tokensToRedeem.mul(UINT_FP_SCALING_FACTOR).div(initialSupply);
        uint tokenMargin = _takePercentage(_safeUintCast(s.longBalance), tokenPercentage);

        s.longBalance = s.longBalance.sub(_safeIntCast(tokenMargin));
        assert(s.longBalance >= 0);
        s.nav = _computeNavForTokens(s.currentTokenState.tokenPrice, _totalSupply());

        s._sendMargin(tokenMargin);
    }

    function _dispute(TDS.Storage storage s, uint depositMargin) external onlySponsor(s) {
        require(
            s.state == TDS.State.Live,
            "Contract must be Live to dispute"
        );

        uint requiredDeposit = _safeUintCast(_takePercentage(s._getRequiredMargin(s.currentTokenState), s.fixedParameters.disputeDeposit));

        uint sendInconsistencyRefund = s._pullSentMargin(depositMargin);

        require(depositMargin >= requiredDeposit);
        uint overpaymentRefund = depositMargin.sub(requiredDeposit);

        s.state = TDS.State.Disputed;
        s.endTime = s.currentTokenState.time;
        s.disputeInfo.disputedNav = s.nav;
        s.disputeInfo.deposit = requiredDeposit;

        // Store the default penalty in case the dispute pushes the sponsor into default.
        s.defaultPenaltyAmount = s._computeDefaultPenalty();
        emit Disputed(s.fixedParameters.symbol, s.endTime, s.nav);

        s._requestOraclePrice(s.endTime);

        // Add the two types of refunds:
        // 1. The refund for ETH sent if it was > depositMargin.
        // 2. The refund for depositMargin > requiredDeposit.
        s._sendMargin(sendInconsistencyRefund.add(overpaymentRefund));
    }

    function _withdraw(TDS.Storage storage s, uint amount) external onlySponsor(s) {
        // Remargin before allowing a withdrawal, but only if in the live state.
        if (s.state == TDS.State.Live) {
            s._remarginInternal();
        }

        // Make sure either in Live or Settled after any necessary remargin.
        require(s.state == TDS.State.Live || s.state == TDS.State.Settled);

        // If the contract has been settled or is in prefunded state then can
        // withdraw up to full balance. If the contract is in live state then
        // must leave at least the required margin. Not allowed to withdraw in
        // other states.
        int withdrawableAmount;
        if (s.state == TDS.State.Settled) {
            withdrawableAmount = s.shortBalance;
        } else {
            // Update throttling snapshot and verify that this withdrawal doesn't go past the throttle limit.
            uint currentTime = s.currentTokenState.time;
            if (s.withdrawThrottle.startTime <= currentTime.sub(SECONDS_PER_DAY)) {
                // We've passed the previous s.withdrawThrottle window. Start new one.
                s.withdrawThrottle.startTime = currentTime;
                s.withdrawThrottle.remainingWithdrawal = _takePercentage(_safeUintCast(s.shortBalance), s.fixedParameters.withdrawLimit);
            }

            int marginMaxWithdraw = s.shortBalance.sub(s._getRequiredMargin(s.currentTokenState));
            int throttleMaxWithdraw = _safeIntCast(s.withdrawThrottle.remainingWithdrawal);

            // Take the smallest of the two withdrawal limits.
            withdrawableAmount = throttleMaxWithdraw < marginMaxWithdraw ? throttleMaxWithdraw : marginMaxWithdraw;

            // Note: this line alone implicitly ensures the withdrawal throttle is not violated, but the above
            // ternary is more explicit.
            s.withdrawThrottle.remainingWithdrawal = s.withdrawThrottle.remainingWithdrawal.sub(amount);
        }

        // Can only withdraw the allowed amount.
        require(
            withdrawableAmount >= _safeIntCast(amount),
            "Attempting to withdraw more than allowed"
        );

        // Transfer amount - Note: important to `-=` before the send so that the
        // function can not be called multiple times while waiting for transfer
        // to return.
        s.shortBalance = s.shortBalance.sub(_safeIntCast(amount));
        emit Withdrawal(s.fixedParameters.symbol, amount);
        s._sendMargin(amount);
    }

    function _acceptPriceAndSettle(TDS.Storage storage s) external onlySponsor(s) {
        // Right now, only confirming prices in the defaulted state.
        require(s.state == TDS.State.Defaulted);

        // Remargin on agreed upon price.
        s._settleAgreedPrice();
    }

    function _setApDelegate(TDS.Storage storage s, address _apDelegate) external onlySponsor(s) {
        s.externalAddresses.apDelegate = _apDelegate;
    }

    // Moves the contract into the Emergency state, where it waits on an Oracle price for the most recent remargin time.
    function _emergencyShutdown(TDS.Storage storage s) external onlyAdmin(s) {
        require(s.state == TDS.State.Live);
        s.state = TDS.State.Emergency;
        s.endTime = s.currentTokenState.time;
        s.defaultPenaltyAmount = s._computeDefaultPenalty();
        emit EmergencyShutdownTransition(s.fixedParameters.symbol, s.endTime);
        s._requestOraclePrice(s.endTime);
    }

    function _settle(TDS.Storage storage s) external {
        s._settleInternal();
    }

    function _createTokens(TDS.Storage storage s, uint marginForPurchase, uint tokensToPurchase) external onlySponsorOrApDelegate(s) {
        // Returns any refund due to sending more margin than the argument indicated (should only be able to happen in
        // the ETH case).
        uint refund = s._pullSentMargin(marginForPurchase);

        // The _createTokensInternal call returns any refund due to the amount sent being larger than the amount
        // required to purchase the tokens, so we add that to the running refund.
        refund = refund.add(s._createTokensInternal(tokensToPurchase, marginForPurchase));

        // Send the accumulated refund.
        s._sendMargin(refund);
    }

    function _deposit(TDS.Storage storage s, uint marginToDeposit) external onlySponsor(s) {
        // Only allow the s.externalAddresses.sponsor to deposit margin.
        uint refund = s._pullSentMargin(marginToDeposit);
        s._depositInternal(marginToDeposit);

        // Send any refund due to sending more margin than the argument indicated (should only be able to happen in the
        // ETH case).
        s._sendMargin(refund);
    }

    // Returns the expected net asset value (NAV) of the contract using the latest available Price Feed price.
    function _calcNAV(TDS.Storage storage s) external view returns (int navNew) {
        (TDS.TokenState memory newTokenState, ) = s._calcNewTokenStateAndBalance();
        navNew = _computeNavForTokens(newTokenState.tokenPrice, _totalSupply());
    }

    // Returns the expected value of each the outstanding tokens of the contract using the latest available Price Feed
    // price.
    function _calcTokenValue(TDS.Storage storage s) external view returns (int newTokenValue) {
        (TDS.TokenState memory newTokenState,) = s._calcNewTokenStateAndBalance();
        newTokenValue = newTokenState.tokenPrice;
    }

    // Returns the expected balance of the short margin account using the latest available Price Feed price.
    function _calcShortMarginBalance(TDS.Storage storage s) external view returns (int newShortMarginBalance) {
        (, newShortMarginBalance) = s._calcNewTokenStateAndBalance();
    }

    function _calcExcessMargin(TDS.Storage storage s) external view returns (int newExcessMargin) {
        (TDS.TokenState memory newTokenState, int newShortMarginBalance) = s._calcNewTokenStateAndBalance();
        // If the contract is in/will be moved to a settled state, the margin requirement will be 0.
        int requiredMargin = newTokenState.time >= s.endTime ? 0 : s._getRequiredMargin(newTokenState);
        return newShortMarginBalance.sub(requiredMargin);
    }

    function _getCurrentRequiredMargin(TDS.Storage storage s) external view returns (int requiredMargin) {
        if (s.state == TDS.State.Settled) {
            // No margin needs to be maintained when the contract is settled.
            return 0;
        }

         return s._getRequiredMargin(s.currentTokenState);
    }

    function _canBeSettled(TDS.Storage storage s) external view returns (bool canBeSettled) {
        TDS.State currentState = s.state;

        if (currentState == TDS.State.Settled) {
            return false;
        }

        // Technically we should also check if price will default the contract, but that isn't a normal flow of
        // operations that we want to simulate: we want to discourage the sponsor remargining into a default.
        (uint priceFeedTime, ) = s._getLatestPrice();
        if (currentState == TDS.State.Live && (priceFeedTime < s.endTime)) {
            return false;
        }

        return s.externalAddresses.oracle.hasPrice(s.fixedParameters.product, s.endTime);
    }

    function _getUpdatedUnderlyingPrice(TDS.Storage storage s) external view returns (int underlyingPrice, uint time) {
        (TDS.TokenState memory newTokenState, ) = s._calcNewTokenStateAndBalance();
        return (newTokenState.underlyingPrice, newTokenState.time);
    }

    function _calcNewTokenStateAndBalance(TDS.Storage storage s) internal view returns (TDS.TokenState memory newTokenState, int newShortMarginBalance)
    {
        // TODO: there's a lot of repeated logic in this method from elsewhere in the contract. It should be extracted
        // so the logic can be written once and used twice. However, much of this was written post-audit, so it was
        // deemed preferable not to modify any state changing code that could potentially introduce new security
        // bugs. This should be done before the next contract audit.

        if (s.state == TDS.State.Settled) {
            // If the contract is Settled, just return the current contract state.
            return (s.currentTokenState, s.shortBalance);
        }

        // Grab the price feed pricetime.
        (uint priceFeedTime, int priceFeedPrice) = s._getLatestPrice();

        bool isContractLive = s.state == TDS.State.Live;
        bool isContractPostExpiry = priceFeedTime >= s.endTime;

        // If the time hasn't advanced since the last remargin, short circuit and return the most recently computed values.
        if (isContractLive && priceFeedTime <= s.currentTokenState.time) {
            return (s.currentTokenState, s.shortBalance);
        }

        // Determine which previous price state to use when computing the new NAV.
        // If the contract is live, we use the reference for the linear return type or if the contract will immediately
        // move to expiry. 
        bool shouldUseReferenceTokenState = isContractLive &&
            (s.fixedParameters.returnType == TokenizedDerivativeParams.ReturnType.Linear || isContractPostExpiry);
        TDS.TokenState memory lastTokenState = shouldUseReferenceTokenState ? s.referenceTokenState : s.currentTokenState;

        // Use the oracle settlement price/time if the contract is frozen or will move to expiry on the next remargin.
        (uint recomputeTime, int recomputePrice) = !isContractLive || isContractPostExpiry ?
            (s.endTime, s.externalAddresses.oracle.getPrice(s.fixedParameters.product, s.endTime)) :
            (priceFeedTime, priceFeedPrice);

        // Init the returned short balance to the current short balance.
        newShortMarginBalance = s.shortBalance;

        // Subtract the oracle fees from the short balance.
        newShortMarginBalance = isContractLive ?
            newShortMarginBalance.sub(
                _safeIntCast(s._computeExpectedOracleFees(s.currentTokenState.time, recomputeTime))) :
            newShortMarginBalance;

        // Compute the new NAV
        newTokenState = s._computeNewTokenState(lastTokenState, recomputePrice, recomputeTime);
        int navNew = _computeNavForTokens(newTokenState.tokenPrice, _totalSupply());
        newShortMarginBalance = newShortMarginBalance.sub(_getLongDiff(navNew, s.longBalance, newShortMarginBalance));

        // If the contract is frozen or will move into expiry, we need to settle it, which means adding the default
        // penalty and dispute deposit if necessary.
        if (!isContractLive || isContractPostExpiry) {
            // Subtract default penalty (if necessary) from the short balance.
            bool inDefault = !s._satisfiesMarginRequirement(newShortMarginBalance, newTokenState);
            if (inDefault) {
                int expectedDefaultPenalty = isContractLive ? s._computeDefaultPenalty() : s._getDefaultPenalty();
                int defaultPenalty = (newShortMarginBalance < expectedDefaultPenalty) ?
                    newShortMarginBalance :
                    expectedDefaultPenalty;
                newShortMarginBalance = newShortMarginBalance.sub(defaultPenalty);
            }

            // Add the dispute deposit to the short balance if necessary.
            if (s.state == TDS.State.Disputed && navNew != s.disputeInfo.disputedNav) {
                int depositValue = _safeIntCast(s.disputeInfo.deposit);
                newShortMarginBalance = newShortMarginBalance.add(depositValue);
            }
        }
    }

    function _computeInitialNav(TDS.Storage storage s, int latestUnderlyingPrice, uint latestTime, uint startingTokenPrice)
        internal
        returns (int navNew)
    {
        int unitNav = _safeIntCast(startingTokenPrice);
        s.referenceTokenState = TDS.TokenState(latestUnderlyingPrice, unitNav, latestTime);
        s.currentTokenState = TDS.TokenState(latestUnderlyingPrice, unitNav, latestTime);
        // Starting NAV is always 0 in the TokenizedDerivative case.
        navNew = 0;
    }

    function _remargin(TDS.Storage storage s) external onlySponsorOrAdmin(s) {
        s._remarginInternal();
    }

    function _withdrawUnexpectedErc20(TDS.Storage storage s, address erc20Address, uint amount) external onlySponsor(s) {
        if(address(s.externalAddresses.marginCurrency) == erc20Address) {
            uint currentBalance = s.externalAddresses.marginCurrency.balanceOf(address(this));
            int totalBalances = s.shortBalance.add(s.longBalance);
            assert(totalBalances >= 0);
            uint withdrawableAmount = currentBalance.sub(_safeUintCast(totalBalances)).sub(s.disputeInfo.deposit);
            require(withdrawableAmount >= amount);
        }

        IERC20 erc20 = IERC20(erc20Address);
        require(erc20.transfer(msg.sender, amount));
    }

    function _setExternalAddresses(TDS.Storage storage s, TokenizedDerivativeParams.ConstructorParams memory params) internal {

        // Note: not all "ERC20" tokens conform exactly to this interface (BNB, OMG, etc). The most common way that
        // tokens fail to conform is that they do not return a bool from certain state-changing operations. This
        // contract was not designed to work with those tokens because of the additional complexity they would
        // introduce.
        s.externalAddresses.marginCurrency = IERC20(params.marginCurrency);

        s.externalAddresses.oracle = OracleInterface(params.oracle);
        s.externalAddresses.store = StoreInterface(params.store);
        s.externalAddresses.priceFeed = PriceFeedInterface(params.priceFeed);
        s.externalAddresses.returnCalculator = ReturnCalculatorInterface(params.returnCalculator);

        // Verify that the price feed and s.externalAddresses.oracle support the given s.fixedParameters.product.
        require(s.externalAddresses.oracle.isIdentifierSupported(params.product));
        require(s.externalAddresses.priceFeed.isIdentifierSupported(params.product));

        s.externalAddresses.sponsor = params.sponsor;
        s.externalAddresses.admin = params.admin;
    }

    function _setFixedParameters(TDS.Storage storage s, TokenizedDerivativeParams.ConstructorParams memory params, string memory symbol) internal {
        // Ensure only valid enum values are provided.
        require(params.returnType == TokenizedDerivativeParams.ReturnType.Compound
            || params.returnType == TokenizedDerivativeParams.ReturnType.Linear);

        // Fee must be 0 if the returnType is linear.
        require(params.returnType == TokenizedDerivativeParams.ReturnType.Compound || params.fixedYearlyFee == 0);

        // The default penalty must be less than the required margin.
        require(params.defaultPenalty <= UINT_FP_SCALING_FACTOR);

        s.fixedParameters.returnType = params.returnType;
        s.fixedParameters.defaultPenalty = params.defaultPenalty;
        s.fixedParameters.product = params.product;
        s.fixedParameters.fixedFeePerSecond = params.fixedYearlyFee.div(SECONDS_PER_YEAR);
        s.fixedParameters.disputeDeposit = params.disputeDeposit;
        s.fixedParameters.supportedMove = params.supportedMove;
        s.fixedParameters.withdrawLimit = params.withdrawLimit;
        s.fixedParameters.creationTime = params.creationTime;
        s.fixedParameters.symbol = symbol;
    }

    // _remarginInternal() allows other functions to call remargin internally without satisfying permission checks for
    // _remargin().
    function _remarginInternal(TDS.Storage storage s) internal {
        // If the state is not live, remargining does not make sense.
        require(s.state == TDS.State.Live);

        (uint latestTime, int latestPrice) = s._getLatestPrice();
        // Checks whether contract has ended.
        if (latestTime <= s.currentTokenState.time) {
            // If the price feed hasn't advanced, remargining should be a no-op.
            return;
        }

        // Save the penalty using the current state in case it needs to be used.
        int potentialPenaltyAmount = s._computeDefaultPenalty();

        if (latestTime >= s.endTime) {
            s.state = TDS.State.Expired;
            emit Expired(s.fixedParameters.symbol, s.endTime);

            // Applies the same update a second time to effectively move the current state to the reference state.
            int recomputedNav = s._computeNav(s.currentTokenState.underlyingPrice, s.currentTokenState.time);
            assert(recomputedNav == s.nav);

            uint feeAmount = s._deductOracleFees(s.currentTokenState.time, s.endTime);

            // Save the precomputed default penalty in case the expiry price pushes the sponsor into default.
            s.defaultPenaltyAmount = potentialPenaltyAmount;

            // We have no idea what the price was, exactly at s.endTime, so we can't set
            // s.currentTokenState, or update the nav, or do anything.
            s._requestOraclePrice(s.endTime);
            s._payOracleFees(feeAmount);
            return;
        }
        uint feeAmount = s._deductOracleFees(s.currentTokenState.time, latestTime);

        // Update nav of contract.
        int navNew = s._computeNav(latestPrice, latestTime);

        // Update the balances of the contract.
        s._updateBalances(navNew);

        // Make sure contract has not moved into default.
        bool inDefault = !s._satisfiesMarginRequirement(s.shortBalance, s.currentTokenState);
        if (inDefault) {
            s.state = TDS.State.Defaulted;
            s.defaultPenaltyAmount = potentialPenaltyAmount;
            s.endTime = latestTime; // Change end time to moment when default occurred.
            emit Default(s.fixedParameters.symbol, latestTime, s.nav);
            s._requestOraclePrice(latestTime);
        }

        s._payOracleFees(feeAmount);
    }

    function _createTokensInternal(TDS.Storage storage s, uint tokensToPurchase, uint navSent) internal returns (uint refund) {
        s._remarginInternal();

        // Verify that remargining didn't push the contract into expiry or default.
        require(s.state == TDS.State.Live);

        int purchasedNav = _computeNavForTokens(s.currentTokenState.tokenPrice, tokensToPurchase);

        if (purchasedNav < 0) {
            purchasedNav = 0;
        }

        // Ensures that requiredNav >= navSent.
        refund = navSent.sub(_safeUintCast(purchasedNav));

        s.longBalance = s.longBalance.add(purchasedNav);

        ExpandedIERC20 thisErc20Token = ExpandedIERC20(address(this));

        thisErc20Token.mint(msg.sender, tokensToPurchase);
        emit TokensCreated(s.fixedParameters.symbol, tokensToPurchase);

        s.nav = _computeNavForTokens(s.currentTokenState.tokenPrice, _totalSupply());

        // Make sure this still satisfies the margin requirement.
        require(s._satisfiesMarginRequirement(s.shortBalance, s.currentTokenState));
    }

    function _depositInternal(TDS.Storage storage s, uint value) internal {
        // Make sure that we are in a "depositable" state.
        require(s.state == TDS.State.Live);
        s.shortBalance = s.shortBalance.add(_safeIntCast(value));
        emit Deposited(s.fixedParameters.symbol, value);
    }

    function _settleInternal(TDS.Storage storage s) internal {
        TDS.State startingState = s.state;
        require(startingState == TDS.State.Disputed || startingState == TDS.State.Expired
                || startingState == TDS.State.Defaulted || startingState == TDS.State.Emergency);
        s._settleVerifiedPrice();
        if (startingState == TDS.State.Disputed) {
            int depositValue = _safeIntCast(s.disputeInfo.deposit);
            if (s.nav != s.disputeInfo.disputedNav) {
                s.shortBalance = s.shortBalance.add(depositValue);
            } else {
                s.longBalance = s.longBalance.add(depositValue);
            }
        }
    }

    // Deducts the fees from the margin account.
    function _deductOracleFees(TDS.Storage storage s, uint lastTimeOracleFeesPaid, uint currentTime) internal returns (uint feeAmount) {
        feeAmount = s._computeExpectedOracleFees(lastTimeOracleFeesPaid, currentTime);
        s.shortBalance = s.shortBalance.sub(_safeIntCast(feeAmount));
        // If paying the Oracle fee reduces the held margin below requirements, the rest of remargin() will default the
        // contract.
    }

    // Pays out the fees to the Oracle.
    function _payOracleFees(TDS.Storage storage s, uint feeAmount) internal {
        if (feeAmount == 0) {
            return;
        }

        if (address(s.externalAddresses.marginCurrency) == address(0x0)) {
            s.externalAddresses.store.payOracleFees.value(feeAmount)();
        } else {
            require(s.externalAddresses.marginCurrency.approve(address(s.externalAddresses.store), feeAmount));
            s.externalAddresses.store.payOracleFeesErc20(address(s.externalAddresses.marginCurrency));
        }
    }

    function _computeExpectedOracleFees(TDS.Storage storage s, uint lastTimeOracleFeesPaid, uint currentTime)
        internal
        view
        returns (uint feeAmount)
    {
        // The profit from corruption is set as the max(longBalance, shortBalance).
        int pfc = s.shortBalance < s.longBalance ? s.longBalance : s.shortBalance;
        uint expectedFeeAmount = s.externalAddresses.store.computeOracleFees(lastTimeOracleFeesPaid, currentTime, _safeUintCast(pfc));

        // Ensure the fee returned can actually be paid by the short margin account.
        uint shortBalance = _safeUintCast(s.shortBalance);
        return (shortBalance < expectedFeeAmount) ? shortBalance : expectedFeeAmount;
    }

    function _computeNewTokenState(TDS.Storage storage s,
        TDS.TokenState memory beginningTokenState, int latestUnderlyingPrice, uint recomputeTime)
        internal
        view
        returns (TDS.TokenState memory newTokenState)
    {
        int underlyingReturn = s.externalAddresses.returnCalculator.computeReturn(
            beginningTokenState.underlyingPrice, latestUnderlyingPrice);
        int tokenReturn = underlyingReturn.sub(
            _safeIntCast(s.fixedParameters.fixedFeePerSecond.mul(recomputeTime.sub(beginningTokenState.time))));
        int tokenMultiplier = tokenReturn.add(INT_FP_SCALING_FACTOR);
        
        // In the compound case, don't allow the token price to go below 0.
        if (s.fixedParameters.returnType == TokenizedDerivativeParams.ReturnType.Compound && tokenMultiplier < 0) {
            tokenMultiplier = 0;
        }

        int newTokenPrice = _takePercentage(beginningTokenState.tokenPrice, tokenMultiplier);
        newTokenState = TDS.TokenState(latestUnderlyingPrice, newTokenPrice, recomputeTime);
    }

    function _satisfiesMarginRequirement(TDS.Storage storage s, int balance, TDS.TokenState memory tokenState)
        internal
        view
        returns (bool doesSatisfyRequirement) 
    {
        return s._getRequiredMargin(tokenState) <= balance;
    }

    function _requestOraclePrice(TDS.Storage storage s, uint requestedTime) internal {
        uint expectedTime = s.externalAddresses.oracle.requestPrice(s.fixedParameters.product, requestedTime);
        if (expectedTime == 0) {
            // The Oracle price is already available, settle the contract right away.
            s._settleInternal();
        }
    }

    function _getLatestPrice(TDS.Storage storage s) internal view returns (uint latestTime, int latestUnderlyingPrice) {
        (latestTime, latestUnderlyingPrice) = s.externalAddresses.priceFeed.latestPrice(s.fixedParameters.product);
        require(latestTime != 0);
    }

    function _computeNav(TDS.Storage storage s, int latestUnderlyingPrice, uint latestTime) internal returns (int navNew) {
        if (s.fixedParameters.returnType == TokenizedDerivativeParams.ReturnType.Compound) {
            navNew = s._computeCompoundNav(latestUnderlyingPrice, latestTime);
        } else {
            assert(s.fixedParameters.returnType == TokenizedDerivativeParams.ReturnType.Linear);
            navNew = s._computeLinearNav(latestUnderlyingPrice, latestTime);
        }
    }

    function _computeCompoundNav(TDS.Storage storage s, int latestUnderlyingPrice, uint latestTime) internal returns (int navNew) {
        s.referenceTokenState = s.currentTokenState;
        s.currentTokenState = s._computeNewTokenState(s.currentTokenState, latestUnderlyingPrice, latestTime);
        navNew = _computeNavForTokens(s.currentTokenState.tokenPrice, _totalSupply());
        emit NavUpdated(s.fixedParameters.symbol, navNew, s.currentTokenState.tokenPrice);
    }

    function _computeLinearNav(TDS.Storage storage s, int latestUnderlyingPrice, uint latestTime) internal returns (int navNew) {
        // Only update the time - don't update the prices becuase all price changes are relative to the initial price.
        s.referenceTokenState.time = s.currentTokenState.time;
        s.currentTokenState = s._computeNewTokenState(s.referenceTokenState, latestUnderlyingPrice, latestTime);
        navNew = _computeNavForTokens(s.currentTokenState.tokenPrice, _totalSupply());
        emit NavUpdated(s.fixedParameters.symbol, navNew, s.currentTokenState.tokenPrice);
    }

    function _recomputeNav(TDS.Storage storage s, int oraclePrice, uint recomputeTime) internal returns (int navNew) {
        // We're updating `last` based on what the Oracle has told us.
        assert(s.endTime == recomputeTime);
        s.currentTokenState = s._computeNewTokenState(s.referenceTokenState, oraclePrice, recomputeTime);
        navNew = _computeNavForTokens(s.currentTokenState.tokenPrice, _totalSupply());
        emit NavUpdated(s.fixedParameters.symbol, navNew, s.currentTokenState.tokenPrice);
    }

    // Function is internally only called by `_settleAgreedPrice` or `_settleVerifiedPrice`. This function handles all 
    // of the settlement logic including assessing penalties and then moves the state to `Settled`.
    function _settleWithPrice(TDS.Storage storage s, int price) internal {

        // Remargin at whatever price we're using (verified or unverified).
        s._updateBalances(s._recomputeNav(price, s.endTime));

        bool inDefault = !s._satisfiesMarginRequirement(s.shortBalance, s.currentTokenState);

        if (inDefault) {
            int expectedDefaultPenalty = s._getDefaultPenalty();
            int penalty = (s.shortBalance < expectedDefaultPenalty) ?
                s.shortBalance :
                expectedDefaultPenalty;

            s.shortBalance = s.shortBalance.sub(penalty);
            s.longBalance = s.longBalance.add(penalty);
        }

        s.state = TDS.State.Settled;
        emit Settled(s.fixedParameters.symbol, s.endTime, s.nav);
    }

    function _updateBalances(TDS.Storage storage s, int navNew) internal {
        // Compute difference -- Add the difference to owner and subtract
        // from counterparty. Then update nav state variable.
        int longDiff = _getLongDiff(navNew, s.longBalance, s.shortBalance);
        s.nav = navNew;

        s.longBalance = s.longBalance.add(longDiff);
        s.shortBalance = s.shortBalance.sub(longDiff);
    }

    function _getDefaultPenalty(TDS.Storage storage s) internal view returns (int penalty) {
        return s.defaultPenaltyAmount;
    }

    function _computeDefaultPenalty(TDS.Storage storage s) internal view returns (int penalty) {
        return _takePercentage(s._getRequiredMargin(s.currentTokenState), s.fixedParameters.defaultPenalty);
    }

    function _getRequiredMargin(TDS.Storage storage s, TDS.TokenState memory tokenState)
        internal
        view
        returns (int requiredMargin)
    {
        int leverageMagnitude = _absoluteValue(s.externalAddresses.returnCalculator.leverage());

        int effectiveNotional;
        if (s.fixedParameters.returnType == TokenizedDerivativeParams.ReturnType.Linear) {
            int effectiveUnitsOfUnderlying = _safeIntCast(_totalSupply().mul(s.fixedParameters.initialTokenUnderlyingRatio).div(UINT_FP_SCALING_FACTOR)).mul(leverageMagnitude);
            effectiveNotional = effectiveUnitsOfUnderlying.mul(tokenState.underlyingPrice).div(INT_FP_SCALING_FACTOR);
        } else {
            int currentNav = _computeNavForTokens(tokenState.tokenPrice, _totalSupply());
            effectiveNotional = currentNav.mul(leverageMagnitude);
        }

        // Take the absolute value of the notional since a negative notional has similar risk properties to a positive
        // notional of the same size, and, therefore, requires the same margin.
        requiredMargin = _takePercentage(_absoluteValue(effectiveNotional), s.fixedParameters.supportedMove);
    }

    function _pullSentMargin(TDS.Storage storage s, uint expectedMargin) internal returns (uint refund) {
        if (address(s.externalAddresses.marginCurrency) == address(0x0)) {
            // Refund is any amount of ETH that was sent that was above the amount that was expected.
            // Note: SafeMath will force a revert if msg.value < expectedMargin.
            return msg.value.sub(expectedMargin);
        } else {
            // If we expect an ERC20 token, no ETH should be sent.
            require(msg.value == 0);
            _pullAuthorizedTokens(s.externalAddresses.marginCurrency, expectedMargin);

            // There is never a refund in the ERC20 case since we use the argument to determine how much to "pull".
            return 0;
        }
    }

    function _sendMargin(TDS.Storage storage s, uint amount) internal {
        // There's no point in attempting a send if there's nothing to send.
        if (amount == 0) {
            return;
        }

        if (address(s.externalAddresses.marginCurrency) == address(0x0)) {
            msg.sender.transfer(amount);
        } else {
            require(s.externalAddresses.marginCurrency.transfer(msg.sender, amount));
        }
    }

    function _settleAgreedPrice(TDS.Storage storage s) internal {
        int agreedPrice = s.currentTokenState.underlyingPrice;

        s._settleWithPrice(agreedPrice);
    }

    function _settleVerifiedPrice(TDS.Storage storage s) internal {
        int oraclePrice = s.externalAddresses.oracle.getPrice(s.fixedParameters.product, s.endTime);
        s._settleWithPrice(oraclePrice);
    }

    function _pullAuthorizedTokens(IERC20 erc20, uint amountToPull) private {
        // If nothing is being pulled, there's no point in calling a transfer.
        if (amountToPull > 0) {
            require(erc20.transferFrom(msg.sender, address(this), amountToPull));
        }
    }

    // Gets the change in balance for the long side.
    // Note: there's a function for this because signage is tricky here, and it must be done the same everywhere.
    function _getLongDiff(int navNew, int longBalance, int shortBalance) private pure returns (int longDiff) {
        int newLongBalance = navNew;

        // Long balance cannot go below zero.
        if (newLongBalance < 0) {
            newLongBalance = 0;
        }

        longDiff = newLongBalance.sub(longBalance);

        // Cannot pull more margin from the short than is available.
        if (longDiff > shortBalance) {
            longDiff = shortBalance;
        }
    }

    function _computeNavForTokens(int tokenPrice, uint numTokens) private pure returns (int navNew) {
        int navPreDivision = _safeIntCast(numTokens).mul(tokenPrice);
        navNew = navPreDivision.div(INT_FP_SCALING_FACTOR);

        // The navNew division above truncates by default. Instead, we prefer to ceil this value to ensure tokens
        // cannot be purchased or backed with less than their true value.
        if ((navPreDivision % INT_FP_SCALING_FACTOR) != 0) {
            navNew = navNew.add(1);
        }
    }

    function _totalSupply() private view returns (uint totalSupply) {
        ExpandedIERC20 thisErc20Token = ExpandedIERC20(address(this));
        return thisErc20Token.totalSupply();
    }

    function _takePercentage(uint value, uint percentage) private pure returns (uint result) {
        return value.mul(percentage).div(UINT_FP_SCALING_FACTOR);
    }

    function _takePercentage(int value, uint percentage) private pure returns (int result) {
        return value.mul(_safeIntCast(percentage)).div(INT_FP_SCALING_FACTOR);
    }

    function _takePercentage(int value, int percentage) private pure returns (int result) {
        return value.mul(percentage).div(INT_FP_SCALING_FACTOR);
    }

    function _absoluteValue(int value) private pure returns (int result) {
        return value < 0 ? value.mul(-1) : value;
    }

    function _safeIntCast(uint value) private pure returns (int result) {
        require(value <= INT_MAX);
        return int(value);
    }

    function _safeUintCast(int value) private pure returns (uint result) {
        require(value >= 0);
        return uint(value);
    }

    // Note that we can't have the symbol parameter be `indexed` due to:
    // TypeError: Indexed reference types cannot yet be used with ABIEncoderV2.
    // An event emitted when the NAV of the contract changes.
    event NavUpdated(string symbol, int newNav, int newTokenPrice);
    // An event emitted when the contract enters the Default state on a remargin.
    event Default(string symbol, uint defaultTime, int defaultNav);
    // An event emitted when the contract settles.
    event Settled(string symbol, uint settleTime, int finalNav);
    // An event emitted when the contract expires.
    event Expired(string symbol, uint expiryTime);
    // An event emitted when the contract's NAV is disputed by the sponsor.
    event Disputed(string symbol, uint timeDisputed, int navDisputed);
    // An event emitted when the contract enters emergency shutdown.
    event EmergencyShutdownTransition(string symbol, uint shutdownTime);
    // An event emitted when tokens are created.
    event TokensCreated(string symbol, uint numTokensCreated);
    // An event emitted when tokens are redeemed.
    event TokensRedeemed(string symbol, uint numTokensRedeemed);
    // An event emitted when margin currency is deposited.
    event Deposited(string symbol, uint amount);
    // An event emitted when margin currency is withdrawn.
    event Withdrawal(string symbol, uint amount);
}

// TODO(mrice32): make this and TotalReturnSwap derived classes of a single base to encap common functionality.
contract TokenizedDerivative is ERC20, AdminInterface, ExpandedIERC20 {
    using TokenizedDerivativeUtils for TDS.Storage;

    // Note: these variables are to give ERC20 consumers information about the token.
    string public name;
    string public symbol;
    uint8 public constant decimals = 18; // solhint-disable-line const-name-snakecase

    TDS.Storage public derivativeStorage;

    constructor(
        TokenizedDerivativeParams.ConstructorParams memory params,
        string memory _name,
        string memory _symbol
    ) public {
        // Set token properties.
        name = _name;
        symbol = _symbol;

        // Initialize the contract.
        derivativeStorage._initialize(params, _symbol);
    }

    // Creates tokens with sent margin and returns additional margin.
    function createTokens(uint marginForPurchase, uint tokensToPurchase) external payable {
        derivativeStorage._createTokens(marginForPurchase, tokensToPurchase);
    }

    // Creates tokens with sent margin and deposits additional margin in short account.
    function depositAndCreateTokens(uint marginForPurchase, uint tokensToPurchase) external payable {
        derivativeStorage._depositAndCreateTokens(marginForPurchase, tokensToPurchase);
    }

    // Redeems tokens for margin currency.
    function redeemTokens(uint tokensToRedeem) external {
        derivativeStorage._redeemTokens(tokensToRedeem);
    }

    // Triggers a price dispute for the most recent remargin time.
    function dispute(uint depositMargin) external payable {
        derivativeStorage._dispute(depositMargin);
    }

    // Withdraws `amount` from short margin account.
    function withdraw(uint amount) external {
        derivativeStorage._withdraw(amount);
    }

    // Pays (Oracle and service) fees for the previous period, updates the contract NAV, moves margin between long and
    // short accounts to reflect the new NAV, and checks if both accounts meet minimum requirements.
    function remargin() external {
        derivativeStorage._remargin();
    }

    // Forgo the Oracle verified price and settle the contract with last remargin price. This method is only callable on
    // contracts in the `Defaulted` state, and the default penalty is always transferred from the short to the long
    // account.
    function acceptPriceAndSettle() external {
        derivativeStorage._acceptPriceAndSettle();
    }

    // Assigns an address to be the contract's Delegate AP. Replaces previous value. Set to 0x0 to indicate there is no
    // Delegate AP.
    function setApDelegate(address apDelegate) external {
        derivativeStorage._setApDelegate(apDelegate);
    }

    // Moves the contract into the Emergency state, where it waits on an Oracle price for the most recent remargin time.
    function emergencyShutdown() external {
        derivativeStorage._emergencyShutdown();
    }

    // Returns the expected net asset value (NAV) of the contract using the latest available Price Feed price.
    function calcNAV() external view returns (int navNew) {
        return derivativeStorage._calcNAV();
    }

    // Returns the expected value of each the outstanding tokens of the contract using the latest available Price Feed
    // price.
    function calcTokenValue() external view returns (int newTokenValue) {
        return derivativeStorage._calcTokenValue();
    }

    // Returns the expected balance of the short margin account using the latest available Price Feed price.
    function calcShortMarginBalance() external view returns (int newShortMarginBalance) {
        return derivativeStorage._calcShortMarginBalance();
    }

    // Returns the expected short margin in excess of the margin requirement using the latest available Price Feed
    // price.  Value will be negative if the short margin is expected to be below the margin requirement.
    function calcExcessMargin() external view returns (int excessMargin) {
        return derivativeStorage._calcExcessMargin();
    }

    // Returns the required margin, as of the last remargin. Note that `calcExcessMargin` uses updated values using the
    // latest available Price Feed price.
    function getCurrentRequiredMargin() external view returns (int requiredMargin) {
        return derivativeStorage._getCurrentRequiredMargin();
    }

    // Returns whether the contract can be settled, i.e., is it valid to call settle() now.
    function canBeSettled() external view returns (bool canContractBeSettled) {
        return derivativeStorage._canBeSettled();
    }

    // Returns the updated underlying price that was used in the calc* methods above. It will be a price feed price if
    // the contract is Live and will remain Live, or an Oracle price if the contract is settled/about to be settled.
    // Reverts if no Oracle price is available but an Oracle price is required.
    function getUpdatedUnderlyingPrice() external view returns (int underlyingPrice, uint time) {
        return derivativeStorage._getUpdatedUnderlyingPrice();
    }

    // When an Oracle price becomes available, performs a final remargin, assesses any penalties, and moves the contract
    // into the `Settled` state.
    function settle() external {
        derivativeStorage._settle();
    }

    // Adds the margin sent along with the call (or in the case of an ERC20 margin currency, authorized before the call)
    // to the short account.
    function deposit(uint amountToDeposit) external payable {
        derivativeStorage._deposit(amountToDeposit);
    }

    // Allows the sponsor to withdraw any ERC20 balance that is not the margin token.
    function withdrawUnexpectedErc20(address erc20Address, uint amount) external {
        derivativeStorage._withdrawUnexpectedErc20(erc20Address, amount);
    }

    // ExpandedIERC20 methods.
    modifier onlyThis {
        require(msg.sender == address(this));
        _;
    }

    // Only allow calls from this contract or its libraries to burn tokens.
    function burn(uint value) external onlyThis {
        // Only allow calls from this contract or its libraries to burn tokens.
        _burn(msg.sender, value);
    }

    // Only allow calls from this contract or its libraries to mint tokens.
    function mint(address to, uint256 value) external onlyThis {
        _mint(to, value);
    }

    // These events are actually emitted by TokenizedDerivativeUtils, but we unfortunately have to define the events
    // here as well.
    event NavUpdated(string symbol, int newNav, int newTokenPrice);
    event Default(string symbol, uint defaultTime, int defaultNav);
    event Settled(string symbol, uint settleTime, int finalNav);
    event Expired(string symbol, uint expiryTime);
    event Disputed(string symbol, uint timeDisputed, int navDisputed);
    event EmergencyShutdownTransition(string symbol, uint shutdownTime);
    event TokensCreated(string symbol, uint numTokensCreated);
    event TokensRedeemed(string symbol, uint numTokensRedeemed);
    event Deposited(string symbol, uint amount);
    event Withdrawal(string symbol, uint amount);
}

contract TokenizedDerivativeCreator is ContractCreator, Testable {
    struct Params {
        uint defaultPenalty; // Percentage of mergin requirement * 10^18
        uint supportedMove; // Expected percentage move in the underlying that the long is protected against.
        bytes32 product;
        uint fixedYearlyFee; // Percentage of nav * 10^18
        uint disputeDeposit; // Percentage of mergin requirement * 10^18
        address returnCalculator;
        uint startingTokenPrice;
        uint expiry;
        address marginCurrency;
        uint withdrawLimit; // Percentage of shortBalance * 10^18
        TokenizedDerivativeParams.ReturnType returnType;
        uint startingUnderlyingPrice;
        string name;
        string symbol;
    }

    AddressWhitelist public sponsorWhitelist;
    AddressWhitelist public returnCalculatorWhitelist;
    AddressWhitelist public marginCurrencyWhitelist;

    constructor(
        address registryAddress,
        address _oracleAddress,
        address _storeAddress,
        address _priceFeedAddress,
        address _sponsorWhitelist,
        address _returnCalculatorWhitelist,
        address _marginCurrencyWhitelist,
        bool _isTest
    )
        public
        ContractCreator(registryAddress, _oracleAddress, _storeAddress, _priceFeedAddress)
        Testable(_isTest)
    {
        sponsorWhitelist = AddressWhitelist(_sponsorWhitelist);
        returnCalculatorWhitelist = AddressWhitelist(_returnCalculatorWhitelist);
        marginCurrencyWhitelist = AddressWhitelist(_marginCurrencyWhitelist);
    }

    function createTokenizedDerivative(Params memory params)
        public
        returns (address derivativeAddress)
    {
        TokenizedDerivative derivative = new TokenizedDerivative(_convertParams(params), params.name, params.symbol);

        address[] memory parties = new address[](1);
        parties[0] = msg.sender;

        _registerContract(parties, address(derivative));

        return address(derivative);
    }

    // Converts createTokenizedDerivative params to TokenizedDerivative constructor params.
    function _convertParams(Params memory params)
        private
        view
        returns (TokenizedDerivativeParams.ConstructorParams memory constructorParams)
    {
        // Copy and verify externally provided variables.
        require(sponsorWhitelist.isOnWhitelist(msg.sender));
        constructorParams.sponsor = msg.sender;

        require(returnCalculatorWhitelist.isOnWhitelist(params.returnCalculator));
        constructorParams.returnCalculator = params.returnCalculator;

        require(marginCurrencyWhitelist.isOnWhitelist(params.marginCurrency));
        constructorParams.marginCurrency = params.marginCurrency;

        constructorParams.defaultPenalty = params.defaultPenalty;
        constructorParams.supportedMove = params.supportedMove;
        constructorParams.product = params.product;
        constructorParams.fixedYearlyFee = params.fixedYearlyFee;
        constructorParams.disputeDeposit = params.disputeDeposit;
        constructorParams.startingTokenPrice = params.startingTokenPrice;
        constructorParams.expiry = params.expiry;
        constructorParams.withdrawLimit = params.withdrawLimit;
        constructorParams.returnType = params.returnType;
        constructorParams.startingUnderlyingPrice = params.startingUnderlyingPrice;

        // Copy internal variables.
        constructorParams.priceFeed = priceFeedAddress;
        constructorParams.oracle = oracleAddress;
        constructorParams.store = storeAddress;
        constructorParams.admin = oracleAddress;
        constructorParams.creationTime = getCurrentTime();
    }
}