Fix flag docs in tests.

Fix flag docs in tests.

pragma solidity ^0.4.18;

import "ds-test/test.sol";

import "./item_store_registry.sol";
import "./item_store_ipfs_sha256.sol";
import "./item_store_ipfs_sha256_proxy.sol";

/**
 * @title ItemStoreIpfsSha256Test
 * @author Jonathan Brown <jbrown@mix-blockchain.org>
 * @dev Testing contract for ItemStoreIpfsSha256.
 */
contract ItemStoreIpfsSha256Test is DSTest {

    byte constant UPDATABLE = 0x01;           // True if the item is updatable. After creation can only be disabled.
    byte constant ENFORCE_REVISIONS = 0x02;   // True if the item is enforcing revisions. After creation can only be enabled.
    byte constant RETRACTABLE = 0x04;         // True if the item can be retracted. After creation can only be disabled.
    byte constant TRANSFERABLE = 0x08;        // True if the item can be transfered to another user or disowned. After creation can only be disabled.
    byte constant DISOWN = 0x10;              // True if the item should not have an owner at creation.

    ItemStoreRegistry itemStoreRegistry;
    ItemStoreIpfsSha256 itemStore;
    ItemStoreIpfsSha256Proxy itemStoreProxy;

    function setUp() public {
        itemStoreRegistry = new ItemStoreRegistry();
        itemStore = new ItemStoreIpfsSha256(itemStoreRegistry);
        itemStoreProxy = new ItemStoreIpfsSha256Proxy(itemStore);
    }

    function testControlCreateSameNonce() public {
        itemStore.create(0x0000, 0x1234);
        itemStore.create(0x0001, 0x1234);
    }

    function testFailCreateSameNonce() public {
        itemStore.create(0x0000, 0x1234);
        itemStore.create(0x0000, 0x1234);
    }

    function testCreate() public {
        bytes20 itemId0 = itemStore.create(0x0000, 0x1234);
        assertTrue(itemStore.getInUse(itemId0));
        assertEq(itemStore.getFlags(itemId0), 0);
        assertEq(itemStore.getOwner(itemId0), this);
        assertEq(itemStore.getRevisionCount(itemId0), 1);
        assertTrue(!itemStore.getUpdatable(itemId0));
        assertTrue(!itemStore.getEnforceRevisions(itemId0));
        assertTrue(!itemStore.getRetractable(itemId0));
        assertTrue(!itemStore.getTransferable(itemId0));
        assertEq(itemStore.getRevisionIpfsHash(itemId0, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId0, 0), block.timestamp);

        bytes20 itemId1 = itemStore.create(UPDATABLE | ENFORCE_REVISIONS | RETRACTABLE | TRANSFERABLE | DISOWN | bytes32(0x01), 0x1234);
        assertTrue(itemStore.getInUse(itemId1));
        assertEq(itemStore.getFlags(itemId1), UPDATABLE | ENFORCE_REVISIONS | RETRACTABLE | TRANSFERABLE | DISOWN);
        assertEq(itemStore.getOwner(itemId1), 0);
        assertEq(itemStore.getRevisionCount(itemId1), 1);
        assertTrue(itemStore.getUpdatable(itemId1));
        assertTrue(itemStore.getEnforceRevisions(itemId1));
        assertTrue(itemStore.getRetractable(itemId1));
        assertTrue(itemStore.getTransferable(itemId1));
        assertEq(itemStore.getRevisionIpfsHash(itemId1, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId1, 0), block.timestamp);

        bytes20 itemId2 = itemStore.create(UPDATABLE | ENFORCE_REVISIONS | RETRACTABLE | TRANSFERABLE | DISOWN | bytes32(0x02), 0x2345);
        assertTrue(itemStore.getInUse(itemId2));
        assertEq(itemStore.getFlags(itemId2), UPDATABLE | ENFORCE_REVISIONS | RETRACTABLE | TRANSFERABLE | DISOWN);
        assertEq(itemStore.getOwner(itemId2), 0);
        assertEq(itemStore.getRevisionCount(itemId2), 1);
        assertTrue(itemStore.getUpdatable(itemId2));
        assertTrue(itemStore.getEnforceRevisions(itemId2));
        assertTrue(itemStore.getRetractable(itemId2));
        assertTrue(itemStore.getTransferable(itemId2));
        assertEq(itemStore.getRevisionIpfsHash(itemId2, 0), 0x2345);
        assertEq(itemStore.getRevisionTimestamp(itemId2, 0), block.timestamp);

        assertTrue(itemId0 != itemId1);
        assertTrue(itemId0 != itemId2);
        assertTrue(itemId1 != itemId2);
    }

    function testControlCreateNewRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
    }

    function testFailCreateNewRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStoreProxy.createNewRevision(itemId, 0x2345);
    }

    function testControlCreateNewRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
    }

    function testFailCreateNewRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
    }

    function testCreateNewRevision() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0);
        uint revisionId = itemStore.createNewRevision(itemId, 1);
        assertEq(revisionId, 1);
        revisionId = itemStore.createNewRevision(itemId, 2);
        assertEq(revisionId, 2);
        revisionId = itemStore.createNewRevision(itemId, 3);
        assertEq(revisionId, 3);
        revisionId = itemStore.createNewRevision(itemId, 4);
        assertEq(revisionId, 4);
        revisionId = itemStore.createNewRevision(itemId, 5);
        assertEq(revisionId, 5);
        revisionId = itemStore.createNewRevision(itemId, 6);
        assertEq(revisionId, 6);
        revisionId = itemStore.createNewRevision(itemId, 7);
        assertEq(revisionId, 7);
        revisionId = itemStore.createNewRevision(itemId, 8);
        assertEq(revisionId, 8);
        revisionId = itemStore.createNewRevision(itemId, 9);
        assertEq(revisionId, 9);
        revisionId = itemStore.createNewRevision(itemId, 10);
        assertEq(revisionId, 10);
        revisionId = itemStore.createNewRevision(itemId, 11);
        assertEq(revisionId, 11);
        revisionId = itemStore.createNewRevision(itemId, 12);
        assertEq(revisionId, 12);
        revisionId = itemStore.createNewRevision(itemId, 13);
        assertEq(revisionId, 13);
        assertEq(itemStore.getRevisionCount(itemId), 14);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 1), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 2), 2);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 3), 3);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 4), 4);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 5), 5);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 6), 6);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 7), 7);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 8), 8);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 9), 9);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 10), 10);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 11), 11);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 12), 12);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 13), 13);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 1), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 2), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 3), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 4), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 5), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 6), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 7), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 8), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 9), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 10), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 11), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 12), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 13), block.timestamp);
    }

    function testControlUpdateLatestRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.updateLatestRevision(itemId, 0x2345);
    }

    function testFailUpdateLatestRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStoreProxy.updateLatestRevision(itemId, 0x2345);
    }

    function testControlUpdateLatestRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.updateLatestRevision(itemId, 0x2345);
    }

    function testFailUpdateLatestRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.updateLatestRevision(itemId, 0x2345);
    }

    function testControlUpdateLatestRevisionEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.updateLatestRevision(itemId, 0x2345);
    }

    function testFailUpdateLatestRevisionEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE | ENFORCE_REVISIONS, 0x1234);
        itemStore.updateLatestRevision(itemId, 0x2345);
    }

    function testUpdateLatestRevision() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        itemStore.updateLatestRevision(itemId, 0x2345);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x2345);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
    }

    function testControlRetractLatestRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.retractLatestRevision(itemId);
    }

    function testFailRetractLatestRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStoreProxy.retractLatestRevision(itemId);
    }

    function testControlRetractLatestRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.retractLatestRevision(itemId);
    }

    function testFailRetractLatestRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.setNotUpdatable(itemId);
        itemStore.retractLatestRevision(itemId);
    }

    function testControlRetractLatestRevisionEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.retractLatestRevision(itemId);
    }

    function testFailRetractLatestRevisionEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE | ENFORCE_REVISIONS, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.retractLatestRevision(itemId);
    }

    function testControlRetractLatestRevisionDoesntHaveAdditionalRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.retractLatestRevision(itemId);
    }

    function testFailRetractLatestRevisionDoesntHaveAdditionalRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.retractLatestRevision(itemId);
    }

    function testRetractLatestRevision() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.createNewRevision(itemId, 0x3456);
        assertEq(itemStore.getRevisionCount(itemId), 3);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 1), 0x2345);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 2), 0x3456);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 1), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 2), block.timestamp);
        itemStore.retractLatestRevision(itemId);
        assertEq(itemStore.getRevisionCount(itemId), 2);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 1), 0x2345);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 1), block.timestamp);
        itemStore.retractLatestRevision(itemId);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
    }

    function testControlRestartNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.restart(itemId, 0x2345);
    }

    function testFailRestartNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStoreProxy.restart(itemId, 0x2345);
    }

    function testControlRestartNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.restart(itemId, 0x2345);
    }

    function testFailRestartNotUpdatable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.restart(itemId, 0x2345);
    }

    function testControlRestartEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.restart(itemId, 0x2345);
    }

    function testFailRestartEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE | ENFORCE_REVISIONS, 0x1234);
        itemStore.restart(itemId, 0x2345);
    }

    function testRestart() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.createNewRevision(itemId, 0x3456);
        assertEq(itemStore.getRevisionCount(itemId), 3);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 1), 0x2345);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 2), 0x3456);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 1), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 2), block.timestamp);
        itemStore.restart(itemId, 0x4567);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x4567);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
    }

    function testControlRetractNotOwner() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        itemStore.retract(itemId);
    }

    function testFailRetractNotOwner() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        itemStoreProxy.retract(itemId);
    }

    function testControlRetractNotRetractable() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        itemStore.retract(itemId);
    }

    function testFailRetractNotRetractable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.retract(itemId);
    }

    function testRetract() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        assertTrue(itemStore.getInUse(itemId));
        assertEq(itemStore.getOwner(itemId), this);
        assertTrue(!itemStore.getUpdatable(itemId));
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        itemStore.retract(itemId);
        assertTrue(itemStore.getInUse(itemId));
        assertEq(itemStore.getOwner(itemId), 0);
        assertTrue(!itemStore.getUpdatable(itemId));
        assertEq(itemStore.getRevisionCount(itemId), 0);
    }

    function testControlTransferEnableNotTransferable() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
    }

    function testFailTransferEnableNotTransferable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStoreProxy.transferEnable(itemId);
    }

    function testControlTransferDisableNotEnabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStoreProxy.transferDisable(itemId);
    }

    function testFailTransferDisableNotEnabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferDisable(itemId);
    }

    function testControlTransferNotTransferable() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testFailTransferNotTransferable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testControlTransferNotEnabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testFailTransferNotEnabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testControlTransferDisabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testFailTransferDisabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStoreProxy.transferDisable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testTransfer() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        assertEq(itemStore.getOwner(itemId), this);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        itemStoreProxy.transferEnable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
        assertEq(itemStore.getOwner(itemId), itemStoreProxy);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
    }

    function testControlDisownNotOwner() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStore.disown(itemId);
    }

    function testFailDisownNotOwner() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.disown(itemId);
    }

    function testControlDisownNotTransferable() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStore.disown(itemId);
    }

    function testFailDisownNotTransferable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.disown(itemId);
    }

    function testDisown() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        assertEq(itemStore.getOwner(itemId), this);
        itemStore.disown(itemId);
        assertEq(itemStore.getOwner(itemId), 0);
    }

    function testControlSetNotUpdatableNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.setNotUpdatable(itemId);
    }

    function testFailSetNotUpdatableNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStoreProxy.setNotUpdatable(itemId);
    }

    function testSetNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        assertTrue(itemStore.getUpdatable(itemId));
        itemStore.setNotUpdatable(itemId);
        assertTrue(!itemStore.getUpdatable(itemId));
    }

    function testControlSetEnforceRevisionsNotOwner() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.setEnforceRevisions(itemId);
    }

    function testFailSetEnforceRevisionsNotOwner() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStoreProxy.setEnforceRevisions(itemId);
    }

    function testSetEnforceRevisions() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        assertTrue(!itemStore.getEnforceRevisions(itemId));
        itemStore.setEnforceRevisions(itemId);
        assertTrue(itemStore.getEnforceRevisions(itemId));
    }

    function testControlSetNotRetractableNotOwner() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        itemStore.setNotRetractable(itemId);
    }

    function testFailSetNotRetractableNotOwner() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        itemStoreProxy.setNotRetractable(itemId);
    }

    function testSetNotRetractable() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        assertTrue(itemStore.getRetractable(itemId));
        itemStore.setNotRetractable(itemId);
        assertTrue(!itemStore.getRetractable(itemId));
    }

    function testControlSetNotTransferableNotOwner() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStore.setNotTransferable(itemId);
    }

    function testFailSetNotTransferableNotOwner() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.setNotTransferable(itemId);
    }

    function testSetNotTransferable() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        assertTrue(itemStore.getTransferable(itemId));
        itemStore.setNotTransferable(itemId);
        assertTrue(!itemStore.getTransferable(itemId));
    }

}

