Fix #20

Fix #20

pragma solidity ^0.4.11;

import "./historical/StandardToken.sol";
import "./PLCRVoting.sol";
import "./Parameterizer.sol";

contract Registry {

    // ------
    // EVENTS
    // ------

    event _Application(string domain, uint deposit);
    event _Challenge(string domain, uint deposit, uint pollID);
    event _Deposit(string domain, uint added, uint newTotal);
    event _Withdrawal(string domain, uint withdrew, uint newTotal);
    event _NewDomainWhitelisted(string domain);
    event _ApplicationRemoved(string domain);
    event _ListingRemoved(string domain);
    event _ChallengeFailed(uint challengeID);
    event _ChallengeSucceeded(uint challengeID);
    event _RewardClaimed(address voter, uint challengeID, uint reward);

    struct Listing {
        uint applicationExpiry; // expiration date of apply stage
        bool whitelisted;       // indicates registry status
        address owner;          // owner of Listing
        uint unstakedDeposit;   // number of unlocked tokens with potential risk if challenged
        uint challengeID;       // identifier of canonical challenge
    }

    struct Challenge {
        uint rewardPool;        // (remaining) pool of tokens distributed amongst winning voters
        address challenger;     // owner of Challenge
        bool resolved;          // indication of if challenge is resolved
        uint stake;             // number of tokens at risk for either party during challenge
        uint totalTokens;       // (remaining) amount of tokens used for voting by the winning side
    }

    // maps challengeIDs to associated challenge data
    mapping(uint => Challenge) public challengeMap;
    // maps domainHashes to associated listing data
    mapping(bytes32 => Listing) public listingMap;
    // maps challengeIDs and address to token claim data
    mapping(uint => mapping(address => bool)) public tokenClaims;

    // Global Variables
    StandardToken public token;
    PLCRVoting public voting;
    Parameterizer public parameterizer;

    // ------------
    // CONSTRUCTOR:
    // ------------

    function Registry(
        address _tokenAddr,
        address _paramsAddr
    ) {
        token = StandardToken(_tokenAddr);
        parameterizer = Parameterizer(_paramsAddr);
        voting = new PLCRVoting(_tokenAddr);
    }

    // --------------------
    // PUBLISHER INTERFACE:
    // --------------------

    //Allow a user to start an application
    //take tokens from user and set apply stage end time
    function apply(string domain, uint amount) external {
        require(!isWhitelisted(domain));
        require(!appExists(domain));
        require(amount >= parameterizer.get("minDeposit"));

        //set owner
        Listing storage listing = listingMap[sha3(domain)];
        listing.owner = msg.sender;

        //transfer tokens
        require(token.transferFrom(listing.owner, this, amount));

        //set apply stage end time
        listing.applicationExpiry = block.timestamp + parameterizer.get("applyStageLen");
        listing.unstakedDeposit = amount;

        _Application(domain, amount);
    }

    //Allow the owner of a domain in the listing to increase their deposit
    function deposit(string domain, uint amount) external {
        Listing storage listing = listingMap[sha3(domain)];

        require(listing.owner == msg.sender);
        require(token.transferFrom(msg.sender, this, amount));

        listing.unstakedDeposit += amount;

        _Deposit(domain, amount, listing.unstakedDeposit);
    }

    //Allow the owner of a domain in the listing to withdraw
    //tokens not locked in a challenge (unstaked).
    //The publisher's domain remains whitelisted
    function withdraw(string domain, uint amount) external {
        Listing storage listing = listingMap[sha3(domain)];

        require(listing.owner == msg.sender);
        require(amount <= listing.unstakedDeposit);
        require(listing.unstakedDeposit - amount >= parameterizer.get("minDeposit"));

        require(token.transfer(msg.sender, amount));

        listing.unstakedDeposit -= amount;

        _Withdrawal(domain, amount, listing.unstakedDeposit);
    }

    //Allow the owner of a domain to remove the domain from the whitelist
    //Return all tokens to the owner
    function exit(string domain) external {
        Listing storage listing = listingMap[sha3(domain)];

        require(msg.sender == listing.owner);
        require(isWhitelisted(domain));
        // cannot exit during ongoing challenge
        require(listing.challengeID == 0 || challengeMap[listing.challengeID].resolved);

        //remove domain & return tokens
        resetListing(domain);
    }

    // -----------------------
    // TOKEN HOLDER INTERFACE:
    // -----------------------

    //start a poll for a domain in the apply stage or already on the whitelist
    //tokens are taken from the challenger and the publisher's tokens are locked
    function challenge(string domain) external returns (uint challengeID) {
        bytes32 domainHash = sha3(domain);
        Listing storage listing = listingMap[domainHash];
        //to be challenged, domain must be in apply stage or already on the whitelist
        require(appExists(domain) || listing.whitelisted);
        // prevent multiple challenges
        require(listing.challengeID == 0 || challengeMap[listing.challengeID].resolved);
        uint deposit = parameterizer.get("minDeposit");
        if (listing.unstakedDeposit < deposit) {
            // not enough tokens, publisher auto-delisted
            resetListing(domain);
            return 0;
        }
        //take tokens from challenger
        require(token.transferFrom(msg.sender, this, deposit));
        //start poll
        uint pollID = voting.startPoll(
            parameterizer.get("voteQuorum"),
            parameterizer.get("commitPeriodLen"),
            parameterizer.get("revealPeriodLen")
        );

        challengeMap[pollID] = Challenge({
            challenger: msg.sender,
            rewardPool: ((100 - parameterizer.get("dispensationPct")) * deposit) / 100,
            stake: deposit,
            resolved: false,
            totalTokens: 0
        });

        listingMap[domainHash].challengeID = pollID;       // update listing to store most recent challenge
        listingMap[domainHash].unstakedDeposit -= deposit; // lock tokens for listing during challenge

        _Challenge(domain, deposit, pollID);
        return pollID;
    }

    /**
    @notice updates a domain's status from application to listing, or resolves a challenge if one exists
    @param _domain The domain whose status is being updated
    */
    function updateStatus(string _domain) public {
        if (canBeWhitelisted(_domain)) {
          whitelistApplication(_domain);
          _NewDomainWhitelisted(_domain);
        } else if (challengeCanBeResolved(_domain)) {
          resolveChallenge(_domain);
        } else {
          revert();
        }
    }

    // ----------------
    // TOKEN FUNCTIONS:
    // ----------------

    // called by voter to claim reward for each completed vote
    // someone must call updateStatus() before this can be called
    function claimReward(uint _challengeID, uint _salt) public {
        // ensure voter has not already claimed tokens and challenge results have been processed
        require(tokenClaims[_challengeID][msg.sender] == false);
        require(challengeMap[_challengeID].resolved = true);

        uint voterTokens = voting.getNumPassingTokens(msg.sender, _challengeID, _salt);
        uint reward = calculateVoterReward(msg.sender, _challengeID, _salt);

        // subtract voter's information to preserve the participation ratios of other voters
        // compared to the remaining pool of rewards
        challengeMap[_challengeID].totalTokens -= voterTokens;
        challengeMap[_challengeID].rewardPool -= reward;

        require(token.transfer(msg.sender, reward));

        // ensures a voter cannot claim tokens again

        tokenClaims[_challengeID][msg.sender] = true;

        _RewardClaimed(msg.sender, _challengeID, reward);
    }

    /**
    @dev Calculate the provided voter's token reward for the given poll
    @param _voter Address of the voter whose reward balance is to be returned
    @param _challengeID pollID of the challenge a reward balance is being queried for
    @param _salt the salt for the voter's commit hash in the given poll
    @return a uint indicating the voter's reward in nano-adToken
    */
    function calculateVoterReward(address _voter, uint _challengeID, uint _salt)
    public constant returns (uint) {
        uint totalTokens = challengeMap[_challengeID].totalTokens;
        uint rewardPool = challengeMap[_challengeID].rewardPool;
        uint voterTokens = voting.getNumPassingTokens(_voter, _challengeID, _salt);
        return (voterTokens * rewardPool) / totalTokens;
    }

    // --------
    // GETTERS:
    // --------

    /**
    @dev determines whether a domain is an application which can be whitelisted
    @param _domain the domain whose status should be examined
    */
    function canBeWhitelisted(string _domain) constant public returns (bool) {
      bytes32 domainHash = sha3(_domain);
      uint challengeID = listingMap[domainHash].challengeID;

      // TODO: change name of appExists to appWasMade.
      if (appExists(_domain) && isExpired(listingMap[domainHash].applicationExpiry) &&
          !isWhitelisted(_domain) &&
          (challengeID == 0 || challengeMap[challengeID].resolved == true))
      { return true; }

      return false;
    }

    //return true if domain is whitelisted
    function isWhitelisted(string domain) constant public returns (bool whitelisted) {
        return listingMap[sha3(domain)].whitelisted;
    }

    //return true if apply(domain) was called for this domain
    function appExists(string domain) constant public returns (bool exists) {
        return listingMap[sha3(domain)].applicationExpiry > 0;
    }

    // return true if the listing has an unresolved challenge
    function challengeExists(string _domain) constant public returns (bool) {
        bytes32 domainHash = sha3(_domain);
        uint challengeID = listingMap[domainHash].challengeID;

        return (listingMap[domainHash].challengeID > 0 && !challengeMap[challengeID].resolved);
    }

    /**
    @notice determines whether voting has concluded in a challenge for a given domain. Throws if no challenge exists.
    @param _domain a domain with an unresolved challenge
    */
    function challengeCanBeResolved(string _domain) constant public returns (bool) {
        bytes32 domainHash = sha3(_domain);
        uint challengeID = listingMap[domainHash].challengeID;

        require(challengeExists(_domain));

        return voting.pollEnded(challengeID);
    }

    /**
    @notice Determines the number of tokens to awarded to the winning party in a challenge
    @param _challengeID The challengeID to determine a reward for
    */
    function determineReward(uint _challengeID) public constant returns (uint) {
      require(!challengeMap[_challengeID].resolved && voting.pollEnded(_challengeID));

      if(voting.getTotalNumberOfTokensForWinningOption(_challengeID) == 0) {
        // Edge case, nobody voted, give all tokens to the winner.
        return 2 * challengeMap[_challengeID].stake;
      }

      return (2 * challengeMap[_challengeID].stake) - challengeMap[_challengeID].rewardPool;
    }

    //return true if termDate has passed
    function isExpired(uint termDate) constant public returns (bool expired) {
        return termDate < block.timestamp;
    }

    //delete listing from whitelist and return tokens to owner
    function resetListing(string domain) internal {
        bytes32 domainHash = sha3(domain);
        Listing storage listing = listingMap[domainHash];
        //transfer any remaining balance back to the owner
        if (listing.unstakedDeposit > 0)
            require(token.transfer(listing.owner, listing.unstakedDeposit));
        delete listingMap[domainHash];
    }

    // ----------------
    // PRIVATE FUNCTIONS:
    // ----------------

    /**
    @dev determines the winner in a challenge, rewards them tokens, and either whitelists or de-whitelists the domain
    @param _domain a domain with an unresolved challenge
    */
    function resolveChallenge(string _domain) private {
        bytes32 domainHash = sha3(_domain);
        uint challengeID = listingMap[domainHash].challengeID;

        // winner gets back their full staked deposit, and dispensationPct*loser's stake
        uint reward = determineReward(challengeID);
        bool wasWhitelisted = isWhitelisted(_domain);

        if (voting.isPassed(challengeID)) { // The challenge failed
            whitelistApplication(_domain);
            listingMap[domainHash].unstakedDeposit += reward; // give stake back to applicant

            _ChallengeFailed(challengeID);
            if (!wasWhitelisted) { _NewDomainWhitelisted(_domain); }
        }
        else { // The challenge succeeded
            resetListing(_domain);
            require(token.transfer(challengeMap[challengeID].challenger, reward));

            _ChallengeSucceeded(challengeID);
            if (wasWhitelisted) { _ListingRemoved(_domain); }
            else { _ApplicationRemoved(_domain); }
        }

        // set flag on challenge being processed
        challengeMap[challengeID].resolved = true;

        // store the total tokens used for voting by the winning side for reward purposes
        challengeMap[challengeID].totalTokens =
          voting.getTotalNumberOfTokensForWinningOption(challengeID);
    }

    /**
    @dev Called by updateStatus if the applicationExpiry date passed without a challenge being made
    @param _domain the domainHash to whitelist
    */
    function whitelistApplication(string _domain) private {
        bytes32 domainHash = sha3(_domain);

        listingMap[domainHash].whitelisted = true;
    }
}

