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 |