pragma solidity ^0.4.18;

import "ds-test/test.sol";

import "./item_store_registry.sol";
import "./item_store_ipfs_sha256.sol";
import "./item_store_ipfs_sha256_proxy.sol";

/**
 * @title ItemStoreIpfsSha256Test
 * @author Jonathan Brown <jbrown@mix-blockchain.org>
 * @dev Testing contract for ItemStoreIpfsSha256.
 */
contract ItemStoreIpfsSha256Test is DSTest {

    byte constant UPDATABLE = 0x01;           // True if the item is updatable. After creation can only be disabled.
    byte constant ENFORCE_REVISIONS = 0x02;   // True if the item is enforcing revisions. After creation can only be enabled.
    byte constant RETRACTABLE = 0x04;         // True if the item can be retracted. After creation can only be disabled.
    byte constant TRANSFERABLE = 0x08;        // True if the item be transfered to another user or disowned. After creation can only be disabled.
    byte constant DISOWN = 0x10;              // True if the item should not have an owner.

    ItemStoreRegistry itemStoreRegistry;
    ItemStoreIpfsSha256 itemStore;
    ItemStoreIpfsSha256Proxy itemStoreProxy;

    function setUp() public {
        itemStoreRegistry = new ItemStoreRegistry();
        itemStore = new ItemStoreIpfsSha256(itemStoreRegistry);
        itemStoreProxy = new ItemStoreIpfsSha256Proxy(itemStore);
    }

    function testControlCreateSameNonce() public {
        itemStore.create(0x0000, 0x1234);
        itemStore.create(0x0001, 0x1234);
    }

    function testFailCreateSameNonce() public {
        itemStore.create(0x0000, 0x1234);
        itemStore.create(0x0000, 0x1234);
    }

    function testCreate() public {
        bytes20 itemId0 = itemStore.create(0x0000, 0x1234);
        assertTrue(itemStore.getInUse(itemId0));
        assertEq(itemStore.getFlags(itemId0), 0);
        assertEq(itemStore.getOwner(itemId0), this);
        assertEq(itemStore.getRevisionCount(itemId0), 1);
        assertTrue(!itemStore.getUpdatable(itemId0));
        assertTrue(!itemStore.getEnforceRevisions(itemId0));
        assertTrue(!itemStore.getRetractable(itemId0));
        assertTrue(!itemStore.getTransferable(itemId0));
        assertEq(itemStore.getRevisionIpfsHash(itemId0, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId0, 0), block.timestamp);

        bytes20 itemId1 = itemStore.create(UPDATABLE | ENFORCE_REVISIONS | RETRACTABLE | TRANSFERABLE | DISOWN | bytes32(0x01), 0x1234);
        assertTrue(itemStore.getInUse(itemId1));
        assertEq(itemStore.getFlags(itemId1), UPDATABLE | ENFORCE_REVISIONS | RETRACTABLE | TRANSFERABLE | DISOWN);
        assertEq(itemStore.getOwner(itemId1), 0);
        assertEq(itemStore.getRevisionCount(itemId1), 1);
        assertTrue(itemStore.getUpdatable(itemId1));
        assertTrue(itemStore.getEnforceRevisions(itemId1));
        assertTrue(itemStore.getRetractable(itemId1));
        assertTrue(itemStore.getTransferable(itemId1));
        assertEq(itemStore.getRevisionIpfsHash(itemId1, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId1, 0), block.timestamp);

        bytes20 itemId2 = itemStore.create(UPDATABLE | ENFORCE_REVISIONS | RETRACTABLE | TRANSFERABLE | DISOWN | bytes32(0x02), 0x2345);
        assertTrue(itemStore.getInUse(itemId2));
        assertEq(itemStore.getFlags(itemId2), UPDATABLE | ENFORCE_REVISIONS | RETRACTABLE | TRANSFERABLE | DISOWN);
        assertEq(itemStore.getOwner(itemId2), 0);
        assertEq(itemStore.getRevisionCount(itemId2), 1);
        assertTrue(itemStore.getUpdatable(itemId2));
        assertTrue(itemStore.getEnforceRevisions(itemId2));
        assertTrue(itemStore.getRetractable(itemId2));
        assertTrue(itemStore.getTransferable(itemId2));
        assertEq(itemStore.getRevisionIpfsHash(itemId2, 0), 0x2345);
        assertEq(itemStore.getRevisionTimestamp(itemId2, 0), block.timestamp);

        assertTrue(itemId0 != itemId1);
        assertTrue(itemId0 != itemId2);
        assertTrue(itemId1 != itemId2);
    }

    function testControlCreateNewRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
    }

    function testFailCreateNewRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStoreProxy.createNewRevision(itemId, 0x2345);
    }

    function testControlCreateNewRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
    }

    function testFailCreateNewRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
    }

    function testCreateNewRevision() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0);
        uint revisionId = itemStore.createNewRevision(itemId, 1);
        assertEq(revisionId, 1);
        revisionId = itemStore.createNewRevision(itemId, 2);
        assertEq(revisionId, 2);
        revisionId = itemStore.createNewRevision(itemId, 3);
        assertEq(revisionId, 3);
        revisionId = itemStore.createNewRevision(itemId, 4);
        assertEq(revisionId, 4);
        revisionId = itemStore.createNewRevision(itemId, 5);
        assertEq(revisionId, 5);
        revisionId = itemStore.createNewRevision(itemId, 6);
        assertEq(revisionId, 6);
        revisionId = itemStore.createNewRevision(itemId, 7);
        assertEq(revisionId, 7);
        revisionId = itemStore.createNewRevision(itemId, 8);
        assertEq(revisionId, 8);
        revisionId = itemStore.createNewRevision(itemId, 9);
        assertEq(revisionId, 9);
        revisionId = itemStore.createNewRevision(itemId, 10);
        assertEq(revisionId, 10);
        revisionId = itemStore.createNewRevision(itemId, 11);
        assertEq(revisionId, 11);
        revisionId = itemStore.createNewRevision(itemId, 12);
        assertEq(revisionId, 12);
        revisionId = itemStore.createNewRevision(itemId, 13);
        assertEq(revisionId, 13);
        assertEq(itemStore.getRevisionCount(itemId), 14);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 1), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 2), 2);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 3), 3);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 4), 4);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 5), 5);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 6), 6);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 7), 7);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 8), 8);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 9), 9);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 10), 10);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 11), 11);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 12), 12);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 13), 13);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 1), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 2), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 3), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 4), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 5), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 6), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 7), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 8), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 9), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 10), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 11), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 12), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 13), block.timestamp);
    }

    function testControlUpdateLatestRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.updateLatestRevision(itemId, 0x2345);
    }

    function testFailUpdateLatestRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStoreProxy.updateLatestRevision(itemId, 0x2345);
    }

    function testControlUpdateLatestRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.updateLatestRevision(itemId, 0x2345);
    }

    function testFailUpdateLatestRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.updateLatestRevision(itemId, 0x2345);
    }

    function testControlUpdateLatestRevisionEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.updateLatestRevision(itemId, 0x2345);
    }

    function testFailUpdateLatestRevisionEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE | ENFORCE_REVISIONS, 0x1234);
        itemStore.updateLatestRevision(itemId, 0x2345);
    }

    function testUpdateLatestRevision() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        itemStore.updateLatestRevision(itemId, 0x2345);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x2345);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
    }

    function testControlRetractLatestRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.retractLatestRevision(itemId);
    }

    function testFailRetractLatestRevisionNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStoreProxy.retractLatestRevision(itemId);
    }

    function testControlRetractLatestRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.retractLatestRevision(itemId);
    }

    function testFailRetractLatestRevisionNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.setNotUpdatable(itemId);
        itemStore.retractLatestRevision(itemId);
    }

    function testControlRetractLatestRevisionEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.retractLatestRevision(itemId);
    }

    function testFailRetractLatestRevisionEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE | ENFORCE_REVISIONS, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.retractLatestRevision(itemId);
    }

    function testControlRetractLatestRevisionDoesntHaveAdditionalRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.retractLatestRevision(itemId);
    }

    function testFailRetractLatestRevisionDoesntHaveAdditionalRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.retractLatestRevision(itemId);
    }

    function testRetractLatestRevision() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.createNewRevision(itemId, 0x3456);
        assertEq(itemStore.getRevisionCount(itemId), 3);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 1), 0x2345);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 2), 0x3456);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 1), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 2), block.timestamp);
        itemStore.retractLatestRevision(itemId);
        assertEq(itemStore.getRevisionCount(itemId), 2);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 1), 0x2345);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 1), block.timestamp);
        itemStore.retractLatestRevision(itemId);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
    }

    function testControlRestartNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.restart(itemId, 0x2345);
    }

    function testFailRestartNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStoreProxy.restart(itemId, 0x2345);
    }

    function testControlRestartNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.restart(itemId, 0x2345);
    }

    function testFailRestartNotUpdatable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.restart(itemId, 0x2345);
    }

    function testControlRestartEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.restart(itemId, 0x2345);
    }

    function testFailRestartEnforceRevisions() public {
        bytes20 itemId = itemStore.create(UPDATABLE | ENFORCE_REVISIONS, 0x1234);
        itemStore.restart(itemId, 0x2345);
    }

    function testRestart() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.createNewRevision(itemId, 0x2345);
        itemStore.createNewRevision(itemId, 0x3456);
        assertEq(itemStore.getRevisionCount(itemId), 3);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 1), 0x2345);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 2), 0x3456);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 1), block.timestamp);
        assertEq(itemStore.getRevisionTimestamp(itemId, 2), block.timestamp);
        itemStore.restart(itemId, 0x4567);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x4567);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
    }

    function testControlRetractNotOwner() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        itemStore.retract(itemId);
    }

    function testFailRetractNotOwner() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        itemStoreProxy.retract(itemId);
    }

    function testControlRetractNotRetractable() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        itemStore.retract(itemId);
    }

    function testFailRetractNotRetractable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.retract(itemId);
    }

    function testRetract() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        assertTrue(itemStore.getInUse(itemId));
        assertEq(itemStore.getOwner(itemId), this);
        assertTrue(!itemStore.getUpdatable(itemId));
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        itemStore.retract(itemId);
        assertTrue(itemStore.getInUse(itemId));
        assertEq(itemStore.getOwner(itemId), 0);
        assertTrue(!itemStore.getUpdatable(itemId));
        assertEq(itemStore.getRevisionCount(itemId), 0);
    }

    function testControlTransferEnableNotTransferable() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
    }

    function testFailTransferEnableNotTransferable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStoreProxy.transferEnable(itemId);
    }

    function testControlTransferDisableNotEnabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStoreProxy.transferDisable(itemId);
    }

    function testFailTransferDisableNotEnabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferDisable(itemId);
    }

    function testControlTransferNotTransferable() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testFailTransferNotTransferable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testControlTransferNotEnabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testFailTransferNotEnabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testControlTransferDisabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testFailTransferDisabled() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.transferEnable(itemId);
        itemStoreProxy.transferDisable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
    }

    function testTransfer() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        assertEq(itemStore.getOwner(itemId), this);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
        itemStoreProxy.transferEnable(itemId);
        itemStore.transfer(itemId, itemStoreProxy);
        assertEq(itemStore.getOwner(itemId), itemStoreProxy);
        assertEq(itemStore.getRevisionCount(itemId), 1);
        assertEq(itemStore.getRevisionIpfsHash(itemId, 0), 0x1234);
        assertEq(itemStore.getRevisionTimestamp(itemId, 0), block.timestamp);
    }

    function testControlDisownNotOwner() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStore.disown(itemId);
    }

    function testFailDisownNotOwner() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.disown(itemId);
    }

    function testControlDisownNotTransferable() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStore.disown(itemId);
    }

    function testFailDisownNotTransferable() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.disown(itemId);
    }

    function testDisown() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        assertEq(itemStore.getOwner(itemId), this);
        itemStore.disown(itemId);
        assertEq(itemStore.getOwner(itemId), 0);
    }

    function testControlSetNotUpdatableNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStore.setNotUpdatable(itemId);
    }

    function testFailSetNotUpdatableNotOwner() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        itemStoreProxy.setNotUpdatable(itemId);
    }

    function testSetNotUpdatable() public {
        bytes20 itemId = itemStore.create(UPDATABLE, 0x1234);
        assertTrue(itemStore.getUpdatable(itemId));
        itemStore.setNotUpdatable(itemId);
        assertTrue(!itemStore.getUpdatable(itemId));
    }

    function testControlSetEnforceRevisionsNotOwner() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStore.setEnforceRevisions(itemId);
    }

    function testFailSetEnforceRevisionsNotOwner() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        itemStoreProxy.setEnforceRevisions(itemId);
    }

    function testSetEnforceRevisions() public {
        bytes20 itemId = itemStore.create(0, 0x1234);
        assertTrue(!itemStore.getEnforceRevisions(itemId));
        itemStore.setEnforceRevisions(itemId);
        assertTrue(itemStore.getEnforceRevisions(itemId));
    }

    function testControlSetNotRetractableNotOwner() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        itemStore.setNotRetractable(itemId);
    }

    function testFailSetNotRetractableNotOwner() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        itemStoreProxy.setNotRetractable(itemId);
    }

    function testSetNotRetractable() public {
        bytes20 itemId = itemStore.create(RETRACTABLE, 0x1234);
        assertTrue(itemStore.getRetractable(itemId));
        itemStore.setNotRetractable(itemId);
        assertTrue(!itemStore.getRetractable(itemId));
    }

    function testControlSetNotTransferableNotOwner() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStore.setNotTransferable(itemId);
    }

    function testFailSetNotTransferableNotOwner() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        itemStoreProxy.setNotTransferable(itemId);
    }

    function testSetNotTransferable() public {
        bytes20 itemId = itemStore.create(TRANSFERABLE, 0x1234);
        assertTrue(itemStore.getTransferable(itemId));
        itemStore.setNotTransferable(itemId);
        assertTrue(!itemStore.getTransferable(itemId));
    }

}