pragma solidity ^0.4.11;

import "./historical/StandardToken.sol";
import "./PLCRVoting.sol";
import "./Parameterizer.sol";

contract Registry {

    // ------
    // EVENTS
    // ------

    event _Application(string domain, uint deposit);
    event _Challenge(string domain, uint deposit, uint pollID);
    event _Deposit(string domain, uint added, uint newTotal);
    event _Withdrawal(string domain, uint withdrew, uint newTotal);
    event _NewDomainWhitelisted(string domain);
    event _ApplicationRemoved(string domain);
    event _ListingRemoved(string domain);
    event _ChallengeFailed(uint challengeID);
    event _ChallengeSucceeded(uint challengeID);
    event _RewardClaimed(address voter, uint challengeID, uint reward);

    struct Listing {
        uint applicationExpiry; // expiration date of apply stage
        bool whitelisted;       // indicates registry status
        address owner;          // owner of Listing
        uint unstakedDeposit;   // number of unlocked tokens with potential risk if challenged
        uint challengeID;       // identifier of canonical challenge
    }

    struct Challenge {
        uint rewardPool;        // (remaining) pool of tokens distributed amongst winning voters
        address challenger;     // owner of Challenge
        bool resolved;          // indication of if challenge is resolved
        uint stake;             // number of tokens at risk for either party during challenge
        uint totalTokens;       // (remaining) amount of tokens used for voting by the winning side
    }

    // maps challengeIDs to associated challenge data
    mapping(uint => Challenge) public challengeMap;
    // maps domainHashes to associated listing data
    mapping(bytes32 => Listing) public listingMap;
    // maps challengeIDs and address to token claim data
    mapping(uint => mapping(address => bool)) public tokenClaims;

    // Global Variables
    StandardToken public token;
    PLCRVoting public voting;
    Parameterizer public parameterizer;

    // ------------
    // CONSTRUCTOR:
    // ------------

    function Registry(
        address _tokenAddr,
        address _paramsAddr
    ) {
        token = StandardToken(_tokenAddr);
        parameterizer = Parameterizer(_paramsAddr);
        voting = new PLCRVoting(_tokenAddr);
    }

    // --------------------
    // PUBLISHER INTERFACE:
    // --------------------

    //Allow a user to start an application
    //take tokens from user and set apply stage end time
    function apply(string domain, uint amount) external {
        require(!isWhitelisted(domain));
        require(!appExists(domain));
        require(amount >= parameterizer.get("minDeposit"));

        //set owner
        Listing storage listing = listingMap[sha3(domain)];
        listing.owner = msg.sender;

        //transfer tokens
        require(token.transferFrom(listing.owner, this, amount));

        //set apply stage end time
        listing.applicationExpiry = block.timestamp + parameterizer.get("applyStageLen");
        listing.unstakedDeposit = amount;

        _Application(domain, amount);
    }

    //Allow the owner of a domain in the listing to increase their deposit
    function deposit(string domain, uint amount) external {
        Listing storage listing = listingMap[sha3(domain)];

        require(listing.owner == msg.sender);
        require(token.transferFrom(msg.sender, this, amount));

        listing.unstakedDeposit += amount;

        _Deposit(domain, amount, listing.unstakedDeposit);
    }

    //Allow the owner of a domain in the listing to withdraw
    //tokens not locked in a challenge (unstaked).
    //The publisher's domain remains whitelisted
    function withdraw(string domain, uint amount) external {
        Listing storage listing = listingMap[sha3(domain)];

        require(listing.owner == msg.sender);
        require(amount <= listing.unstakedDeposit);
        require(listing.unstakedDeposit - amount >= parameterizer.get("minDeposit"));

        require(token.transfer(msg.sender, amount));

        listing.unstakedDeposit -= amount;

        _Withdrawal(domain, amount, listing.unstakedDeposit);
    }

    //Allow the owner of a domain to remove the domain from the whitelist
    //Return all tokens to the owner
    function exit(string domain) external {
        Listing storage listing = listingMap[sha3(domain)];

        require(msg.sender == listing.owner);
        require(isWhitelisted(domain));
        // cannot exit during ongoing challenge
        require(listing.challengeID == 0 || challengeMap[listing.challengeID].resolved);

        //remove domain & return tokens
        resetListing(domain);
    }

    // -----------------------
    // TOKEN HOLDER INTERFACE:
    // -----------------------

    //start a poll for a domain in the apply stage or already on the whitelist
    //tokens are taken from the challenger and the publisher's tokens are locked
    function challenge(string domain) external returns (uint challengeID) {
        bytes32 domainHash = sha3(domain);
        Listing storage listing = listingMap[domainHash];
        //to be challenged, domain must be in apply stage or already on the whitelist
        require(appExists(domain) || listing.whitelisted);
        // prevent multiple challenges
        require(listing.challengeID == 0 || challengeMap[listing.challengeID].resolved);
        uint deposit = parameterizer.get("minDeposit");
        if (listing.unstakedDeposit < deposit) {
            // not enough tokens, publisher auto-delisted
            resetListing(domain);
            return 0;
        }
        //take tokens from challenger
        require(token.transferFrom(msg.sender, this, deposit));
        //start poll
        uint pollID = voting.startPoll(
            parameterizer.get("voteQuorum"),
            parameterizer.get("commitPeriodLen"),
            parameterizer.get("revealPeriodLen")
        );

        challengeMap[pollID] = Challenge({
            challenger: msg.sender,
            rewardPool: ((100 - parameterizer.get("dispensationPct")) * deposit) / 100,
            stake: deposit,
            resolved: false,
            totalTokens: 0
        });

        listingMap[domainHash].challengeID = pollID;       // update listing to store most recent challenge
        listingMap[domainHash].unstakedDeposit -= deposit; // lock tokens for listing during challenge

        _Challenge(domain, deposit, pollID);
        return pollID;
    }

    /**
    @notice updates a domain's status from application to listing, or resolves a challenge if one exists
    @param _domain The domain whose status is being updated
    */
    function updateStatus(string _domain) public {
        if (canBeWhitelisted(_domain)) {
          whitelistApplication(_domain);
          _NewDomainWhitelisted(_domain);
        } else if (challengeCanBeResolved(_domain)) {
          resolveChallenge(_domain);
        } else {
          revert();
        }
    }

    // ----------------
    // TOKEN FUNCTIONS:
    // ----------------

    // called by voter to claim reward for each completed vote
    // someone must call updateStatus() before this can be called
    function claimReward(uint _challengeID, uint _salt) public {
        // ensure voter has not already claimed tokens and challenge results have been processed
        require(tokenClaims[_challengeID][msg.sender] == false);
        require(challengeMap[_challengeID].resolved = true);

        uint voterTokens = voting.getNumPassingTokens(msg.sender, _challengeID, _salt);
        uint reward = calculateVoterReward(msg.sender, _challengeID, _salt);

        // subtract voter's information to preserve the participation ratios of other voters
        // compared to the remaining pool of rewards
        challengeMap[_challengeID].totalTokens -= voterTokens;
        challengeMap[_challengeID].rewardPool -= reward;

        require(token.transfer(msg.sender, reward));

        // ensures a voter cannot claim tokens again

        tokenClaims[_challengeID][msg.sender] = true;

        _RewardClaimed(msg.sender, _challengeID, reward);
    }

    /**
    @dev Calculate the provided voter's token reward for the given poll
    @param _voter Address of the voter whose reward balance is to be returned
    @param _challengeID pollID of the challenge a reward balance is being queried for
    @param _salt the salt for the voter's commit hash in the given poll
    @return a uint indicating the voter's reward in nano-adToken
    */
    function calculateVoterReward(address _voter, uint _challengeID, uint _salt)
    public constant returns (uint) {
        uint totalTokens = challengeMap[_challengeID].totalTokens;
        uint rewardPool = challengeMap[_challengeID].rewardPool;
        uint voterTokens = voting.getNumPassingTokens(_voter, _challengeID, _salt);
        return (voterTokens * rewardPool) / totalTokens;
    }

    // --------
    // GETTERS:
    // --------

    /**
    @dev determines whether a domain is an application which can be whitelisted
    @param _domain the domain whose status should be examined
    */
    function canBeWhitelisted(string _domain) constant public returns (bool) {
      bytes32 domainHash = sha3(_domain);
      uint challengeID = listingMap[domainHash].challengeID;

      // TODO: change name of appExists to appWasMade.
      if (appExists(_domain) && isExpired(listingMap[domainHash].applicationExpiry) &&
          !isWhitelisted(_domain) &&
          (challengeID == 0 || challengeMap[challengeID].resolved == true))
      { return true; }

      return false;
    }

    //return true if domain is whitelisted
    function isWhitelisted(string domain) constant public returns (bool whitelisted) {
        return listingMap[sha3(domain)].whitelisted;
    }

    //return true if apply(domain) was called for this domain
    function appExists(string domain) constant public returns (bool exists) {
        return listingMap[sha3(domain)].applicationExpiry > 0;
    }

    // return true if the listing has an unresolved challenge
    function challengeExists(string _domain) constant public returns (bool) {
        bytes32 domainHash = sha3(_domain);
        uint challengeID = listingMap[domainHash].challengeID;

        return (listingMap[domainHash].challengeID > 0 && !challengeMap[challengeID].resolved);
    }

    /**
    @notice determines whether voting has concluded in a challenge for a given domain. Throws if no challenge exists.
    @param _domain a domain with an unresolved challenge
    */
    function challengeCanBeResolved(string _domain) constant public returns (bool) {
        bytes32 domainHash = sha3(_domain);
        uint challengeID = listingMap[domainHash].challengeID;

        require(challengeExists(_domain));

        return voting.pollEnded(challengeID);
    }

    //return true if termDate has passed
    function isExpired(uint termDate) constant public returns (bool expired) {
        return termDate < block.timestamp;
    }

    //delete listing from whitelist and return tokens to owner
    function resetListing(string domain) internal {
        bytes32 domainHash = sha3(domain);
        Listing storage listing = listingMap[domainHash];
        //transfer any remaining balance back to the owner
        if (listing.unstakedDeposit > 0)
            require(token.transfer(listing.owner, listing.unstakedDeposit));
        delete listingMap[domainHash];
    }

    // ----------------
    // PRIVATE FUNCTIONS:
    // ----------------

    /**
    @dev determines the winner in a challenge, rewards them tokens, and either whitelists or de-whitelists the domain
    @param _domain a domain with an unresolved challenge
    */
    function resolveChallenge(string _domain) private {
        bytes32 domainHash = sha3(_domain);
        uint challengeID = listingMap[domainHash].challengeID;

        // winner gets back their full staked deposit, and dispensationPct*loser's stake
        uint reward = (2 * challengeMap[challengeID].stake) - challengeMap[challengeID].rewardPool;
        bool wasWhitelisted = isWhitelisted(_domain);

        if (voting.isPassed(challengeID)) { // The challenge failed
            whitelistApplication(_domain);
            listingMap[domainHash].unstakedDeposit += reward; // give stake back to applicant

            _ChallengeFailed(challengeID);
            if (!wasWhitelisted) { _NewDomainWhitelisted(_domain); }
        }
        else { // The challenge succeeded
            resetListing(_domain);
            require(token.transfer(challengeMap[challengeID].challenger, reward));

            _ChallengeSucceeded(challengeID);
            if (wasWhitelisted) { _ListingRemoved(_domain); }
            else { _ApplicationRemoved(_domain); }
        }

        // set flag on challenge being processed
        challengeMap[challengeID].resolved = true;

        // store the total tokens used for voting by the winning side for reward purposes
        challengeMap[challengeID].totalTokens =
          voting.getTotalNumberOfTokensForWinningOption(challengeID);
    }

    /**
    @dev Called by updateStatus if the applicationExpiry date passed without a challenge being made
    @param _domain the domainHash to whitelist
    */
    function whitelistApplication(string _domain) private {
        bytes32 domainHash = sha3(_domain);

        listingMap[domainHash].whitelisted = true;
    }
}


