Gnosis Prediction Market Platform Crowdfunding Contract Security Audited Source Code

Originally posted Dec 13 2016, updated Dec 16 2016 with bug bounty details.

This is not advise as to whether the Gnosis prediction market platform is a good investment or not. This is just a quick look at the source code behind what Gnosis is offering.

Reference Introducing the upcoming Gnosis Token Launch! and Introducing the Gnosis Token Launch.

10 million Gnosis tokens (GNO) will be created in a Dutch auction.

A bug bounty may also be is on offer – see We’ve opened a bug bounty program for Gnosis smart contracts!.

See also the older post Gnosis Prediction Market Platform And Crowdfunding Contract Source Code.


Architecture


Table of contents


The source code below is from https://github.com/ConsenSys/gnosis-contracts/tree/security_audit at Dec 13 2016 08:12:04 UTC.

Tokens

AbstractToken.sol

/// Implements ERC 20 Token standard: https://github.com/ethereum/EIPs/issues/20

/// @title Abstract token contract - Functions to be implemented by token contracts.
contract Token {
    function transfer(address to, uint256 value) returns (bool success);
    function transferFrom(address from, address to, uint256 value) returns (bool success);
    function approve(address spender, uint256 value) returns (bool success);

    // This is not an abstract function, because solc won't recognize generated getter functions for public variables as functions.
    function totalSupply() constant returns (uint256 supply) {}
    function balanceOf(address owner) constant returns (uint256 balance);
    function allowance(address owner, address spender) constant returns (uint256 remaining);

    // Token meta data
    // Those are not abstract functions, because solc won't recognize generated getter functions for public variables as functions.
    function name() constant returns (string) {}
    function symbol() constant returns (string) {}
    function decimals() constant returns (uint8) {}

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

StandardToken.sol

pragma solidity 0.4.4;
import "Tokens/AbstractToken.sol";


/// @title Standard token contract - Standard token interface implementation.
contract StandardToken is Token {

    /*
     *  Data structures
     */
    mapping (address => uint256) balances;
    mapping (address => mapping (address => uint256)) allowed;
    uint256 public totalSupply;

    /*
     *  Public functions
     */
    /// @dev Transfers sender's tokens to a given address. Returns success.
    /// @param _to Address of token receiver.
    /// @param _value Number of tokens to transfer.
    /// @return Returns success of function call.
    function transfer(address _to, uint256 _value)
        public
        returns (bool)
    {
        if (balances[msg.sender] < _value) {
            // Balance too low
            throw;
        }
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        Transfer(msg.sender, _to, _value);
        return true;
    }

    /// @dev Allows allowed third party to transfer tokens from one address to another. Returns success.
    /// @param _from Address from where tokens are withdrawn.
    /// @param _to Address to where tokens are sent.
    /// @param _value Number of tokens to transfer.
    /// @return Returns success of function call.
    function transferFrom(address _from, address _to, uint256 _value)
        public
        returns (bool)
    {
        if (balances[_from] < _value || allowed[_from][msg.sender] < _value) {
            // Balance or allowance too low
            throw;
        }
        balances[_to] += _value;
        balances[_from] -= _value;
        allowed[_from][msg.sender] -= _value;
        Transfer(_from, _to, _value);
        return true;
    }

    /// @dev Sets approved amount of tokens for spender. Returns success.
    /// @param _spender Address of allowed account.
    /// @param _value Number of approved tokens.
    /// @return Returns success of function call.
    function approve(address _spender, uint256 _value)
        public
        returns (bool)
    {
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
        return true;
    }

    /*
     * Read functions
     */
    /// @dev Returns number of allowed tokens for given address.
    /// @param _owner Address of token owner.
    /// @param _spender Address of token spender.
    /// @return Returns remaining allowance for spender.
    function allowance(address _owner, address _spender)
        constant
        public
        returns (uint256)
    {
        return allowed[_owner][_spender];
    }

    /// @dev Returns number of tokens owned by given address.
    /// @param _owner Address of token owner.
    /// @return Returns balance of owner.
    function balanceOf(address _owner)
        constant
        public
        returns (uint256)
    {
        return balances[_owner];
    }
}

Dutch Auction

AbstractDutchAuction.sol

/// @title Abstract dutch auction contract - Functions to be implemented by dutch auction contracts.
contract DutchAuction {
    function tokenLaunched() returns (bool launched);
}

DutchAuction.sol

pragma solidity 0.4.4;
import "Tokens/AbstractToken.sol";


/// @title Dutch auction contract - creation of Gnosis tokens.
/// @author Stefan George - 
contract DutchAuction {

    /*
     *  Events
     */
    event BidSubmission(address indexed sender, uint256 amount);

    /*
     *  Constants
     */
    uint constant public CEILING = 1500000 ether;
    uint constant public TOTAL_TOKENS = 10000000; // 10M
    uint constant public MAX_TOKENS_SOLD = 9000000; // 9M
    uint constant public WAITING_PERIOD = 7 days;

    /*
     *  Storage
     */
    Token public gnosisToken;
    address public wallet;
    address public owner;
    uint public startBlock;
    uint public endTime;
    uint public totalReceived;
    uint public finalPrice;
    mapping (address => uint) public bids;
    Stages public stage = Stages.AuctionStarted;

    /*
     *  Enums
     */
    enum Stages {
        AuctionStarted,
        AuctionEnded
    }

    /*
     *  Modifiers
     */
    modifier atStage(Stages _stage) {
        if (stage != _stage) {
            // Contract not in expected state
            throw;
        }
        _;
    }

    modifier isOwner() {
        if (msg.sender != owner) {
            // Only owner is allowed to proceed
            throw;
        }
        _;
    }

    modifier timedTransitions() {
        if (stage == Stages.AuctionStarted && calcTokenPrice() <= calcStopPrice()) {
            finalizeAuction();
        }
        _;
    }

    /*
     *  Public functions
     */
    /// @dev Contract constructor function sets start date.
    function DutchAuction()
        public
    {
        startBlock = block.number;
        owner = msg.sender;
    }

    /// @dev Setup function sets external contracts' addresses.
    /// @param _gnosisToken Gnosis token address.
    /// @param _wallet Gnosis founders address.
    function setup(address _gnosisToken, address _wallet)
        public
        isOwner
    {
        if (wallet != 0 || address(gnosisToken) != 0) {
            // Setup was executed already
            throw;
        }
        wallet = _wallet;
        gnosisToken = Token(_gnosisToken);
    }

    /// @dev Returns if one week after auction passed.
    /// @return Returns if one week after auction passed.
    function tokenLaunched()
        public
        timedTransitions
        returns (bool)
    {
        return block.timestamp > endTime + WAITING_PERIOD;
    }

    /// @dev Returns correct stage, even if a function with timedTransitions modifier has not yet been called yet.
    /// @return Returns current auction stage.
    function updateStage()
        public
        timedTransitions
        returns (Stages)
    {
        return stage;
    }

    /// @dev Calculates current token price.
    /// @return Returns token price.
    function calcCurrentTokenPrice()
        public
        timedTransitions
        returns (uint)
    {
        if (stage == Stages.AuctionEnded) {
            return finalPrice;
        }
        return calcTokenPrice();
    }

    /// @dev Allows to send a bid to the auction.
    function bid()
        public
        payable
        timedTransitions
        atStage(Stages.AuctionStarted)
    {
        uint amount = msg.value;
        if (totalReceived + amount > CEILING) {
            amount = CEILING - totalReceived;
            // Send change back
            if (!msg.sender.send(msg.value - amount)) {
                // Sending failed
                throw;
            }
        }
        // Forward funding to ether wallet
        if (amount == 0 || !wallet.send(amount)) {
            // No amount sent or sending failed
            throw;
        }
        bids[msg.sender] += amount;
        totalReceived += amount;
        if (totalReceived == CEILING) {
            finalizeAuction();
        }
        BidSubmission(msg.sender, amount);
    }

    /// @dev Claims tokens for bidder after auction.
    function claimTokens()
        public
        timedTransitions
        atStage(Stages.AuctionEnded)
    {
        uint tokenCount = bids[msg.sender] * 10**18 / finalPrice;
        bids[msg.sender] = 0;
        gnosisToken.transfer(msg.sender, tokenCount);
    }

    /// @dev Calculates stop price.
    /// @return Returns stop price.
    function calcStopPrice()
        constant
        public
        returns (uint)
    {
        return totalReceived / MAX_TOKENS_SOLD;
    }

    /// @dev Calculates token price.
    /// @return Returns token price.
    function calcTokenPrice()
        constant
        public
        returns (uint)
    {
        return 20000 * 1 ether / (block.number - startBlock + 1);
    }

    /*
     *  Private functions
     */
    function finalizeAuction()
        private
    {
        stage = Stages.AuctionEnded;
        if (totalReceived == CEILING) {
            finalPrice = calcTokenPrice();
        }
        else {
            finalPrice = calcStopPrice();
        }
        uint soldTokens = totalReceived * 10**18 / finalPrice;
        // Auction contract transfers all unsold tokens to Gnosis inventory multisig
        gnosisToken.transfer(wallet, TOTAL_TOKENS * 10**18 - soldTokens);
        endTime = block.timestamp;
    }
}

GnosisToken.sol

pragma solidity 0.4.4;
import "Tokens/StandardToken.sol";
import "DO/AbstractDutchAuction.sol";


/// @title Gnosis token contract - Holds tokens of Gnosis.
/// @author Stefan George - 
contract GnosisToken is StandardToken {

    /*
     *  Token meta data
     */
    string constant public name = "Gnosis Token";
    string constant public symbol = "GNO";
    uint8 constant public decimals = 18;

    /*
     *  External contracts
     */
    DutchAuction public dutchAuction;

    /*
     *  Modifiers
     */
    modifier tokenLaunched() {
        if (!dutchAuction.tokenLaunched() && msg.sender != address(dutchAuction)) {
            // Token was not launched yet and sender is not auction contract
            throw;
        }
        _;
    }

    /*
     *  Public functions
     */
    /// @dev Contract constructor function sets owner.
    function GnosisToken(address _dutchAuction)
        public
    {
        totalSupply = 10000000 * 10**18;
        dutchAuction = DutchAuction(_dutchAuction);
        balances[_dutchAuction] = totalSupply;
    }

    /// @dev Transfers sender's tokens to a given address. Returns success.
    /// @param to Address of token receiver.
    /// @param value Number of tokens to transfer.
    /// @return Returns success of function call.
    function transfer(address to, uint256 value)
        public
        tokenLaunched
        returns (bool)
    {
        return super.transfer(to, value);
    }

    /// @dev Allows allowed third party to transfer tokens from one address to another. Returns success.
    /// @param from Address from where tokens are withdrawn.
    /// @param to Address to where tokens are sent.
    /// @param value Number of tokens to transfer.
    /// @return Returns success of function call.
    function transferFrom(address from, address to, uint256 value)
        public
        tokenLaunched
        returns (bool)
    {
        return super.transferFrom(from, to, value);
    }
}

Wallets

MultiSigWallet.sol

pragma solidity 0.4.4;


/// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution.
/// @author Stefan George - 
contract MultiSigWallet {

    uint constant public MAX_OWNER_COUNT = 50;

    event Confirmation(address sender, bytes32 transactionHash);
    event Revocation(address sender, bytes32 transactionHash);
    event Submission(bytes32 transactionHash);
    event Execution(bytes32 transactionHash);
    event ExecutionFailure(bytes32 transactionHash);
    event Deposit(address sender, uint value);
    event OwnerAddition(address owner);
    event OwnerRemoval(address owner);
    event RequirementChange(uint required);

    mapping (bytes32 => Transaction) public transactions;
    mapping (bytes32 => mapping (address => bool)) public confirmations;
    mapping (bytes32 => uint) public nonces;
    mapping (address => bool) public isOwner;
    address[] public owners;
    bytes32[] public transactionHashes;
    uint public required;

    struct Transaction {
        address destination;
        uint value;
        bytes data;
        uint nonce;
        bool executed;
    }

    modifier onlyWallet() {
        if (msg.sender != address(this))
            throw;
        _;
    }

    modifier ownerDoesNotExist(address owner) {
        if (isOwner[owner])
            throw;
        _;
    }

    modifier ownerExists(address owner) {
        if (!isOwner[owner])
            throw;
        _;
    }

    modifier confirmed(bytes32 transactionHash, address owner) {
        if (!confirmations[transactionHash][owner])
            throw;
        _;
    }

    modifier notConfirmed(bytes32 transactionHash, address owner) {
        if (confirmations[transactionHash][owner])
            throw;
        _;
    }

    modifier notExecuted(bytes32 transactionHash) {
        if (transactions[transactionHash].executed)
            throw;
        _;
    }

    modifier notNull(address destination) {
        if (destination == 0)
            throw;
        _;
    }

    modifier validNonce(address destination, uint value, bytes data, uint nonce) {
        if (nonce > nonces[keccak256(destination, value, data)])
            throw;
        _;
    }

    modifier validRequirement(uint ownerCount, uint _required) {
        if (   ownerCount > MAX_OWNER_COUNT
            || _required > ownerCount
            || _required == 0
            || ownerCount == 0)
            throw;
        _;
    }

    /// @dev Fallback function allows to deposit ether.
    function()
        payable
    {
        if (msg.value > 0)
            Deposit(msg.sender, msg.value);
    }

    /*
     * Public functions
     */
    /// @dev Contract constructor sets initial owners and required number of confirmations.
    /// @param _owners List of initial owners.
    /// @param _required Number of required confirmations.
    function MultiSigWallet(address[] _owners, uint _required)
        public
        validRequirement(_owners.length, _required)
    {
        for (uint i=0; i<_owners.length; i++)
            isOwner[_owners[i]] = true;
        owners = _owners;
        required = _required;
    }

    /// @dev Allows to add a new owner. Transaction has to be sent by wallet.
    /// @param owner Address of new owner.
    function addOwner(address owner)
        public
        onlyWallet
        ownerDoesNotExist(owner)
        validRequirement(owners.length + 1, required)
    {
        isOwner[owner] = true;
        owners.push(owner);
        OwnerAddition(owner);
    }

    /// @dev Allows to remove an owner. Transaction has to be sent by wallet.
    /// @param owner Address of owner.
    function removeOwner(address owner)
        public
        onlyWallet
        ownerExists(owner)
    {
        isOwner[owner] = false;
        for (uint i=0; i owners.length)
            changeRequirement(owners.length);
        OwnerRemoval(owner);
    }

    /// @dev Allows to change the number of required confirmations. Transaction has to be sent by wallet.
    /// @param _required Number of required confirmations.
    function changeRequirement(uint _required)
        public
        onlyWallet
        validRequirement(owners.length, _required)
    {
        required = _required;
        RequirementChange(_required);
    }

    /// @dev Allows an owner to submit and confirm a transaction.
    /// @param destination Transaction target address.
    /// @param value Transaction ether value.
    /// @param data Transaction data payload.
    /// @param nonce Internal transaction nonce to identify transactions with identical arguments.
    /// @return Returns hash identifying a transaction.
    function submitTransaction(address destination, uint value, bytes data, uint nonce)
        public
        returns (bytes32 transactionHash)
    {
        transactionHash = addTransaction(destination, value, data, nonce);
        confirmTransaction(transactionHash);
    }

    /// @dev Allows an owner to confirm a transaction.
    /// @param transactionHash Hash identifying a transaction.
    function confirmTransaction(bytes32 transactionHash)
        public
        ownerExists(msg.sender)
    {
        addConfirmation(transactionHash, msg.sender);
        executeTransaction(transactionHash);
    }

    /// @dev Allows an owner to revoke a confirmation for a transaction.
    /// @param transactionHash Hash identifying a transaction.
    function revokeConfirmation(bytes32 transactionHash)
        public
        ownerExists(msg.sender)
        confirmed(transactionHash, msg.sender)
        notExecuted(transactionHash)
    {
        confirmations[transactionHash][msg.sender] = false;
        Revocation(msg.sender, transactionHash);
    }

    /// @dev Allows anyone to execute a confirmed transaction.
    /// @param transactionHash Hash identifying a transaction.
    function executeTransaction(bytes32 transactionHash)
        public
        notExecuted(transactionHash)
    {
        if (isConfirmed(transactionHash)) {
            Transaction tx = transactions[transactionHash];
            tx.executed = true;
            if (tx.destination.call.value(tx.value)(tx.data))
                Execution(transactionHash);
            else {
                ExecutionFailure(transactionHash);
                tx.executed = false;
            }
        }
    }

    /// @dev Returns the confirmation status of a transaction.
    /// @param transactionHash Hash identifying a transaction.
    /// @return Confirmation status.
    function isConfirmed(bytes32 transactionHash)
        public
        constant
        returns (bool)
    {
        uint count = 0;
        for (uint i=0; i

MultiSigWalletWithDailyLimit.sol

pragma solidity 0.4.4;
import "Wallets/MultiSigWallet.sol";


/// @title Multisignature wallet with daily limit - Allows an owner to withdraw a daily limit without multisig.
/// @author Stefan George - 
contract MultiSigWalletWithDailyLimit is MultiSigWallet {

    event DailyLimitChange(uint dailyLimit);

    uint public dailyLimit;
    uint public lastDay;
    uint public spentToday;

    /*
     * Public functions
     */
    /// @dev Contract constructor sets initial owners, required number of confirmations and daily withdraw limit.
    /// @param _owners List of initial owners.
    /// @param _required Number of required confirmations.
    /// @param _dailyLimit Amount in wei, which can be withdrawn without confirmations on a daily basis.
    function MultiSigWalletWithDailyLimit(address[] _owners, uint _required, uint _dailyLimit)
        public
        MultiSigWallet(_owners, _required)
    {
        dailyLimit = _dailyLimit;
    }

    /// @dev Allows to change the daily limit. Transaction has to be sent by wallet.
    /// @param _dailyLimit Amount in wei.
    function changeDailyLimit(uint _dailyLimit)
        public
        onlyWallet
    {
        dailyLimit = _dailyLimit;
        DailyLimitChange(_dailyLimit);
    }

    /// @dev Allows anyone to execute a confirmed transaction or ether withdraws until daily limit is reached.
    /// @param transactionHash Hash identifying a transaction.
    function executeTransaction(bytes32 transactionHash)
        public
        notExecuted(transactionHash)
    {
        Transaction tx = transactions[transactionHash];
        if (isConfirmed(transactionHash) || tx.data.length == 0 && underLimit(tx.value)) {
            tx.executed = true;
            if (tx.destination.call.value(tx.value)(tx.data))
                Execution(transactionHash);
            else {
                ExecutionFailure(transactionHash);
                tx.executed = false;
            }
        }
    }

    /*
     * Internal functions
     */
    /// @dev Returns if amount is within daily limit and updates daily spending.
    /// @param amount Amount to withdraw.
    /// @return Returns if amount is under daily limit.
    function underLimit(uint amount)
        internal
        returns (bool)
    {
        if (now > lastDay + 24 hours) {
            lastDay = now;
            spentToday = 0;
        }
        if (spentToday + amount > dailyLimit)
            return false;
        spentToday += amount;
        return true;
    }
}
This entry was posted in Blog and tagged , , , , , . Bookmark the permalink.