payable functions

payable functions

pragma solidity ^0.4.11;
contract PotOfGold {

    struct Pot {
        string name;
        uint buyIn;

        address[] players;

        uint lastPlayerBlockNumber; // uint8(uint256(sha3(block.blockhash(lastPlayerBlockNumber+1)))) % 3 == loser index

        address loser;
    }

    address public owner;

    Pot[] allPots;
    mapping(string => Pot) nameToPot;

    event newPot(string name, uint buyIn, address creator);
    event potJoin(string name, address newPlayer);
    event potExpired(string name);
    event potClosed(string name, address loser);

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

    function ownerWithdraw(){
        require(msg.sender == owner);

        int toWithdraw = int(this.balance - 100 finney); // leave 0.1 ether for gas (?)
        require(toWithdraw > 0);

        owner.transfer(uint(toWithdraw));
    }

    function createPot(string name, uint buyIn) payable {
        require(msg.value > 0); // must bet something
        require(bytes(name).length > 0); // name mustn't be empty
        require(nameToPot[name].buyIn == 0); // there isn't already a pot with this name

        Pot pot = nameToPot[name];
        pot.name = name;
        pot.buyIn = msg.value;
        pot.players.push(msg.sender);

        allPots.push(pot);

        newPot(name, msg.value, msg.sender);
    }

    function joinPot(string name) payable {
        Pot pot = nameToPot[name];
        require(pot.buyIn > 0); // pot exists
        require(pot.loser == 0); // pot isn't over
        require(pot.players.length < 3); // pot isn't full
        require(msg.value == pot.buyIn); // must pay buyIn amount

        pot.players.push(msg.sender);
        if(pot.players.length == 3){
           pot.lastPlayerBlockNumber = block.number;
        }

        potJoin(name, msg.sender);
    }

    /*function leavePot(string name) {
        Pot pot = nameToPot[name];
        if(pot.buyIn == 0) throw; // no pot
        if(pot.loser != 0) throw; // pot is over
        if(pot.players.length == 3) throw; // pot is full but waiting for a loser

        if(pot.players[0] != msg.sender && pot.players[1] != msg.sender) throw; // no such player in pot

        address[] newPlayers;
        for(uint i = 0; i < pot.players.length; i++){
            if(pot.players[i] != msg.sender){
                newPlayers.push(pot.players[i]);
            }
        }

        pot.players = newPlayers;

        msg.sender.transfer((pot.buyIn * 99) / 100); // leaving fee 1%
    }*/

    function solvePot(string name){
        Pot pot = nameToPot[name];
        require(pot.loser == 0); // pot isn't over
        require(pot.players.length == 3); // pot full
        require(block.number > pot.lastPlayerBlockNumber);

        bytes32 blockHash = block.blockhash(pot.lastPlayerBlockNumber + 1);
        if(blockHash == 0) { // pot expired due to hash storage limits - players didn't solve pot
            pot.loser = msg.sender; // doesn't matter, everybody loses a fee

            for(uint i = 0; i < pot.players.length; i++){
                pot.players[i].transfer((pot.buyIn * 99) / 100); // return money minus 1% fee
            }

            potExpired(name);
            return;
        }

        bytes32 potShaResult = sha3(msg.sender, blockHash);
        uint8 loserIndex = uint8(uint256(potShaResult) % 3);

        pot.loser = pot.players[loserIndex];

        address winner1 = pot.players[(loserIndex + 1) % 3];
        address winner2 = pot.players[(loserIndex + 2) % 3];

        uint winAmount = ((pot.buyIn / 2) * 99) / 100;
        uint returnAmount = pot.buyIn + winAmount;

        winner1.transfer(returnAmount);
        winner2.transfer(returnAmount);

        potClosed(name, pot.loser);
    }
}