test unavailable investment actions by asset's status

pragma solidity ^0.4.23;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/investment/InvestmentAsset.sol";
import "../contracts/SwapyExchange.sol";
import "./helpers/ThrowProxy.sol";
import "./AssetCall.sol";

contract TestInvestmentAsset_investment {

    SwapyExchange protocol = SwapyExchange(DeployedAddresses.SwapyExchange());

    address token = protocol.token();
    bytes8 version = protocol.latestVersion();
    address _library = protocol.getLibrary(version);

    InvestmentAsset assetInstance = new InvestmentAsset(
        _library,
        address(protocol),
        address(this),
        version,
        bytes5("USD"),
        uint256(500),
        uint256(360),
        uint256(10),
        token
    );

    ThrowProxy throwProxy = new ThrowProxy(address(assetInstance));
    AssetCall throwableAsset = new AssetCall(address(throwProxy));
    AssetCall asset = new AssetCall(address(assetInstance));
    // Truffle looks for `initialBalance` when it compiles the test suite
    // and funds this test contract with the specified amount on deployment.
    uint public initialBalance = 10 ether;

    function() payable public {

    }

    function shouldThrow(bool result) public {
        Assert.isFalse(result, "Should throw an exception");
    }

    // Testing invest() function
    function testInvestorAddressMustBeValid() {
        bool result = throwableAsset.invest(true);
        throwProxy.shouldThrow();
    }

    function testUnavailableActionsWhenAvaiable() public {
        bool available = refuseInvestment(address(assetInstance)) &&
            withdrawFunds(address(assetInstance)) &&
            returnInvestment(address(assetInstance), 1100 finney) &&
            asset.cancelInvestment();
        Assert.isFalse(available, "Should not be executed");
    }

    function testUserCanInvest() public {
        uint256 previousBalance = address(this).balance;
        uint256 previousAssetBalance = address(assetInstance).balance;
        bool result = asset.invest.value(1 ether)(false);
        InvestmentAsset.Status currentStatus = assetInstance.status();
        bool isPending = currentStatus == InvestmentAsset.Status.PENDING_OWNER_AGREEMENT;
        Assert.equal(result, true, "Asset must be invested");
        Assert.equal(isPending, true, "The asset must be locked for investments");
        Assert.equal(
            previousBalance - address(this).balance,
            address(assetInstance).balance - previousAssetBalance,
            "balance changes must be equal"
        );
    }

    function testUnavailableActionsWhenPending() public {
        bool available = asset.invest.value(1 ether)(false) &&
            returnInvestment(address(assetInstance), 1100 finney);
        Assert.isFalse(available, "Should not be executed");
    }

    // Testing cancelInvestment() function
    function testOnlyInvestorCanCancelInvestment() public {
        bool result = throwableAsset.cancelInvestment();
        throwProxy.shouldThrow();
    }

    function testInvestorCanCancelInvestment() public {
        uint256 previousBalance = address(asset).balance;
        uint256 previousAssetBalance = address(assetInstance).balance;
        bool result = asset.cancelInvestment();
        InvestmentAsset.Status currentStatus = assetInstance.status();
        bool isAvailable = currentStatus == InvestmentAsset.Status.AVAILABLE;
        Assert.equal(result, true, "Investment must be canceled");
        Assert.equal(isAvailable, true, "The asset must be available for investments");
        Assert.equal(
            address(asset).balance - previousBalance,
            previousAssetBalance - address(assetInstance).balance,
            "balance changes must be equal"
        );
    }

    // Testing refuseInvestment() function
    function testOnlyOwnerCanRefuseInvestment() public {
        asset.invest(false);
        bool result = refuseInvestment(address(throwProxy));
        throwProxy.shouldThrow();
    }

    function testOwnerCanRefuseInvestment() public {
        uint256 previousBalance = address(asset).balance;
        uint256 previousAssetBalance = address(assetInstance).balance;
        bool result = refuseInvestment(address(assetInstance));
        InvestmentAsset.Status currentStatus = assetInstance.status();
        bool isAvailable = currentStatus == InvestmentAsset.Status.AVAILABLE;
        Assert.equal(result, true, "Investment must be refused");
        Assert.equal(isAvailable, true, "The asset must be available for investments");
        Assert.equal(
            address(asset).balance - previousBalance,
            previousAssetBalance - address(assetInstance).balance,
            "balance changes must be equal"
        );
    }

    // Testing withdrawFunds() function
    function testOnlyOwnerCanWithdrawFunds() public {
        asset.invest(false);
        bool result = withdrawFunds(address(throwProxy));
        throwProxy.shouldThrow();
    }

    function testOwnerCanWithdrawFunds() public {
        uint256 previousBalance = address(this).balance;
        uint256 previousAssetBalance = address(assetInstance).balance;
        bool result = withdrawFunds(address(assetInstance));
        InvestmentAsset.Status currentStatus = assetInstance.status();
        bool isInvested = currentStatus == InvestmentAsset.Status.INVESTED;
        Assert.equal(result, true, "Investment must be accepted");
        Assert.equal(isInvested, true, "The asset must be invested");
        Assert.equal(
            address(this).balance - previousBalance,
            previousAssetBalance - address(assetInstance).balance,
            "balance changes must be equal"
        );
    }

    function testUnavailableActionsWhenInvested() public {
        bool available = asset.invest.value(1 ether)(false) &&
            refuseInvestment(address(assetInstance)) &&
            withdrawFunds(address(assetInstance)) &&
            asset.cancelInvestment();
        Assert.isFalse(available, "Should not be executed");
    }

    // Testing returnInvestment() function
    function testOnlyOwnerCanReturnInvestment() public {
        returnInvestment(address(throwProxy), 1100 finney);
        throwProxy.shouldThrow();
    }

    function testOwnerCanReturnInvestment() public {
        uint256 previousBalance = address(this).balance;
        uint256 previousAssetBalance = address(asset).balance;
        bool result = returnInvestment(address(assetInstance), 1100 finney);
        Assert.equal(result, true, "Investment must be returned");
        InvestmentAsset.Status currentStatus = assetInstance.status();
        bool isReturned = currentStatus == InvestmentAsset.Status.RETURNED;
        Assert.equal(isReturned, true, "The asset must be returned");
        Assert.equal(
            previousBalance - address(this).balance,
            address(asset).balance - previousAssetBalance,
            "balance changes must be equal"
        );
    }

    function testUnavailableActionsWhenReturned() public {
        bool available = asset.invest.value(1 ether)(false) &&
            refuseInvestment(address(assetInstance)) &&
            withdrawFunds(address(assetInstance)) &&
            returnInvestment(address(assetInstance), 1100 finney) &&
            asset.cancelInvestment();
        Assert.isFalse(available, "Should not be executed");
    }

    function refuseInvestment(address _asset) returns(bool) {
        return _asset.call(abi.encodeWithSignature("refuseInvestment()"));
    }

    function withdrawFunds(address _asset) returns(bool) {
        return _asset.call(abi.encodeWithSignature("withdrawFunds()"));
    }

    function returnInvestment(address _asset, uint256 _value) payable returns(bool) {
        return _asset.call.value(_value)(abi.encodeWithSignature("returnInvestment()"));
    }

}

