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 Gnosis contracts published – bug bounty program will start soon.
This is the longest set of Solidity source code I’ve seen so far – 4,779 lines of code and comments.
See https://github.com/ConsenSys/gnosis-contracts for the original contents, and https://github.com/ConsenSys/gnosis.js/ for the code to interact with these contract.
Website – https://www.gnosis.pm/.
Architecture
Table of contents
- DAO
- Event Factory
- Market Crowdfunding
- Market Factories
- Market Makers
- Oracles
- State Channels
- Tokens
- Utils
- Wallets
The source code below is from https://github.com/ConsenSys/gnosis-contracts at Nov 03 2016 14:14:32 UTC.
DAO
AbstractDAO.sol
1 2 3 4 5 |
/// @title Abstract DAO contract - Functions to be implemented by DAO contracts. contract DAO { function calcBaseFee(address sender, uint tokenCount) returns (uint fee); function calcBaseFeeForShares(address sender, uint shareCount) returns (uint fee); } |
AbstractDAOAuction.sol
1 2 3 4 |
/// @title Abstract DAO auction contract - Functions to be implemented by DAO auction contracts. contract DAOAuction { function tokenLaunched() returns (bool launched); } |
DAO.sol
A placeholder contract for a future DAO governance contract. Offers interfaces to calculate fees for trades based on sender and token count. Can be upgraded by a wallet contract controlled by Gnosis founders.
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 |
pragma solidity ^0.4.0; import "EventFactory/AbstractEventFactory.sol"; /// @title DAO contract - Placeholder contract to be updated with governance logic at a later stage. /// @author Stefan George - <stefan.george@consensys.net> contract DAO { /* * External contracts */ EventFactory public eventFactory; /* * Storage */ address public wallet; address public owner; /* * Modifiers */ modifier isOwner() { if (msg.sender != owner) { // Only owner is allowed to do this action. throw; } _; } modifier isWallet() { if (msg.sender != wallet) { // Only wallet is allowed to do this action. throw; } _; } /* * Read and write functions */ /// @dev Exchanges DAO contract and updates events and token contracts. /// @param daoAddress Address of new DAO contract. function changeDAO(address daoAddress) external isWallet { eventFactory.changeDAO(daoAddress); } /// @dev Setup function sets external contracts' addresses. /// @param eventFactoryAddress Events address. /// @param walletAddress Wallet address. function setup(address eventFactoryAddress, address walletAddress) external isOwner { if (address(eventFactory) != 0 || wallet != 0) { // Setup was executed already throw; } eventFactory = EventFactory(eventFactoryAddress); wallet = walletAddress; } /// @dev Contract constructor function sets owner. function DAO() { owner = msg.sender; } /* * Read functions */ /// @dev Returns base fee for amount of tokens. /// @param sender Buyers address. /// @param tokenCount Amount of invested tokens. /// @return fee Returns fee. function calcBaseFee(address sender, uint tokenCount) constant external returns (uint fee) { return 0; } /// @dev Returns base fee for wanted amount of shares. /// @param shareCount Amount of shares to buy. /// @return fee Returns fee. function calcBaseFeeForShares(address sender, uint shareCount) constant external returns (uint fee) { return 0; } } |
DAODutchAuction.sol
Implements the Dutch auction used by Gnosis for the token launch. Accepts bids using bid function until funding goal or stop price is reached. After the auction is completed, every successful bidder can claim his tokens using the claimTokens function.
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 |
pragma solidity ^0.4.0; import "Tokens/AbstractToken.sol"; /// @title DAO Dutch auction contract - Sale of Gnosis tokens. /// @author Stefan George - <stefan.george@consensys.net> contract DAODutchAuction { /* * Events */ event BidSubmission(address indexed investor, uint256 amount); /* * External contracts */ Token public daoToken; /* * Constants */ uint constant public WAITING_PERIOD = 7 days; /* * Storage */ address public tokenWallet; address public etherWallet; address public owner; uint public startBlock; uint public endTime; uint public totalRaised; uint public finalPrice; // user => amount mapping (address => uint) bids; Stages public stage = Stages.AuctionStarted; 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(); } _; } /* * Constants */ uint constant public FUNDING_GOAL = 1250000 ether; uint constant public TOTAL_TOKENS = 10000000; // 10M uint constant public MAX_TOKENS_SOLD = 9000000; // 9M /* * Read and write functions */ /// @dev Allows to send a bid to the auction. function bid() public payable timedTransitions atStage(Stages.AuctionStarted) { uint investment = msg.value; if (totalRaised + investment > FUNDING_GOAL) { investment = FUNDING_GOAL - totalRaised; // Send change back if (!msg.sender.send(msg.value - investment)) { // Sending failed throw; } } // Forward funding to ether wallet if (investment == 0 || !etherWallet.send(investment)) { // No investment done or sending failed throw; } bids[msg.sender] += investment; totalRaised += investment; if (totalRaised == FUNDING_GOAL) { finalizeAuction(); } BidSubmission(msg.sender, investment); } function finalizeAuction() private { stage = Stages.AuctionEnded; if (totalRaised == FUNDING_GOAL) { finalPrice = calcTokenPrice(); } else { finalPrice = calcStopPrice(); } uint soldTokens = totalRaised * 10**18 / finalPrice; // Auction contract transfers all unsold tokens to founders' multisig-wallet daoToken.transfer(tokenWallet, TOTAL_TOKENS * 10**18 - soldTokens); endTime = block.timestamp; } /// @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; daoToken.transfer(msg.sender, tokenCount); } /// @dev Setup function sets external contracts' addresses. /// @param _daoToken DAO token address. /// @param _tokenWallet DAO founders address. function setup(address _daoToken, address _tokenWallet, address _etherWallet) external isOwner { if (tokenWallet != 0 || etherWallet != 0 || address(daoToken) != 0) { // Setup was executed already throw; } tokenWallet = _tokenWallet; etherWallet = _etherWallet; daoToken = Token(_daoToken); } /// @dev Contract constructor function sets start date. function DAODutchAuction() { startBlock = block.number; owner = msg.sender; } /// @dev Default function triggers bid function if auction is still running or claimTokens otherwise. function() external payable { if (stage == Stages.AuctionStarted && calcTokenPrice() > calcStopPrice()) { // Auction is still going and bids are accepted bid(); } else { // Auction ended and tokens can be assigned claimTokens(); // Return Ether if (msg.value > 0 && !msg.sender.send(msg.value)) { // Sending failed throw; } } } /* * Read functions */ /// @dev Calculates stop price. /// @return stopPrice Returns stop price. function calcStopPrice() constant public returns (uint stopPrice) { return totalRaised / MAX_TOKENS_SOLD; } /// @dev Calculates token price. /// @return tokenPrice Returns token price. function calcTokenPrice() constant public returns (uint tokenPrice) { return 20000 * 1 ether / (block.number - startBlock + 1); } /// @dev Returns if one week after auction passed. /// @return launched Returns if one week after auction passed. function tokenLaunched() external timedTransitions returns (bool launched) { return endTime + WAITING_PERIOD < block.timestamp; } } |
DAOToken.sol
Gnosis token (GNO) contract. At deployment all tokens are assigned to the auction contract. Auction contract transfers tokens to bidders according to their bid and final token price. Tokens can be traded after the auction is over and one week passed.
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 |
pragma solidity ^0.4.0; import "Tokens/StandardToken.sol"; import "DAO/AbstractDAOAuction.sol"; /// @title Gnosis token contract - Holds tokens of Gnosis. /// @author Stefan George - <stefan.george@consensys.net> contract DAOToken is StandardToken { /* * Token meta data */ string constant public name = "Gnosis Token"; string constant public symbol = "GNO"; uint8 constant public decimals = 18; /* * External contracts */ DAOAuction public daoAuction; /* * Modifiers */ modifier tokenLaunched() { if (!daoAuction.tokenLaunched() && msg.sender != address(daoAuction)) { // Token was not launched yet and sender is not auction contract throw; } _; } /* * Read and write 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 success Returns success of function call. function transfer(address to, uint256 value) public tokenLaunched returns (bool success) { 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 success Returns success of function call. function transferFrom(address from, address to, uint256 value) public tokenLaunched returns (bool success) { return super.transferFrom(from, to, value); } /// @dev Contract constructor function sets owner. function DAOToken(address _daoAuction) { daoAuction = DAOAuction(_daoAuction); uint _totalSupply = 10000000 * 10**18; balances[_daoAuction] = _totalSupply; totalSupply = _totalSupply; } } |
Event Factory
AbstractEventFactory.sol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/// @title Abstract event factory contract - Functions to be implemented by events contracts. contract EventFactory { function createEvent(bytes32 descriptionHash, bool isRanged, int lowerBound, int upperBound, uint8 outcomeCount, address oracleAddress, address tokenAddress, bytes32[] data) returns (bytes32 eventHash); function buyAllOutcomes(bytes32 eventHash, uint shareCount); function sellAllOutcomes(bytes32 eventHash, uint shareCount); function redeemWinnings(bytes32 eventHash) returns (uint winnings); function changeDAO(address _shareholderContractAddress); function permitPermanentApproval(address spender); function revokePermanentApproval(address spender); function calcBaseFee(uint tokenCount) constant returns (uint fee); function calcBaseFeeForShares(uint shareCount) constant returns (uint fee); function isPermanentlyApproved(address owner, address spender) constant returns (bool isApproved); function getDAO() constant returns (address daoAddress); function getEventHashes(bytes32[] descriptionHashes) constant returns (uint[] allEventHashes); function getEvents(bytes32[] eventHashes, address oracleAddress) constant returns (uint[] allEvents); function getEvent(bytes32 eventHash) constant returns (bytes32 descriptionHash, bool isRanged, int lowerBound, int upperBound, uint outcomeCount, address token, address oracle, bytes32 oracleEventIdentifier, bool isWinningOutcomeSet, int winningOutcome); function getOutcomeToken(bytes32 eventHash, uint outcomeIndex) constant returns (address eventToken); function getShares(address user, bytes32[] _eventHashes) constant returns (uint[] allShares); event EventCreation(address indexed creator, bytes32 indexed eventHash); } |
EventFactory.sol
Allows creation of new events used to resolve markets. Events can resolve to a number in a range (ranged event) or an outcome out of a list of outcomes. An example for a ranged event is the Apple stock price on date X. An example for a non-ranged event would be the winner of the World Cup on date X. For every outcome, an outcome token is created. Ranged events are represented with two outcome tokens for long and short positions. Non-ranged events have an outcome token for every defined outcome. Every event defines an oracle contract to resolve the event and a token contract denominating the currency used in markets using this event. The event factory contract allows to buy and sell all outcomes for 1 unit of the currency of the event. Winnings can be redeemed after the event has been resolved using the defined oracle.
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 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 |
pragma solidity ^0.4.0; import "Oracles/AbstractOracle.sol"; import "DAO/AbstractDAO.sol"; import "Utils/Lockable.sol"; import "EventFactory/OutcomeToken.sol"; /// @title Event factory contract - Event management and share token storage. /// @author Stefan George - <stefan.george@consensys.net> /// @author Martin Koeppelmann - <martin.koeppelmann@consensys.net> contract EventFactory is Lockable { /* * Events */ event EventCreation(address indexed creator, bytes32 indexed eventHash); /* * External contracts */ DAO dao = DAO({{DAO}}); /* * Constants */ uint8 constant SHORT = 0; uint8 constant LONG = 1; uint16 constant OUTCOME_RANGE = 10000; /* * Data structures */ // event hash => Event mapping (bytes32 => Event) events; // description hash => creator => event hash mapping (bytes32 => mapping (address => bytes32)) eventHashes; // owner address => approved address => is approved? mapping (address => mapping (address => bool)) permanentApprovals; struct Event { bytes32 descriptionHash; bool isRanged; int lowerBound; int upperBound; Token token; Oracle oracle; bytes32 oracleEventIdentifier; bool isWinningOutcomeSet; int winningOutcome; OutcomeToken[] outcomeTokens; } /* * Modifiers */ modifier isDAOContract() { if (msg.sender != address(dao)) { // Only DAO contract is allowed to proceed throw; } _; } /* * Read and write functions */ /// @dev Sets new DAO contract address. /// @param daoAddress New DAO contract address. function changeDAO(address daoAddress) external isDAOContract { dao = DAO(daoAddress); } function addEvent( address creator, bytes32 eventHash, bytes32 descriptionHash, bool isRanged, int lowerBound, int upperBound, uint8 outcomeCount, address token, address oracle, bytes32 oracleEventIdentifier ) private { eventHashes[descriptionHash][creator] = eventHash; events[eventHash].descriptionHash = descriptionHash; events[eventHash].oracle = Oracle(oracle); events[eventHash].oracleEventIdentifier = oracleEventIdentifier; events[eventHash].isRanged = isRanged; events[eventHash].lowerBound = lowerBound; events[eventHash].upperBound = upperBound; events[eventHash].token = Token(token); // Create event tokens for each outcome for (uint8 i=0; i<outcomeCount; i++) { events[eventHash].outcomeTokens.push(new OutcomeToken()); } } /// @dev Creates event and pays fees to oracles used to resolve event. Returns event hash. /// @param descriptionHash Hash identifying off chain event description. /// @param isRanged Is event a ranged event? /// @param lowerBound Lower bound for valid outcomes for ranged events. /// @param upperBound Upper bound for valid outcomes for ranged events. /// @param outcomeCount Number of outcomes. /// @param token Address of token contract in which market is traded. /// @param oracle Address of resolving/oracle contract. /// @param data Encoded data used to resolve event. /// @return eventHash Returns event hash. function createEvent( bytes32 descriptionHash, bool isRanged, int lowerBound, int upperBound, uint8 outcomeCount, address token, address oracle, bytes32[] data ) external returns (bytes32 eventHash) { // Calculate event hash eventHash = sha3(descriptionHash, isRanged, lowerBound, upperBound, outcomeCount, token, oracle, data); // Check that event doesn't exist and is valid if ( events[eventHash].descriptionHash > 0 || isRanged && lowerBound >= upperBound || descriptionHash == 0 || outcomeCount < 2 || token == 0 || oracle == 0) { // Event exists already or bounds or outcome count are invalid or description hash or token or oracle are not set throw; } // Pay fee var (oracleFee, oracleToken) = Oracle(oracle).getFee(data); if ( oracleFee > 0 && ( !Token(oracleToken).transferFrom(msg.sender, this, oracleFee) || !Token(oracleToken).approve(oracle, oracleFee))) { // Tokens could not be transferred or approved throw; } // Register event with oracle bytes32 oracleEventIdentifier = Oracle(oracle).registerEvent(data); if (oracleEventIdentifier == 0) { // Event could not be registered with oracle throw; } // Add event to storage addEvent( msg.sender, eventHash, descriptionHash, isRanged, lowerBound, upperBound, outcomeCount, token, oracle, oracleEventIdentifier ); EventCreation(msg.sender, eventHash); } /// @dev Buys equal number of shares of all outcomes, exchanging invested tokens and all outcomes 1:1. Returns success. /// @param eventHash Hash identifying an event. /// @param tokenCount Number of tokens to invest. function buyAllOutcomes(bytes32 eventHash, uint tokenCount) external { // Transfer tokens to events contract if (tokenCount > 0 && !events[eventHash].token.transferFrom(msg.sender, this, tokenCount)) { // Tokens could not be transferred throw; } // Calculate base fee uint fee = calcBaseFee(tokenCount); uint addedShares = tokenCount - fee; // Transfer fee to DAO contract if (fee > 0 && !events[eventHash].token.transfer(dao, fee)) { // Sending failed throw; } // Issue new event tokens to owner. for (uint8 i=0; i<events[eventHash].outcomeTokens.length; i++) { events[eventHash].outcomeTokens[i].issueTokens(msg.sender, addedShares); } } /// @dev Sells equal number of shares of all outcomes, exchanging invested tokens and all outcomes 1:1. Returns success. /// @param eventHash Hash identifying an event. /// @param shareCount Number of shares to sell. function sellAllOutcomes(bytes32 eventHash, uint shareCount) external { // Revoke tokens of all outcomes for (uint i=0; i<events[eventHash].outcomeTokens.length; i++) { events[eventHash].outcomeTokens[i].revokeTokens(msg.sender, shareCount); } // Transfer redeemed tokens if (shareCount > 0 && !events[eventHash].token.transfer(msg.sender, shareCount)) { // Tokens could not be transferred throw; } } /// @dev Redeems winnings of sender for given event. Returns success. /// @param eventHash Hash identifying an event. /// @return winnings Returns winnings. function redeemWinnings(bytes32 eventHash) external isUnlocked returns (uint winnings) { // Check is winning outcome is already set if (!events[eventHash].isWinningOutcomeSet) { if (!events[eventHash].oracle.isOutcomeSet(events[eventHash].oracleEventIdentifier)) { // Winning outcome is not set throw; } // Set winning outcome events[eventHash].winningOutcome = events[eventHash].oracle.getOutcome(events[eventHash].oracleEventIdentifier); events[eventHash].isWinningOutcomeSet = true; } // Calculate winnings for ranged events if (events[eventHash].isRanged) { uint16 convertedWinningOutcome; // Outcome is lower than defined lower bound if (events[eventHash].winningOutcome < events[eventHash].lowerBound) { convertedWinningOutcome = 0; } // Outcome is higher than defined upper bound else if (events[eventHash].winningOutcome > events[eventHash].upperBound) { convertedWinningOutcome = OUTCOME_RANGE; } // Map outcome on outcome range else { convertedWinningOutcome = uint16( OUTCOME_RANGE * (events[eventHash].winningOutcome - events[eventHash].lowerBound) / (events[eventHash].upperBound - events[eventHash].lowerBound) ); } uint factorShort = OUTCOME_RANGE - convertedWinningOutcome; uint factorLong = OUTCOME_RANGE - factorShort; winnings = ( events[eventHash].outcomeTokens[SHORT].balanceOf(msg.sender) * factorShort + events[eventHash].outcomeTokens[LONG].balanceOf(msg.sender) * factorLong ) / OUTCOME_RANGE; } // Calculate winnings for non ranged events else { winnings = events[eventHash].outcomeTokens[uint(events[eventHash].winningOutcome)].balanceOf(msg.sender); } // Revoke all tokens of all outcomes for (uint8 i=0; i<events[eventHash].outcomeTokens.length; i++) { uint shareCount = events[eventHash].outcomeTokens[i].balanceOf(msg.sender); events[eventHash].outcomeTokens[i].revokeTokens(msg.sender, shareCount); } // Payout winnings if (winnings > 0 && !events[eventHash].token.transfer(msg.sender, winnings)) { // Tokens could not be transferred throw; } } /// @dev Approves address to trade unlimited event shares. /// @param spender Address of allowed account. function permitPermanentApproval(address spender) external { permanentApprovals[msg.sender][spender] = true; } /// @dev Revokes approval for address to trade unlimited event shares. /// @param spender Address of allowed account. function revokePermanentApproval(address spender) external { permanentApprovals[msg.sender][spender] = false; } /* * Read functions */ /// @dev Returns base fee for amount of tokens. /// @param tokenCount Amount of invested tokens. /// @return fee Returns fee. function calcBaseFee(uint tokenCount) constant public returns (uint fee) { return dao.calcBaseFee(msg.sender, tokenCount); } /// @dev Returns base fee for wanted amount of shares. /// @param shareCount Amount of shares to buy. /// @return fee Returns fee. function calcBaseFeeForShares(uint shareCount) constant external returns (uint fee) { return dao.calcBaseFeeForShares(msg.sender, shareCount); } /// @dev Returns whether the address is allowed to trade unlimited event shares. /// @param owner Address of allowed account. /// @return approved Returns approval status. function isPermanentlyApproved(address owner, address spender) constant external returns (bool approved) { return permanentApprovals[owner][spender]; } /// @dev Returns DAO address. /// @return dao Returns DAO address. function getDAO() constant external returns (address daoAddress) { return dao; } /// @dev Returns all event hashes for all given description hashes. /// @param descriptionHashes Array of hashes identifying off chain event descriptions. /// @param creators Array event creator addresses. /// @return allEventHashes Encoded event hashes. function getEventHashes(bytes32[] descriptionHashes, address[] creators) constant external returns (uint[] allEventHashes) { // Calculate array size uint arrPos = 0; uint count; for (uint i=0; i<descriptionHashes.length; i++) { count = 0; for (uint j=0; j<creators.length; j++) { if (eventHashes[descriptionHashes[i]][creators[j]] > 0) { count += 1; } } if (count > 0) { arrPos += 2 + count; } } // Fill array allEventHashes = new uint[](arrPos); arrPos = 0; for (i=0; i<descriptionHashes.length; i++) { count = 0; for (j=0; j<creators.length; j++) { if (eventHashes[descriptionHashes[i]][creators[j]] > 0) { allEventHashes[arrPos + 2 + count] = uint(eventHashes[descriptionHashes[i]][creators[j]]); count += 1; } } if (count > 0) { allEventHashes[arrPos] = uint(descriptionHashes[i]); allEventHashes[arrPos + 1] = count; arrPos += 2 + count; } } } /// @dev Returns all encoded events for all given event hashes. /// @param _eventHashes Array of hashes identifying events. /// @param oracle Filter events by oracle. /// @param token Filter events by token. /// @return allEvents Encoded events. function getEvents(bytes32[] _eventHashes, address oracle, address token) constant external returns (uint[] allEvents) { // Calculate array size uint arrPos = 0; for (uint i=0; i<_eventHashes.length; i++) { if (events[_eventHashes[i]].descriptionHash > 0 && (oracle == 0 || events[_eventHashes[i]].oracle == oracle) && (token == 0 || events[_eventHashes[i]].token == token)) { arrPos += 11 + events[_eventHashes[i]].outcomeTokens.length; } } // Fill array allEvents = new uint[](arrPos); arrPos = 0; for (i=0; i<_eventHashes.length; i++) { if (events[_eventHashes[i]].descriptionHash > 0 && (oracle == 0 || events[_eventHashes[i]].oracle == oracle) && (token == 0 || events[_eventHashes[i]].token == token)) { Event _event = events[_eventHashes[i]]; allEvents[arrPos] = uint(_eventHashes[i]); allEvents[arrPos + 1] = uint(_event.descriptionHash); if (_event.isRanged) { allEvents[arrPos + 2] = 1; } else { allEvents[arrPos + 2] = 0; } allEvents[arrPos + 3] = uint(_event.lowerBound); allEvents[arrPos + 4] = uint(_event.upperBound); allEvents[arrPos + 5] = uint(_event.token); allEvents[arrPos + 6] = uint(_event.oracle); allEvents[arrPos + 7] = uint(_event.oracleEventIdentifier); // Event result if (_event.isWinningOutcomeSet) { allEvents[arrPos + 8] = 1; } else { allEvents[arrPos + 8] = 0; } allEvents[arrPos + 9] = uint(_event.winningOutcome); // Event token addresses allEvents[arrPos + 10] = _event.outcomeTokens.length; for (uint j=0; j<_event.outcomeTokens.length; j++) { allEvents[arrPos + 11 + j] = uint(_event.outcomeTokens[j]); } arrPos += 11 + _event.outcomeTokens.length; } } } /// @dev Returns event for event hash. /// @param eventHash Hash identifying an event. /// @return descriptionHash Hash identifying off chain event description. /// @return isRanged Is event a ranged event? /// @return lowerBound Lower bound for valid outcomes for ranged events. /// @return upperBound Upper bound for valid outcomes for ranged events. /// @return outcomeCount Number of outcomes. /// @return token Address of token contract in which market is traded. /// @return oracle Address of resolving/oracle contract. /// @return oracleEventIdentifier Identifier to get oracle result. /// @return isWinningOutcomeSet Was winning outcome set? /// @return winningOutcome Winning outcome. function getEvent(bytes32 eventHash) constant external returns ( bytes32 descriptionHash, bool isRanged, int lowerBound, int upperBound, uint outcomeCount, address token, address oracle, bytes32 oracleEventIdentifier, bool isWinningOutcomeSet, int winningOutcome ) { descriptionHash = events[eventHash].descriptionHash; isRanged = events[eventHash].isRanged; lowerBound = events[eventHash].lowerBound; upperBound = events[eventHash].upperBound; outcomeCount = events[eventHash].outcomeTokens.length; token = events[eventHash].token; oracle = events[eventHash].oracle; oracleEventIdentifier = events[eventHash].oracleEventIdentifier; isWinningOutcomeSet = events[eventHash].isWinningOutcomeSet; winningOutcome = events[eventHash].winningOutcome; } /// @dev Returns token address of outcome. /// @param eventHash Hash identifying an event. /// @param outcomeIndex Index of outcome. /// @return outcomeToken Returns address of event token. function getOutcomeToken(bytes32 eventHash, uint outcomeIndex) constant external returns (address outcomeToken) { return events[eventHash].outcomeTokens[outcomeIndex]; } /// @dev Returns array of encoded shares sender holds in events identified with event hashes. /// @param owner Shareholder's address. /// @param _eventHashes Array of hashes identifying events. /// @return allShares Encoded shares in events by owner. function getShares(address owner, bytes32[] _eventHashes) constant external returns (uint[] allShares) { // Calculate array size uint arrPos = 0; for (uint i=0; i<_eventHashes.length; i++) { for (uint8 j=0; j<events[_eventHashes[i]].outcomeTokens.length; j++) { if (events[_eventHashes[i]].outcomeTokens[j].balanceOf(owner) > 0) { arrPos += 2 + events[_eventHashes[i]].outcomeTokens.length; break; } } } // Fill array allShares = new uint[](arrPos); arrPos = 0; for (i=0; i<_eventHashes.length; i++) { for (j=0; j<events[_eventHashes[i]].outcomeTokens.length; j++) { if (events[_eventHashes[i]].outcomeTokens[j].balanceOf(owner) > 0) { // Add shares allShares[arrPos] = uint(_eventHashes[i]); // event hash allShares[arrPos + 1] = events[_eventHashes[i]].outcomeTokens.length; for (j=0; j<events[_eventHashes[i]].outcomeTokens.length; j++) { allShares[arrPos + 2 + j] = events[_eventHashes[i]].outcomeTokens[j].balanceOf(owner); } arrPos += 2 + j; break; } } } } } |
OutcomeToken.sol
Outcome tokens are created for every outcome of an event. Only the event factory contract is allowed to issue and revoke outcome tokens using buy and sell all outcome functions.
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 |
pragma solidity ^0.4.0; import "Tokens/AbstractToken.sol"; import "EventFactory/OutcomeTokenLibrary.sol"; /// @title Outcome token contract - Issuing and trading event outcomes. /// @author Stefan George - <stefan.george@consensys.net> contract OutcomeToken is Token { /* * External contracts */ address eventFactory; /* * Data structures */ OutcomeTokenLibrary.Data outcomeTokenData; /* * Modifiers */ modifier isEventFactory () { if (msg.sender != eventFactory) { // Only event contract is allowed to proceed. throw; } _; } /* * Read and write functions */ /// @dev Events contract issues new tokens for address. Returns success. /// @param _for Address of receiver. /// @param tokenCount Number of tokens to issue. /// @return success Returns success of function call. function issueTokens(address _for, uint tokenCount) external isEventFactory { outcomeTokenData.balances[_for] += tokenCount; outcomeTokenData.totalTokens += tokenCount; } /// @dev Events contract revokes tokens for address. Returns success. /// @param _for Address of token holder. /// @param tokenCount Number of tokens to revoke. /// @return success Returns success of function call. function revokeTokens(address _for, uint tokenCount) external isEventFactory { if (tokenCount > outcomeTokenData.balances[_for]) { // Balance too low throw; } outcomeTokenData.balances[_for] -= tokenCount; outcomeTokenData.totalTokens -= tokenCount; } /// @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 success Returns success of function call. function transfer(address to, uint256 value) public returns (bool success) { return OutcomeTokenLibrary.transfer(outcomeTokenData, 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 success Returns success of function call. function transferFrom(address from, address to, uint256 value) public returns (bool success) { return OutcomeTokenLibrary.transferFrom(outcomeTokenData, from, to, value, eventFactory); } /// @dev Sets approved amount of tokens for spender. Returns success. /// @param spender Address of allowed actokenCount. /// @param value Number of approved tokens. /// @return success Returns success of function call. function approve(address spender, uint256 value) public returns (bool success) { return OutcomeTokenLibrary.approve(outcomeTokenData, spender, value); } /* * Read functions */ /// @dev Returns number of tokens owned by given address. /// @param owner Address of token owner. /// @return balance Returns owner's balance. function balanceOf(address owner) constant public returns (uint256 balance) { return outcomeTokenData.balances[owner]; } /// @dev Returns number of allowed tokens for given address. /// @param owner Address of token owner. /// @param spender Address of token spender. /// @return remaining Returns remaining allowance. function allowance(address owner, address spender) constant public returns (uint256 remaining) { return outcomeTokenData.allowed[owner][spender]; } /// @dev Returns total supply of tokens. /// @return total Returns total amount of tokens. function totalSupply() constant public returns (uint256 total) { return outcomeTokenData.totalTokens; } /// @dev Constructor sets events contract address. function OutcomeToken() { eventFactory = msg.sender; } } |
OutcomeTokenLibrary.sol
Implements basic token functionality like transfer and transferFrom. The library is used to reduce the deployment gas costs of outcome tokens.
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 |
pragma solidity ^0.4.0; import "EventFactory/AbstractEventFactory.sol"; /// @title Outcome token library - Standard token interface functions. /// @author Stefan George - <stefan.george@consensys.net> library OutcomeTokenLibrary { event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); struct Data { mapping (address => uint256) balances; mapping (address => mapping (address => uint256)) allowed; uint256 totalTokens; } /// @dev Transfers sender's tokens to a given address. Returns success. /// @param self Storage reference. /// @param _to Address of token receiver. /// @param _value Number of tokens to transfer. /// @return success Returns success of function call. function transfer(Data storage self, address _to, uint256 _value) returns (bool success) { if ( self.balances[msg.sender] < _value || self.balances[_to] + _value < self.balances[_to]) { // Balance too low or overflow throw; } self.balances[msg.sender] -= _value; self.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 self Storage reference. /// @param _from Address from where tokens are withdrawn. /// @param _to Address to where tokens are sent. /// @param _value Number of tokens to transfer. /// @param eventFactoryAddress Address of events contract. /// @return success Returns success of function call. function transferFrom(Data storage self, address _from, address _to, uint256 _value, address eventFactoryAddress) returns (bool success) { if ( self.balances[_from] < _value || self.balances[_to] + _value < self.balances[_to] || ( self.allowed[_from][msg.sender] < _value && !EventFactory(eventFactoryAddress).isPermanentlyApproved(_from, msg.sender))) { // Balance too low or overflow or allowance too low and not permanently approved throw; } self.balances[_to] += _value; self.balances[_from] -= _value; if (!EventFactory(eventFactoryAddress).isPermanentlyApproved(_from, msg.sender)) { self.allowed[_from][msg.sender] -= _value; } Transfer(_from, _to, _value); return true; } /// @dev Sets approved amount of tokens for spender. Returns success. /// @param self Storage reference. /// @param _spender Address of allowed account. /// @param _value Number of approved tokens. /// @return success Returns success of function call. function approve(Data storage self, address _spender, uint256 _value) returns (bool success) { self.allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; } } |
Market Crowdfunding
MarketCrowdfunding.sol
Allows crowdfunding of automated market makers and distributes collected fees among investors.
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 |
pragma solidity ^0.4.0; import "MarketFactories/AbstractMarketFactory.sol"; import "Tokens/AbstractToken.sol"; import "MarketMakers/AbstractMarketMaker.sol"; import "EventFactory/AbstractEventFactory.sol"; /// @title Market crowdfunding contract - Allows crowdfunding of markets. /// @author Stefan George - <stefan.george@consensys.net> /// @author Martin Koeppelmann - <martin.koeppelmann@consensys.net> contract MarketCrowdfunding { /* * Events */ event CampaignCreation(address indexed creator, bytes32 indexed campaignHash); event Funding(address indexed investor, uint256 investment, bytes32 indexed campaignHash); /* * External contracts */ EventFactory constant eventFactory = EventFactory({{EventFactory}}); /* * Data structures */ // campaign hash => Campaign mapping(bytes32 => Campaign) public campaigns; // event hash => campaign hashes mapping (bytes32 => bytes32[]) public campaignHashes; // user address => campaign hash => funding mapping(address => mapping(bytes32 => uint)) public shares; struct Campaign { MarketFactory marketFactory; Token token; MarketMaker marketMaker; bytes32 eventHash; bytes32 marketHash; uint fee; uint initialFunding; // For market uint totalFunding; // Funding for market and initial shares uint raisedAmount; uint closingAtTimestamp; uint collectedFees; /* outcome => shares */ uint[] initialShares; } /* * Read and write functions */ /// @dev Starts a new crowdfunding campaign to fund a market for an event. Returns campaign hash. /// @param marketFactory Address of market factory contract. /// @param eventHash Hash identifying event for market. /// @param fee Fee charged by investors for trades on market. /// @param initialFunding Initial funding for automated market maker. /// @param totalFunding Total funding needed for campaign to complete successfully. /// @param marketMaker Contract address of automated market maker. /// @param closingAtTimestamp Block number when campaign ends. Funding has to be completed until this block. /// @param initialShares An array of an initial share distribution. The market maker buys those shares from his own market on creation. This is why total funding is not necessarily equal to initial funding of market. /// @return campaignHash Returns campaign hash. function startCampaign(address marketFactory, bytes32 eventHash, uint fee, uint initialFunding, uint totalFunding, address marketMaker, uint closingAtTimestamp, uint[] initialShares ) external returns (bytes32 campaignHash) { campaignHash = sha3(marketFactory, eventHash, fee, initialFunding, totalFunding, marketMaker, closingAtTimestamp, initialShares); var (, , , , eventOutcomeCount, eventTokenAddress, , , , ) = eventFactory.getEvent(eventHash); if (campaigns[campaignHash].closingAtTimestamp > 0 || eventOutcomeCount == 0) { // Campaign exists already or event is invalid throw; } campaigns[campaignHash].marketFactory = MarketFactory(marketFactory); campaigns[campaignHash].eventHash = eventHash; campaigns[campaignHash].fee = fee; campaigns[campaignHash].initialFunding = initialFunding; campaigns[campaignHash].totalFunding = totalFunding; campaigns[campaignHash].marketMaker = MarketMaker(marketMaker); campaigns[campaignHash].token = Token(eventTokenAddress); campaigns[campaignHash].closingAtTimestamp = closingAtTimestamp; campaigns[campaignHash].initialShares = initialShares; campaignHashes[eventHash].push(campaignHash); CampaignCreation(msg.sender, campaignHash); } /// @dev Creates market once funding is successfully completed. Returns success. /// @param campaignHash Hash identifying campaign. function createMarket(bytes32 campaignHash) external returns (bytes32 marketHash) { MarketFactory marketFactory = campaigns[campaignHash].marketFactory; Token token = campaigns[campaignHash].token; if (campaigns[campaignHash].raisedAmount < campaigns[campaignHash].totalFunding) { // Campaign funding goal was not reached throw; } if (!token.approve(marketFactory, campaigns[campaignHash].totalFunding)) { // Tokens could not be transferred throw; } marketHash = marketFactory.createMarket(campaigns[campaignHash].eventHash, campaigns[campaignHash].fee, campaigns[campaignHash].initialFunding, campaigns[campaignHash].marketMaker); if (marketHash == 0) { // Market could not be created throw; } campaigns[campaignHash].marketHash = marketHash; uint totalCosts = 0; for (uint8 i=0; i<campaigns[campaignHash].initialShares.length; i++) { uint buyValue = campaigns[campaignHash].totalFunding - campaigns[campaignHash].initialFunding - totalCosts; totalCosts += marketFactory.buyShares(marketHash, i, campaigns[campaignHash].initialShares[i], buyValue); } } /// @dev Funds campaign until total funding is reached with Ether or tokens. Returns success. /// @param campaignHash Hash identifying campaign. /// @param tokens Number of tokens used for funding in case funding is not done in Ether. function fund(bytes32 campaignHash, uint tokens) external { Token token = campaigns[campaignHash].token; if ( campaigns[campaignHash].closingAtTimestamp < now || campaigns[campaignHash].raisedAmount == campaigns[campaignHash].totalFunding) { // Campaign is over or funding goal was reached throw; } uint investment = tokens; if (campaigns[campaignHash].raisedAmount + investment > campaigns[campaignHash].totalFunding) { // Sender send too much value, difference is returned to sender investment = campaigns[campaignHash].totalFunding - campaigns[campaignHash].raisedAmount; } if (investment == 0 || !token.transferFrom(msg.sender, this, investment)) { // Tokens could not be transferred throw; } campaigns[campaignHash].raisedAmount += investment; shares[msg.sender][campaignHash] += investment; Funding(msg.sender, investment, campaignHash); } /// @dev Withdraws funds from an investor in case of an unsuccessful campaign. Returns success. /// @param campaignHash Hash identifying campaign. function withdrawFunding(bytes32 campaignHash) external { if ( campaigns[campaignHash].closingAtTimestamp >= now || campaigns[campaignHash].raisedAmount == campaigns[campaignHash].totalFunding) { // Campaign is still going or market has been created throw; } uint funding = shares[msg.sender][campaignHash]; shares[msg.sender][campaignHash] = 0; Token token = campaigns[campaignHash].token; if (funding > 0 && !token.transfer(msg.sender, funding)) { // Tokens could not be transferred throw; } } /// @dev Withdraws fees earned by market. Has to be done before a market investor can get its share of those winnings. Returns success. /// @param campaignHash Hash identifying campaign. function withdrawContractFees(bytes32 campaignHash) external { campaigns[campaignHash].collectedFees += campaigns[campaignHash].marketFactory.withdrawFees(campaigns[campaignHash].marketHash); } /// @dev Withdraws investor's share of earned fees by a market. Returns success. /// @param campaignHash Hash identifying campaign. function withdrawFees(bytes32 campaignHash) external { if (campaigns[campaignHash].collectedFees == 0) { // No fees collected throw; } Token token = campaigns[campaignHash].token; uint userFees = campaigns[campaignHash].collectedFees * shares[msg.sender][campaignHash] / campaigns[campaignHash].totalFunding; shares[msg.sender][campaignHash] = 0; if (userFees > 0 && !token.transfer(msg.sender, userFees)) { // Tokens could not be transferred throw; } } /* * Read functions */ /// @dev Returns array of all campaign hashes of corresponding event hashes. /// @param eventHashes Array of event hashes identifying events. /// @return allCampaignHashes Returns all campaign hashes. function getCampaignHashes(bytes32[] eventHashes) constant external returns (uint[] allCampaignHashes) { // Calculate array size uint arrPos = 0; for (uint i=0; i<eventHashes.length; i++) { uint campaignHashesCount = campaignHashes[eventHashes[i]].length; if (campaignHashesCount > 0) { arrPos += 2 + campaignHashesCount; } } // Fill array allCampaignHashes = new uint[](arrPos); arrPos = 0; for (i=0; i<eventHashes.length; i++) { campaignHashesCount = campaignHashes[eventHashes[i]].length; if (campaignHashesCount > 0) { allCampaignHashes[arrPos] = uint(eventHashes[i]); allCampaignHashes[arrPos + 1] = campaignHashesCount; for (uint j=0; j<campaignHashesCount; j++) { allCampaignHashes[arrPos + 2 + j] = uint(campaignHashes[eventHashes[i]][j]); } arrPos += 2 + campaignHashesCount; } } } /// @dev Returns array of encoded campaigns. /// @param _campaignHashes Array of campaign hashes identifying campaigns. /// @return _campaignHashes Returns campaign hashes. function getCampaigns(bytes32[] _campaignHashes) constant external returns (uint[] allCampaigns) { // Calculate array size uint arrPos = 0; for (uint i=0; i<_campaignHashes.length; i++) { arrPos += 13 + campaigns[_campaignHashes[i]].initialShares.length; } // Fill array allCampaigns = new uint[](arrPos); arrPos = 0; for (i=0; i<_campaignHashes.length; i++) { bytes32 campaignHash = _campaignHashes[i]; Campaign campaign = campaigns[campaignHash]; allCampaigns[arrPos] = uint(campaignHash); allCampaigns[arrPos + 1] = uint(campaign.marketFactory); allCampaigns[arrPos + 2] = uint(campaign.token); allCampaigns[arrPos + 3] = uint(campaign.marketMaker); allCampaigns[arrPos + 4] = uint(campaign.eventHash); allCampaigns[arrPos + 5] = uint(campaign.marketHash); allCampaigns[arrPos + 6] = campaign.fee; allCampaigns[arrPos + 7] = campaign.initialFunding; allCampaigns[arrPos + 8] = campaign.totalFunding; allCampaigns[arrPos + 9] = campaign.raisedAmount; allCampaigns[arrPos + 10] = campaign.closingAtTimestamp; allCampaigns[arrPos + 11] = campaign.collectedFees; allCampaigns[arrPos + 12] = campaign.initialShares.length; for (uint j=0; j<campaign.initialShares.length; j++) { allCampaigns[arrPos + 13 + j] = campaign.initialShares[j]; } arrPos += 13 + campaign.initialShares.length; } } /// @dev Returns array of encoded investments an investor holds in campaigns. /// @param _campaignHashes Array of campaign hashes identifying campaigns. /// @return allShares Returns all user's shares. function getShares(address user, bytes32[] _campaignHashes) constant external returns (uint[] allShares) { // Calculate array size uint arrPos = 0; for (uint i=0; i<_campaignHashes.length; i++) { bytes32 campaignHash = _campaignHashes[i]; if (shares[user][campaignHash] > 0) { arrPos += 2; } } // Fill array allShares = new uint[](arrPos); arrPos = 0; for (i=0; i<_campaignHashes.length; i++) { campaignHash = _campaignHashes[i]; if (shares[user][campaignHash] > 0) { allShares[arrPos] = uint(campaignHash); allShares[arrPos + 1] = shares[user][campaignHash]; arrPos += 2; } } } } |
Market Factories
AbstractMarketFactory.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 |
/// @title Abstract markets contract - Functions to be implemented by markets contracts. contract MarketFactory { function createMarket(bytes32 eventHash, uint fee, uint initialFunding, address marketMakerAddress) returns (bytes32 marketHash); function closeMarket(bytes32 marketHash); function withdrawFees(bytes32 marketHash) returns (uint fees); function buyShares(bytes32 marketHash, uint8 outcomeIndex, uint shareCount, uint maxSpending) returns (uint totalCosts); function sellShares(bytes32 marketHash, uint8 outcomeIndex, uint shareCount, uint expectedEarnings) returns (uint netEarnings); function shortSellShares(bytes32 marketHash, uint8 outcomeIndex, uint shareCount, uint expectedEarnings) returns (uint totalCosts); function calcMarketFee(bytes32 marketHash, uint tokenCount) constant returns (uint fee); function getMarketHashes(bytes32[] eventHashes, address[] investors) constant returns (uint[] allMarketHashes); function getMarkets(bytes32[] marketHashes, address investor) constant returns (uint[] allMarkets); function getMarket(bytes32 marketHash) constant returns (bytes32 eventHash, uint fee, uint collectedFees, uint initialFunding, address investor, address marketMaker, uint createdAtBlock); function getShareDistributionWithTimestamp(bytes32 marketHash) constant returns (uint[] shareDistribution); function getShareDistribution(bytes32 marketHash) constant returns (uint[256] shareDistribution); function getMinFunding() constant returns (uint minFunding); // Market factory meta data // This is not an abstract functions, because solc won't recognize generated getter functions for public variables as functions. function name() constant returns (string) {} event MarketCreation(address indexed investor, bytes32 indexed marketHash); event MarketClosing(address indexed investor, bytes32 indexed marketHash); } |
DefaultMarketFactory.sol
Allows to create markets and trade outcome tokens on markets with a market maker. A market is always associated to an event and has to be funded in the event’s denomination. A market maker contract like the LMSR market maker has to be defined to allow trading. A fee charged for every trade on a market can be defined to return the investment used to fund the market maker.
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 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 |
pragma solidity ^0.4.0; import "EventFactory/AbstractEventFactory.sol"; import "MarketMakers/AbstractMarketMaker.sol"; import "MarketFactories/AbstractMarketFactory.sol"; import "Tokens/AbstractToken.sol"; /// @title Market library - Market management and share trading. /// @author Stefan George - <stefan.george@consensys.net> /// @author Martin Koeppelmann - <martin.koeppelmann@consensys.net> contract DefaultMarketFactory is MarketFactory { /* * External contracts */ EventFactory constant eventFactory = EventFactory({{EventFactory}}); /* * Constants */ uint constant MAX_FEE = 500000; // 50% uint constant FEE_RANGE = 1000000; // 100% // Market factory meta data string constant public name = "Default Market Manager"; /* * Data structures */ // market hash => Market mapping (bytes32 => Market) markets; // event hash => investor => market hashes mapping (bytes32 => mapping(address => bytes32)) marketHashes; struct Market { bytes32 eventHash; uint fee; uint collectedFees; uint initialFunding; address investor; MarketMaker marketMaker; uint createdAtBlock; uint[] shares; } /* * Modifiers */ modifier isInvestor (bytes32 marketHash) { if (msg.sender != markets[marketHash].investor) { // Only Investor is allowed to proceed throw; } _; } /* * Read and write functions */ /// @dev Creates market and sets initial shares for market maker. Returns market hash. /// @param eventHash Hash identifying an event. /// @param fee Fee charged by market maker for trades. /// @param initialFunding Initial funding for market maker in tokens. /// @param marketMaker Address of automated market maker contract. /// @return marketHash Returns market hash. function createMarket(bytes32 eventHash, uint fee, uint initialFunding, address marketMaker) public returns (bytes32 marketHash) { var (, , , , eventOutcomeCount, eventToken, , , , ) = eventFactory.getEvent(eventHash); marketHash = sha3(eventHash, msg.sender, marketMaker); // Validate data if (markets[marketHash].eventHash > 0 || fee > MAX_FEE || eventOutcomeCount == 0) { // There is already a market with this hash or fee is too high or no event for the market exists throw; } // Calculate fee charged by gnosis uint buyAllOutcomesCosts = initialFunding + eventFactory.calcBaseFeeForShares(initialFunding); // Transfer funding to markets contract and invest initial funding if ( buyAllOutcomesCosts == 0 || !Token(eventToken).transferFrom(msg.sender, this, buyAllOutcomesCosts) || !Token(eventToken).approve(eventFactory, buyAllOutcomesCosts)) { // Sender doesn't have enough tokens to do the funding or token approval failed or buy all outcomes could not be completed throw; } eventFactory.buyAllOutcomes(eventHash, buyAllOutcomesCosts); // Add invested shares to market markets[marketHash].shares = new uint[](eventOutcomeCount); for (uint8 i=0; i<eventOutcomeCount; i++) { markets[marketHash].shares[i] = initialFunding; } // Add market to storage marketHashes[eventHash][msg.sender] = marketHash; markets[marketHash].fee = fee; markets[marketHash].initialFunding = initialFunding; markets[marketHash].eventHash = eventHash; markets[marketHash].investor = msg.sender; markets[marketHash].marketMaker = MarketMaker(marketMaker); markets[marketHash].createdAtBlock = block.number; MarketCreation(msg.sender, marketHash); } /// @dev Closes market and transfers shares to investor. Returns success. /// @param marketHash Hash identifying a market. function closeMarket(bytes32 marketHash) public isInvestor(marketHash) { // Transfer shares to investor for (uint8 i=0; i<markets[marketHash].shares.length; i++) { Token(eventFactory.getOutcomeToken(markets[marketHash].eventHash, i)).transfer(msg.sender, markets[marketHash].shares[i]); markets[marketHash].shares[i] = 0; } // Delete market from storage delete markets[marketHash]; MarketClosing(msg.sender, marketHash); } /// @dev Withdraws fees earned on market to investor. Returns fees. /// @param marketHash Hash identifying a market. /// @return fees Returns fees. function withdrawFees(bytes32 marketHash) public isInvestor(marketHash) returns (uint fees) { var (, , , , , eventToken, , , , ) = eventFactory.getEvent(markets[marketHash].eventHash); fees = markets[marketHash].collectedFees; markets[marketHash].collectedFees = 0; // Send fees to investor if (fees > 0 && !Token(eventToken).transfer(msg.sender, fees)) { // Tokens could not be transferred throw; } } /// @dev Buys shares of defined market and outcome. Returns price including fee. /// @param marketHash Hash identifying a market. /// @param outcomeIndex Outcome selected to buy shares from. /// @param shareCount Number of shares to buy. /// @param maxSpending Number of shares to invest. /// @return totalCosts Returns total costs. function buyShares(bytes32 marketHash, uint8 outcomeIndex, uint shareCount, uint maxSpending) public returns (uint totalCosts) { var (, , , , , eventToken, , , , ) = eventFactory.getEvent(markets[marketHash].eventHash); // Calculate costs for requested shares uint costs = markets[marketHash].marketMaker.calcCostsBuying(marketHash, markets[marketHash].initialFunding, markets[marketHash].shares, outcomeIndex, shareCount); if (costs == 0) { // Amount of shares too low, rounding issue throw; } // Calculate fee charged by market uint fee = calcMarketFee(marketHash, costs); // Calculate fee charged by gnosis uint baseFee = eventFactory.calcBaseFeeForShares(shareCount); totalCosts = costs + fee + baseFee; // Check costs don't exceed max spending if (totalCosts > maxSpending) { // Shares are more expensive throw; } // Transfer tokens to markets contract and buy all outcomes if ( totalCosts == 0 || !Token(eventToken).transferFrom(msg.sender, this, totalCosts) || !Token(eventToken).approve(eventFactory, costs + baseFee)) { // Sender did not send enough tokens or token approval could not be completed throw; } eventFactory.buyAllOutcomes(markets[marketHash].eventHash, costs + baseFee); // Add new allocated shares to market for (uint8 i=0; i<markets[marketHash].shares.length; i++) { markets[marketHash].shares[i] += costs; } // Protect against malicious markets if (shareCount > markets[marketHash].shares[outcomeIndex]) { // Market maker out of funds throw; } // Add fee to collected fees markets[marketHash].collectedFees += fee; // Transfer shares to buyer markets[marketHash].shares[outcomeIndex] -= shareCount; Token(eventFactory.getOutcomeToken(markets[marketHash].eventHash, outcomeIndex)).transfer(msg.sender, shareCount); } /// @dev Sells shares of defined market and outcome. Returns earnings minus fee. /// @param marketHash Hash identifying a market. /// @param outcomeIndex Outcome selected to sell shares from. /// @param shareCount Number of shares to sell. /// @param expectedEarnings Number of shares to invest in case of a market traded in tokens. /// @return netEarnings Returns net earnings. function sellShares(bytes32 marketHash, uint8 outcomeIndex, uint shareCount, uint expectedEarnings) public returns (uint netEarnings) { var (, , , , , eventToken, , , , ) = eventFactory.getEvent(markets[marketHash].eventHash); // Calculate earnings for requested shares uint earnings = markets[marketHash].marketMaker.calcEarningsSelling(marketHash, markets[marketHash].initialFunding, markets[marketHash].shares, outcomeIndex, shareCount); if (earnings == 0) { // Amount of shares too low, rounding issue throw; } // Calculate fee charged by market uint fee = calcMarketFee(marketHash, earnings); netEarnings = earnings - fee; if (netEarnings < expectedEarnings) { // Invalid sell order throw; } // Transfer event tokens to markets contract to redeem all outcomes Token(eventFactory.getOutcomeToken(markets[marketHash].eventHash, outcomeIndex)).transferFrom(msg.sender, this, shareCount); eventFactory.sellAllOutcomes(markets[marketHash].eventHash, earnings); // Add shares transferred to market markets[marketHash].shares[outcomeIndex] += shareCount; // Lower shares of market by sold shares for (uint8 i=0; i<markets[marketHash].shares.length; i++) { if (markets[marketHash].shares[i] >= earnings) { markets[marketHash].shares[i] -= earnings; } else { // Market maker out of funds, revert state to protect against malicious market makers throw; } } // Add fee to collected fees markets[marketHash].collectedFees += fee; // Transfer earnings to seller if (netEarnings > 0 && !Token(eventToken).transfer(msg.sender, netEarnings)) { // Tokens could ot be transferred throw; } } /// @dev Short sells outcome by buying all outcomes and selling selected outcome shares. Returns invested tokens. /// @param marketHash Hash identifying a market. /// @param outcomeIndex Outcome selected to sell shares from. /// @param shareCount Number of shares to buy from all outcomes. /// @param expectedEarnings Money to earn from selling selected outcome. /// @return totalCosts Returns total costs. function shortSellShares(bytes32 marketHash, uint8 outcomeIndex, uint shareCount, uint expectedEarnings) public returns (uint totalCosts) { bytes32 eventHash = markets[marketHash].eventHash; var (, , , , eventOutcomeCount, eventToken, , , , ) = eventFactory.getEvent(eventHash); // Calculate fee charged by gnosis uint buyAllOutcomesCosts = shareCount + eventFactory.calcBaseFeeForShares(shareCount); // Buy all outcomes if ( buyAllOutcomesCosts == 0 || !Token(eventToken).transferFrom(msg.sender, this, buyAllOutcomesCosts) || !Token(eventToken).approve(eventFactory, buyAllOutcomesCosts)) { // Sender did not send enough tokens or buy all outcomes failed throw; } eventFactory.buyAllOutcomes(eventHash, buyAllOutcomesCosts); // Short sell selected shares if (!Token(eventFactory.getOutcomeToken(eventHash, outcomeIndex)).approve(this, shareCount)) { throw; } uint earnings = this.sellShares(marketHash, outcomeIndex, shareCount, expectedEarnings); if (earnings == 0) { // Could not sell shares for expected price throw; } // Transfer shares to buyer for (uint8 i =0; i<eventOutcomeCount; i++) { if (i != outcomeIndex){ Token(eventFactory.getOutcomeToken(eventHash, i)).transfer(msg.sender, shareCount); } } // Send change back to buyer if (earnings > 0 && !Token(eventToken).transfer(msg.sender, earnings)) { // Couldn't send user change back throw; } totalCosts = buyAllOutcomesCosts - earnings; } /* * Read functions */ /// @dev Returns all market hashes for all given event hashes. /// @param eventHashes Array of hashes identifying eventFactory. /// @param investors Array of investor addresses. /// @return allMarketHashes Returns all market hashes for markets created by one of the investors. function getMarketHashes(bytes32[] eventHashes, address[] investors) constant public returns (uint[] allMarketHashes) { // Calculate array size uint arrPos = 0; uint count; for (uint i=0; i<eventHashes.length; i++) { count = 0; for (uint j=0; j<investors.length; j++) { if (marketHashes[eventHashes[i]][investors[j]] > 0) { count += 1; } } if (count > 0) { arrPos += 2 + count; } } // Fill array allMarketHashes = new uint[](arrPos); arrPos = 0; for (i=0; i<eventHashes.length; i++) { count = 0; for (j=0; j<investors.length; j++) { if (marketHashes[eventHashes[i]][investors[j]] > 0) { allMarketHashes[arrPos + 2 + count] = uint(marketHashes[eventHashes[i]][investors[j]]); count += 1; } } if (count > 0) { allMarketHashes[arrPos] = uint(eventHashes[i]); allMarketHashes[arrPos + 1] = count; arrPos += 2 + count; } } } /// @dev Calculates market fee for invested tokens. /// @param marketHash Hash identifying market. /// @param tokenCount Amount of invested tokens. /// @return fee Returns fee. function calcMarketFee(bytes32 marketHash, uint tokenCount) constant public returns (uint fee) { return tokenCount * markets[marketHash].fee / FEE_RANGE; } /// @dev Returns all encoded markets for all given market hashes. /// @param marketHashes Array of hashes identifying markets. /// @param investor Filter markets by investor. /// @return allMarkets Returns all markets. function getMarkets(bytes32[] marketHashes, address investor) constant public returns (uint[] allMarkets) { // Calculate array size uint arrPos = 0; for (uint i=0; i<marketHashes.length; i++) { if (markets[marketHashes[i]].eventHash > 0 && (investor == 0 || markets[marketHashes[i]].investor == investor)) { arrPos += 9 + markets[marketHashes[i]].shares.length; } } // Fill array allMarkets = new uint[](arrPos); arrPos = 0; for (i=0; i<marketHashes.length; i++) { if (markets[marketHashes[i]].eventHash > 0 && (investor == 0 || markets[marketHashes[i]].investor == investor)) { bytes32 marketHash = marketHashes[i]; Market market = markets[marketHash]; allMarkets[arrPos] = uint(marketHash); allMarkets[arrPos + 1] = uint(market.eventHash); allMarkets[arrPos + 2] = market.fee; allMarkets[arrPos + 3] = market.collectedFees; allMarkets[arrPos + 4] = market.initialFunding; allMarkets[arrPos + 5] = uint(market.investor); allMarkets[arrPos + 6] = uint(market.marketMaker); allMarkets[arrPos + 7] = uint(market.createdAtBlock); allMarkets[arrPos + 8] = market.shares.length; for (uint j=0; j<market.shares.length; j++) { allMarkets[arrPos + 9 + j] = market.shares[j]; } arrPos += 9 + market.shares.length; } } } /// @dev Returns distribution of market shares at a specific time. /// @param marketHash Hash identifying a market. /// @return shareDistribution Returns share distribution and timestamp. function getShareDistributionWithTimestamp(bytes32 marketHash) constant public returns (uint[] shareDistribution) { shareDistribution = new uint[](markets[marketHash].shares.length + 1); shareDistribution[0] = now; for (uint8 i=0; i<markets[marketHash].shares.length; i++) { shareDistribution[1 + i] = markets[marketHash].shares[i]; } } /// @dev Returns distribution of market shares. /// @param marketHash Hash identifying a market. /// @return shareDistribution Returns share distribution. function getShareDistribution(bytes32 marketHash) constant public returns (uint[256] shareDistribution) { for (uint8 i=0; i<markets[marketHash].shares.length; i++) { shareDistribution[i] = markets[marketHash].shares[i]; } } /// @dev Returns market for market hash. /// @param marketHash Hash identifying a market. /// @return eventHash Hash identifying an event. /// @return fee Fee charged by market maker for trades. /// @return collectedFees Fees collected market maker for trades. /// @return initialFunding Initial funding for market maker in tokens. /// @return marketMaker Address of automated market maker contract. /// @return createdAtBlock Returns block number when market was created. function getMarket(bytes32 marketHash) constant public returns ( bytes32 eventHash, uint fee, uint collectedFees, uint initialFunding, address investor, address marketMaker, uint createdAtBlock ) { eventHash = markets[marketHash].eventHash; fee = markets[marketHash].fee; collectedFees = markets[marketHash].collectedFees; initialFunding = markets[marketHash].initialFunding; investor = markets[marketHash].investor; marketMaker = markets[marketHash].marketMaker; createdAtBlock = markets[marketHash].createdAtBlock; } /// @dev Returns minimum funding for market creation. /// @return minFunding Returns minimum funding in Wei. function getMinFunding() constant public returns (uint minFunding) { return 0; } } |
HunchGameMarketFactory.sol
Inherits functionality from DefaultMarketFactory and adds functionality required for the HunchGame Gnosis App (high-score etc.).
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 |
pragma solidity ^0.4.0; import "EventFactory/AbstractEventFactory.sol"; import "MarketFactories/DefaultMarketFactory.sol"; import "Tokens/HunchGameToken.sol"; /// @title Hunch Game token contract - Implements token trading and high-score calculation. /// @author Stefan George - <stefan.george@consensys.net> /// @author Martin Koeppelmann - <martin.koeppelmann@consensys.net> contract HunchGameMarketFactory is DefaultMarketFactory { /* * External contracts */ EventFactory constant eventFactory = EventFactory({{EventFactory}}); HunchGameToken constant hunchGameToken = HunchGameToken({{HunchGameToken}}); /* * Constants */ uint16 constant TWELVE_HOURS = 12 hours; // 12h uint constant CREDIT = 1000 * 10**18; uint constant MIN_FUNDING = 10 * 10**18; // Minimum amount of tokens needed to create a market // Market factory meta data string constant public name = "HunchGame Market Manager"; /* * Data structures */ // owner address => timestamp mapping (address => uint) lastCredits; // owner address => event hash => number of tokens mapping (address => mapping (bytes32 => int)) tokensInEvents; // owner address => event hash => outcomeIndex => number of shares mapping (address => mapping (bytes32 => mapping(uint8 => uint))) eventShares; // owner address => high score mapping (address => uint) highScores; /* * Modifiers */ modifier minFunding (uint initialFunding) { if (initialFunding < MIN_FUNDING) { // Check there is enough funding throw; } _; } /* * Read and write functions */ /// @dev Returns user level. /// @param userAddress Address of user account. /// @return level Returns user's level. function userLevel(address userAddress) public returns (uint level) { level = 0; while (10**level * CREDIT < highScores[userAddress]) { level += 1; } } /// @dev Adds credits to user account every 12 hours. function addCredit() public { if (lastCredits[msg.sender] + TWELVE_HOURS >= now) { // Last credits were issued less than 12 hours ago throw; } uint level = userLevel(msg.sender); uint addedCredit = 10**level * CREDIT; hunchGameToken.issueTokens(msg.sender, addedCredit); lastCredits[msg.sender] = now; } /// @dev Buys more credits with Ether. One 12h credits for 0.5 Ether. function buyCredits() public payable { if (msg.value == 0) { // No ether was sent throw; } uint level = userLevel(msg.sender); uint addedCredit = 10**level * CREDIT * msg.value / (10**18 / 2); hunchGameToken.issueTokens(msg.sender, addedCredit); } /// @dev Creates market and sets initial shares for market maker. Returns market hash. /// @param eventHash Hash identifying an event. /// @param fee Fee charged by market maker for trades. /// @param initialFunding Initial funding for market maker in tokens. /// @param marketMakerAddress Address of automated market maker contract. /// @return marketHash Returns market hash. function createMarket(bytes32 eventHash, uint fee, uint initialFunding, address marketMakerAddress) public minFunding(initialFunding) returns (bytes32 marketHash) { var (, , , , , eventTokenAddress, , , , ) = eventFactory.getEvent(eventHash); if (eventTokenAddress != address(hunchGameToken)) { // Event is not using Hunch Game tokens throw; } return super.createMarket(eventHash, fee, initialFunding, marketMakerAddress); } /// @dev Wraps buy share function of market contract. /// @param marketHash Hash identifying a market. /// @param outcomeIndex Outcome selected to buy shares from. /// @param shareCount Number of shares to buy. /// @param maxSpending Number of shares to invest in case of a market traded in tokens. /// @return costs Returns total costs. function buyShares(bytes32 marketHash, uint8 outcomeIndex, uint shareCount, uint maxSpending) public returns (uint costs) { bytes32 eventHash = markets[marketHash].eventHash; costs = super.buyShares(marketHash, outcomeIndex, shareCount, maxSpending); if (costs > 0) { tokensInEvents[msg.sender][eventHash] += int(costs); eventShares[msg.sender][eventHash][outcomeIndex] += shareCount; } } /// @dev Short sells outcome by buying all outcomes and selling selected outcome shares. Returns invested tokens. /// @param marketHash Hash identifying a market. /// @param outcomeIndex Outcome selected to sell shares from. /// @param shareCount Number of shares to buy from all outcomes. /// @param expectedEarnings Money to earn from selling selected outcome. /// @return costs Returns total costs. function shortSellShares(bytes32 marketHash, uint8 outcomeIndex, uint shareCount, uint expectedEarnings) public returns (uint costs) { costs = super.shortSellShares(marketHash, outcomeIndex, shareCount, expectedEarnings); if (costs > 0) { bytes32 eventHash = markets[marketHash].eventHash; var (, , , , eventOutcomeCount, , , , , ) = eventFactory.getEvent(eventHash); for (uint8 i =0; i<eventOutcomeCount; i++) { if (i != outcomeIndex){ eventShares[msg.sender][eventHash][i] += shareCount; } } tokensInEvents[msg.sender][eventHash] += int(costs); } } /// @dev Wraps sell shares function of market contract. /// @param marketHash Hash identifying a market. /// @param outcomeIndex Outcome selected to sell shares from. /// @param shareCount Number of shares to sell. /// @param expectedEarnings Number of shares to invest in case of a market traded in tokens. /// @return earnings Returns total earnings. function sellShares(bytes32 marketHash, uint8 outcomeIndex, uint shareCount, uint expectedEarnings) public returns (uint earnings) { bytes32 eventHash = markets[marketHash].eventHash; // User transfers shares to HunchGame and HunchGame approves market contract to sell shares earnings = super.sellShares(marketHash, outcomeIndex, shareCount, expectedEarnings); if ( int(earnings) > tokensInEvents[msg.sender][eventHash] && eventShares[msg.sender][eventHash][outcomeIndex] >= shareCount) { if (tokensInEvents[msg.sender][eventHash] < 0) { highScores[msg.sender] += earnings; } else { highScores[msg.sender] += earnings - uint(tokensInEvents[msg.sender][eventHash]); } } tokensInEvents[msg.sender][eventHash] -= int(earnings); eventShares[msg.sender][eventHash][outcomeIndex] -= shareCount; } /// @dev Wraps redeem winnings function in event contract. /// @param eventHash Hash identifying an event. /// @return winnings Returns total winnings. function redeemWinnings(bytes32 eventHash) public returns (uint winnings) { var (, , , , eventOutcomeCount, , , , , ) = eventFactory.getEvent(eventHash); bool fraudulentNumberOfShares = false; for (uint8 i=0; i<eventOutcomeCount; i++) { uint shareCount = Token(eventFactory.getOutcomeToken(eventHash, i)).balanceOf(msg.sender); if (eventShares[msg.sender][eventHash][i] < shareCount) { fraudulentNumberOfShares = true; eventShares[msg.sender][eventHash][i] = 0; } Token(eventFactory.getOutcomeToken(eventHash, i)).transferFrom(msg.sender, this, shareCount); } uint balanceBeforeRedemption = hunchGameToken.balanceOf(this); winnings = eventFactory.redeemWinnings(eventHash); if (winnings == 0) { // No winnings earned throw; } hunchGameToken.transfer(msg.sender, winnings); if (int(winnings) > tokensInEvents[msg.sender][eventHash] && !fraudulentNumberOfShares) { if (tokensInEvents[msg.sender][eventHash] < 0) { highScores[msg.sender] += winnings; } else { highScores[msg.sender] += winnings - uint(tokensInEvents[msg.sender][eventHash]); } } tokensInEvents[msg.sender][eventHash] -= int(winnings); } /* * Read functions */ /// @dev Returns all tokens sender has invested in events. /// @param user User's address. /// @param eventHashes Array of hashes identifying events. /// @return allTokens Returns all tokens in events. function getTokensInEvents(address user, bytes32[] eventHashes) constant public returns (uint[] allTokens) { // Calculate array size uint arrPos = 0; for (uint i=0; i<eventHashes.length; i++) { if (tokensInEvents[user][eventHashes[i]] > 0) { arrPos += 2; } } // Fill array allTokens = new uint[](arrPos); arrPos = 0; for (i=0; i<eventHashes.length; i++) { if (tokensInEvents[user][eventHashes[i]] > 0) { allTokens[arrPos] = uint(eventHashes[i]); allTokens[arrPos + 1] = uint(tokensInEvents[user][eventHashes[i]]); arrPos += 2; } } } /// @dev Returns all high scores for all given user addresses. /// @param userAddresses Users' addresses. /// @return allHighScores Returns high-scores of all users. function getHighScores(address[] userAddresses) constant public returns (uint[] allHighScores) { // Calculate array site uint arrPos = 0; for (uint i=0; i<userAddresses.length; i++) { if (highScores[userAddresses[i]] > 0) { arrPos += 2; } } // Fill array allHighScores = new uint[](arrPos); arrPos = 0; for (i=0; i<userAddresses.length; i++) { if (highScores[userAddresses[i]] > 0) { allHighScores[arrPos] = uint(userAddresses[i]); allHighScores[arrPos + 1] = highScores[userAddresses[i]]; arrPos += 2; } } } /// @dev Returns timestamp of last credits. /// @param _owner Address of token owner. /// @return lastCredit Returns timestamp of last credit. function getLastCredit(address _owner) constant public returns (uint lastCredit) { return lastCredits[_owner]; } /// @dev Returns minimum funding for market creation. /// @return success Returns success of function call. function getMinFunding() constant public returns (uint minFunding) { return MIN_FUNDING; } } |
Market Makers
AbstractMarketMaker.sol
1 2 3 4 5 6 7 8 9 |
/// @title Abstract market maker contract - Functions to be implemented by market maker contracts. contract MarketMaker { function calcCostsBuying(bytes32 marketHash, uint initialFunding, uint[] shareDistribution, uint8 outcomeIndex, uint shareCount) constant returns (uint costs); function calcEarningsSelling(bytes32 marketHash, uint initialFunding, uint[] shareDistribution, uint8 outcomeIndex, uint shareCount) constant returns (uint earnings); // Market maker meta data // This is not an abstract functions, because solc won't recognize generated getter functions for public variables as functions. function name() constant returns (string) {} } |
LMSRMarketMaker.sol
Calculates prices for event outcome tokens based on demand using a logarithmic market scoring rule. Implementing Robin Hanson’s designed LMSR market maker.
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 |
pragma solidity ^0.4.0; import "Utils/MathLibrary.sol"; import "MarketMakers/AbstractMarketMaker.sol"; /// @title LMSR market maker contract - Calculates share prices based on share distribution and initial funding. /// @author Stefan George - <stefan.george@consensys.net> /// @author Martin Koeppelmann - <martin.koeppelmann@consensys.net> /// @author Michael Lu - <michael.lu@consensys.net> contract LMSRMarketMaker is MarketMaker { /* * Constants */ uint constant ONE = 0x10000000000000000; // Market maker meta data string constant public name = "LMSR Market Maker"; /* * Read functions */ /// @dev Returns costs to buy given number of shares. /// @param marketHash Market hash identifying market. /// @param initialFunding Initial funding for market maker. /// @param shareDistribution Array of shares of all outcomes. /// @param outcomeIndex Outcome selected to buy shares from. /// @param shareCount Number of shares to buy. /// @return costs Returns costs. function calcCostsBuying(bytes32 marketHash, uint initialFunding, uint[] shareDistribution, uint8 outcomeIndex, uint shareCount) constant public returns (uint costs) { // C = b * ln(e^(q1/b) + e^(q2/b) + ...) // We have to invert it so that it is accurate uint invB = MathLibrary.ln(shareDistribution.length * ONE) / 10000; // map initial funding to 10k uint[2] memory shareRange = getShareRange(shareDistribution); uint c1 = calcCosts(invB, shareRange, shareDistribution, initialFunding); shareDistribution[outcomeIndex] -= shareCount; uint c2 = calcCosts(invB, shareRange, shareDistribution, initialFunding); // Calculate costs costs = (c2-c1) * (initialFunding / 10000) * (100000 + 2) / 100000 / ONE; if (costs > shareCount) { // Make sure costs are not bigger than 1 per share costs = shareCount; } } /// @dev Returns earnings for selling given number of shares. /// @param marketHash Market hash identifying market. /// @param initialFunding Initial funding for market maker. /// @param shareDistribution Array of shares of all outcomes. /// @param outcomeIndex Outcome selected to sell shares from. /// @param shareCount Number of shares to sell. /// @return earnings Returns earnings. function calcEarningsSelling(bytes32 marketHash, uint initialFunding, uint[] shareDistribution, uint8 outcomeIndex, uint shareCount) constant public returns (uint earnings) { // We have to invert it so that it is accurate uint invB = MathLibrary.ln(shareDistribution.length * ONE) / 10000; // map initial funding to 10k uint[2] memory shareRange = getShareRange(shareDistribution); shareRange[1] += shareCount; uint c1 = calcCosts(invB, shareRange, shareDistribution, initialFunding); shareDistribution[outcomeIndex] += shareCount; uint c2 = calcCosts(invB, shareRange, shareDistribution, initialFunding); // Calculate earnings earnings = (c1-c2) * (initialFunding / 10000) * (100000 - 2) / 100000 / ONE; } function calcCosts(uint invB, uint[2] shareRange, uint[] shareDistribution, uint initialFunding) private returns(uint costs) { // Inside the ln() uint innerSum = 0; uint initialFundingDivisor = initialFunding / 10000; for (uint8 i=0; i<shareDistribution.length; i++) { innerSum += MathLibrary.eExp((shareRange[1] - shareRange[0] - (shareDistribution[i] - shareRange[0])) / initialFundingDivisor * invB); } costs = MathLibrary.ln(innerSum) * ONE / invB; } function getShareRange(uint[] shareDistribution) private returns (uint[2] shareRange) { // Lowest shares shareRange[0] = shareDistribution[0]; // Highest shares shareRange[1] = shareDistribution[0]; for (uint8 i=0; i<shareDistribution.length; i++) { if (shareDistribution[i] < shareRange[0]) { shareRange[0] = shareDistribution[i]; } if (shareDistribution[i]> shareRange[1]) { shareRange[1] = shareDistribution[i]; } } } } |
Oracles
All oracle contracts implement the oracle interface for time-limited events developed by Gnosis in cooperation with other oracle providers like Reality Keys. Specifications will be released soon.
AbstractFallbackOracle.sol
1 2 3 4 5 6 |
/// @title Abstract oracle contract - Functions to be implemented by oracles. contract FallbackOracle { function isValidSigner(bytes32 descriptionHash, address signer) returns (bool isValid); function isOutcomeSet(bytes32 descriptionHash) returns (bool isSet); function getOutcome(bytes32 descriptionHash) returns (int outcome); } |
AbstractOracle.sol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/// @title Abstract oracle contract - Functions to be implemented by oracles. contract Oracle { function registerEvent(bytes32[] data) returns (bytes32 eventIdentifier); // function setOutcome(bytes32 eventIdentifier, bytes32[] data) returns (bool success); function isOutcomeSet(bytes32 eventIdentifier) constant returns (bool isSet); function getFee(bytes32[] data) constant returns (uint fee, address token); function getOutcome(bytes32 eventIdentifier) constant returns (int outcome); function getEventData(bytes32 eventIdentifier) constant returns (bytes32[] data); // Oracle meta data // This is not an abstract functions, because solc won't recognize generated getter functions for public variables as functions. function name() constant returns (string) {} event EventRegistration(address indexed creator, bytes32 indexed eventIdentifier); } |
Oraclize.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 |
pragma solidity ^0.4.0; import "Oracles/OraclizeFakeAPI.sol"; /// @title Oraclize contract - Allows to resolve Oraclize events with fake API. /// @author Stefan George - <stefan.george@consensys.net> contract Oraclize { /* * Data structures */ // queryHash => sender mapping(bytes32 => address) querySenders; /// @dev Fake sending query to Oraclize. /// @param timestamp Timestamp, when to set the oracle result. /// @param dataSource Data source used to retrieve result. /// @param arg Query arguments. E.g. a URL. /// @return queryHash Returns query hash. function oraclize_query(uint timestamp, string dataSource, string arg) public returns (bytes32 queryHash) { queryHash = sha3(timestamp, dataSource, arg); querySenders[queryHash] = msg.sender; } /// @dev Setting fake result in oracle. /// @param timestamp Timestamp, when to set the oracle result. /// @param dataSource Data source used to retrieve result. /// @param arg Query arguments. E.g. a URL. /// @param result Oraclize result. /// @param proof TLSNotary proof. function oraclize_setResult(uint timestamp, string dataSource, string arg, string result, bytes proof) public { bytes32 queryHash = sha3(timestamp, dataSource, arg); usingOraclize(querySenders[queryHash]).__callback(queryHash, result, proof); } } |
OraclizeFakeApi.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 |
pragma solidity ^0.4.0; import "Oracles/Oraclize.sol"; /// @title Oraclize fake API contract - Fake API to test Oraclize. /// @author Stefan George - <stefan.george@consensys.net> contract usingOraclize { /* * Constants */ byte constant proofType_TLSNotary = 0x10; byte constant proofStorage_IPFS = 0x01; /* * External contracts */ Oraclize constant oraclize = Oraclize({{Oraclize}}); /// @dev Fake setting of TLSNotary proof. /// @param proof TLSNotary proof. function oraclize_setProof(byte proof) {} /// @dev Callback called by Oraclize to set result. /// @param queryID Query id of Oraclize query. /// @param result Result returned as string. /// @param proof TLSNotary proof. function __callback(bytes32 queryID, string result, bytes proof) {} /// @dev Returns address of Oraclize contract. function oraclize_cbAddress() returns (address) { return oraclize; } /// @dev Fake sending query to Oraclize. /// @param timestamp Timestamp, when to set the oracle result. /// @param dataSource Data source used to retrieve result. /// @param arg Query arguments. E.g. a URL. function oraclize_query(uint timestamp, string dataSource, string arg) returns (bytes32) { return oraclize.oraclize_query(timestamp, dataSource, arg); } } |
DefaultFallbackOracle.sol
Implements a simple fallback oracle solution allowing to overwrite oracle results with another account controlled by a separate private key.
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 |
/// @title Abstract oracle contract - Functions to be implemented by oracles. contract DefaultFallbackOracle { /* * Storage */ address owner; // signer address => is valid? mapping (address => bool) invalidSigners; // description hash => signer address => is valid? mapping (bytes32 => mapping (address => bool)) invalidSignersForDescriptions; // description hash => OracleOutcome mapping (bytes32 => OracleOutcome) oracleOutcomes; struct OracleOutcome { int outcome; bool isSet; } /* * Modifiers */ modifier isOwner() { if (msg.sender != owner) { // Only owner is allowed to do this action. throw; } _; } /* * Read and write functions */ /// @dev Sets signer as invalid. /// @param signer Signer's address. function setInvalidSigner(address signer) public isOwner { invalidSigners[signer] = true; } /// @dev Sets signer as invalid. /// @param signer Signer's address. function setInvalidSignerForDescription(bytes32 descriptionHash, address signer) public isOwner { invalidSignersForDescriptions[descriptionHash][signer] = true; } /// @dev Sets the outcome for an event. /// @param descriptionHash Hash identifying off chain event description. /// @param outcome Event's outcome. function setOutcome(bytes32 descriptionHash, int outcome) public isOwner { OracleOutcome oracleOutcome = oracleOutcomes[descriptionHash]; oracleOutcome.outcome = outcome; oracleOutcome.isSet = true; } /// @dev Contract constructor function sets owner. function DefaultFallbackOracle() { owner = msg.sender; } /* * Read functions */ /// @dev Votes for given outcome by adding Ether sent to outcome's stake. Returns success. /// @param signer Signer's address. /// @return isValid Returns if signer is valid. function isValidSigner(bytes32 descriptionHash, address signer) public returns (bool isValid) { return !(invalidSigners[signer] || invalidSignersForDescriptions[descriptionHash][signer]); } /// @dev Returns if winning outcome is set for given event. /// @param descriptionHash Hash identifying an event. /// @return isSet Returns if outcome is set. function isOutcomeSet(bytes32 descriptionHash) public returns (bool isSet) { return oracleOutcomes[descriptionHash].isSet; } /// @dev Returns winning outcome for given event. /// @param descriptionHash Hash identifying an event. /// @return outcome Returns outcome. function getOutcome(bytes32 descriptionHash) public returns (int outcome) { return oracleOutcomes[descriptionHash].outcome; } } |
DifficultyOracle.sol
Allows to resolve markets based on the difficulty at block X.
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 |
pragma solidity ^0.4.0; import "Oracles/AbstractOracle.sol"; /// @title Difficulty oracle contract - On chain oracle to resolve difficulty events at given block. /// @author Stefan George - <stefan.george@consensys.net> /// @author Martin Koeppelmann - <martin.koeppelmann@consensys.net> contract DifficultyOracle is Oracle { /* * Constants */ // Oracle meta data string constant public name = "Difficulty Oracle"; /* * Data structures */ // block number => result mapping (uint => uint) difficultyResults; // event identifier => block number mapping (bytes32 => uint) eventIdentifiers; /* * Read and write functions */ /// @dev Sets difficulty as winning outcome for a specific block. Returns success. /// @param eventIdentifier Hash identifying an event. /// @param data Encodes data used to resolve event. In this case block number. function setOutcome(bytes32 eventIdentifier, bytes32[] data) external { uint blockNumber = eventIdentifiers[eventIdentifier]; if (block.number < blockNumber || difficultyResults[blockNumber] != 0) { // Block number was not reached yet or it was set already throw; } difficultyResults[blockNumber] = block.difficulty; } /// @dev Validates and registers event. Returns event identifier. /// @param data Array of oracle addresses used for event resolution. /// @return eventIdentifier Returns event identifier. function registerEvent(bytes32[] data) public returns (bytes32 eventIdentifier) { uint blockNumber = uint(data[0]); if (blockNumber <= block.number) { // Block number was already reached throw; } eventIdentifier = sha3(data); eventIdentifiers[eventIdentifier] = blockNumber; EventRegistration(msg.sender, eventIdentifier); } /* * Read functions */ /// @dev Returns if winning outcome is set for given event. /// @param eventIdentifier Hash identifying an event. /// @return isSet Returns if outcome is set. function isOutcomeSet(bytes32 eventIdentifier) constant public returns (bool isSet) { uint blockNumber = eventIdentifiers[eventIdentifier]; // Difficulty will never be == 0 return difficultyResults[blockNumber] > 0; } /// @dev Returns winning outcome for given event. /// @param eventIdentifier Hash identifying an event. /// @return outcome Returns outcome. function getOutcome(bytes32 eventIdentifier) constant public returns (int outcome) { uint blockNumber = eventIdentifiers[eventIdentifier]; return int(difficultyResults[blockNumber]); } /// @dev Returns data needed to identify an event. /// @param eventIdentifier Hash identifying an event. /// @return data Returns event data. function getEventData(bytes32 eventIdentifier) constant public returns (bytes32[] data) { data = new bytes32[](1); data[0] = bytes32(eventIdentifiers[eventIdentifier]); } /// @dev Returns total fees for oracle. /// @param data Event data used for event resolution. /// @return fee Returns fee. /// @return token Returns token. function getFee(bytes32[] data) constant public returns (uint fee, address token) { fee = 0; token = 0; } } |
FutarchyOracle.sol
Allows to resolve a market based on the outcome of a dependent market to use markets for decision making. An introduction to the futarchy concept can be found here: https://blog.ethereum.org/2014/08/21/introduction-futarchy/
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 |
pragma solidity ^0.4.0; import "Oracles/AbstractOracle.sol"; import "EventFactory/AbstractEventFactory.sol"; import "MarketFactories/AbstractMarketFactory.sol"; import "Tokens/AbstractToken.sol"; /// @title Futarchy oracle contract - Allows resolving an event based on other events. /// @author Stefan George - <stefan.george@consensys.net> /// @author Martin Koeppelmann - <martin.koeppelmann@consensys.net> contract FutarchyOracle is Oracle { /* * External contracts */ EventFactory constant eventFactory = EventFactory({{EventFactory}}); MarketFactory constant marketFactory = MarketFactory({{DefaultMarketFactory}}); Token constant etherToken = Token({{EtherToken}}); address constant marketMaker = {{LMSRMarketMaker}}; /* * Constants */ // Oracle meta data string constant public name = "Futarchy Oracle"; /* * Data structures */ // proposalHash => FutarchyDecision mapping (bytes32 => FutarchyDecision) public futarchyDecisions; struct FutarchyDecision { bytes32 marketHash1; bytes32 marketHash2; uint decisionTime; bool isWinningOutcomeSet; int winningOutcome; } /* * Read and write functions */ // @dev Contract constructor allows market contract to permanently trade event shares. function FutarchyOracle() { // Give permanent approval to market contract to trade event shares eventFactory.permitPermanentApproval(marketFactory); } /// @dev Creates parent and depended eventFactory and marketFactory for futarchy decision. Returns success. /// @param proposalHash Hash identifying proposal description. /// @param decisionTime Time when parent event can be resolved. /// @param lowerBound Lower bound for valid outcomes for depended eventFactory. /// @param upperBound Upper bound for valid outcomes for depended eventFactory.. /// @param resolverAddress Resolver for depended eventFactory. /// @param data Encoded data used to resolve event. /// @param initialFunding Initial funding for marketFactory. function createFutarchyDecision(bytes32 proposalHash, uint decisionTime, int lowerBound, int upperBound, address resolverAddress, bytes32[] data, uint initialFunding ) public { macro: $futarchyDecision = futarchyDecisions[proposalHash]; if ($futarchyDecision.decisionTime > 0 || now >= decisionTime) { // Futarchy decision exists already or decision time is in the past throw; } $futarchyDecision.decisionTime = decisionTime; // Create parent event traded in Ether bytes32[] memory parentData = new bytes32[](1); parentData[0] = proposalHash; bytes32 parentEventHash = eventFactory.createEvent(proposalHash, false, 0, 0, 2, etherToken, this, parentData); if (parentEventHash == 0) { // Creation of parent event failed throw; } // Buy all outcomes of parent event to create depended eventFactory uint buyAllOutcomesCosts = initialFunding + eventFactory.calcBaseFeeForShares(initialFunding); buyAllOutcomesCosts += eventFactory.calcBaseFeeForShares(buyAllOutcomesCosts); if ( !etherToken.transferFrom(msg.sender, this, buyAllOutcomesCosts) || !etherToken.approve(eventFactory, buyAllOutcomesCosts)) { // Buy all outcomes failed throw; } eventFactory.buyAllOutcomes(parentEventHash, buyAllOutcomesCosts); // Create depended eventFactory traded in parent event shares bytes32[2] memory dependedEventFactoryHashes = createEventFactory(proposalHash, lowerBound, upperBound, resolverAddress, data, parentEventHash); if (dependedEventFactoryHashes[0] == 0 || dependedEventFactoryHashes[1] == 0) { // Creation of eventFactory failed throw; } // Create marketFactory based on depended eventFactory bytes32[2] memory dependedMarketFactoryHashes = createMarketFactory(dependedEventFactoryHashes, initialFunding); if (dependedMarketFactoryHashes[0] == 0 || dependedMarketFactoryHashes[1] == 0) { // Creation of marketFactory failed throw; } // Add futarchy decision $futarchyDecision.marketHash1 = dependedMarketFactoryHashes[0]; $futarchyDecision.marketHash2 = dependedMarketFactoryHashes[1]; $futarchyDecision.decisionTime = decisionTime; } function createEventFactory(bytes32 proposalHash, int lowerBound, int upperBound, address resolverAddress, bytes32[] data, bytes32 parentEventHash ) private returns (bytes32[2] dependedEventFactoryHashes) { dependedEventFactoryHashes[0] = eventFactory.createEvent(proposalHash, true, lowerBound, upperBound, 2, eventFactory.getOutcomeToken(parentEventHash, 0), resolverAddress, data); dependedEventFactoryHashes[1] = eventFactory.createEvent(proposalHash, true, lowerBound, upperBound, 2, eventFactory.getOutcomeToken(parentEventHash, 1), resolverAddress, data); } function createMarketFactory(bytes32[2] dependedEventFactoryHashes, uint shareCount) private returns (bytes32[2] dependedMarketFactoryHashes) { // MarketFactory have no fee dependedMarketFactoryHashes[0] = marketFactory.createMarket(dependedEventFactoryHashes[0], 0, shareCount, marketMaker); dependedMarketFactoryHashes[1] = marketFactory.createMarket(dependedEventFactoryHashes[1], 0, shareCount, marketMaker); } /// @dev Sets difficulty as winning outcome for a specific block. Returns success. /// @param proposalHash Hash identifying proposal description. /// @param data Not used. function setOutcome(bytes32 proposalHash, bytes32[] data) external { macro: $futarchyDecision = futarchyDecisions[proposalHash]; if (now < $futarchyDecision.decisionTime || $futarchyDecision.isWinningOutcomeSet) { // Decision time is not reached yet or outcome was set already throw; } uint[256] memory shareDistributionMarket1 = marketFactory.getShareDistribution($futarchyDecision.marketHash1); uint[256] memory shareDistributionMarket2 = marketFactory.getShareDistribution($futarchyDecision.marketHash2); if (int(shareDistributionMarket1[0] - shareDistributionMarket1[1]) > int(shareDistributionMarket2[0] - shareDistributionMarket2[1])) { $futarchyDecision.winningOutcome = 0; } else { $futarchyDecision.winningOutcome = 1; } $futarchyDecision.isWinningOutcomeSet = true; } /* * Read functions */ /// @dev Validates and registers event. Returns event identifier. /// @param data Array of oracle addresses used for event resolution. /// @return proposalHash Returns proposal hash. function registerEvent(bytes32[] data) public returns (bytes32 proposalHash) { proposalHash = data[0]; if (futarchyDecisions[proposalHash].decisionTime == 0) { // There is no futarchy event throw; } EventRegistration(msg.sender, proposalHash); } /// @dev Returns if winning outcome is set. /// @param proposalHash Hash identifying proposal description. /// @return isSet Returns if outcome is set. function isOutcomeSet(bytes32 proposalHash) constant public returns (bool isSet) { return futarchyDecisions[proposalHash].isWinningOutcomeSet; } /// @dev Returns winning outcome/difficulty for a specific block number. /// @param proposalHash Hash identifying proposal description. /// @return outcome Returns outcome. function getOutcome(bytes32 proposalHash) constant public returns (int outcome) { return futarchyDecisions[proposalHash].winningOutcome; } /// @dev Returns encoded futarchy decisions associated to proposal hashes. /// @param proposalHashes Array of hashes identifying proposals. /// @return allFutarchyDecisions Returns encoded futarchy decisions. function getFutarchyDecisions(bytes32[] proposalHashes) constant public returns (uint[] allFutarchyDecisions) { // Calculate array size uint arrPos = 0; for (uint i=0; i<proposalHashes.length; i++) { macro: $futarchyDecision = futarchyDecisions[proposalHashes[i]]; if ($futarchyDecision.decisionTime > 0) { arrPos += 6; } } // Fill array allFutarchyDecisions = new uint[](arrPos); arrPos = 0; for (i=0; i<proposalHashes.length; i++) { macro: $futarchyDecision = futarchyDecisions[proposalHashes[i]]; allFutarchyDecisions[arrPos] = uint(proposalHashes[i]); allFutarchyDecisions[arrPos + 1] = uint($futarchyDecision.marketHash1); allFutarchyDecisions[arrPos + 2] = uint($futarchyDecision.marketHash2); allFutarchyDecisions[arrPos + 3] = $futarchyDecision.decisionTime; if ($futarchyDecision.isWinningOutcomeSet) { allFutarchyDecisions[arrPos + 4] = 1; } else { allFutarchyDecisions[arrPos + 4] = 0; } allFutarchyDecisions[arrPos + 5] = uint($futarchyDecision.winningOutcome); } } /// @dev Returns data needed to identify an event. /// @param proposalHash Hash identifying an event. /// @return data Returns event data. function getEventData(bytes32 proposalHash) constant public returns (bytes32[] data) { data = new bytes32[](3); data[0] = futarchyDecisions[proposalHash].marketHash1; data[1] = futarchyDecisions[proposalHash].marketHash2; data[2] = bytes32(futarchyDecisions[proposalHash].decisionTime); } /// @dev Returns total fees for oracle. /// @param data Event data used for event resolution. /// @return fee Returns fee. /// @return token Returns token. function getFee(bytes32[] data) constant public returns (uint fee, address token) { fee = 0; token = 0; } } |
OraclizeOracle.sol
An oracle contract wrapping around the oracle service provided by Oraclize. Allows to resolve any ranged event supported by Oraclize.
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 |
pragma solidity ^0.4.0; import "Oracles/OraclizeFakeAPI.sol"; import "Oracles/AbstractOracle.sol"; /// @title Oraclize proxy resolver contract - On chain oracle to resolve events using Oraclize oracle. /// @author Stefan George - <stefan.george@consensys.net> contract OraclizeOracle is Oracle, usingOraclize { /* * Data structures */ // queryID => Result mapping (bytes32 => Result) results; // queryID => is winning outcome set? mapping (bytes32 => bool) winningOutcomeSet; // queryID => mapping (bytes32 => bytes32[]) eventData; struct Result { int result; uint precision; bytes proof; } /* * Constants */ // Oracle meta data string constant public name = "Oraclize wrapper Oracle"; /* * Read and write functions */ /// @dev Contract constructor tells Oraclize to store TLSNotary proof with result. function OraclizeOracle() { oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS); } /// @dev Callback called by Oraclize to set result. /// @param queryID Query id of Oraclize query. /// @param result Result returned as string. /// @param proof TLSNotary proof. function __callback(bytes32 queryID, string result, bytes proof) { if (msg.sender != oraclize_cbAddress()) { // Only Oraclize is allowed to call callback throw; } results[queryID].result = parseToInt(result, results[queryID].precision); results[queryID].proof = proof; winningOutcomeSet[queryID] = true; } /// @dev Validates and registers event. Returns event identifier. /// @param data Array of oracle addresses used for event resolution. /// @return eventIdentifier Returns event identifier. function registerEvent(bytes32[] data) public returns (bytes32 queryID) { uint dataSourceIndex = uint(data[0]); string memory dataSource; if (dataSourceIndex == 0) { dataSource = "URL"; } else if (dataSourceIndex == 1) { dataSource = "WolframAlpha"; } else if (dataSourceIndex == 2) { dataSource = "Blockchain"; } else { // Wrong data source type throw; } uint timestamp = uint(data[1]); uint precision = uint(data[2]); bytes32[] memory urlData = new bytes32[](data.length - 3); for (uint i=3; i<data.length; i++) { urlData[i-3] = data[i]; } string memory url = bytes32ArrayToString(urlData); // Send query to Oraclize queryID = oraclize_query(timestamp, dataSource, url); // Save query information results[queryID].precision = precision; eventData[queryID] = urlData; EventRegistration(msg.sender, queryID); } /* * Read functions */ /// @dev Returns if winning outcome is set for given event. /// @param eventIdentifier Hash identifying an event. /// @return isSet Returns if outcome is set. function isOutcomeSet(bytes32 eventIdentifier) constant public returns (bool isSet) { return winningOutcomeSet[eventIdentifier]; } /// @dev Returns winning outcome for given event. /// @param eventIdentifier Hash identifying an event. /// @return outcome Returns outcome. function getOutcome(bytes32 eventIdentifier) constant public returns (int outcome) { return results[eventIdentifier].result; } /// @dev Returns string concatenating bytes32. /// @param data Array of bytes32 strings. /// @return concatenation Returns concatenated string. function bytes32ArrayToString (bytes32[] data) constant public returns (string concatenation) { bytes memory bytesString = new bytes(data.length * 32); uint urlLength; for (uint i=0; i<data.length; i++) { for (uint j=0; j<32; j++) { byte char = data[i][j]; if (char != 0) { bytesString[urlLength] = char; urlLength += 1; } } } bytes memory bytesStringTrimmed = new bytes(urlLength); for (i=0; i<urlLength; i++) { bytesStringTrimmed[i] = bytesString[i]; } return string(bytesStringTrimmed); } /// @dev Parses a string to an integer. /// @param _a String with number. /// @param _b Number of decimal places. /// @return mint Returns parsed integer. function parseToInt(string _a, uint _b) constant public returns (int mint) { bytes memory bresult = bytes(_a); mint = 0; bool decimals = false; for (uint i=0; i<bresult.length; i++) { if ((bresult[i] >= 48) && (bresult[i] <= 57)) { if (decimals) { if (_b == 0) { break; } else { _b--; } } mint *= 10; mint += int(bresult[i]) - 48; } else if (bresult[i] == 46) { decimals = true; } } mint *= int(10**_b); if (bresult[0] == 45) { mint *= -1; } } /// @dev Returns data needed to identify an event. /// @param eventIdentifier Hash identifying an event. /// @return data Returns event data. function getEventData(bytes32 eventIdentifier) constant public returns (bytes32[] data) { return eventData[eventIdentifier]; } /// @dev Returns total fees for oracle. /// @param data Event data used for event resolution. /// @return fee Returns fee. /// @return token Returns token. function getFee(bytes32[] data) constant public returns (uint fee, address token) { fee = 0; token = 0; } } |
UltimateOracle.sol
Allows to use signed results by off-chain oracle providers like Reality Keys. Multiple oracles can be combined to resolve an event. A majority vote will define the final outcome. An oracle result can be challenged by anyone within a challenge period. If an oracle was challenged its result will be overwritten by the Ultimate Oracle. When an oracle result was challenged, a second market is opened to define the right outcome. Everyone can put Ether on the outcome they believe is the right outcome. If the frontrunner is not changed within a decision period, the frontrunner will be set as the final ultimate outcome. Everyone participating on the winning side will get its fraction of the losing side. To make sure that this fraction is not diluted by a last minute bet on the clear winner, the amount allowed to bid on the frontrunner is in relation to all Ether bet on all other outcomes. The Ultimate Oracle contracts supports the use of fallback oracles.
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 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 |
pragma solidity ^0.4.0; import "Oracles/AbstractOracle.sol"; import "Oracles/AbstractFallbackOracle.sol"; import "Tokens/AbstractToken.sol"; /// @title Ultimate oracle contract - Manages oracle outcomes and allows usage of ultimate oracle /// @author Stefan George - <stefan.george@consensys.net> /// @author Martin Koeppelmann - <martin.koeppelmann@consensys.net> contract UltimateOracle is Oracle { /* * Constants */ uint constant CHALLENGE_PERIOD = 12 hours; uint constant CHALLENGE_FEE = 100 ether; uint constant TWENTY_FOUR_HOURS = 1 days; uint constant SPREAD_MULTIPLIER = 3; // Max. 300% spread between amount on front-runner and all others // Oracle meta data string constant public name = "Ultimate Oracle"; // Token used to pay fees Token constant token = Token({{EtherToken}}); /* * Data structures */ // description hash => ultimate outcome mapping (bytes32 => UltimateOutcome) public ultimateOutcomes; // user address => event description hash => outcome => amount mapping (address => mapping (bytes32 => mapping(int => uint))) public shares; // user address => event description hash => outcome => amount mapping (address => mapping (bytes32 => mapping(int => uint))) public deposits; // description hash => oracle address => oracle outcome mapping (bytes32 => mapping (address => OracleOutcome)) public oracleOutcomes; // event identifier => oracle addresses mapping (bytes32 => address[]) public eventOracles; // event identifier => description hash mapping (bytes32 => bytes32) public descriptionHashes; // oracle address => fallback oracle address mapping (address => FallbackOracle) public fallbackOracles; struct OracleOutcome { uint submissionAtTimestamp; int outcome; bool challenged; } struct UltimateOutcome { bool isFinal; uint closingAtTimestamp; int frontRunner; uint totalShares; uint totalDeposits; /* outcome => amount */ mapping(int => uint) totalSharesForOutcome; /* outcome => amount */ mapping(int => uint) totalDepositsForOutcome; } /* * Read and write functions */ /// @dev Challenges outcome set by oracles and starts ultimate oracle. Returns success. /// @param descriptionHash Hash identifying off chain event description. /// @param oracle Challenged oracle. /// @param outcome Voted outcome. function challengeOracle(bytes32 descriptionHash, address oracle, int outcome) public { OracleOutcome oracleOutcome = oracleOutcomes[descriptionHash][oracle]; if ( oracleOutcome.challenged || oracleOutcome.submissionAtTimestamp == 0 || oracleOutcome.submissionAtTimestamp + CHALLENGE_PERIOD < now || !token.transferFrom(msg.sender, this, CHALLENGE_FEE)) { // The oracle has already been challenged or an oracle hasn't submitted a result yet or challenge period is over or fee cannot be paid. throw; } oracleOutcome.challenged = true; // Add deposit deposits[msg.sender][descriptionHash][outcome] += CHALLENGE_FEE; UltimateOutcome ultimateOutcome = ultimateOutcomes[descriptionHash]; ultimateOutcome.totalDepositsForOutcome[outcome] += CHALLENGE_FEE; ultimateOutcome.totalDeposits += CHALLENGE_FEE; // Vote for outcome voteForUltimateOutcome(descriptionHash, outcome, CHALLENGE_FEE); } /// @dev Votes for given outcome by adding Ether sent to outcome's stake. Returns success. /// @param descriptionHash Hash identifying off chain event description. /// @param outcome Voted outcome. /// @param amount Voted amount. function voteForUltimateOutcome(bytes32 descriptionHash, int outcome, uint amount) public { UltimateOutcome ultimateOutcome = ultimateOutcomes[descriptionHash]; // First vote required challenge fee if (ultimateOutcome.totalShares == 0) { amount = CHALLENGE_FEE; } // Amount has to be in max. spread else { uint maxAmount = (ultimateOutcome.totalShares - ultimateOutcome.totalSharesForOutcome[outcome]) * SPREAD_MULTIPLIER - ultimateOutcome.totalSharesForOutcome[outcome]; if (amount > maxAmount) { amount = maxAmount; } } uint transferableAmount; uint deposit = deposits[msg.sender][descriptionHash][outcome]; if (deposit > amount) { deposits[msg.sender][descriptionHash][outcome] -= amount; ultimateOutcome.totalDepositsForOutcome[outcome] -= amount; ultimateOutcome.totalDeposits -= amount; transferableAmount = 0; } else { deposits[msg.sender][descriptionHash][outcome] = 0; ultimateOutcome.totalDepositsForOutcome[outcome] -= deposit; ultimateOutcome.totalDeposits -= deposit; transferableAmount = amount - deposit; } if ( ultimateOutcome.isFinal || ultimateOutcome.closingAtTimestamp <= now && ultimateOutcome.closingAtTimestamp > 0 || transferableAmount > 0 && !token.transferFrom(msg.sender, this, transferableAmount)) { // Result is already final or voting period passed or tokens could not be transferred throw; } // Execute vote shares[msg.sender][descriptionHash][outcome] += amount; ultimateOutcome.totalSharesForOutcome[outcome] += amount; ultimateOutcome.totalShares += amount; if (ultimateOutcome.totalSharesForOutcome[ultimateOutcome.frontRunner] < ultimateOutcome.totalSharesForOutcome[outcome]) { // Front runner changed ultimateOutcome.closingAtTimestamp = now + TWENTY_FOUR_HOURS; ultimateOutcome.frontRunner = outcome; } } /// @dev Sets ultimate outcome result if closing block number is passed. Returns success. /// @param descriptionHash Hash identifying off chain event description. function setUltimateOutcome(bytes32 descriptionHash) public { UltimateOutcome ultimateOutcome = ultimateOutcomes[descriptionHash]; if (ultimateOutcome.closingAtTimestamp > now) { // Voting period is not over yet throw; } ultimateOutcome.isFinal = true; } /// @dev Withdraws user's winnings from total winnings. Returns success. /// @param descriptionHash Hash identifying off chain event description. function redeemWinnings(bytes32 descriptionHash) public { UltimateOutcome ultimateOutcome = ultimateOutcomes[descriptionHash]; if (!ultimateOutcome.isFinal) { // Final outcome not set throw; } // Calculate winnings uint totalFrontRunner = ultimateOutcome.totalSharesForOutcome[ultimateOutcome.frontRunner] + ultimateOutcome.totalDepositsForOutcome[ultimateOutcome.frontRunner]; uint totalShares = ultimateOutcome.totalSharesForOutcome[ultimateOutcome.frontRunner]; uint totalWinnings = ultimateOutcome.totalShares + ultimateOutcome.totalDeposits - totalFrontRunner; // Shareholder shares uint shareholderShares = shares[msg.sender][descriptionHash][ultimateOutcome.frontRunner]; shares[msg.sender][descriptionHash][ultimateOutcome.frontRunner] = 0; // Shareholder deposit uint shareholderDeposit = deposits[msg.sender][descriptionHash][ultimateOutcome.frontRunner]; deposits[msg.sender][descriptionHash][ultimateOutcome.frontRunner] = 0; // Add winnings uint shareholderWinnings = totalWinnings * shareholderShares / totalShares + shareholderShares + shareholderDeposit; // Send winnings if (shareholderWinnings > 0 && !token.transfer(msg.sender, shareholderWinnings)) { // Transfer failed throw; } } /// @dev Pays fees defined by oracles. Returns success. /// @param data Array of oracle addresses used for event resolution. /// @return success Returns success of function call. /// @return eventIdentifier Returns event identifier. function registerEvent(bytes32[] data) public returns (bytes32 eventIdentifier) { eventIdentifier = sha3(data); bytes32 descriptionHash = data[0]; descriptionHashes[eventIdentifier] = descriptionHash; // Validating signatures uint fee; address feeToken; uint8 v; bytes32 r; bytes32 s; address oracle; for (uint i=1; i<data.length; i+=5) { fee = uint(data[i]); feeToken = address(data[i + 1]); v = uint8(data[i + 2]); r = data[i + 3]; s = data[i + 4]; oracle = ecrecover(sha3(descriptionHash, fee, feeToken), v, r, s); FallbackOracle fallbackOracle = fallbackOracles[oracle]; if (address(fallbackOracle) > 0 && !fallbackOracle.isValidSigner(descriptionHash, oracle)) { // Oracle cannot be registered because it is marked as invalid throw; } eventOracles[eventIdentifier].push(oracle); if (fee > 0 && !Token(feeToken).transferFrom(msg.sender, oracle, fee)) { // Tokens could not be transferred throw; } } EventRegistration(msg.sender, eventIdentifier); } /// @dev Sets winning outcomes for given oracles. Returns success. /// @param eventIdentifier Hash identifying an event. /// @param data Array of encoded signed oracle results used for event resolution. function setOutcome(bytes32 eventIdentifier, bytes32[] data) public { bytes32 descriptionHash = descriptionHashes[eventIdentifier]; int outcome; uint8 v; bytes32 r; bytes32 s; address oracle; for (uint i=0; i<data.length/4; i++) { outcome = int(data[i * 4]); v = uint8(data[i * 4 + 1]); r = data[i * 4 + 2]; s = data[i * 4 + 3]; oracle = ecrecover(sha3(descriptionHash, outcome), v, r, s); FallbackOracle fallbackOracle = fallbackOracles[oracle]; // Check for invalid oracle if (address(fallbackOracle) > 0 && !fallbackOracle.isValidSigner(descriptionHash, oracle)) { if (!fallbackOracle.isOutcomeSet(descriptionHash)) { // Oracle cannot be set continue; } outcome = fallbackOracle.getOutcome(descriptionHash); } OracleOutcome oracleOutcome = oracleOutcomes[descriptionHash][oracle]; if (oracleOutcome.submissionAtTimestamp == 0) { oracleOutcome.outcome = outcome; oracleOutcome.submissionAtTimestamp = now; } } } /// @dev Sets fallback oracle for off-chain-oracle. Returns success. /// @param fallbackOracle Address of fallback oracle. function registerFallbackOracle(address fallbackOracle) public { if (address(fallbackOracles[msg.sender]) > 0) { // It has already been set throw; } fallbackOracles[msg.sender] = FallbackOracle(fallbackOracle); } /* * Read functions */ /// @dev Returns array with encoded results published by oracles and state of ultimate oracle. /// @param descriptionHashes Hash identifying off chain event description. /// @param oracles Array with oracle addresses. /// @return allOracleOutcomes Returns all encoded oracle outcomes. function getOracleOutcomes(bytes32[] descriptionHashes, address[] oracles) constant public returns (uint[] allOracleOutcomes) { // Calculate array size uint arrPos = 0; uint oracleCount; for (uint i=0; i<descriptionHashes.length; i++) { bytes32 descriptionHash = descriptionHashes[i]; oracleCount = 0; for (uint j=0; j<oracles.length; j++) { address oracle = oracles[j]; if (oracleOutcomes[descriptionHash][oracle].submissionAtTimestamp > 0) { arrPos += 4; oracleCount +=1; } } if (oracleCount > 0) { arrPos += 2; } } // Fill array allOracleOutcomes = new uint[](arrPos); arrPos = 0; for (i=0; i<descriptionHashes.length; i++) { descriptionHash = descriptionHashes[i]; oracleCount = 0; for (j=0; j<oracles.length; j++) { OracleOutcome oracleOutcome = oracleOutcomes[descriptionHash][oracles[j]]; if (oracleOutcome.submissionAtTimestamp > 0) { allOracleOutcomes[arrPos + 2 + j*4] = uint(oracles[j]); allOracleOutcomes[arrPos + 3 + j*4] = oracleOutcome.submissionAtTimestamp; allOracleOutcomes[arrPos + 4 + j*4] = uint(oracleOutcome.outcome); if (oracleOutcome.challenged) { allOracleOutcomes[arrPos + 5 + j*4] = 1; } else { allOracleOutcomes[arrPos + 5 + j*4] = 0; } oracleCount += 1; } } if (oracleCount > 0) { allOracleOutcomes[arrPos] = uint(descriptionHash); allOracleOutcomes[arrPos + 1] = oracleCount; arrPos += 2 + oracleCount * 4; } } } /// @dev Returns array of ultimate oracles' results for given description hashes. /// @param descriptionHashes Array of hashes identifying off chain event description. /// @param outcomes Array of outcomes with shares. /// @return allUltimateOutcomes Returns all encoded ultimate outcomes. function getUltimateOutcomes(bytes32[] descriptionHashes, int[] outcomes) constant public returns (uint[] allUltimateOutcomes) { allUltimateOutcomes = new uint[](descriptionHashes.length * 10); for (uint i=0; i<descriptionHashes.length; i+= 10) { bytes32 descriptionHash = descriptionHashes[i]; UltimateOutcome ultimateOutcome = ultimateOutcomes[descriptionHash]; allUltimateOutcomes[i] = uint(descriptionHash); if (ultimateOutcome.isFinal) { allUltimateOutcomes[i + 1] = 1; } else { allUltimateOutcomes[i + 1] = 0; } allUltimateOutcomes[i + 2] = ultimateOutcome.closingAtTimestamp; allUltimateOutcomes[i + 3] = uint(ultimateOutcome.frontRunner); allUltimateOutcomes[i + 4] = ultimateOutcome.totalShares; allUltimateOutcomes[i + 5] = uint(ultimateOutcome.totalSharesForOutcome[ultimateOutcome.frontRunner]); allUltimateOutcomes[i + 6] = uint(ultimateOutcome.totalSharesForOutcome[outcomes[i]]); allUltimateOutcomes[i + 7] = ultimateOutcome.totalDeposits; allUltimateOutcomes[i + 8] = uint(ultimateOutcome.totalDepositsForOutcome[ultimateOutcome.frontRunner]); allUltimateOutcomes[i + 9] = uint(ultimateOutcome.totalDepositsForOutcome[outcomes[i]]); } } /// @dev Returns sender's investments in ultimate oracles for given description hashes. /// @param user User's address. /// @param descriptionHashes Array of hashes identifying off chain event description. /// @param outcomes Array of outcomes with shares. /// @return allShares Returns all user's shares. function getShares(address user, bytes32[] descriptionHashes, int[] outcomes) constant public returns (uint[] allShares) { allShares = new uint[](descriptionHashes.length); for (uint i=0; i<descriptionHashes.length; i++) { bytes32 descriptionHash = descriptionHashes[i]; allShares[i] = shares[user][descriptionHash][outcomes[i]]; } } /// @dev Returns all fallback oracles associated to given oracles. /// @param oracles List of off-chain-oracles. /// @return allFallbackOracles Returns all associated fallback oracles. function getFallbackOracles(address[] oracles) constant public returns (address[] allFallbackOracles) { allFallbackOracles = new address[](oracles.length); for (uint i=0; i<oracles.length; i++) { FallbackOracle fallbackOracle = fallbackOracles[oracles[i]]; allFallbackOracles[i] = address(fallbackOracle); } } function getStatusAndWinningOutcome(bytes32 eventIdentifier) private returns (bool winningOutcomeIsSet, int winningOutcome) { bytes32 descriptionHash = descriptionHashes[eventIdentifier]; address[] oracles = eventOracles[eventIdentifier]; int[] memory outcomes = new int[](oracles.length); uint8[] memory validations = new uint8[](oracles.length); uint finalizedOracles = 0; // Count the validations for each outcome for (uint8 i=0; i<oracles.length; i++) { OracleOutcome oracleOutcome = oracleOutcomes[descriptionHash][oracles[i]]; if ( oracleOutcome.submissionAtTimestamp == 0 || oracleOutcome.submissionAtTimestamp + CHALLENGE_PERIOD > now || oracleOutcome.challenged && !ultimateOutcomes[descriptionHash].isFinal) { // Outcome was not submitted or challenge period is not over yet or it was challenged and ultimate outcome is not set yet continue; } finalizedOracles += 1; int outcome; if (oracleOutcome.challenged) { outcome = ultimateOutcomes[descriptionHash].frontRunner; } else { outcome = oracleOutcomes[descriptionHash][oracles[i]].outcome; } for (uint8 j=0; j<=i; j++) { if (outcome == outcomes[j]) { validations[j] += 1; break; } else if (outcomes[j] == 0) { outcomes[j] = outcome; validations[j] += 1; } } } // Get outcome with most validations and check majority uint8 favoriteOutcomeValidations = 0; uint8 favoriteOutcomeIndex = 0; for (i=0; i<oracles.length; i++) { if (validations[i] > favoriteOutcomeValidations) { favoriteOutcomeValidations = validations[i]; favoriteOutcomeIndex = i; } } winningOutcomeIsSet = false; // There is a majority vote if (favoriteOutcomeValidations * 2 > oracles.length) { winningOutcome = outcomes[favoriteOutcomeIndex]; winningOutcomeIsSet = true; } // Check if there is a deadlock and use ultimate oracle in this case else if ( (favoriteOutcomeValidations + (oracles.length - finalizedOracles)) * 2 <= oracles.length && ultimateOutcomes[descriptionHash].isFinal) { winningOutcome = ultimateOutcomes[descriptionHash].frontRunner; winningOutcomeIsSet = true; } } /// @dev Returns if winning outcome is set for given event. /// @param eventIdentifier Hash identifying an event. /// @return isSet Returns if outcome is set. function isOutcomeSet(bytes32 eventIdentifier) constant public returns (bool isSet) { var (winningOutcomeIsSet, ) = getStatusAndWinningOutcome(eventIdentifier); isSet = winningOutcomeIsSet; } /// @dev Returns winning outcome for given event. /// @param eventIdentifier Hash identifying an event. /// @return outcome Returns outcome. function getOutcome(bytes32 eventIdentifier) constant public returns (int outcome) { var (winningOutcomeIsSet, winningOutcome) = getStatusAndWinningOutcome(eventIdentifier); if (winningOutcomeIsSet) { outcome = winningOutcome; } else { // Outcome is not set throw; } } /// @dev Returns data needed to identify an event. /// @param eventIdentifier Hash identifying an event. /// @return data Returns event data. function getEventData(bytes32 eventIdentifier) constant public returns (bytes32[] data) { data = new bytes32[](1 + eventOracles[eventIdentifier].length); data[0] = descriptionHashes[eventIdentifier]; for (uint i=0; i<eventOracles[eventIdentifier].length; i++) { data[1 + i] = bytes32(eventOracles[eventIdentifier][i]); } } /// @dev Returns total fees for oracles. /// @param data Event data used for event resolution. /// @return fee Returns fee. /// @return _token Returns token. function getFee(bytes32[] data) constant public returns (uint fee, address _token) { _token = address(data[2]); for (uint i=1; i<data.length; i+=5) { fee += uint(data[i]); if (_token != address(data[i + 1])) { // All selected oracles have to use the same fee token throw; } } } } |
State Channels
StateChannel.sol
Generic settlement contract for off-chain transactions. Can be used to settle Gnosis trade agreements between multiple parties.
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 |
pragma solidity ^0.4.0; contract Token { function transfer(address to, uint256 value) returns (bool); function transferFrom(address from, address to, uint256 value) returns (bool); } contract StateChannelProxy { address public owner; address public stateChannel; bool public isSettled = false; modifier isOwnerOrStateChannel () { if (!(msg.sender == stateChannel || isSettled && msg.sender == owner)) throw; _; } modifier isStateChannel () { if (msg.sender != stateChannel) throw; _; } function sendTransaction(address destination, uint value, bytes data) isOwnerOrStateChannel { if (!destination.call.value(value)(data)) throw; } function setSettled() isStateChannel { isSettled = true; } function StateChannelProxy(address _owner) { owner = _owner; stateChannel = msg.sender; } } /// @title State channel - Generic settlement contract for state channels. /// @author Stefan George - <stefan.george@consensys.net> contract StateChannel { mapping (address => StateChannelProxy) public proxyContracts; address[] owners; State public state; Token public securityToken; uint public securityValue; uint public challengePeriod; struct State { uint timestamp; uint nonce; bytes32 hash; address requester; bytes32[] txHashes; uint txIndex; } function requestSettleZeroState() { if (state.timestamp > 0 || !securityToken.transferFrom(msg.sender, this, securityValue)) throw; state.timestamp = now; state.requester = msg.sender; } function settleZeroState() { if ( !(state.timestamp > 0 && state.timestamp + challengePeriod <= now && state.hash == 0) || !securityToken.transfer(msg.sender, securityValue)) throw; for (uint i=0; i<owners.length; i++) proxyContracts[owners[i]].setSettled(); } function requestSettlement(bytes32 txsHash, uint nonce, uint timestamp, bytes32 hash, bytes32 secret, uint8[] sigV, bytes32[] sigR, bytes32[] sigS) { bytes32 stateHash = sha3(txsHash, nonce, timestamp, hash); for (uint i=0; i<owners.length; i++) if (owners[i] != ecrecover(stateHash, sigV[i], sigR[i], sigS[i])) throw; if ( state.timestamp > 0 || now > timestamp || hash > 0 && sha3(secret) != hash || !securityToken.transferFrom(msg.sender, this, securityValue)) throw; state.timestamp = now; state.nonce = nonce; state.hash = stateHash; state.requester = msg.sender; } function submitTradeHashes(bytes32[] txHashes, uint nonce, uint timestamp, bytes32 hash) { bytes32 txsHash = sha3(txHashes); bytes32 stateHash = sha3(txsHash, nonce, timestamp, hash); if (state.hash == stateHash && state.timestamp + challengePeriod <= now) state.txHashes = txHashes; } function executeTrade(address sender, address destination, uint value, bytes data) { bytes32 txHash = sha3(sender, destination, value, data); if (state.txHashes[state.txIndex] == txHash) { StateChannelProxy(sender).sendTransaction(destination, value, data); state.txIndex += 1; if (state.txIndex == state.txHashes.length) for (uint i=0; i<owners.length; i++) proxyContracts[owners[i]].setSettled(); } } function punishWrongState(bytes32 txHash, uint nonce, uint timestamp, bytes32 hash, uint8[] sigV, bytes32[] sigR, bytes32[] sigS) { bytes32 stateHash = sha3(txHash, nonce, timestamp, hash); for (uint i=0; i<owners.length; i++) if (owners[i] != ecrecover(stateHash, sigV[i], sigR[i], sigS[i])) throw; if ( state.nonce > nonce || now > timestamp || !securityToken.transfer(msg.sender, securityValue)) throw; delete state; } function StateChannel(address[] _owners, address _securityToken, uint _securityValue, uint _challengePeriod) { for (uint i=0; i<_owners.length; i++) proxyContracts[_owners[i]] = new StateChannelProxy(_owners[i]); owners = _owners; securityToken = Token(_securityToken); securityValue = _securityValue; challengePeriod = _challengePeriod; } } |
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); } |
EtherToken.sol
An ERC20 compliant token exchanging Ether and Ether-tokens 1:1. All Gnosis contracts allow using tokens implementing the ERC20 token interface and don’t allow direct usage of Ether.
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 |
pragma solidity ^0.4.0; import "Tokens/StandardToken.sol"; /// @title Token contract - Token exchanging Ether 1:1. /// @author Stefan George - <stefan.george@consensys.net> contract EtherToken is StandardToken { /* * Constants */ // Token meta data string constant public name = "Ether Token"; string constant public symbol = "ETH"; uint8 constant public decimals = 18; /* * Read and write functions */ /// @dev Buys tokens with Ether, exchanging them 1:1. Returns success. function buyTokens() external payable { balances[msg.sender] += msg.value; totalSupply += msg.value; } /// @dev Sells tokens in exchange for Ether, exchanging them 1:1. Returns success. /// @param count Number of tokens to sell. function sellTokens(uint count) external { if (count > balances[msg.sender]) { // Balance is too low throw; } balances[msg.sender] -= count; totalSupply -= count; if (!msg.sender.send(count)) { // Sending failed throw; } } } |
HunchGameToken.sol
Token used by the HunchGame Gnosis App. Allows HunchGameMarketFactory to issue new HunchGame tokens. The account used to deploy the contract receives almost unlimited amount of tokens for market making.
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 |
pragma solidity ^0.4.0; import "Tokens/StandardToken.sol"; /// @title Hunch Game token contract - Implements token trading and high-score calculation. /// @author Stefan George - <stefan.george@consensys.net> /// @author Martin Koeppelmann - <martin.koeppelmann@consensys.net> contract HunchGameToken is StandardToken { /* * Constants */ // Token meta data string constant public name = "HunchGame Token"; string constant public symbol = "HGT"; uint8 constant public decimals = 18; /* * Data structures */ address owner; address hunchGameMarketFactory; /* * Modifiers */ modifier isOwner() { if (msg.sender != owner) { // Only DAO contract is allowed to proceed throw; } _; } modifier isHunchGameMarketFactory() { if (msg.sender != hunchGameMarketFactory) { // Only HunchGame market factory contract is allowed to proceed throw; } _; } /* * Read and write functions */ /// @dev Contract constructor sets initial tokens to contract owner. function HunchGameToken() { owner = msg.sender; uint initialTokens = 2**256 / 2; balances[msg.sender] += initialTokens; totalSupply += initialTokens; } /// @dev Issues tokens for user. /// @param user User's address. /// @param tokenCount Number of tokens to issue. function issueTokens(address user, uint tokenCount) public isHunchGameMarketFactory { balances[user] += tokenCount; totalSupply += tokenCount; } /// @dev Setup function sets external contracts' addresses. /// @param _hunchGameMarketFactory HunchGame market factory address. function setup(address _hunchGameMarketFactory) external isOwner { if (hunchGameMarketFactory != 0) { // Setup was executed already throw; } hunchGameMarketFactory = _hunchGameMarketFactory; } /// @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 success Returns success of function call. function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { if ( balances[_from] < _value || (allowed[_from][msg.sender] < _value && msg.sender != hunchGameMarketFactory)) { // Balance too low or allowance too low and sender is not HunchGame market factory throw; } balances[_to] += _value; balances[_from] -= _value; if (msg.sender != hunchGameMarketFactory) { allowed[_from][msg.sender] -= _value; } Transfer(_from, _to, _value); return true; } } |
StandardToken.sol
An implementation of the ERC standard token. Discussion: https://github.com/ethereum/EIPs/issues/20
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.0; 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; /* * Read and write 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 success Returns success of function call. function transfer(address _to, uint256 _value) public returns (bool success) { 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 success Returns success of function call. function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { 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 success Returns success of function call. function approve(address _spender, uint256 _value) public returns (bool success) { 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 remaining Returns remaining allowance for spender. function allowance(address _owner, address _spender) constant public returns (uint256 remaining) { return allowed[_owner][_spender]; } /// @dev Returns number of tokens owned by given address. /// @param _owner Address of token owner. /// @return balance Returns balance of owner. function balanceOf(address _owner) constant public returns (uint256 balance) { return balances[_owner]; } } |
Utils
Lockable.sol
Allows an inheriting contract to use global locks for function calls.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
pragma solidity ^0.4.0; /// @title Lockable contract - Allows to limit code execution to one time per transaction. /// @author Stefan George - <stefan.george@consensys.net> contract Lockable { bool internal isLocked; modifier isUnlocked () { if (isLocked) { // There is a global lock active. throw; } isLocked = true; _; isLocked = false; } } |
MathLibrary.sol
Provides basic math functionality for calculating natural exponential function and natural logarithmic function. This is a Solidity version of Chris Calderon’s implementation in Serpent, which is part of the Augur project: https://github.com/AugurProject/augur-core/blob/develop/src/data_api/fxpFunctions.se
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 |
pragma solidity ^0.4.0; // This is a Solidity version of Chris Calderon's implementation in Serpent, which is part of the Augur project. // Original code: https://github.com/AugurProject/augur-core/blob/develop/src/data_api/fxpFunctions.se // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // License details at: <http://www.gnu.org/licenses/>. /// @title Math library - Allows logarithmic and exponential functions. /// @author Michael Lu - <michael.lu@consensys.net> /// @author Stefan George - <stefan.george@consensys.net> library MathLibrary { /* * We set 1 as 2**64. 4 would be represented as 4*2**64. * 0.5 would be represented 2**63. * To save space and allow for more in-depth manipulation, * we have changed 1 :: 2**64 :: 16 ** 16 :: 0x10000000000000000 */ /* * Constants */ // This is equal to 1 in our calculations uint constant ONE = 0x10000000000000000; /* * Read functions */ /// @dev Returns natural exponential function value of given x. /// @param x X. /// @return exp Returns exponential value. function eExp(uint x) constant returns (uint exp) { /* This is equivalent to ln(2) */ uint ln2 = 0xb17217f7d1cf79ac; uint y = x * ONE / ln2; uint shift = 2**(y / ONE); uint z = y % ONE; uint zpow = z; uint result = ONE; result += 0xb172182739bc0e46 * zpow / ONE; zpow = zpow * z / ONE; result += 0x3d7f78a624cfb9b5 * zpow / ONE; zpow = zpow * z / ONE; result += 0xe359bcfeb6e4531 * zpow / ONE; zpow = zpow * z / ONE; result += 0x27601df2fc048dc * zpow / ONE; zpow = zpow * z / ONE; result += 0x5808a728816ee8 * zpow / ONE; zpow = zpow * z / ONE; result += 0x95dedef350bc9 * zpow / ONE; result += 0x16aee6e8ef; exp = shift * result; } /// @dev Returns natural logarithm value of given x. /// @param x X. /// @return log Returns logarithmic value. function ln(uint x) constant returns (uint log) { uint log2e = 0x171547652b82fe177; // binary search for floor(log2(x)) uint ilog2 = floorLog2(x); // lagrange interpolation for log2 uint z = x / (2**ilog2); uint zpow = ONE; uint const = ONE * 10; uint result = const; result -= 0x443b9c5adb08cc45f * zpow / ONE; zpow = zpow * z / ONE; result += 0xf0a52590f17c71a3f * zpow / ONE; zpow = zpow * z / ONE; result -= 0x2478f22e787502b023 * zpow / ONE; zpow = zpow * z / ONE; result += 0x48c6de1480526b8d4c * zpow / ONE; zpow = zpow * z / ONE; result -= 0x70c18cae824656408c * zpow / ONE; zpow = zpow * z / ONE; result += 0x883c81ec0ce7abebb2 * zpow / ONE; zpow = zpow * z / ONE; result -= 0x81814da94fe52ca9f5 * zpow / ONE; zpow = zpow * z / ONE; result += 0x616361924625d1acf5 * zpow / ONE; zpow = zpow * z / ONE; result -= 0x39f9a16fb9292a608d * zpow / ONE; zpow = zpow * z / ONE; result += 0x1b3049a5740b21d65f * zpow / ONE; zpow = zpow * z / ONE; result -= 0x9ee1408bd5ad96f3e * zpow / ONE; zpow = zpow * z / ONE; result += 0x2c465c91703b7a7f4 * zpow / ONE; zpow = zpow * z / ONE; result -= 0x918d2d5f045a4d63 * zpow / ONE; zpow = zpow * z / ONE; result += 0x14ca095145f44f78 * zpow / ONE; zpow = zpow * z / ONE; result -= 0x1d806fc412c1b99 * zpow / ONE; zpow = zpow * z / ONE; result += 0x13950b4e1e89cc * zpow / ONE; log = ((ilog2 * ONE + result - const) * ONE / log2e); } function floorLog2(uint x) constant private returns (uint lo) { lo = 0; uint y = x / ONE; uint hi = 191; uint mid = (hi + lo) / 2; while ((lo + 1) != hi) { if (y < 2**mid){ hi = mid; } else { lo = mid; } mid = (hi + lo) / 2; } } } |
Wallets
MistWallet.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 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 |
pragma solidity ^0.4.0; //sol Wallet // Multi-sig, daily-limited account proxy/wallet. // @authors: // Gav Wood <g@ethdev.com> // inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a // single, or, crucially, each of a number of, designated owners. // usage: // use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by // some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the // interior is executed. contract multiowned { // TYPES // struct for the status of a pending operation. struct PendingState { uint yetNeeded; uint ownersDone; uint index; } // EVENTS // this contract only has six types of events: it can accept a confirmation, in which case // we record owner and operation (hash) alongside it. event Confirmation(address owner, bytes32 operation); event Revoke(address owner, bytes32 operation); // some others are in the case of an owner changing. event OwnerChanged(address oldOwner, address newOwner); event OwnerAdded(address newOwner); event OwnerRemoved(address oldOwner); // the last one is emitted if the required signatures change event RequirementChanged(uint newRequirement); // MODIFIERS // simple single-sig function modifier. modifier onlyowner { if (isOwner(msg.sender)) _; } // multi-sig function modifier: the operation must have an intrinsic hash in order // that later attempts can be realised as the same underlying operation and // thus count as confirmations. modifier onlymanyowners(bytes32 _operation) { if (confirmAndCheck(_operation)) _; } // METHODS // constructor is given number of sigs required to do protected "onlymanyowners" transactions // as well as the selection of addresses capable of confirming them. function multiowned(address[] _owners, uint _required) { m_numOwners = _owners.length + 1; m_owners[1] = uint(msg.sender); m_ownerIndex[uint(msg.sender)] = 1; for (uint i = 0; i < _owners.length; ++i) { m_owners[2 + i] = uint(_owners[i]); m_ownerIndex[uint(_owners[i])] = 2 + i; } m_required = _required; } // Revokes a prior confirmation of the given operation function revoke(bytes32 _operation) external { uint ownerIndex = m_ownerIndex[uint(msg.sender)]; // make sure they're an owner if (ownerIndex == 0) return; uint ownerIndexBit = 2**ownerIndex; var pending = m_pending[_operation]; if (pending.ownersDone & ownerIndexBit > 0) { pending.yetNeeded++; pending.ownersDone -= ownerIndexBit; Revoke(msg.sender, _operation); } } // Replaces an owner `_from` with another `_to`. function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { if (isOwner(_to)) return; uint ownerIndex = m_ownerIndex[uint(_from)]; if (ownerIndex == 0) return; clearPending(); m_owners[ownerIndex] = uint(_to); m_ownerIndex[uint(_from)] = 0; m_ownerIndex[uint(_to)] = ownerIndex; OwnerChanged(_from, _to); } function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { if (isOwner(_owner)) return; clearPending(); if (m_numOwners >= c_maxOwners) reorganizeOwners(); if (m_numOwners >= c_maxOwners) return; m_numOwners++; m_owners[m_numOwners] = uint(_owner); m_ownerIndex[uint(_owner)] = m_numOwners; OwnerAdded(_owner); } function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external { uint ownerIndex = m_ownerIndex[uint(_owner)]; if (ownerIndex == 0) return; if (m_required > m_numOwners - 1) return; m_owners[ownerIndex] = 0; m_ownerIndex[uint(_owner)] = 0; clearPending(); reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot OwnerRemoved(_owner); } function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { if (_newRequired > m_numOwners) return; m_required = _newRequired; clearPending(); RequirementChanged(_newRequired); } // Gets an owner by 0-indexed position (using numOwners as the count) function getOwner(uint ownerIndex) external constant returns (address) { return address(m_owners[ownerIndex + 1]); } function isOwner(address _addr) returns (bool) { return m_ownerIndex[uint(_addr)] > 0; } function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { var pending = m_pending[_operation]; uint ownerIndex = m_ownerIndex[uint(_owner)]; // make sure they're an owner if (ownerIndex == 0) return false; // determine the bit to set for this owner. uint ownerIndexBit = 2**ownerIndex; return !(pending.ownersDone & ownerIndexBit == 0); } // INTERNAL METHODS function confirmAndCheck(bytes32 _operation) internal returns (bool) { // determine what index the present sender is: uint ownerIndex = m_ownerIndex[uint(msg.sender)]; // make sure they're an owner if (ownerIndex == 0) return; var pending = m_pending[_operation]; // if we're not yet working on this operation, switch over and reset the confirmation status. if (pending.yetNeeded == 0) { // reset count of confirmations needed. pending.yetNeeded = m_required; // reset which owners have confirmed (none) - set our bitmap to 0. pending.ownersDone = 0; pending.index = m_pendingIndex.length++; m_pendingIndex[pending.index] = _operation; } // determine the bit to set for this owner. uint ownerIndexBit = 2**ownerIndex; // make sure we (the message sender) haven't confirmed this operation previously. if (pending.ownersDone & ownerIndexBit == 0) { Confirmation(msg.sender, _operation); // ok - check if count is enough to go ahead. if (pending.yetNeeded <= 1) { // enough confirmations: reset and run interior. delete m_pendingIndex[m_pending[_operation].index]; delete m_pending[_operation]; return true; } else { // not enough: record that this owner in particular confirmed. pending.yetNeeded--; pending.ownersDone |= ownerIndexBit; } } } function reorganizeOwners() private { uint free = 1; while (free < m_numOwners) { while (free < m_numOwners && m_owners[free] != 0) free++; while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) { m_owners[free] = m_owners[m_numOwners]; m_ownerIndex[m_owners[free]] = free; m_owners[m_numOwners] = 0; } } } function clearPending() internal { uint length = m_pendingIndex.length; for (uint i = 0; i < length; ++i) if (m_pendingIndex[i] != 0) delete m_pending[m_pendingIndex[i]]; delete m_pendingIndex; } // FIELDS // the number of owners that must confirm the same operation before it is run. uint public m_required; // pointer used to find a free slot in m_owners uint public m_numOwners; // list of owners uint[256] m_owners; uint constant c_maxOwners = 250; // index on the list of owners to allow reverse lookup mapping(uint => uint) m_ownerIndex; // the ongoing operations. mapping(bytes32 => PendingState) m_pending; bytes32[] m_pendingIndex; } // inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable) // on a particular resource per calendar day. is multiowned to allow the limit to be altered. resource that method // uses is specified in the modifier. contract daylimit is multiowned { // MODIFIERS // simple modifier for daily limit. modifier limitedDaily(uint _value) { if (underLimit(_value)) _; } // METHODS // constructor - stores initial daily limit and records the present day's index. function daylimit(uint _limit) { m_dailyLimit = _limit; m_lastDay = today(); } // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external { m_dailyLimit = _newLimit; } // resets the amount already spent today. needs many of the owners to confirm. function resetSpentToday() onlymanyowners(sha3(msg.data)) external { m_spentToday = 0; } // INTERNAL METHODS // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and // returns true. otherwise just returns false. function underLimit(uint _value) internal onlyowner returns (bool) { // reset the spend limit if we're on a different day to last time. if (today() > m_lastDay) { m_spentToday = 0; m_lastDay = today(); } // check to see if there's enough left - if so, subtract and return true. // overflow protection // dailyLimit check if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { m_spentToday += _value; return true; } return false; } // determines today's index. function today() private constant returns (uint) { return now / 1 days; } // FIELDS uint public m_dailyLimit; uint public m_spentToday; uint public m_lastDay; } // interface contract for multisig proxy contracts; see below for docs. contract multisig { // EVENTS // logged events: // Funds has arrived into the wallet (record how much). event Deposit(address _from, uint value); // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). event SingleTransact(address owner, uint value, address to, bytes data); // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); // Confirmation still needed for a transaction. event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); // FUNCTIONS // TODO: document function changeOwner(address _from, address _to) external; function execute(address _to, uint _value, bytes _data) external returns (bytes32); function confirm(bytes32 _h) returns (bool); } // usage: // bytes32 h = Wallet(w).from(oneOwner).transact(to, value, data); // Wallet(w).from(anotherOwner).confirm(h); contract Wallet is multisig, multiowned, daylimit { // TYPES // Transaction structure to remember details of transaction lest it need be saved for a later call. struct Transaction { address to; uint value; bytes data; } // METHODS // constructor - just pass on the owner array to the multiowned and // the limit to daylimit function Wallet(address[] _owners, uint _required, uint _daylimit) multiowned(_owners, _required) daylimit(_daylimit) { } // kills the contract sending everything to `_to`. function kill(address _to) onlymanyowners(sha3(msg.data)) external { suicide(_to); } // gets called when no other function matches function() payable { // just being sent some cash? if (msg.value > 0) Deposit(msg.sender, msg.value); } // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. // If not, goes into multisig process. We provide a hash on return to allow the sender to provide // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value // and _data arguments). They still get the option of using them if they want, anyways. function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) { // first, take the opportunity to check that we're under the daily limit. if (underLimit(_value)) { SingleTransact(msg.sender, _value, _to, _data); // yes - just execute the call. _to.call.value(_value)(_data); return 0; } // determine our operation hash. _r = sha3(msg.data, block.number); if (!confirm(_r) && m_txs[_r].to == 0) { m_txs[_r].to = _to; m_txs[_r].value = _value; m_txs[_r].data = _data; ConfirmationNeeded(_r, msg.sender, _value, _to, _data); } } // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order // to determine the body of the transaction from the hash provided. function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) { if (m_txs[_h].to != 0) { m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data); delete m_txs[_h]; return true; } } // INTERNAL METHODS function clearPending() internal { uint length = m_pendingIndex.length; for (uint i = 0; i < length; ++i) delete m_txs[m_pendingIndex[i]]; super.clearPending(); } // FIELDS // pending transactions we have at present. mapping (bytes32 => Transaction) m_txs; } |
MultiSigWallet.sol
Allows multiple parties to agree on transactions before execution. Allows to add and remove owners and update the number of required confirmations.
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 |
pragma solidity ^0.4.0; /// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution. /// @author Stefan George - <stefan.george@consensys.net> contract MultiSigWallet { event Confirmation(address sender, bytes32 transactionHash); event Revocation(address sender, bytes32 transactionHash); event Submission(address sender, bytes32 transactionHash); event Execution(address sender, bytes32 transactionHash); event Deposit(address sender, uint value); event OwnerAddition(address owner); event OwnerRemoval(address owner); event RequiredUpdate(uint required); mapping (bytes32 => Transaction) public transactions; mapping (bytes32 => mapping (address => bool)) public confirmations; mapping (address => bool) public isOwner; address[] public owners; 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 maxRequired(address[] _owners, uint _required) { if (_required > _owners.length) throw; _; } modifier notExecuted(bytes32 transactionHash) { if (transactions[transactionHash].executed) throw; _; } function addOwner(address owner) external onlyWallet ownerDoesNotExist(owner) { isOwner[owner] = true; owners.push(owner); OwnerAddition(owner); } function removeOwner(address owner) external onlyWallet ownerExists(owner) { for (uint i=0; i<owners.length - 1; i++) { if (owners[i] == owner) isOwner[owner] = false; if (!isOwner[owner]) owners[i] = owners[i+1]; } owners.length -= 1; if (required > owners.length) required = owners.length; OwnerRemoval(owner); } function updateRequired(uint _required) external onlyWallet maxRequired(owners, _required) { required = _required; RequiredUpdate(_required); } function confirmationCount(bytes32 transactionHash) constant public returns (uint count) { for (uint i=0; i<owners.length; i++) if (confirmations[transactionHash][owners[i]]) count += 1; } function submitTransaction(address destination, uint value, bytes data, uint nonce) external ownerExists(msg.sender) notExecuted(sha3(destination, value, data, nonce)) returns (bytes32 transactionHash) { transactionHash = sha3(destination, value, data, nonce); transactions[transactionHash] = Transaction({ destination: destination, value: value, data: data, nonce: nonce, executed: false }); Submission(msg.sender, transactionHash); confirmTransaction(transactionHash); } function confirmTransaction(bytes32 transactionHash) public ownerExists(msg.sender) notExecuted(transactionHash) { confirmations[transactionHash][msg.sender] = true; Confirmation(msg.sender, transactionHash); if (confirmationCount(transactionHash) >= required) { Transaction tx = transactions[transactionHash]; tx.executed = true; if (!tx.destination.call.value(tx.value)(tx.data)) throw; Execution(msg.sender, transactionHash); } } function revokeConfirmation(bytes32 transactionHash) external ownerExists(msg.sender) notExecuted(transactionHash) { confirmations[transactionHash][msg.sender] = false; Revocation(msg.sender, transactionHash); } function MultiSigWallet(address[] _owners, uint _required) maxRequired(_owners, _required) { for (uint i=0; i<_owners.length; i++) isOwner[_owners[i]] = true; required = _required; owners = _owners; } function() payable { if (msg.value > 0) Deposit(msg.sender, msg.value); } } |