pragma solidity ^0.4.11;
contract PotOfGold {

    struct Pot {
        string name;
        uint buyIn;

        address[] players;

        uint lastPlayerBlockNumber; // uint8(uint256(sha3(block.blockhash(lastPlayerBlockNumber+1)))) % 3 == loser index

        address loser;
    }

    address public owner;

    Pot[] allPots;
    mapping(string => Pot) nameToPot;

    event newPot(string name, uint buyIn, address creator);
    event potJoin(string name, address newPlayer);
    event potExpired(string name);
    event potClosed(string name, address loser);

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

    function ownerWithdraw(){
        require(msg.sender == owner);

        int toWithdraw = int(this.balance - 100 finney); // leave 0.1 ether for gas (?)
        require(toWithdraw > 0);

        owner.transfer(uint(toWithdraw));
    }

    function createPot(string name, uint buyIn) {
        require(buyIn > 0); // must bet something
        require(bytes(name).length > 0); // name mustn't be empty
        require(nameToPot[name].buyIn == 0); // there isn't already a pot with this name

        Pot pot = nameToPot[name];
        pot.name = name;
        pot.buyIn = buyIn;
        pot.players.push(msg.sender);
        pot.buyIn = buyIn;

        allPots.push(pot);

        newPot(name, buyIn, msg.sender);
    }

    function joinPot(string name) {
        Pot pot = nameToPot[name];
        require(pot.buyIn > 0); // pot exists
        require(pot.loser == 0); // pot isn't over
        require(pot.players.length < 3); // pot isn't full
        require(msg.value == pot.buyIn); // must bet buyIn amount

        pot.players.push(msg.sender);
        if(pot.players.length == 3){
           pot.lastPlayerBlockNumber = block.number;
        }

        potJoin(name, msg.sender);
    }

    /*function leavePot(string name) {
        Pot pot = nameToPot[name];
        if(pot.buyIn == 0) throw; // no pot
        if(pot.loser != 0) throw; // pot is over
        if(pot.players.length == 3) throw; // pot is full but waiting for a loser

        if(pot.players[0] != msg.sender && pot.players[1] != msg.sender) throw; // no such player in pot

        address[] newPlayers;
        for(uint i = 0; i < pot.players.length; i++){
            if(pot.players[i] != msg.sender){
                newPlayers.push(pot.players[i]);
            }
        }

        pot.players = newPlayers;

        msg.sender.transfer((pot.buyIn * 99) / 100); // leaving fee 1%
    }*/

    function solvePot(string name){
        Pot pot = nameToPot[name];
        require(pot.loser == 0); // pot isn't over
        require(pot.players.length == 3); // pot full
        require(block.number > pot.lastPlayerBlockNumber);

        bytes32 blockHash = block.blockhash(pot.lastPlayerBlockNumber + 1);
        if(blockHash == 0) { // pot expired due to hash storage limits - players didn't solve pot
            pot.loser = msg.sender; // doesn't matter, everybody loses a fee

            for(uint i = 0; i < pot.players.length; i++){
                pot.players[i].transfer((pot.buyIn * 99) / 100); // return money minus 1% fee
            }

            potExpired(name);
            return;
        }

        bytes32 potShaResult = sha3(msg.sender, blockHash);
        uint8 loserIndex = uint8(uint256(potShaResult) % 3);

        pot.loser = pot.players[loserIndex];

        address winner1 = pot.players[(loserIndex + 1) % 3];
        address winner2 = pot.players[(loserIndex + 2) % 3];

        uint winAmount = ((pot.buyIn / 2) * 99) / 100;
        uint returnAmount = pot.buyIn + winAmount;

        winner1.transfer(returnAmount);
        winner2.transfer(returnAmount);

        potClosed(name, pot.loser);
    }
}


Normalize return variable names in IERC721Enumerable (#3079)

* Name missing return variable in IERC721Enumerable

For consistency among all functions.

* remove return variable names

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Enumerable.sol)

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Enumerable is IERC721 {
    /**
     * @dev Returns the total amount of tokens stored by the contract.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
     * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);

    /**
     * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
     * Use along with {totalSupply} to enumerate all tokens.
     */
    function tokenByIndex(uint256 index) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Enumerable.sol)

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Enumerable is IERC721 {
    /**
     * @dev Returns the total amount of tokens stored by the contract.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
     * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);

    /**
     * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
     * Use along with {totalSupply} to enumerate all tokens.
     */
    function tokenByIndex(uint256 index) external view returns (uint256);
}


ICO: updated pre-collected ether

ICO: updated pre-collected ether

pragma solidity 0.4.15;

import './ownership/multiowned.sol';
import './crowdsale/FixedTimeBonuses.sol';
import './crowdsale/FundsRegistry.sol';
import './crowdsale/InvestmentAnalytics.sol';
import './security/ArgumentsChecker.sol';
import './STQToken.sol';
import 'zeppelin-solidity/contracts/ReentrancyGuard.sol';
import 'zeppelin-solidity/contracts/math/Math.sol';
import 'zeppelin-solidity/contracts/math/SafeMath.sol';