pragma solidity ^0.4.23;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/investment/InvestmentAsset.sol";
import "../contracts/SwapyExchange.sol";
import "./helpers/ThrowProxy.sol";
import "./AssetCall.sol";

contract TestInvestmentAsset_investment {

    SwapyExchange protocol = SwapyExchange(DeployedAddresses.SwapyExchange());

    address token = protocol.token();
    bytes8 version = protocol.latestVersion();
    address _library = protocol.getLibrary(version);

    InvestmentAsset assetInstance = new InvestmentAsset(
        _library,
        address(protocol),
        address(this),
        version,
        bytes5("USD"),
        uint256(500),
        uint256(360),
        uint256(10),
        token
    );

    ThrowProxy throwProxy = new ThrowProxy(address(assetInstance));
    AssetCall throwableAsset = new AssetCall(address(throwProxy));
    AssetCall asset = new AssetCall(address(assetInstance));
    // Truffle looks for `initialBalance` when it compiles the test suite
    // and funds this test contract with the specified amount on deployment.
    uint public initialBalance = 10 ether;

    function() payable public {

    }

    function shouldThrow(bool result) public {
        Assert.isFalse(result, "Should throw an exception");
    }

    // Testing invest() function
    function testInvestorAddressMustBeValid() {
        bool result = throwableAsset.invest(true);
        throwProxy.shouldThrow();
    }

    function testUserCanInvest() public {
        uint256 previousBalance = address(this).balance;
        uint256 previousAssetBalance = address(assetInstance).balance;
        bool result = asset.invest.value(1 ether)(false);
        InvestmentAsset.Status currentStatus = assetInstance.status();
        bool isPending = currentStatus == InvestmentAsset.Status.PENDING_OWNER_AGREEMENT;
        Assert.equal(result, true, "Asset must be invested");
        Assert.equal(isPending, true, "The asset must be locked for investments");
        Assert.equal(
            previousBalance - address(this).balance,
            address(assetInstance).balance - previousAssetBalance,
            "balance changes must be equal"
        );
    }

    // Testing cancelInvestment() function
    function testOnlyInvestorCanCancelInvestment() public {
        bool result = throwableAsset.cancelInvestment();
        throwProxy.shouldThrow();
    }

    function testInvestorCanCancelInvestment() public {
        uint256 previousBalance = address(asset).balance;
        uint256 previousAssetBalance = address(assetInstance).balance;
        bool result = asset.cancelInvestment();
        InvestmentAsset.Status currentStatus = assetInstance.status();
        bool isAvailable = currentStatus == InvestmentAsset.Status.AVAILABLE;
        Assert.equal(result, true, "Investment must be canceled");
        Assert.equal(isAvailable, true, "The asset must be available for investments");
        Assert.equal(
            address(asset).balance - previousBalance,
            previousAssetBalance - address(assetInstance).balance,
            "balance changes must be equal"
        );
    }

    // Testing refuseInvestment() function
    function testOnlyOwnerCanRefuseInvestment() public {
        asset.invest(false);
        bool result = refuseInvestment(address(throwProxy));
        throwProxy.shouldThrow();
    }

    function testOwnerCanRefuseInvestment() public {
        uint256 previousBalance = address(asset).balance;
        uint256 previousAssetBalance = address(assetInstance).balance;
        bool result = refuseInvestment(address(assetInstance));
        InvestmentAsset.Status currentStatus = assetInstance.status();
        bool isAvailable = currentStatus == InvestmentAsset.Status.AVAILABLE;
        Assert.equal(result, true, "Investment must be refused");
        Assert.equal(isAvailable, true, "The asset must be available for investments");
        Assert.equal(
            address(asset).balance - previousBalance,
            previousAssetBalance - address(assetInstance).balance,
            "balance changes must be equal"
        );
    }

    // Testing withdrawFunds() function
    function testOnlyOwnerCanWithdrawFunds() public {
        asset.invest(false);
        bool result = withdrawFunds(address(throwProxy));
        throwProxy.shouldThrow();
    }

    function testOwnerCanWithdrawFunds() public {
        uint256 previousBalance = address(asset).balance;
        uint256 previousAssetBalance = address(assetInstance).balance;
        bool result = withdrawFunds(address(assetInstance));
        InvestmentAsset.Status currentStatus = assetInstance.status();
        bool isInvested = currentStatus == InvestmentAsset.Status.INVESTED;
        Assert.equal(result, true, "Investment must be accepted");
        Assert.equal(isInvested, true, "The asset must be invested");
        Assert.equal(
            address(asset).balance - previousBalance,
            previousAssetBalance - address(assetInstance).balance,
            "balance changes must be equal"
        );
    }

    // Testing returnInvestment() function
    function testOnlyOwnerCanReturnInvestment() public {
        returnInvestment(address(throwProxy));
        throwProxy.shouldThrow();
    }

    function testOwnerCanReturnInvestment() public {
        uint256 previousBalance = address(asset).balance;
        uint256 previousAssetBalance = address(assetInstance).balance;
        bool result = returnInvestment(address(assetInstance));
        Assert.equal(result, true, "Investment must be returned");
        InvestmentAsset.Status currentStatus = assetInstance.status();
        bool isReturned = currentStatus == InvestmentAsset.Status.RETURNED;
        Assert.equal(isReturned, true, "The asset must be returned");
        Assert.equal(
            address(asset).balance - previousBalance,
            previousAssetBalance - address(assetInstance).balance,
            "balance changes must be equal"
        );
    }

    function refuseInvestment(address _asset) returns(bool) {
        return _asset.call(abi.encodeWithSignature("refuseInvestment()"));
    }

    function withdrawFunds(address _asset) returns(bool) {
        return _asset.call(abi.encodeWithSignature("withdrawFunds()"));
    }

    function returnInvestment(address _asset) payable returns(bool) {
        return _asset.call(abi.encodeWithSignature("returnInvestment()"));
    }

}


