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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/// 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
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
1 2 3 4 |
/// @title Abstract dutch auction contract - Functions to be implemented by dutch auction contracts. contract DutchAuction { function tokenLaunched() returns (bool launched); } |
DutchAuction.sol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
pragma solidity 0.4.4; import "Tokens/AbstractToken.sol"; /// @title Dutch auction contract - creation of Gnosis tokens. /// @author Stefan George - <stefan.george@consensys.net> 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
pragma solidity 0.4.4; import "Tokens/StandardToken.sol"; import "DO/AbstractDutchAuction.sol"; /// @title Gnosis token contract - Holds tokens of Gnosis. /// @author Stefan George - <stefan.george@consensys.net> 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
pragma solidity 0.4.4; /// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution. /// @author Stefan George - <stefan.george@consensys.net> 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 - 1; i++) if (owners[i] == owner) { owners[i] = owners[owners.length - 1]; break; } owners.length -= 1; if (required > 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<owners.length; i++) { if (confirmations[transactionHash][owners[i]]) count += 1; if (count == required) return true; } } /// @dev Returns the nonce for a new transaction. /// @param destination Transaction target address. /// @param value Transaction ether value. /// @param data Transaction data payload. /// @return Internal transaction nonce to identify transactions with identical arguments. function getNonce(address destination, uint value, bytes data) public constant returns (uint) { return nonces[keccak256(destination, value, data)]; } /// @dev Returns number of confirmations of a transaction. /// @param transactionHash Hash identifying a transaction. /// @return Number of confirmations. function getConfirmationCount(bytes32 transactionHash) public constant returns (uint count) { for (uint i=0; i<owners.length; i++) if (confirmations[transactionHash][owners[i]]) count += 1; } /// @dev Returns total number of transactions after filers are applied. /// @param pending Include pending transactions. /// @param executed Include executed transactions. /// @return Total number of transactions after filters are applied. function getTransactionCount(bool pending, bool executed) public constant returns (uint count) { for (uint i=0; i<transactionHashes.length; i++) if ( pending && !transactions[transactionHashes[i]].executed || executed && transactions[transactionHashes[i]].executed) count += 1; } /* * Internal functions */ /// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet. /// @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 transaction. function addTransaction(address destination, uint value, bytes data, uint nonce) internal notNull(destination) validNonce(destination, value, data, nonce) returns (bytes32 transactionHash) { transactionHash = keccak256(destination, value, data, nonce); if (transactions[transactionHash].destination == 0) { transactions[transactionHash] = Transaction({ destination: destination, value: value, data: data, nonce: nonce, executed: false }); nonces[keccak256(destination, value, data)] += 1; transactionHashes.push(transactionHash); Submission(transactionHash); } } /// @dev Adds a confirmation from an owner for a transaction. /// @param transactionHash Hash identifying a transaction. /// @param owner Address of owner. function addConfirmation(bytes32 transactionHash, address owner) internal notConfirmed(transactionHash, owner) { confirmations[transactionHash][owner] = true; Confirmation(owner, transactionHash); } /* * Web3 functions */ /// @dev Returns list of owners. /// @return List of owner addresses. function getOwners() public constant returns (address[] _owners) { _owners = new address[](owners.length); for (uint i=0; i<owners.length; i++) _owners[i] = owners[i]; } /// @dev Returns array with owner addresses, which confirmed transaction. /// @param transactionHash Hash identifying a transaction. /// @return Returns array of owner addresses. function getConfirmations(bytes32 transactionHash) public constant returns (address[] _confirmations) { address[] memory confirmationsTemp = new address[](owners.length); uint count = 0; uint i; for (i=0; i<owners.length; i++) if (confirmations[transactionHash][owners[i]]) { confirmationsTemp[count] = owners[i]; count += 1; } _confirmations = new address[](count); for (i=0; i<count; i++) _confirmations[i] = confirmationsTemp[i]; } /// @dev Returns list of transaction hashes in defined range. /// @param from Index start position of transaction array. /// @param to Index end position of transaction array. /// @param pending Include pending transactions. /// @param executed Include executed transactions. /// @return Returns array of transaction hashes. function getTransactionHashes(uint from, uint to, bool pending, bool executed) public constant returns (bytes32[] _transactionHashes) { bytes32[] memory transactionHashesTemp = new bytes32[](transactionHashes.length); uint count = 0; uint i; for (i=0; i<transactionHashes.length; i++) if ( pending && !transactions[transactionHashes[i]].executed || executed && transactions[transactionHashes[i]].executed) { transactionHashesTemp[count] = transactionHashes[i]; count += 1; } _transactionHashes = new bytes32[](to - from); for (i=from; i<to; i++) _transactionHashes[i - from] = transactionHashesTemp[i]; } } |
MultiSigWalletWithDailyLimit.sol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
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 - <stefan.george@consensys.net> 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; } } |