/// @title Storiqa ICO contract
contract STQCrowdsale is ArgumentsChecker, ReentrancyGuard, multiowned, InvestmentAnalytics {
    using Math for uint256;
    using SafeMath for uint256;
    using FixedTimeBonuses for FixedTimeBonuses.Data;

    enum IcoState { INIT, ICO, PAUSED, FAILED, DISTRIBUTING_BONUSES, SUCCEEDED }

    /// @dev bookkeeping for last investment bonus
    struct LastInvestment {
        address investor;
        uint payment;

        // time-based bonus which was already received by the investor
        uint timeBonus;
    }

    event StateChanged(IcoState _state);
    event FundTransfer(address backer, uint amount, bool isContribution);

    modifier requiresState(IcoState _state) {
        require(m_state == _state);
        _;
    }

    /// @dev triggers some state changes based on current time
    /// @param investor optional refund parameter
    /// @param payment optional refund parameter
    /// note: function body could be skipped!
    modifier timedStateChange(address investor, uint payment) {
        if (IcoState.INIT == m_state && getCurrentTime() >= getStartTime())
            changeState(IcoState.ICO);

        if (IcoState.ICO == m_state && getCurrentTime() >= getEndTime()) {
            finishICO();

            if (payment > 0)
                investor.transfer(payment);
            // note that execution of further (but not preceding!) modifiers and functions ends here
        } else {
            _;
        }
    }

    /// @dev automatic check for unaccounted withdrawals
    /// @param investor optional refund parameter
    /// @param payment optional refund parameter
    modifier fundsChecker(address investor, uint payment) {
        uint atTheBeginning = m_funds.balance;
        if (atTheBeginning < m_lastFundsAmount) {
            changeState(IcoState.PAUSED);
            if (payment > 0)
                investor.transfer(payment);     // we cant throw (have to save state), so refunding this way
            // note that execution of further (but not preceding!) modifiers and functions ends here
        } else {
            _;

            if (m_funds.balance < atTheBeginning) {
                changeState(IcoState.PAUSED);
            } else {
                m_lastFundsAmount = m_funds.balance;
            }
        }
    }

    // PUBLIC interface

    function STQCrowdsale(address[] _owners, address _token, address _funds, address _teamTokens)
        multiowned(_owners, 2)
        validAddress(_token)
        validAddress(_funds)
        validAddress(_teamTokens)
    {
        require(3 == _owners.length);

        m_token = STQToken(_token);
        m_funds = FundsRegistry(_funds);
        m_teamTokens = _teamTokens;

        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (1 weeks), bonus: 30}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (2 weeks), bonus: 25}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (3 weeks), bonus: 20}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (4 weeks), bonus: 15}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (5 weeks), bonus: 10}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (8 weeks), bonus: 5}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: 1514246400, bonus: 0}));
        m_bonuses.validate(true);

        deployer = msg.sender;
    }

    // PUBLIC interface: payments

    // fallback function as a shortcut
    function() payable {
        require(0 == msg.data.length);
        buy();  // only internal call here!
    }

    /// @notice ICO participation
    function buy() public payable {     // dont mark as external!
        iaOnInvested(msg.sender, msg.value, false);
    }

    function iaOnInvested(address investor, uint payment, bool usingPaymentChannel)
        internal
        nonReentrant
        timedStateChange(investor, payment)
        fundsChecker(investor, payment)
    {
        require(m_state == IcoState.ICO || m_state == IcoState.INIT && isOwner(investor) /* for final test */);

        require(payment >= c_MinInvestment);

        uint startingInvariant = this.balance.add(m_funds.balance);

        // checking for max cap
        uint fundsAllowed = getMaximumFunds().sub(getTotalInvested());
        assert(0 != fundsAllowed);  // in this case state must not be IcoState.ICO
        payment = fundsAllowed.min256(payment);
        uint256 change = msg.value.sub(payment);

        // issue tokens
        var (stq, timeBonus) = calcSTQAmount(payment, usingPaymentChannel ? c_paymentChannelBonusPercent : 0);
        m_token.mint(investor, stq);

        // record payment
        m_funds.invested.value(payment)(investor);
        FundTransfer(investor, payment, true);

        recordInvestment(investor, payment, timeBonus);

        // check if ICO must be closed early
        if (change > 0)
        {
            assert(getMaximumFunds() == getTotalInvested());
            finishICO();

            // send change
            investor.transfer(change);
            assert(startingInvariant == this.balance.add(m_funds.balance).add(change));
        }
        else
            assert(startingInvariant == this.balance.add(m_funds.balance));
    }

    // PUBLIC interface: owners: maintenance

    /// @notice pauses ICO
    function pause()
        external
        timedStateChange(address(0), 0)
        requiresState(IcoState.ICO)
        onlyowner
    {
        changeState(IcoState.PAUSED);
    }

    /// @notice resume paused ICO
    function unpause()
        external
        timedStateChange(address(0), 0)
        requiresState(IcoState.PAUSED)
        onlymanyowners(sha3(msg.data))
    {
        changeState(IcoState.ICO);
        checkTime();
    }

    /// @notice consider paused ICO as failed
    function fail()
        external
        timedStateChange(address(0), 0)
        requiresState(IcoState.PAUSED)
        onlymanyowners(sha3(msg.data))
    {
        changeState(IcoState.FAILED);
    }

    /// @notice In case we need to attach to existent token
    function setToken(address _token)
        external
        validAddress(_token)
        timedStateChange(address(0), 0)
        requiresState(IcoState.PAUSED)
        onlymanyowners(sha3(msg.data))
    {
        m_token = STQToken(_token);
    }

    /// @notice In case we need to attach to existent funds
    function setFundsRegistry(address _funds)
        external
        validAddress(_funds)
        timedStateChange(address(0), 0)
        requiresState(IcoState.PAUSED)
        onlymanyowners(sha3(msg.data))
    {
        m_funds = FundsRegistry(_funds);
    }

    /// @notice explicit trigger for timed state changes
    function checkTime()
        public
        timedStateChange(address(0), 0)
        onlyowner
    {
    }

    /// @notice computing and distributing post-ICO bonuses
    function distributeBonuses(uint investorsLimit)
        external
        timedStateChange(address(0), 0)
        requiresState(IcoState.DISTRIBUTING_BONUSES)
    {
        uint limitIndex = uint(m_lastInvestments.length).min256(m_nextUndestributedBonusIndex + investorsLimit);
        uint iterations = 0;
        uint startingGas = msg.gas;
        while (m_nextUndestributedBonusIndex < limitIndex) {
            if (c_lastInvestmentsBonus > m_lastInvestments[m_nextUndestributedBonusIndex].timeBonus) {
                uint bonus = c_lastInvestmentsBonus.sub(m_lastInvestments[m_nextUndestributedBonusIndex].timeBonus);
                uint bonusSTQ = m_lastInvestments[m_nextUndestributedBonusIndex].payment.mul(c_STQperETH).mul(bonus).div(100);

                m_token.mint(m_lastInvestments[m_nextUndestributedBonusIndex].investor, bonusSTQ);
            }
            m_nextUndestributedBonusIndex++;

            // preventing gas limit hit
            uint avgGasPerIteration = startingGas.sub(msg.gas).div(++iterations);
            if (msg.gas < avgGasPerIteration * 3)
                break;
        }

        if (m_nextUndestributedBonusIndex == m_lastInvestments.length)
            changeState(IcoState.SUCCEEDED);
    }

    function createMorePaymentChannels(uint limit) external returns (uint) {
        require(isOwner(msg.sender) || msg.sender == deployer);
        return createMorePaymentChannelsInternal(limit);
    }

    // INTERNAL functions

    function finishICO() private {
        if (getTotalInvested() < getMinFunds())
            changeState(IcoState.FAILED);
        else
            changeState(IcoState.DISTRIBUTING_BONUSES);
    }

    /// @dev performs only allowed state transitions
    function changeState(IcoState _newState) private {
        assert(m_state != _newState);

        if (IcoState.INIT == m_state) {        assert(IcoState.ICO == _newState); }
        else if (IcoState.ICO == m_state) {    assert(IcoState.PAUSED == _newState || IcoState.FAILED == _newState || IcoState.DISTRIBUTING_BONUSES == _newState); }
        else if (IcoState.PAUSED == m_state) { assert(IcoState.ICO == _newState || IcoState.FAILED == _newState); }
        else if (IcoState.DISTRIBUTING_BONUSES == m_state) { assert(IcoState.SUCCEEDED == _newState); }
        else assert(false);

        m_state = _newState;
        StateChanged(m_state);

        // this should be tightly linked
        if (IcoState.SUCCEEDED == m_state) {
            onSuccess();
        } else if (IcoState.FAILED == m_state) {
            onFailure();
        }
    }

    function onSuccess() private {
        // mint tokens for owners
        uint tokensForTeam = m_token.totalSupply().mul(40).div(60);
        m_token.mint(m_teamTokens, tokensForTeam);

        m_funds.changeState(FundsRegistry.State.SUCCEEDED);
        m_funds.detachController();

        m_token.disableMinting();
        m_token.startCirculation();
        m_token.detachController();
    }

    function onFailure() private {
        m_funds.changeState(FundsRegistry.State.REFUNDING);
        m_funds.detachController();
    }

    function getLargePaymentBonus(uint payment) private constant returns (uint) {
        if (payment > 5000 ether) return 20;
        if (payment > 3000 ether) return 15;
        if (payment > 1000 ether) return 10;
        if (payment > 800 ether) return 8;
        if (payment > 500 ether) return 5;
        if (payment > 200 ether) return 2;
        return 0;
    }

    /// @dev calculates amount of STQ to which payer of _wei is entitled
    function calcSTQAmount(uint _wei, uint extraBonus) private constant returns (uint stq, uint timeBonus) {
        timeBonus = m_bonuses.getBonus(getCurrentTime());
        uint bonus = extraBonus.add(timeBonus).add(getLargePaymentBonus(_wei));

        // apply bonus
        stq = _wei.mul(c_STQperETH).mul(bonus.add(100)).div(100);
    }

    /// @dev records investments in a circular buffer
    function recordInvestment(address investor, uint payment, uint timeBonus) private {
        uint writeTo;
        assert(m_lastInvestments.length <= getLastMaxInvestments());
        if (m_lastInvestments.length < getLastMaxInvestments()) {
            // buffer is still expanding
            writeTo = m_lastInvestments.length++;
        }
        else {
            // reusing buffer
            writeTo = m_nextFreeLastInvestmentIndex++;
            if (m_nextFreeLastInvestmentIndex == m_lastInvestments.length)
                m_nextFreeLastInvestmentIndex = 0;
        }

        assert(writeTo < m_lastInvestments.length);
        m_lastInvestments[writeTo] = LastInvestment(investor, payment, timeBonus);
    }

    /// @dev start time of the ICO, inclusive
    function getStartTime() private constant returns (uint) {
        return c_startTime;
    }

    /// @dev end time of the ICO, inclusive
    function getEndTime() private constant returns (uint) {
        return m_bonuses.getLastTime();
    }

    /// @dev to be overridden in tests
    function getCurrentTime() internal constant returns (uint) {
        return now;
    }

    /// @dev to be overridden in tests
    function getMinFunds() internal constant returns (uint) {
        return c_MinFunds;
    }

    /// @dev to be overridden in tests
    function getMaximumFunds() internal constant returns (uint) {
        return c_MaximumFunds;
    }

    /// @dev amount of investments during all crowdsales
    function getTotalInvested() internal constant returns (uint) {
        return m_funds.totalInvested().add(4426 ether);
    }

    /// @dev to be overridden in tests
    function getLastMaxInvestments() internal constant returns (uint) {
        return c_maxLastInvestments;
    }

    // FIELDS

    /// @notice starting exchange rate of STQ
    uint public constant c_STQperETH = 100000;

    /// @notice minimum investment
    uint public constant c_MinInvestment = 10 finney;

    /// @notice minimum investments to consider ICO as a success
    uint public constant c_MinFunds = 30000 ether;

    /// @notice maximum investments to be accepted during ICO
    uint public constant c_MaximumFunds = 90000 ether;

    /// @notice start time of the ICO
    uint public constant c_startTime = 1508889600;

    /// @notice authorised payment bonus
    uint public constant c_paymentChannelBonusPercent = 2;

    /// @notice timed bonuses
    FixedTimeBonuses.Data m_bonuses;

    uint public constant c_maxLastInvestments = 100;

    uint public constant c_lastInvestmentsBonus = 30;

    /// @dev bookkeeping for last investment bonus
    LastInvestment[] public m_lastInvestments;

    uint m_nextFreeLastInvestmentIndex;

    uint m_nextUndestributedBonusIndex;

    /// @dev state of the ICO
    IcoState public m_state = IcoState.INIT;

    /// @dev contract responsible for token accounting
    STQToken public m_token;

    address public m_teamTokens;

    address public deployer;

    /// @dev contract responsible for investments accounting
    FundsRegistry public m_funds;

    /// @dev last recorded funds
    uint256 public m_lastFundsAmount;
}