adding ERC20 interface

adding ERC20 interface

pragma solidity ^0.4.0;

// https://github.com/ethereum/EIPs/issues/20
contract ERC20 {
    uint256 public totalSupply;
    function balanceOf(address who) public constant returns (uint256);
    function transfer(address to, uint256 value) public returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    function allowance(address owner, address spender) public constant returns (uint256);
    function transferFrom(address from, address to, uint256 value) public returns (bool);
    function approve(address spender, uint256 value) public returns (bool);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

error: pathspec 'contracts/ERC20.sol' did not match any file(s) known to git


Remove greeter.sol smart contract sample.

contract mortal {
    /* Define variable owner of the type address*/
    address owner;

    /* this function is executed at initialization and sets the owner of the contract */
    function mortal() { owner = msg.sender; }

    /* Function to recover the funds on the contract */
    function kill() { if (msg.sender == owner) suicide(owner); }
}

contract greeter is mortal {
    /* define variable greeting of the type string */
    string greeting;

    /* this runs when the contract is executed */
    function greeter(string _greeting) public {
        greeting = _greeting;
    }

    /* main function */
    function greet() constant returns (string) {
        return greeting;
    }
}


corrected isRegisteredRetailer

corrected isRegisteredRetailer

pragma solidity ^0.4.4;

contract owned {
    address owner;

    modifier ownerOnly() {
        if (msg.sender != owner) throw;
        _;
    }

    function owned() {
        owner = msg.sender;
    }

    function getOwner() constant returns (address) {
        return owner;
    }
}

contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract PriceCalculator {
    function getWarrantyPrice(string productId, uint startDate, uint endDate, uint productPrice) constant returns (uint) {
        uint yrs = (endDate - startDate) / 1 years;
        /*the price is allways 5% per year*/
        return productPrice * yrs / 20;
    }
}

contract stateful {
    enum RetailerStatus {Undefined, Requested, Accepted, Rejected, Terminated}
    enum InsuranceStatus {Undefined, Requested, Active, Terminated}
    enum WarrantyStatus {Undefined, Created, Confirmed, Canceled}
    enum UserRole {Undefined, Retailer, Insurance, Owner}
}

contract related is stateful{
    struct PartnerRelations {
        RetailerStatus status;
        uint sales /*the total amount of policies sold by retailer*/;
        uint payments /*the total amount paid by retailer*/;
        uint claims /*the total amount the insurance has paid to the retailer in claims*/;
    }
}

contract InsuranceManager is owned, related {
    struct Insurance {
        string name;
        InsuranceStatus status;
        PriceCalculator priceCalculator;
        mapping (address=>PartnerRelations) retailers;
    }

    mapping (address => Insurance) insurances;
    mapping (uint=>address) insuranceList;
    uint public insuranceCount;

    event InsuranceStatusChanged(
        address indexed insurance,
        InsuranceStatus status
    );

    function isInsurance(address insurance) constant returns (bool) {
        return insurances[insurance].status != InsuranceStatus.Undefined;
    }

    function createInsurance(string name, address priceCalculator) {
        Insurance insurance = insurances[msg.sender];
        InsuranceStatus previousStatus = insurance.status;
        insurance.status = InsuranceStatus.Requested;
        if(previousStatus != InsuranceStatus.Undefined) throw;

        insurance.name = name;
        insurance.priceCalculator = PriceCalculator(priceCalculator);
        insuranceList[insuranceCount++] = msg.sender;

        InsuranceStatusChanged(msg.sender, InsuranceStatus.Requested);
    }

    function setInsuranceState(address insuranceAddress, InsuranceStatus status) ownerOnly {
        Insurance insurance = insurances[insuranceAddress];
        if(insurance.status == InsuranceStatus.Undefined) throw;

        insurance.status = status;
    }

    function getInsurance(uint index) constant returns (string name, address, InsuranceStatus) {
        Insurance insurance = insurances[insuranceList[index]];
        return (insurance.name, insuranceList[index], insurance.status);
    }

    function getInsuranceStatus(address insuranceAddress) constant returns (InsuranceStatus) {
        return insurances[insuranceAddress].status;
    }

    function getWarrantyPrice(address insuranceAddress, string productId, uint startDate, uint endDate, uint productPrice) constant returns (uint) {
        Insurance insurance = insurances[insuranceAddress];
        if(insurance.status != InsuranceStatus.Active) throw;
        return insurance.priceCalculator.getWarrantyPrice(productId, startDate, endDate, productPrice);
    }

    function increaseSalesBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].sales += amount;
    }

    function decreaseSalesBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].sales -= amount;
    }

    function increasePaymentsBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].payments += amount;
    }

    function decreasePaymentsBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].payments -= amount;
    }

    function increaseClaimsBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].claims += amount;
    }

    function decreaseClaimsBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].claims -= amount;
    }
}

contract RetailerManager is owned, related {
    InsuranceManager insuranceManager;

    function RetailerManager(address _insuranceManager){
        insuranceManager = InsuranceManager(_insuranceManager);
    }

    function setSubContractAddresses (address _insuranceManager) ownerOnly {
        insuranceManager = InsuranceManager(_insuranceManager);
    }

    modifier insuranceOnly {
        if(!isInsurance(msg.sender)) throw;
        _;
    }

    function isInsurance(address insurance) constant returns (bool) {
        return insuranceManager.getInsuranceStatus(insurance) != InsuranceStatus.Undefined;
    }

    struct Retailer {
        string companyName;
        mapping (address=>PartnerRelations) partnerRelations /*the mapping holds the relation of the partner with each insurance company*/;
        RetailerStatus status/*in order to easily check for the existence of a retailer the first status is also set on the retailer itself*/;
    }

    mapping (address=>Retailer) retailers;
    mapping (uint=>address) retailerList;
    uint public retailerCount;

    event RetailerRequest(
        string indexed companyName,
        address retailerAddress,
        address indexed insurance
    );

    event RetailerStatusChanged(
        address indexed retailer,
        address indexed insurance,
        RetailerStatus status
    );

    /**
    the retailer send a transaction to request registration with an insurer
    */
    function requestRegistration(string companyName, address insurance) {
        Retailer retailer = retailers[msg.sender];
        retailer.companyName = companyName;
        /*make sure the insurance company exists*/
        if(insuranceManager.getInsuranceStatus(insurance) != InsuranceStatus.Active) {
            throw;
        }
        /*make sure no previous request was made*/
        if(retailer.partnerRelations[insurance].status != RetailerStatus.Undefined){
            throw;
        }

        retailerList[retailerCount++] = msg.sender;
        retailer.partnerRelations[insurance].status = RetailerStatus.Requested;
        retailer.status = RetailerStatus.Accepted;
        retailers[msg.sender] = retailer;
        RetailerRequest(companyName, msg.sender, insurance);
    }

    function getRequestState(address retailer, address insurance) constant returns (RetailerStatus) {
        return retailers[retailer].partnerRelations[insurance].status;
    }

    /**
    sets the status of a retailer's request.
    only the insurance to which the request was made can do this
    */
    function setRequestState(address retailer, RetailerStatus status) insuranceOnly {
        retailers[retailer].partnerRelations[msg.sender].status = status;
        RetailerStatusChanged(retailer, msg.sender, status);
    }

    function getRetailerStatus(address retailer, address insurance) returns (RetailerStatus){
        return retailers[retailer].partnerRelations[insurance].status;
    }

    function getRetailerStatus(address retailer) returns (RetailerStatus){
        return retailers[retailer].status;
    }

    /**
    get the nth retailer in the list
    */
    function getRetailer(uint index, address insuranceAddress) constant returns (address, string, RetailerStatus, RetailerStatus) {
        address retailerAddress = retailerList[index];
        Retailer retailer = retailers[retailerAddress];
        return (retailerAddress, retailer.companyName, retailer.status, retailer.partnerRelations[insuranceAddress].status);
    }

    function getRetailerByAddress(address retailerAddress, address insuranceAddress) constant returns (address, string, RetailerStatus, RetailerStatus) {
        Retailer retailer = retailers[retailerAddress];
        return (retailerAddress, retailer.companyName, retailer.status, retailer.partnerRelations[insuranceAddress].status);
    }

    function getRetailerBalances(address retailer, address insurance) constant returns (uint, uint, uint) {
        PartnerRelations partnerRelation = retailers[retailer].partnerRelations[insurance];
        return (partnerRelation.sales, partnerRelation.payments, partnerRelation.claims);
    }

    function increaseSalesBalance(address retailer, address insurance, uint price) {
        retailers[retailer].partnerRelations[insurance].sales += price;
    }

    function decreaseSalesBalance(address retailer, address insurance, uint price) {
        retailers[retailer].partnerRelations[insurance].sales -= price;
    }

    function increasePaymentsBalance(address retailer, address insurance, uint amount) {
        retailers[retailer].partnerRelations[insurance].payments += amount;
    }

    function decreasePaymentsBalance(address retailer, address insurance, uint amount) {
        retailers[retailer].partnerRelations[insurance].payments -= amount;
    }

    function increaseClaimsBalance(address retailer, address insurance, uint amount) {
        retailers[retailer].partnerRelations[insurance].claims += amount;
    }

    function decreaseClaimsBalance(address retailer, address insurance, uint amount) {
        retailers[retailer].partnerRelations[insurance].claims -= amount;
    }
}

contract Insurechain is mortal, stateful{
    InsuranceManager insuranceManager;
    RetailerManager retailerManager;

    function Insurechain(address _insuranceManager, address _retailerManager) {
        insuranceManager = InsuranceManager(_insuranceManager);
        retailerManager = RetailerManager(_retailerManager);
    }

    function setSubContractAddresses (address _insuranceManager, address _retailerManager) ownerOnly {
        insuranceManager = InsuranceManager(_insuranceManager);
        retailerManager = RetailerManager(_retailerManager);
    }

    struct Claim {
        address retailer /*in theory another retailer than the one who sold the insurance can make a claim*/;
        uint amount;
        string description;
    }

    struct Warranty {
        address retailer;
        uint startDate;
        uint endDate;
        string policyNumber;
        WarrantyStatus status;
        uint productPrice;
        uint warrantyPrice;
        mapping (uint => Claim) claims;
        uint claimCount;
    }

    // mapping of insurance -> productId -> serialNumber -> Warranty
    mapping (address=>mapping( string=>mapping( string=>Warranty ))) warranties;

    modifier insuranceOnly {
        if(!isInsurance(msg.sender)) throw;
        _;
    }

    function isInsurance(address insurance) constant returns (bool) {
        return insuranceManager.getInsuranceStatus(insurance) != InsuranceStatus.Undefined;
    }

    modifier registeredRetailerOnly(address insurance) {
        if(!isRegisteredRetailer(insurance, msg.sender)) throw;
        _;
    }

    function isRegisteredRetailer(address insurance, address retailer) constant returns (bool) {
        return isInsurance(insurance) && retailerManager.getRequestState(retailer, insurance) == RetailerStatus.Accepted;
    }

    function getRole(address user) constant returns (UserRole) {
        if(user == owner) return UserRole.Owner;
        if(retailerManager.getRetailerStatus(user) == RetailerStatus.Accepted) return UserRole.Retailer;
        if(insuranceManager.getInsuranceStatus(user) == InsuranceStatus.Active) return UserRole.Insurance;

        return UserRole.Undefined;
    }

    function getWarrantyQuote(string productId, address insurance, uint startDate, uint endDate, uint productPrice)
                              constant returns(uint warrantyPrice) {
        return insuranceManager.getWarrantyPrice(insurance, productId, startDate, endDate, productPrice);
    }

    /**
        Creates a new warranty.
        productId: The EAN13 that identifies the product
        serialNumber: the particular product serial number
        insurance: the eth address of the insurance
        startDate: start date of the extended warranty
        endDate: start date of the extended warranty
        price: the price in cents
    */
    function createWarranty(string productId, string serialNumber, address insurance, uint startDate, uint endDate, uint productPrice) registeredRetailerOnly(insurance) {
        Warranty warranty = warranties[insurance][productId][serialNumber];
        if(warranty.status != WarrantyStatus.Undefined) throw;

        warranty.status = WarrantyStatus.Created;
        warranty.startDate = startDate;
        warranty.endDate = endDate;
        warranty.productPrice = productPrice;
        warranty.retailer = msg.sender;
        warranty.warrantyPrice = insuranceManager.getWarrantyPrice(insurance, productId, startDate, endDate, productPrice);
        retailerManager.increaseSalesBalance(msg.sender, insurance, warranty.warrantyPrice);
        insuranceManager.increaseSalesBalance(insurance, msg.sender, warranty.warrantyPrice);
    }

    /**
        Confirms a warranty
        productId: The EAN13 that identifies the product
        serialNumber: the particular product serial number
        policyNumber: the policy number of the warranty
    */
    function confirmWarranty(string productId, string serialNumber, string policyNumber) insuranceOnly {
        Warranty warranty = warranties[msg.sender][productId][serialNumber];
        if(warranty.status != WarrantyStatus.Created) throw;

        warranty.status = WarrantyStatus.Confirmed;
        warranty.policyNumber = policyNumber;
    }

    /**
        Cacnels a warranty
        productId: The EAN13 that identifies the product
        serialNumber: the particular product serial number
        policyNumber: the policy number of the warranty
    */
    function cancelWarranty(string productId, string serialNumber, address insuranceAddress) registeredRetailerOnly(insuranceAddress) {
        Warranty warranty = warranties[insuranceAddress][productId][serialNumber];
        /*a warranty can only be canceled if it exists and no claims have been made*/
        if(warranty.status == WarrantyStatus.Undefined || warranty.claimCount > 0) throw;

        warranty.status = WarrantyStatus.Canceled;
        retailerManager.decreaseSalesBalance(msg.sender, insuranceAddress, warranty.warrantyPrice);
        insuranceManager.decreaseSalesBalance(insuranceAddress, msg.sender, warranty.warrantyPrice);
    }

    function getWarranty(string productId, string serialNumber, address insurance) constant returns (uint startDate, uint endDate, WarrantyStatus status,
                         string policyNumber, uint warrantyPrice, uint claimCount) {
        Warranty warranty = warranties[insurance][productId][serialNumber];
        return (warranty.startDate, warranty.endDate, warranty.status, warranty.policyNumber, warranty.warrantyPrice, warranty.claimCount);
    }

    /**
        create a new claim for an insured product
        productId: The EAN13 that identifies the product
        serialNumber: the particular product serial number
    */
    function createClaim(string productId, string serialNumber, address insurance, uint amount, string description) registeredRetailerOnly(insurance) {
        Warranty warranty = warranties[insurance][productId][serialNumber];
        /*create only works for existing and valid warranties*/
        if(warranty.status != WarrantyStatus.Confirmed || warranty.startDate > now || warranty.endDate < now) throw;

        Claim claim = warranty.claims[warranty.claimCount++];
        claim.retailer = msg.sender;
        claim.amount = amount;
        claim.description = description;

        /*increase the retailer's account*/
        retailerManager.increaseClaimsBalance(msg.sender, insurance, amount);
        insuranceManager.increaseClaimsBalance(insurance, msg.sender, amount);
    }

    function getClaim(string productId, string serialNumber, address insurance, uint idx) constant returns (address retailer, uint amount, string description) {
        Claim claim = warranties[insurance][productId][serialNumber].claims[idx];
        return (claim.retailer, claim.amount, claim.description);
    }
}