pragma solidity 0.4.15;

import './ownership/multiowned.sol';
import './crowdsale/FixedTimeBonuses.sol';
import './crowdsale/FundsRegistry.sol';
import './crowdsale/InvestmentAnalytics.sol';
import './security/ArgumentsChecker.sol';
import './STQToken.sol';
import 'zeppelin-solidity/contracts/ReentrancyGuard.sol';
import 'zeppelin-solidity/contracts/math/Math.sol';
import 'zeppelin-solidity/contracts/math/SafeMath.sol';

/// @title Storiqa ICO contract
contract STQCrowdsale is ArgumentsChecker, ReentrancyGuard, multiowned, InvestmentAnalytics {
    using Math for uint256;
    using SafeMath for uint256;
    using FixedTimeBonuses for FixedTimeBonuses.Data;

    enum IcoState { INIT, ICO, PAUSED, FAILED, DISTRIBUTING_BONUSES, SUCCEEDED }

    /// @dev bookkeeping for last investment bonus
    struct LastInvestment {
        address investor;
        uint payment;

        // time-based bonus which was already received by the investor
        uint timeBonus;
    }

    event StateChanged(IcoState _state);
    event FundTransfer(address backer, uint amount, bool isContribution);

    modifier requiresState(IcoState _state) {
        require(m_state == _state);
        _;
    }

    /// @dev triggers some state changes based on current time
    /// @param investor optional refund parameter
    /// @param payment optional refund parameter
    /// note: function body could be skipped!
    modifier timedStateChange(address investor, uint payment) {
        if (IcoState.INIT == m_state && getCurrentTime() >= getStartTime())
            changeState(IcoState.ICO);

        if (IcoState.ICO == m_state && getCurrentTime() >= getEndTime()) {
            finishICO();

            if (payment > 0)
                investor.transfer(payment);
            // note that execution of further (but not preceding!) modifiers and functions ends here
        } else {
            _;
        }
    }

    /// @dev automatic check for unaccounted withdrawals
    /// @param investor optional refund parameter
    /// @param payment optional refund parameter
    modifier fundsChecker(address investor, uint payment) {
        uint atTheBeginning = m_funds.balance;
        if (atTheBeginning < m_lastFundsAmount) {
            changeState(IcoState.PAUSED);
            if (payment > 0)
                investor.transfer(payment);     // we cant throw (have to save state), so refunding this way
            // note that execution of further (but not preceding!) modifiers and functions ends here
        } else {
            _;

            if (m_funds.balance < atTheBeginning) {
                changeState(IcoState.PAUSED);
            } else {
                m_lastFundsAmount = m_funds.balance;
            }
        }
    }

    // PUBLIC interface

    function STQCrowdsale(address[] _owners, address _token, address _funds, address _teamTokens)
        multiowned(_owners, 2)
        validAddress(_token)
        validAddress(_funds)
        validAddress(_teamTokens)
    {
        require(3 == _owners.length);

        m_token = STQToken(_token);
        m_funds = FundsRegistry(_funds);
        m_teamTokens = _teamTokens;

        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (1 weeks), bonus: 30}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (2 weeks), bonus: 25}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (3 weeks), bonus: 20}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (4 weeks), bonus: 15}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (5 weeks), bonus: 10}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: c_startTime + (8 weeks), bonus: 5}));
        m_bonuses.bonuses.push(FixedTimeBonuses.Bonus({endTime: 1514246400, bonus: 0}));
        m_bonuses.validate(true);

        deployer = msg.sender;
    }

    // PUBLIC interface: payments

    // fallback function as a shortcut
    function() payable {
        require(0 == msg.data.length);
        buy();  // only internal call here!
    }

    /// @notice ICO participation
    function buy() public payable {     // dont mark as external!
        iaOnInvested(msg.sender, msg.value, false);
    }

    function iaOnInvested(address investor, uint payment, bool usingPaymentChannel)
        internal
        nonReentrant
        timedStateChange(investor, payment)
        fundsChecker(investor, payment)
    {
        require(m_state == IcoState.ICO || m_state == IcoState.INIT && isOwner(investor) /* for final test */);

        require(payment >= c_MinInvestment);

        uint startingInvariant = this.balance.add(m_funds.balance);

        // checking for max cap
        uint fundsAllowed = getMaximumFunds().sub(getTotalInvested());
        assert(0 != fundsAllowed);  // in this case state must not be IcoState.ICO
        payment = fundsAllowed.min256(payment);
        uint256 change = msg.value.sub(payment);

        // issue tokens
        var (stq, timeBonus) = calcSTQAmount(payment, usingPaymentChannel ? c_paymentChannelBonusPercent : 0);
        m_token.mint(investor, stq);

        // record payment
        m_funds.invested.value(payment)(investor);
        FundTransfer(investor, payment, true);

        recordInvestment(investor, payment, timeBonus);

        // check if ICO must be closed early
        if (change > 0)
        {
            assert(getMaximumFunds() == getTotalInvested());
            finishICO();

            // send change
            investor.transfer(change);
            assert(startingInvariant == this.balance.add(m_funds.balance).add(change));
        }
        else
            assert(startingInvariant == this.balance.add(m_funds.balance));
    }

    // PUBLIC interface: owners: maintenance

    /// @notice pauses ICO
    function pause()
        external
        timedStateChange(address(0), 0)
        requiresState(IcoState.ICO)
        onlyowner
    {
        changeState(IcoState.PAUSED);
    }

    /// @notice resume paused ICO
    function unpause()
        external
        timedStateChange(address(0), 0)
        requiresState(IcoState.PAUSED)
        onlymanyowners(sha3(msg.data))
    {
        changeState(IcoState.ICO);
        checkTime();
    }

    /// @notice consider paused ICO as failed
    function fail()
        external
        timedStateChange(address(0), 0)
        requiresState(IcoState.PAUSED)
        onlymanyowners(sha3(msg.data))
    {
        changeState(IcoState.FAILED);
    }

    /// @notice In case we need to attach to existent token
    function setToken(address _token)
        external
        validAddress(_token)
        timedStateChange(address(0), 0)
        requiresState(IcoState.PAUSED)
        onlymanyowners(sha3(msg.data))
    {
        m_token = STQToken(_token);
    }

    /// @notice In case we need to attach to existent funds
    function setFundsRegistry(address _funds)
        external
        validAddress(_funds)
        timedStateChange(address(0), 0)
        requiresState(IcoState.PAUSED)
        onlymanyowners(sha3(msg.data))
    {
        m_funds = FundsRegistry(_funds);
    }

    /// @notice explicit trigger for timed state changes
    function checkTime()
        public
        timedStateChange(address(0), 0)
        onlyowner
    {
    }

    /// @notice computing and distributing post-ICO bonuses
    function distributeBonuses(uint investorsLimit)
        external
        timedStateChange(address(0), 0)
        requiresState(IcoState.DISTRIBUTING_BONUSES)
    {
        uint limitIndex = uint(m_lastInvestments.length).min256(m_nextUndestributedBonusIndex + investorsLimit);
        uint iterations = 0;
        uint startingGas = msg.gas;
        while (m_nextUndestributedBonusIndex < limitIndex) {
            if (c_lastInvestmentsBonus > m_lastInvestments[m_nextUndestributedBonusIndex].timeBonus) {
                uint bonus = c_lastInvestmentsBonus.sub(m_lastInvestments[m_nextUndestributedBonusIndex].timeBonus);
                uint bonusSTQ = m_lastInvestments[m_nextUndestributedBonusIndex].payment.mul(c_STQperETH).mul(bonus).div(100);

                m_token.mint(m_lastInvestments[m_nextUndestributedBonusIndex].investor, bonusSTQ);
            }
            m_nextUndestributedBonusIndex++;

            // preventing gas limit hit
            uint avgGasPerIteration = startingGas.sub(msg.gas).div(++iterations);
            if (msg.gas < avgGasPerIteration * 3)
                break;
        }

        if (m_nextUndestributedBonusIndex == m_lastInvestments.length)
            changeState(IcoState.SUCCEEDED);
    }

    function createMorePaymentChannels(uint limit) external returns (uint) {
        require(isOwner(msg.sender) || msg.sender == deployer);
        return createMorePaymentChannelsInternal(limit);
    }

    // INTERNAL functions

    function finishICO() private {
        if (getTotalInvested() < getMinFunds())
            changeState(IcoState.FAILED);
        else
            changeState(IcoState.DISTRIBUTING_BONUSES);
    }

    /// @dev performs only allowed state transitions
    function changeState(IcoState _newState) private {
        assert(m_state != _newState);

        if (IcoState.INIT == m_state) {        assert(IcoState.ICO == _newState); }
        else if (IcoState.ICO == m_state) {    assert(IcoState.PAUSED == _newState || IcoState.FAILED == _newState || IcoState.DISTRIBUTING_BONUSES == _newState); }
        else if (IcoState.PAUSED == m_state) { assert(IcoState.ICO == _newState || IcoState.FAILED == _newState); }
        else if (IcoState.DISTRIBUTING_BONUSES == m_state) { assert(IcoState.SUCCEEDED == _newState); }
        else assert(false);

        m_state = _newState;
        StateChanged(m_state);

        // this should be tightly linked
        if (IcoState.SUCCEEDED == m_state) {
            onSuccess();
        } else if (IcoState.FAILED == m_state) {
            onFailure();
        }
    }

    function onSuccess() private {
        // mint tokens for owners
        uint tokensForTeam = m_token.totalSupply().mul(40).div(60);
        m_token.mint(m_teamTokens, tokensForTeam);

        m_funds.changeState(FundsRegistry.State.SUCCEEDED);
        m_funds.detachController();

        m_token.disableMinting();
        m_token.startCirculation();
        m_token.detachController();
    }

    function onFailure() private {
        m_funds.changeState(FundsRegistry.State.REFUNDING);
        m_funds.detachController();
    }

    function getLargePaymentBonus(uint payment) private constant returns (uint) {
        if (payment > 5000 ether) return 20;
        if (payment > 3000 ether) return 15;
        if (payment > 1000 ether) return 10;
        if (payment > 800 ether) return 8;
        if (payment > 500 ether) return 5;
        if (payment > 200 ether) return 2;
        return 0;
    }

    /// @dev calculates amount of STQ to which payer of _wei is entitled
    function calcSTQAmount(uint _wei, uint extraBonus) private constant returns (uint stq, uint timeBonus) {
        timeBonus = m_bonuses.getBonus(getCurrentTime());
        uint bonus = extraBonus.add(timeBonus).add(getLargePaymentBonus(_wei));

        // apply bonus
        stq = _wei.mul(c_STQperETH).mul(bonus.add(100)).div(100);
    }

    /// @dev records investments in a circular buffer
    function recordInvestment(address investor, uint payment, uint timeBonus) private {
        uint writeTo;
        assert(m_lastInvestments.length <= getLastMaxInvestments());
        if (m_lastInvestments.length < getLastMaxInvestments()) {
            // buffer is still expanding
            writeTo = m_lastInvestments.length++;
        }
        else {
            // reusing buffer
            writeTo = m_nextFreeLastInvestmentIndex++;
            if (m_nextFreeLastInvestmentIndex == m_lastInvestments.length)
                m_nextFreeLastInvestmentIndex = 0;
        }

        assert(writeTo < m_lastInvestments.length);
        m_lastInvestments[writeTo] = LastInvestment(investor, payment, timeBonus);
    }

    /// @dev start time of the ICO, inclusive
    function getStartTime() private constant returns (uint) {
        return c_startTime;
    }

    /// @dev end time of the ICO, inclusive
    function getEndTime() private constant returns (uint) {
        return m_bonuses.getLastTime();
    }

    /// @dev to be overridden in tests
    function getCurrentTime() internal constant returns (uint) {
        return now;
    }

    /// @dev to be overridden in tests
    function getMinFunds() internal constant returns (uint) {
        return c_MinFunds;
    }

    /// @dev to be overridden in tests
    function getMaximumFunds() internal constant returns (uint) {
        return c_MaximumFunds;
    }

    /// @dev amount of investments during all crowdsales
    function getTotalInvested() internal constant returns (uint) {
        return m_funds.totalInvested().add(4281 ether /* FIXME update me */);
    }

    /// @dev to be overridden in tests
    function getLastMaxInvestments() internal constant returns (uint) {
        return c_maxLastInvestments;
    }

    // FIELDS

    /// @notice starting exchange rate of STQ
    uint public constant c_STQperETH = 100000;

    /// @notice minimum investment
    uint public constant c_MinInvestment = 10 finney;

    /// @notice minimum investments to consider ICO as a success
    uint public constant c_MinFunds = 30000 ether;

    /// @notice maximum investments to be accepted during ICO
    uint public constant c_MaximumFunds = 90000 ether;

    /// @notice start time of the ICO
    uint public constant c_startTime = 1508889600;

    /// @notice authorised payment bonus
    uint public constant c_paymentChannelBonusPercent = 2;

    /// @notice timed bonuses
    FixedTimeBonuses.Data m_bonuses;

    uint public constant c_maxLastInvestments = 100;

    uint public constant c_lastInvestmentsBonus = 30;

    /// @dev bookkeeping for last investment bonus
    LastInvestment[] public m_lastInvestments;

    uint m_nextFreeLastInvestmentIndex;

    uint m_nextUndestributedBonusIndex;

    /// @dev state of the ICO
    IcoState public m_state = IcoState.INIT;

    /// @dev contract responsible for token accounting
    STQToken public m_token;

    address public m_teamTokens;

    address public deployer;

    /// @dev contract responsible for investments accounting
    FundsRegistry public m_funds;

    /// @dev last recorded funds
    uint256 public m_lastFundsAmount;
}