pragma solidity ^0.4.4;

contract owned {
    address owner;

    modifier ownerOnly() {
        if (msg.sender != owner) throw;
        _;
    }

    function owned() {
        owner = msg.sender;
    }

    function getOwner() constant returns (address) {
        return owner;
    }
}

contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract PriceCalculator {
    function getWarrantyPrice(string productId, uint startDate, uint endDate, uint productPrice) constant returns (uint) {
        uint yrs = (endDate - startDate) / 1 years;
        /*the price is allways 5% per year*/
        return productPrice * yrs / 20;
    }
}

contract stateful {
    enum RetailerStatus {Undefined, Requested, Accepted, Rejected, Terminated}
    enum InsuranceStatus {Undefined, Requested, Active, Terminated}
    enum WarrantyStatus {Undefined, Created, Confirmed, Canceled}
    enum UserRole {Undefined, Retailer, Insurance, Owner}
}

contract related is stateful{
    struct PartnerRelations {
        RetailerStatus status;
        uint sales /*the total amount of policies sold by retailer*/;
        uint payments /*the total amount paid by retailer*/;
        uint claims /*the total amount the insurance has paid to the retailer in claims*/;
    }
}

contract InsuranceManager is owned, related {
    struct Insurance {
        string name;
        InsuranceStatus status;
        PriceCalculator priceCalculator;
        mapping (address=>PartnerRelations) retailers;
    }

    mapping (address => Insurance) insurances;
    mapping (uint=>address) insuranceList;
    uint public insuranceCount;

    event InsuranceStatusChanged(
        address indexed insurance,
        InsuranceStatus status
    );

    function isInsurance(address insurance) constant returns (bool) {
        return insurances[insurance].status != InsuranceStatus.Undefined;
    }

    function createInsurance(string name, address priceCalculator) {
        Insurance insurance = insurances[msg.sender];
        InsuranceStatus previousStatus = insurance.status;
        insurance.status = InsuranceStatus.Requested;
        if(previousStatus != InsuranceStatus.Undefined) throw;

        insurance.name = name;
        insurance.priceCalculator = PriceCalculator(priceCalculator);
        insuranceList[insuranceCount++] = msg.sender;

        InsuranceStatusChanged(msg.sender, InsuranceStatus.Requested);
    }

    function setInsuranceState(address insuranceAddress, InsuranceStatus status) ownerOnly {
        Insurance insurance = insurances[insuranceAddress];
        if(insurance.status == InsuranceStatus.Undefined) throw;

        insurance.status = status;
    }

    function getInsurance(uint index) constant returns (string name, address, InsuranceStatus) {
        Insurance insurance = insurances[insuranceList[index]];
        return (insurance.name, insuranceList[index], insurance.status);
    }

    function getInsuranceStatus(address insuranceAddress) constant returns (InsuranceStatus) {
        return insurances[insuranceAddress].status;
    }

    function getWarrantyPrice(address insuranceAddress, string productId, uint startDate, uint endDate, uint productPrice) constant returns (uint) {
        Insurance insurance = insurances[insuranceAddress];
        if(insurance.status != InsuranceStatus.Active) throw;
        return insurance.priceCalculator.getWarrantyPrice(productId, startDate, endDate, productPrice);
    }

    function increaseSalesBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].sales += amount;
    }

    function decreaseSalesBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].sales -= amount;
    }

    function increasePaymentsBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].payments += amount;
    }

    function decreasePaymentsBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].payments -= amount;
    }

    function increaseClaimsBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].claims += amount;
    }

    function decreaseClaimsBalance(address insurance, address retailer, uint amount) {
        insurances[insurance].retailers[retailer].claims -= amount;
    }
}

contract RetailerManager is owned, related {
    InsuranceManager insuranceManager;

    function RetailerManager(address _insuranceManager){
        insuranceManager = InsuranceManager(_insuranceManager);
    }

    function setSubContractAddresses (address _insuranceManager) ownerOnly {
        insuranceManager = InsuranceManager(_insuranceManager);
    }

    modifier insuranceOnly {
        if(!isInsurance(msg.sender)) throw;
        _;
    }

    function isInsurance(address insurance) constant returns (bool) {
        return insuranceManager.getInsuranceStatus(insurance) != InsuranceStatus.Undefined;
    }

    struct Retailer {
        string companyName;
        mapping (address=>PartnerRelations) partnerRelations /*the mapping holds the relation of the partner with each insurance company*/;
        RetailerStatus status/*in order to easily check for the existence of a retailer the first status is also set on the retailer itself*/;
    }

    mapping (address=>Retailer) retailers;
    mapping (uint=>address) retailerList;
    uint public retailerCount;

    event RetailerRequest(
        string indexed companyName,
        address retailerAddress,
        address indexed insurance
    );

    event RetailerStatusChanged(
        address indexed retailer,
        address indexed insurance,
        RetailerStatus status
    );

    /**
    the retailer send a transaction to request registration with an insurer
    */
    function requestRegistration(string companyName, address insurance) {
        Retailer retailer = retailers[msg.sender];
        retailer.companyName = companyName;
        /*make sure the insurance company exists*/
        if(insuranceManager.getInsuranceStatus(insurance) != InsuranceStatus.Active) {
            throw;
        }
        /*make sure no previous request was made*/
        if(retailer.partnerRelations[insurance].status != RetailerStatus.Undefined){
            throw;
        }

        retailerList[retailerCount++] = msg.sender;
        retailer.partnerRelations[insurance].status = RetailerStatus.Requested;
        retailer.status = RetailerStatus.Accepted;
        retailers[msg.sender] = retailer;
        RetailerRequest(companyName, msg.sender, insurance);
    }

    function getRequestState(address retailer, address insurance) constant returns (RetailerStatus) {
        return retailers[retailer].partnerRelations[insurance].status;
    }

    /**
    sets the status of a retailer's request.
    only the insurance to which the request was made can do this
    */
    function setRequestState(address retailer, RetailerStatus status) insuranceOnly {
        retailers[retailer].partnerRelations[msg.sender].status = status;
        RetailerStatusChanged(retailer, msg.sender, status);
    }

    function getRetailerStatus(address retailer, address insurance) returns (RetailerStatus){
        return retailers[retailer].partnerRelations[insurance].status;
    }

    function getRetailerStatus(address retailer) returns (RetailerStatus){
        return retailers[retailer].status;
    }

    /**
    get the nth retailer in the list
    */
    function getRetailer(uint index, address insuranceAddress) constant returns (address, string, RetailerStatus, RetailerStatus) {
        address retailerAddress = retailerList[index];
        Retailer retailer = retailers[retailerAddress];
        return (retailerAddress, retailer.companyName, retailer.status, retailer.partnerRelations[insuranceAddress].status);
    }

    function getRetailerByAddress(address retailerAddress, address insuranceAddress) constant returns (address, string, RetailerStatus, RetailerStatus) {
        Retailer retailer = retailers[retailerAddress];
        return (retailerAddress, retailer.companyName, retailer.status, retailer.partnerRelations[insuranceAddress].status);
    }

    function getRetailerBalances(address retailer, address insurance) constant returns (uint, uint, uint) {
        PartnerRelations partnerRelation = retailers[retailer].partnerRelations[insurance];
        return (partnerRelation.sales, partnerRelation.payments, partnerRelation.claims);
    }

    function increaseSalesBalance(address retailer, address insurance, uint price) {
        retailers[retailer].partnerRelations[insurance].sales += price;
    }

    function decreaseSalesBalance(address retailer, address insurance, uint price) {
        retailers[retailer].partnerRelations[insurance].sales -= price;
    }

    function increasePaymentsBalance(address retailer, address insurance, uint amount) {
        retailers[retailer].partnerRelations[insurance].payments += amount;
    }

    function decreasePaymentsBalance(address retailer, address insurance, uint amount) {
        retailers[retailer].partnerRelations[insurance].payments -= amount;
    }

    function increaseClaimsBalance(address retailer, address insurance, uint amount) {
        retailers[retailer].partnerRelations[insurance].claims += amount;
    }

    function decreaseClaimsBalance(address retailer, address insurance, uint amount) {
        retailers[retailer].partnerRelations[insurance].claims -= amount;
    }
}

contract Insurechain is mortal, stateful{
    InsuranceManager insuranceManager;
    RetailerManager retailerManager;

    function Insurechain(address _insuranceManager, address _retailerManager) {
        insuranceManager = InsuranceManager(_insuranceManager);
        retailerManager = RetailerManager(_retailerManager);
    }

    function setSubContractAddresses (address _insuranceManager, address _retailerManager) ownerOnly {
        insuranceManager = InsuranceManager(_insuranceManager);
        retailerManager = RetailerManager(_retailerManager);
    }

    struct Claim {
        address retailer /*in theory another retailer than the one who sold the insurance can make a claim*/;
        uint amount;
        string description;
    }

    struct Warranty {
        address retailer;
        uint startDate;
        uint endDate;
        string policyNumber;
        WarrantyStatus status;
        uint productPrice;
        uint warrantyPrice;
        mapping (uint => Claim) claims;
        uint claimCount;
    }

    // mapping of insurance -> productId -> serialNumber -> Warranty
    mapping (address=>mapping( string=>mapping( string=>Warranty ))) warranties;

    modifier insuranceOnly {
        if(!isInsurance(msg.sender)) throw;
        _;
    }

    function isInsurance(address insurance) constant returns (bool) {
        return insuranceManager.getInsuranceStatus(insurance) != InsuranceStatus.Undefined;
    }

    modifier registeredRetailerOnly(address insurance) {
        if(!isRegisteredRetailer(insurance, msg.sender)) throw;
        _;
    }

    function isRegisteredRetailer(address insurance, address retailer) constant returns (bool) {
        return isInsurance(insurance) && retailerManager.getRetailerStatus(msg.sender, insurance) == RetailerStatus.Accepted;
    }

    function getRole(address user) constant returns (UserRole) {
        if(user == owner) return UserRole.Owner;
        if(retailerManager.getRetailerStatus(user) == RetailerStatus.Accepted) return UserRole.Retailer;
        if(insuranceManager.getInsuranceStatus(user) == InsuranceStatus.Active) return UserRole.Insurance;

        return UserRole.Undefined;
    }

    function getWarrantyQuote(string productId, address insurance, uint startDate, uint endDate, uint productPrice)
                              constant returns(uint warrantyPrice) {
        return insuranceManager.getWarrantyPrice(insurance, productId, startDate, endDate, productPrice);
    }

    /**
        Creates a new warranty.
        productId: The EAN13 that identifies the product
        serialNumber: the particular product serial number
        insurance: the eth address of the insurance
        startDate: start date of the extended warranty
        endDate: start date of the extended warranty
        price: the price in cents
    */
    function createWarranty(string productId, string serialNumber, address insurance, uint startDate, uint endDate, uint productPrice) registeredRetailerOnly(insurance) {
        Warranty warranty = warranties[insurance][productId][serialNumber];
        if(warranty.status != WarrantyStatus.Undefined) throw;

        warranty.status = WarrantyStatus.Created;
        warranty.startDate = startDate;
        warranty.endDate = endDate;
        warranty.productPrice = productPrice;
        warranty.retailer = msg.sender;
        warranty.warrantyPrice = insuranceManager.getWarrantyPrice(insurance, productId, startDate, endDate, productPrice);
        retailerManager.increaseSalesBalance(msg.sender, insurance, warranty.warrantyPrice);
        insuranceManager.increaseSalesBalance(insurance, msg.sender, warranty.warrantyPrice);
    }

    /**
        Confirms a warranty
        productId: The EAN13 that identifies the product
        serialNumber: the particular product serial number
        policyNumber: the policy number of the warranty
    */
    function confirmWarranty(string productId, string serialNumber, string policyNumber) insuranceOnly {
        Warranty warranty = warranties[msg.sender][productId][serialNumber];
        if(warranty.status != WarrantyStatus.Created) throw;

        warranty.status = WarrantyStatus.Confirmed;
        warranty.policyNumber = policyNumber;
    }

    /**
        Cacnels a warranty
        productId: The EAN13 that identifies the product
        serialNumber: the particular product serial number
        policyNumber: the policy number of the warranty
    */
    function cancelWarranty(string productId, string serialNumber, address insuranceAddress) registeredRetailerOnly(insuranceAddress) {
        Warranty warranty = warranties[insuranceAddress][productId][serialNumber];
        /*a warranty can only be canceled if it exists and no claims have been made*/
        if(warranty.status == WarrantyStatus.Undefined || warranty.claimCount > 0) throw;

        warranty.status = WarrantyStatus.Canceled;
        retailerManager.decreaseSalesBalance(msg.sender, insuranceAddress, warranty.warrantyPrice);
        insuranceManager.decreaseSalesBalance(insuranceAddress, msg.sender, warranty.warrantyPrice);
    }

    function getWarranty(string productId, string serialNumber, address insurance) constant returns (uint startDate, uint endDate, WarrantyStatus status,
                         string policyNumber, uint warrantyPrice, uint claimCount) {
        Warranty warranty = warranties[insurance][productId][serialNumber];
        return (warranty.startDate, warranty.endDate, warranty.status, warranty.policyNumber, warranty.warrantyPrice, warranty.claimCount);
    }

    /**
        create a new claim for an insured product
        productId: The EAN13 that identifies the product
        serialNumber: the particular product serial number
    */
    function createClaim(string productId, string serialNumber, address insurance, uint amount, string description) registeredRetailerOnly(insurance) {
        Warranty warranty = warranties[insurance][productId][serialNumber];
        /*create only works for existing and valid warranties*/
        if(warranty.status != WarrantyStatus.Confirmed || warranty.startDate > now || warranty.endDate < now) throw;

        Claim claim = warranty.claims[warranty.claimCount++];
        claim.retailer = msg.sender;
        claim.amount = amount;
        claim.description = description;

        /*increase the retailer's account*/
        retailerManager.increaseClaimsBalance(msg.sender, insurance, amount);
        insuranceManager.increaseClaimsBalance(insurance, msg.sender, amount);
    }

    function getClaim(string productId, string serialNumber, address insurance, uint idx) constant returns (address retailer, uint amount, string description) {
        Claim claim = warranties[insurance][productId][serialNumber].claims[idx];
        return (claim.retailer, claim.amount, claim.description);
    }
}


There's no need to throw when burning 0 tokens

Throwing when trying to burn 0 tokens is an unnecessary special case.
If another contract wants to burn() a variable amount, it should not be forced to deal with this special case of burning 0.

pragma solidity ^0.4.18;

import './StandardToken.sol';

/**
 * @title Burnable Token
 * @dev Token that can be irreversibly burned (destroyed).
 */
contract BurnableToken is StandardToken {

    event Burn(address indexed burner, uint256 value);

    /**
     * @dev Burns a specific amount of tokens.
     * @param _value The amount of token to be burned.
     */
    function burn(uint256 _value) public {
        require(_value <= balances[msg.sender]);
        // no need to require value <= totalSupply, since that would imply the
        // sender's balance is greater than the totalSupply, which *should* be an assertion failure

        address burner = msg.sender;
        balances[burner] = balances[burner].sub(_value);
        totalSupply = totalSupply.sub(_value);
        Burn(burner, _value);
    }
}

pragma solidity ^0.4.18;

import './StandardToken.sol';

/**
 * @title Burnable Token
 * @dev Token that can be irreversibly burned (destroyed).
 */
contract BurnableToken is StandardToken {

    event Burn(address indexed burner, uint256 value);

    /**
     * @dev Burns a specific amount of tokens.
     * @param _value The amount of token to be burned.
     */
    function burn(uint256 _value) public {
        require(_value > 0);
        require(_value <= balances[msg.sender]);
        // no need to require value <= totalSupply, since that would imply the
        // sender's balance is greater than the totalSupply, which *should* be an assertion failure

        address burner = msg.sender;
        balances[burner] = balances[burner].sub(_value);
        totalSupply = totalSupply.sub(_value);
        Burn(burner, _value);
    }
}


Address: explain dangers of isContract (#2994)

Co-authored-by: Ivo Georgiev <cf37f587706e192c1741adbf805403a5d73ea074@strem.io>

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.0 (utils/Address.sol)

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.0 (utils/Address.sol)

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}