Be more aggressive about keeping the blockchain clean.

contract DirectDemocracy is PermissionsProvider, PersistentProtectedContract {
    // PoC-8
    // include "action.sol"

    uint public quorum;
    uint8 public quorumPercent;
    uint public marginForVictory;
    uint8 public marginForVictoryPercent;
    uint public minimumVotingWindow;
    uint public maximumVotingWindow;

    uint VOTING_WINDOW_MIN = 600; // 10 minutes
    bytes32 OWNERS_DB = "OwnersDb";

    mapping (address => Proposal) public proposals;
    mapping (address => bool) permittedAction;

    enum Vote {
        Null, Yes, No, Abstain
    }

    struct Proposal {
        uint timestamp;
        mapping (address => Vote) votes;
        uint numVotes;
        address proposedAction;
    }

    function DirectDemocracy() {
        quorum = 1;
        quorumPercent = 60;
        marginForVictory = 0;
        marginForVictoryPercent = 0;
    }

    function permitted(address action) returns (bool result) {
        return permittedAction[action]
        || api == 0x0
        || ApiProvider(api).contracts(OWNERS_DB) == 0x0;
    }

    function addOwner(address owner) returns (bool added) {
        var dbAddress = ApiProvider(api).contracts(OWNERS_DB);
        if (dbAddress == 0x0 || !permittedSender()) return false;

        added = OwnersDb(dbAddress).addOwner(owner);

        if (added) {
            _updateMargins();
        }
    }

    function removeOwner(address owner) returns (bool removed) {
        var dbAddress = ApiProvider(api).contracts(OWNERS_DB);
        var db = OwnersDb(dbAddress);
        var iter = db.owners(owner);

        if (dbAddress == 0x0 || iter == 0x0 || !permittedSender()) {
            return false;
        }

        if (!db.removeOwner(owner)) return false;

        Proposal proposal;
        var numOwners = db.numOwners();

        for (uint i=0; i < numOwners; i+=1) {
            proposal = proposals[iter];
            iter = db.owners(iter);

            if (proposal.proposedAction == 0x0) continue;

            if (proposal.votes[owner] != Vote.Null) {
                delete proposal.votes[owner];
                proposal.numVotes -= 1;
            }
        }

        delete proposals[owner];
        _updateMargins();

        return true;
    }

    function proposeAction(address proposedAction) {
        var dbAddress = ApiProvider(api).contracts(OWNERS_DB);
        var db = OwnersDb(dbAddress);
        if (db.owners(msg.sender) == 0x0
            || Action(proposedAction).owner() != address(this)) {
            return;
        }

        var proposal = proposals[msg.sender];
        proposal.timestamp = block.timestamp;
        proposal.proposedAction = proposedAction;
        proposal.votes[msg.sender] = Vote.Yes;
        proposal.numVotes = 1;
    }

    function withdrawProposedAction() {
        _removeProposal(msg.sender);
    }

    function setQuorumPercent(uint8 percent) {
        if (percent > 100 || !permittedSender()) return;
        quorumPercent = percent;
        _updateMargins();
    }

    function setMarginForVictoryPercent(uint8 percent) {
        if (percent > 100 || !permittedSender()) return;
        marginForVictoryPercent = percent;
        _updateMargins();
    }

    function setMinimumVotingWindow(uint minimumWindow) {
        if (!permittedSender()) return;
        minimumVotingWindow = minimumWindow;
    }

    function setMaximumVotingWindow(uint maximumWindow) {
        if ((maximumWindow < VOTING_WINDOW_MIN && maximumWindow != 0)
            || !permittedSender()) {
            return;
        }
        maximumVotingWindow = maximumWindow;
    }

    function wipeProposedActions() {
        if (!permittedSender()) return;

        var dbAddress = ApiProvider(api).contracts(OWNERS_DB);
        var db = OwnersDb(dbAddress);
        var iter = db.ownersTail();
        var numOwners = db.numOwners();

        for (uint i=0; i < numOwners; i += 1) {
            if (proposals[iter].proposedAction != 0x0) {
                Action(proposals[iter].proposedAction).remove();
                delete proposals[iter];
            }
            iter = db.owners(iter);
        }
    }

    function vote(address owner, address action, Vote vote) {
        // We require the action parameter to make sure the voter knows
        // exactly what they are voting on, to prevent "bait & switch."
        var proposal = proposals[owner];

        if (action == 0x0
            || proposal.proposedAction != action
            || vote == Vote.Null) {
            return;
        }

        if (maximumVotingWindow == 0
            || block.timestamp <= (proposal.timestamp + maximumVotingWindow)) {

            if (proposal.votes[msg.sender] == Vote.Null) {
                proposal.numVotes += 1;
            }

            proposal.votes[msg.sender] = vote;
        }

        if (block.timestamp > (proposal.timestamp + minimumVotingWindow)) {
            var outcome = _checkVotes(owner);

            if (outcome == Vote.Null) return;

            if (outcome == Vote.Yes) {
                permittedAction[proposal.proposedAction] = true;
                Action(proposal.proposedAction).execute();
                delete permittedAction[proposal.proposedAction];
            }
            _removeProposal(owner);
        }
    }

    function spend(address recipient, uint amount) {
        if (!permittedSender()) return;
        recipient.send(amount);
    }

    function remove() {
        if (!permittedSender()) return;
        wipeProposedActions();
        super.remove();
    }

    function _removeProposal(address owner) private {
        if (proposals[owner].proposedAction == 0x0) return;
        Action(proposals[owner].proposedAction).remove();
        delete proposals[owner];
    }

    function _updateMargins() private {
        var numOwners = OwnersDb(ApiProvider(api).contracts(OWNERS_DB)).numOwners();
        quorum = (numOwners * 100) / quorumPercent;
        marginForVictory = (numOwners * 100) / marginForVictoryPercent;
    }

    function _checkVotes(address owner) private returns (Vote result) {
        var proposal = proposals[owner];
        if (proposal.numVotes < quorum) {
            return Vote.Null;
        }

        uint yesTally = 0;
        uint noTally = 0;
        var db = OwnersDb(ApiProvider(api).contracts(OWNERS_DB));
        var numOwners = db.numOwners();
        var iter = db.ownersTail();

        for (uint i=0; i < numOwners; i+=1) {
            iter = db.owners(iter);

            if (proposal.votes[iter] == Vote.Abstain || proposal.votes[iter] == Vote.Null) {
                continue;
            }

            if (proposal.votes[iter] == Vote.Yes) {
                yesTally += 1;
            } else {
                noTally -= 1;
            }
        }

        if (noTally > yesTally && (noTally - yesTally) >= marginForVictory) {
            return Vote.No;

        } else if (yesTally > noTally && (yesTally - noTally) >= marginForVictory) {
            return Vote.Yes;
        }

        return Vote.Null;
    }
}

contract DirectDemocracy is PermissionsProvider, PersistentProtectedContract {
    // PoC-8
    // include "action.sol"

    uint public quorum;
    uint8 public quorumPercent;
    uint public marginForVictory;
    uint8 public marginForVictoryPercent;
    uint public minimumVotingWindow;
    uint public maximumVotingWindow;

    uint VOTING_WINDOW_MIN = 600; // 10 minutes
    bytes32 OWNERS_DB = "OwnersDb";

    mapping (address => Proposal) public proposals;
    mapping (address => bool) permittedAction;

    enum Vote {
        Null, Yes, No, Abstain
    }

    struct Proposal {
        uint timestamp;
        mapping (address => Vote) votes;
        uint numVotes;
        address proposedAction;
    }

    function DirectDemocracy() {
        quorum = 1;
        quorumPercent = 60;
        marginForVictory = 0;
        marginForVictoryPercent = 0;
    }

    function permitted(address action) returns (bool result) {
        return permittedAction[action]
        || api == 0x0
        || ApiProvider(api).contracts(OWNERS_DB) == 0x0;
    }

    function addOwner(address owner) returns (bool added) {
        var dbAddress = ApiProvider(api).contracts(OWNERS_DB);
        if (dbAddress == 0x0 || !permittedSender()) return false;

        added = OwnersDb(dbAddress).addOwner(owner);

        if (added) {
            _updateMargins();
        }
    }

    function removeOwner(address owner) returns (bool removed) {
        var dbAddress = ApiProvider(api).contracts(OWNERS_DB);
        var db = OwnersDb(dbAddress);
        var iter = db.owners(owner);

        if (dbAddress == 0x0 || iter == 0x0 || !permittedSender()) {
            return false;
        }

        if (!db.removeOwner(owner)) return false;

        Proposal proposal;
        var numOwners = db.numOwners();

        for (uint i=0; i < numOwners; i+=1) {
            proposal = proposals[iter];
            iter = db.owners(iter);

            if (proposal.proposedAction == 0x0) continue;

            if (proposal.votes[owner] != Vote.Null) {
                delete proposal.votes[owner];
                proposal.numVotes -= 1;
            }
        }

        delete proposals[owner];
        _updateMargins();

        return true;
    }

    function proposeAction(address proposedAction) {
        var dbAddress = ApiProvider(api).contracts(OWNERS_DB);
        var db = OwnersDb(dbAddress);
        if (db.owners(msg.sender) == 0x0
            || Action(proposedAction).owner() != address(this)) {
            return;
        }

        var proposal = proposals[msg.sender];
        proposal.timestamp = block.timestamp;
        proposal.proposedAction = proposedAction;
        proposal.votes[msg.sender] = Vote.Yes;
        proposal.numVotes = 1;
    }

    function withdrawProposedAction() {
        _removeProposal(msg.sender);
    }

    function setQuorumPercent(uint8 percent) {
        if (percent > 100 || !permittedSender()) return;
        quorumPercent = percent;
        _updateMargins();
    }

    function setMarginForVictoryPercent(uint8 percent) {
        if (percent > 100 || !permittedSender()) return;
        marginForVictoryPercent = percent;
        _updateMargins();
    }

    function setMinimumVotingWindow(uint minimumWindow) {
        if (!permittedSender()) return;
        minimumVotingWindow = minimumWindow;
    }

    function setMaximumVotingWindow(uint maximumWindow) {
        if ((maximumWindow < VOTING_WINDOW_MIN && maximumWindow != 0)
            || !permittedSender()) {
            return;
        }
        maximumVotingWindow = maximumWindow;
    }

    function wipeProposedActions() {
        if (!permittedSender()) return;

        var dbAddress = ApiProvider(api).contracts(OWNERS_DB);
        var db = OwnersDb(dbAddress);
        var iter = db.ownersTail();
        var numOwners = db.numOwners();

        for (uint i=0; i < numOwners; i += 1) {
            if (proposals[iter].proposedAction != 0x0) {
                Action(proposals[iter].proposedAction).remove();
                delete proposals[iter];
            }
            iter = db.owners(iter);
        }
    }

    function vote(address owner, address action, Vote vote) {
        // We require the action parameter to make sure the voter knows
        // exactly what they are voting on, to prevent "bait & switch."
        var proposal = proposals[owner];

        if (action == 0x0
            || proposal.proposedAction != action
            || vote == Vote.Null) {
            return;
        }

        if (maximumVotingWindow == 0
            || block.timestamp <= (proposal.timestamp + maximumVotingWindow)) {

            if (proposal.votes[msg.sender] == Vote.Null) {
                proposal.numVotes += 1;
            }

            proposal.votes[msg.sender] = vote;
        }

        if (block.timestamp > (proposal.timestamp + minimumVotingWindow)) {
            var outcome = _checkVotes(owner);

            if (outcome == Vote.Null) return;

            if (outcome == Vote.Yes) {
                permittedAction[proposal.proposedAction] = true;
                Action(proposal.proposedAction).execute();
                delete permittedAction[proposal.proposedAction];
            }
            _removeProposal(owner);
        }
    }

    function spend(address recipient, uint amount) {
        if (!permittedSender()) return;
        recipient.send(amount);
    }

    function remove() {
        if (!permittedSender()) return;
        super.remove();
    }

    function _removeProposal(address owner) private {
        if (proposals[owner].proposedAction == 0x0) return;
        delete proposals[owner];
    }

    function _updateMargins() private {
        var numOwners = OwnersDb(ApiProvider(api).contracts(OWNERS_DB)).numOwners();
        quorum = (numOwners * 100) / quorumPercent;
        marginForVictory = (numOwners * 100) / marginForVictoryPercent;
    }

    function _checkVotes(address owner) private returns (Vote result) {
        var proposal = proposals[owner];
        if (proposal.numVotes < quorum) {
            return Vote.Null;
        }

        uint yesTally = 0;
        uint noTally = 0;
        var db = OwnersDb(ApiProvider(api).contracts(OWNERS_DB));
        var numOwners = db.numOwners();
        var iter = db.ownersTail();

        for (uint i=0; i < numOwners; i+=1) {
            iter = db.owners(iter);

            if (proposal.votes[iter] == Vote.Abstain || proposal.votes[iter] == Vote.Null) {
                continue;
            }

            if (proposal.votes[iter] == Vote.Yes) {
                yesTally += 1;
            } else {
                noTally -= 1;
            }
        }

        if (noTally > yesTally && (noTally - yesTally) >= marginForVictory) {
            return Vote.No;

        } else if (yesTally > noTally && (yesTally - noTally) >= marginForVictory) {
            return Vote.Yes;
        }

        return Vote.Null;
    }
}