diff --git a/README.md b/README.md new file mode 100644 index 0000000..5caca29 Binary files /dev/null and b/README.md differ diff --git a/contract_10.sol b/contract_10.sol new file mode 100644 index 0000000..dc4da99 --- /dev/null +++ b/contract_10.sol @@ -0,0 +1,801 @@ +// +/* +Copyright (c) 2015-2016 Oraclize srl, Thomas Bertani + + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + + + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + + + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +contract OraclizeI { + address public cbAddress; + function query(uint _timestamp, string _datasource, string _arg) returns (bytes32 _id); + function query_withGasLimit(uint _timestamp, string _datasource, string _arg, uint _gaslimit) returns (bytes32 _id); + function query2(uint _timestamp, string _datasource, string _arg1, string _arg2) returns (bytes32 _id); + function query2_withGasLimit(uint _timestamp, string _datasource, string _arg1, string _arg2, uint _gaslimit) returns (bytes32 _id); + function getPrice(string _datasource) returns (uint _dsprice); + function getPrice(string _datasource, uint gaslimit) returns (uint _dsprice); + function useCoupon(string _coupon); + function setProofType(byte _proofType); + function setCustomGasPrice(uint _gasPrice); +} +contract OraclizeAddrResolverI { + function getAddress() returns (address _addr); +} +contract usingOraclize { + uint constant day = 60*60*24; + uint constant week = 60*60*24*7; + uint constant month = 60*60*24*30; + byte constant proofType_NONE = 0x00; + byte constant proofType_TLSNotary = 0x10; + byte constant proofStorage_IPFS = 0x01; + uint8 constant networkID_auto = 0; + uint8 constant networkID_mainnet = 1; + uint8 constant networkID_testnet = 2; + uint8 constant networkID_morden = 2; + uint8 constant networkID_consensys = 161; + + OraclizeAddrResolverI OAR; + + OraclizeI oraclize; + modifier oraclizeAPI { + address oraclizeAddr = OAR.getAddress(); + if (oraclizeAddr == 0){ + oraclize_setNetwork(networkID_auto); + oraclizeAddr = OAR.getAddress(); + } + oraclize = OraclizeI(oraclizeAddr); + _ + } + modifier coupon(string code){ + oraclize = OraclizeI(OAR.getAddress()); + oraclize.useCoupon(code); + _ + } + + function oraclize_setNetwork(uint8 networkID) internal returns(bool){ + if (getCodeSize(0x1d3b2638a7cc9f2cb3d298a3da7a90b67e5506ed)>0){ + OAR = OraclizeAddrResolverI(0x1d3b2638a7cc9f2cb3d298a3da7a90b67e5506ed); + return true; + } + if (getCodeSize(0x9efbea6358bed926b293d2ce63a730d6d98d43dd)>0){ + OAR = OraclizeAddrResolverI(0x9efbea6358bed926b293d2ce63a730d6d98d43dd); + return true; + } + if (getCodeSize(0x20e12a1f859b3feae5fb2a0a32c18f5a65555bbf)>0){ + OAR = OraclizeAddrResolverI(0x20e12a1f859b3feae5fb2a0a32c18f5a65555bbf); + return true; + } + return false; + } + + function oraclize_query(string datasource, string arg) oraclizeAPI internal returns (bytes32 id){ + uint price = oraclize.getPrice(datasource); + if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price + return oraclize.query.value(price)(0, datasource, arg); + } + function oraclize_query(uint timestamp, string datasource, string arg) oraclizeAPI internal returns (bytes32 id){ + uint price = oraclize.getPrice(datasource); + if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price + return oraclize.query.value(price)(timestamp, datasource, arg); + } + function oraclize_query(uint timestamp, string datasource, string arg, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ + uint price = oraclize.getPrice(datasource, gaslimit); + if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price + return oraclize.query_withGasLimit.value(price)(timestamp, datasource, arg, gaslimit); + } + function oraclize_query(string datasource, string arg, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ + uint price = oraclize.getPrice(datasource, gaslimit); + if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price + return oraclize.query_withGasLimit.value(price)(0, datasource, arg, gaslimit); + } + function oraclize_query(string datasource, string arg1, string arg2) oraclizeAPI internal returns (bytes32 id){ + uint price = oraclize.getPrice(datasource); + if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price + return oraclize.query2.value(price)(0, datasource, arg1, arg2); + } + function oraclize_query(uint timestamp, string datasource, string arg1, string arg2) oraclizeAPI internal returns (bytes32 id){ + uint price = oraclize.getPrice(datasource); + if (price > 1 ether + tx.gasprice*200000) return 0; // unexpectedly high price + return oraclize.query2.value(price)(timestamp, datasource, arg1, arg2); + } + function oraclize_query(uint timestamp, string datasource, string arg1, string arg2, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ + uint price = oraclize.getPrice(datasource, gaslimit); + if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price + return oraclize.query2_withGasLimit.value(price)(timestamp, datasource, arg1, arg2, gaslimit); + } + function oraclize_query(string datasource, string arg1, string arg2, uint gaslimit) oraclizeAPI internal returns (bytes32 id){ + uint price = oraclize.getPrice(datasource, gaslimit); + if (price > 1 ether + tx.gasprice*gaslimit) return 0; // unexpectedly high price + return oraclize.query2_withGasLimit.value(price)(0, datasource, arg1, arg2, gaslimit); + } + function oraclize_cbAddress() oraclizeAPI internal returns (address){ + return oraclize.cbAddress(); + } + function oraclize_setProof(byte proofP) oraclizeAPI internal { + return oraclize.setProofType(proofP); + } + function oraclize_setCustomGasPrice(uint gasPrice) oraclizeAPI internal { + return oraclize.setCustomGasPrice(gasPrice); + } + + function getCodeSize(address _addr) constant internal returns(uint _size) { + assembly { + _size := extcodesize(_addr) + } + } + + + function parseAddr(string _a) internal returns (address){ + bytes memory tmp = bytes(_a); + uint160 iaddr = 0; + uint160 b1; + uint160 b2; + for (uint i=2; i<2+2*20; i+=2){ + iaddr *= 256; + b1 = uint160(tmp[i]); + b2 = uint160(tmp[i+1]); + if ((b1 >= 97)&&(b1 <= 102)) b1 -= 87; + else if ((b1 >= 48)&&(b1 <= 57)) b1 -= 48; + if ((b2 >= 97)&&(b2 <= 102)) b2 -= 87; + else if ((b2 >= 48)&&(b2 <= 57)) b2 -= 48; + iaddr += (b1*16+b2); + } + return address(iaddr); + } + + + function strCompare(string _a, string _b) internal returns (int) { + bytes memory a = bytes(_a); + bytes memory b = bytes(_b); + uint minLength = a.length; + if (b.length < minLength) minLength = b.length; + for (uint i = 0; i < minLength; i ++) + if (a[i] < b[i]) + return -1; + else if (a[i] > b[i]) + return 1; + if (a.length < b.length) + return -1; + else if (a.length > b.length) + return 1; + else + return 0; + } + + function indexOf(string _haystack, string _needle) internal returns (int) + { + bytes memory h = bytes(_haystack); + bytes memory n = bytes(_needle); + if(h.length < 1 || n.length < 1 || (n.length > h.length)) + return -1; + else if(h.length > (2**128 -1)) + return -1; + else + { + uint subindex = 0; + for (uint i = 0; i < h.length; i ++) + { + if (h[i] == n[0]) + { + subindex = 1; + while(subindex < n.length && (i + subindex) < h.length && h[i + subindex] == n[subindex]) + { + subindex++; + } + if(subindex == n.length) + return int(i); + } + } + return -1; + } + } + + function strConcat(string _a, string _b, string _c, string _d, string _e) internal returns (string){ + bytes memory _ba = bytes(_a); + bytes memory _bb = bytes(_b); + bytes memory _bc = bytes(_c); + bytes memory _bd = bytes(_d); + bytes memory _be = bytes(_e); + string memory abcde = new string(_ba.length + _bb.length + _bc.length + _bd.length + _be.length); + bytes memory babcde = bytes(abcde); + uint k = 0; + for (uint i = 0; i < _ba.length; i++) babcde[k++] = _ba[i]; + for (i = 0; i < _bb.length; i++) babcde[k++] = _bb[i]; + for (i = 0; i < _bc.length; i++) babcde[k++] = _bc[i]; + for (i = 0; i < _bd.length; i++) babcde[k++] = _bd[i]; + for (i = 0; i < _be.length; i++) babcde[k++] = _be[i]; + return string(babcde); + } + + function strConcat(string _a, string _b, string _c, string _d) internal returns (string) { + return strConcat(_a, _b, _c, _d, ""); + } + + function strConcat(string _a, string _b, string _c) internal returns (string) { + return strConcat(_a, _b, _c, "", ""); + } + + function strConcat(string _a, string _b) internal returns (string) { + return strConcat(_a, _b, "", "", ""); + } + + // parseInt + function parseInt(string _a) internal returns (uint) { + return parseInt(_a, 0); + } + + // parseInt(parseFloat*10^_b) + function parseInt(string _a, uint _b) internal returns (uint) { + bytes memory bresult = bytes(_a); + uint mint = 0; + bool decimals = false; + for (uint i=0; i= 48)&&(bresult[i] <= 57)){ + if (decimals){ + if (_b == 0) break; + else _b--; + } + mint *= 10; + mint += uint(bresult[i]) - 48; + } else if (bresult[i] == 46) decimals = true; + } + if (_b > 0) mint *= 10**_b; + return mint; + } + + +} +// + +contract Dice is usingOraclize { + + uint public pwin = 5000; //probability of winning (10000 = 100%) + uint public edge = 200; //edge percentage (10000 = 100%) + uint public maxWin = 100; //max win (before edge is taken) as percentage of bankroll (10000 = 100%) + uint public minBet = 1 finney; + uint public maxInvestors = 5; //maximum number of investors + uint public houseEdge = 50; //edge percentage (10000 = 100%) + uint public divestFee = 50; //divest fee percentage (10000 = 100%) + uint public emergencyWithdrawalRatio = 90; //ratio percentage (100 = 100%) + + uint safeGas = 25000; + uint constant ORACLIZE_GAS_LIMIT = 125000; + uint constant INVALID_BET_MARKER = 99999; + uint constant EMERGENCY_TIMEOUT = 7 days; + + struct Investor { + address investorAddress; + uint amountInvested; + bool votedForEmergencyWithdrawal; + } + + struct Bet { + address playerAddress; + uint amountBetted; + uint numberRolled; + } + + struct WithdrawalProposal { + address toAddress; + uint atTime; + } + + //Starting at 1 + mapping(address => uint) public investorIDs; + mapping(uint => Investor) public investors; + uint public numInvestors = 0; + + uint public invested = 0; + + address owner; + address houseAddress; + bool public isStopped; + + WithdrawalProposal public proposedWithdrawal; + + mapping (bytes32 => Bet) bets; + bytes32[] betsKeys; + + uint public amountWagered = 0; + uint public investorsProfit = 0; + uint public investorsLoses = 0; + bool profitDistributed; + + event BetWon(address playerAddress, uint numberRolled, uint amountWon); + event BetLost(address playerAddress, uint numberRolled); + event EmergencyWithdrawalProposed(); + event EmergencyWithdrawalFailed(address withdrawalAddress); + event EmergencyWithdrawalSucceeded(address withdrawalAddress, uint amountWithdrawn); + event FailedSend(address receiver, uint amount); + event ValueIsTooBig(); + + function Dice(uint pwinInitial, + uint edgeInitial, + uint maxWinInitial, + uint minBetInitial, + uint maxInvestorsInitial, + uint houseEdgeInitial, + uint divestFeeInitial, + uint emergencyWithdrawalRatioInitial + ) { + + oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS); + + pwin = pwinInitial; + edge = edgeInitial; + maxWin = maxWinInitial; + minBet = minBetInitial; + maxInvestors = maxInvestorsInitial; + houseEdge = houseEdgeInitial; + divestFee = divestFeeInitial; + emergencyWithdrawalRatio = emergencyWithdrawalRatioInitial; + owner = msg.sender; + houseAddress = msg.sender; + } + + //SECTION I: MODIFIERS AND HELPER FUNCTIONS + + //MODIFIERS + + modifier onlyIfNotStopped { + if (isStopped) throw; + _ + } + + modifier onlyIfStopped { + if (!isStopped) throw; + _ + } + + modifier onlyInvestors { + if (investorIDs[msg.sender] == 0) throw; + _ + } + + modifier onlyNotInvestors { + if (investorIDs[msg.sender] != 0) throw; + _ + } + + modifier onlyOwner { + if (owner != msg.sender) throw; + _ + } + + modifier onlyOraclize { + if (msg.sender != oraclize_cbAddress()) throw; + _ + } + + modifier onlyMoreThanMinInvestment { + if (msg.value <= getMinInvestment()) throw; + _ + } + + modifier onlyMoreThanZero { + if (msg.value == 0) throw; + _ + } + + modifier onlyIfBetSizeIsStillCorrect(bytes32 myid) { + Bet thisBet = bets[myid]; + if ((((thisBet.amountBetted * ((10000 - edge) - pwin)) / pwin ) <= (maxWin * getBankroll()) / 10000)) { + _ + } + else { + bets[myid].numberRolled = INVALID_BET_MARKER; + safeSend(thisBet.playerAddress, thisBet.amountBetted); + return; + } + } + + modifier onlyIfValidRoll(bytes32 myid, string result) { + Bet thisBet = bets[myid]; + uint numberRolled = parseInt(result); + if ((numberRolled < 1 || numberRolled > 10000) && thisBet.numberRolled == 0) { + bets[myid].numberRolled = INVALID_BET_MARKER; + safeSend(thisBet.playerAddress, thisBet.amountBetted); + return; + } + _ + } + + modifier onlyIfInvestorBalanceIsPositive(address currentInvestor) { + if (getBalance(currentInvestor) >= 0) { + _ + } + } + + modifier onlyWinningBets(uint numberRolled) { + if (numberRolled - 1 < pwin) { + _ + } + } + + modifier onlyLosingBets(uint numberRolled) { + if (numberRolled - 1 >= pwin) { + _ + } + } + + modifier onlyAfterProposed { + if (proposedWithdrawal.toAddress == 0) throw; + _ + } + + modifier rejectValue { + if (msg.value != 0) throw; + _ + } + + modifier onlyIfProfitNotDistributed { + if (!profitDistributed) { + _ + } + } + + modifier onlyIfValidGas(uint newGasLimit) { + if (newGasLimit < 25000) throw; + _ + } + + modifier onlyIfNotProcessed(bytes32 myid) { + Bet thisBet = bets[myid]; + if (thisBet.numberRolled > 0) throw; + _ + } + + modifier onlyIfEmergencyTimeOutHasPassed { + if (proposedWithdrawal.atTime + EMERGENCY_TIMEOUT > now) throw; + _ + } + + + //CONSTANT HELPER FUNCTIONS + + function getBankroll() constant returns(uint) { + return invested + investorsProfit - investorsLoses; + } + + function getMinInvestment() constant returns(uint) { + if (numInvestors == maxInvestors) { + uint investorID = searchSmallestInvestor(); + return getBalance(investors[investorID].investorAddress); + } + else { + return 0; + } + } + + function getStatus() constant returns(uint, uint, uint, uint, uint, uint, uint, uint, uint) { + + uint bankroll = getBankroll(); + + uint minInvestment = getMinInvestment(); + + return (bankroll, pwin, edge, maxWin, minBet, amountWagered, (investorsProfit - investorsLoses), minInvestment, betsKeys.length); + } + + function getBet(uint id) constant returns(address, uint, uint) { + if (id < betsKeys.length) { + bytes32 betKey = betsKeys[id]; + return (bets[betKey].playerAddress, bets[betKey].amountBetted, bets[betKey].numberRolled); + } + } + + function numBets() constant returns(uint) { + return betsKeys.length; + } + + function getMinBetAmount() constant returns(uint) { + uint oraclizeFee = OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas); + return oraclizeFee + minBet; + } + + function getMaxBetAmount() constant returns(uint) { + uint oraclizeFee = OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas); + uint betValue = (maxWin * getBankroll()) * pwin / (10000 * (10000 - edge - pwin)); + return betValue + oraclizeFee; + } + + function getLosesShare(address currentInvestor) constant returns (uint) { + return investors[investorIDs[currentInvestor]].amountInvested * (investorsLoses) / invested; + } + + function getProfitShare(address currentInvestor) constant returns (uint) { + return investors[investorIDs[currentInvestor]].amountInvested * (investorsProfit) / invested; + } + + function getBalance(address currentInvestor) constant returns (uint) { + return investors[investorIDs[currentInvestor]].amountInvested + getProfitShare(currentInvestor) - getLosesShare(currentInvestor); + } + + function searchSmallestInvestor() constant returns(uint) { + uint investorID = 1; + for (uint i = 1; i <= numInvestors; i++) { + if (getBalance(investors[i].investorAddress) < getBalance(investors[investorID].investorAddress)) { + investorID = i; + } + } + + return investorID; + } + + // PRIVATE HELPERS FUNCTION + + function safeSend(address addr, uint value) private { + if (this.balance < value) { + ValueIsTooBig(); + return; + } + + if (!(addr.call.gas(safeGas).value(value)())) { + FailedSend(addr, value); + if (addr != houseAddress) { + //Forward to house address all change + if (!(houseAddress.call.gas(safeGas).value(value)())) FailedSend(houseAddress, value); + } + } + } + + function addInvestorAtID(uint id) private { + investorIDs[msg.sender] = id; + investors[id].investorAddress = msg.sender; + investors[id].amountInvested = msg.value; + invested += msg.value; + } + + function profitDistribution() private onlyIfProfitNotDistributed { + uint copyInvested; + + for (uint i = 1; i <= numInvestors; i++) { + address currentInvestor = investors[i].investorAddress; + uint profitOfInvestor = getProfitShare(currentInvestor); + uint losesOfInvestor = getLosesShare(currentInvestor); + investors[i].amountInvested += profitOfInvestor - losesOfInvestor; + copyInvested += investors[i].amountInvested; + } + + delete investorsProfit; + delete investorsLoses; + invested = copyInvested; + + profitDistributed = true; + } + + // SECTION II: BET & BET PROCESSING + + function() { + bet(); + } + + function bet() onlyIfNotStopped onlyMoreThanZero { + uint oraclizeFee = OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas); + uint betValue = msg.value - oraclizeFee; + if ((((betValue * ((10000 - edge) - pwin)) / pwin ) <= (maxWin * getBankroll()) / 10000) && (betValue >= minBet)) { + // encrypted arg: '\n{"jsonrpc":2.0,"method":"generateSignedIntegers","params":{"apiKey":"YOUR_API_KEY","n":1,"min":1,"max":10000},"id":1}' + bytes32 myid = oraclize_query("URL", "json(https://api.random.org/json-rpc/1/invoke).result.random.data.0","BBX1PCQ9134839wTz10OWxXCaZaGk92yF6TES8xA+8IC7xNBlJq5AL0uW3rev7IoApA5DMFmCfKGikjnNbNglKKvwjENYPB8TBJN9tDgdcYNxdWnsYARKMqmjrJKYbBAiws+UU6HrJXUWirO+dBSSJbmjIg+9vmBjSq8KveiBzSGmuQhu7/hSg5rSsSP/r+MhR/Q5ECrOHi+CkP/qdSUTA/QhCCjdzFu+7t3Hs7NU34a+l7JdvDlvD8hoNxyKooMDYNbUA8/eFmPv2d538FN6KJQp+RKr4w4VtAMHdejrLM=", ORACLIZE_GAS_LIMIT + safeGas); + bets[myid] = Bet(msg.sender, betValue, 0); + betsKeys.push(myid); + } + else { + throw; + } + } + + function __callback (bytes32 myid, string result, bytes proof) + onlyOraclize + onlyIfNotProcessed(myid) + onlyIfValidRoll(myid, result) + onlyIfBetSizeIsStillCorrect(myid) { + + Bet thisBet = bets[myid]; + uint numberRolled = parseInt(result); + bets[myid].numberRolled = numberRolled; + isWinningBet(thisBet, numberRolled); + isLosingBet(thisBet, numberRolled); + amountWagered += thisBet.amountBetted; + delete profitDistributed; + } + + function isWinningBet(Bet thisBet, uint numberRolled) private onlyWinningBets(numberRolled) { + uint winAmount = (thisBet.amountBetted * (10000 - edge)) / pwin; + BetWon(thisBet.playerAddress, numberRolled, winAmount); + safeSend(thisBet.playerAddress, winAmount); + investorsLoses += (winAmount - thisBet.amountBetted); + } + + function isLosingBet(Bet thisBet, uint numberRolled) private onlyLosingBets(numberRolled) { + BetLost(thisBet.playerAddress, numberRolled); + safeSend(thisBet.playerAddress, 1); + investorsProfit += (thisBet.amountBetted - 1)*(10000 - houseEdge)/10000; + uint houseProfit = (thisBet.amountBetted - 1)*(houseEdge)/10000; + safeSend(houseAddress, houseProfit); + } + + //SECTION III: INVEST & DIVEST + + function increaseInvestment() onlyIfNotStopped onlyMoreThanZero onlyInvestors { + profitDistribution(); + investors[investorIDs[msg.sender]].amountInvested += msg.value; + invested += msg.value; + } + + function newInvestor() + onlyIfNotStopped + onlyMoreThanZero + onlyNotInvestors + onlyMoreThanMinInvestment { + + profitDistribution(); + + if (numInvestors == maxInvestors) { + uint smallestInvestorID = searchSmallestInvestor(); + divest(investors[smallestInvestorID].investorAddress); + } + + numInvestors++; + addInvestorAtID(numInvestors); + } + + function divest() onlyInvestors rejectValue { + divest(msg.sender); + } + + + function divest(address currentInvestor) + private + onlyIfInvestorBalanceIsPositive(currentInvestor) { + + profitDistribution(); + uint currentID = investorIDs[currentInvestor]; + uint amountToReturn = getBalance(currentInvestor); + invested -= investors[currentID].amountInvested; + uint divestFeeAmount = (amountToReturn*divestFee)/10000; + amountToReturn -= divestFeeAmount; + + delete investors[currentID]; + delete investorIDs[currentInvestor]; + //Reorder investors + + if (currentID != numInvestors) { + // Get last investor + Investor lastInvestor = investors[numInvestors]; + //Set last investor ID to investorID of divesting account + investorIDs[lastInvestor.investorAddress] = currentID; + //Copy investor at the new position in the mapping + investors[currentID] = lastInvestor; + //Delete old position in the mappping + delete investors[numInvestors]; + } + + numInvestors--; + safeSend(currentInvestor, amountToReturn); + safeSend(houseAddress, divestFeeAmount); + } + + function forceDivestOfAllInvestors() onlyOwner rejectValue { + uint copyNumInvestors = numInvestors; + for (uint i = 1; i <= copyNumInvestors; i++) { + divest(investors[1].investorAddress); + } + } + + /* + The owner can use this function to force the exit of an investor from the + contract during an emergency withdrawal in the following situations: + - Unresponsive investor + - Investor demanding to be paid in other to vote, the facto-blackmailing + other investors + */ + function forceDivestOfOneInvestor(address currentInvestor) + onlyOwner + onlyIfStopped + rejectValue { + + divest(currentInvestor); + //Resets emergency withdrawal proposal. Investors must vote again + delete proposedWithdrawal; + } + + //SECTION IV: CONTRACT MANAGEMENT + + function stopContract() onlyOwner rejectValue { + isStopped = true; + } + + function resumeContract() onlyOwner rejectValue { + isStopped = false; + } + + function changeHouseAddress(address newHouse) onlyOwner rejectValue { + houseAddress = newHouse; + } + + function changeOwnerAddress(address newOwner) onlyOwner rejectValue { + owner = newOwner; + } + + function changeGasLimitOfSafeSend(uint newGasLimit) + onlyOwner + onlyIfValidGas(newGasLimit) + rejectValue { + safeGas = newGasLimit; + } + + //SECTION V: EMERGENCY WITHDRAWAL + + function voteEmergencyWithdrawal(bool vote) + onlyInvestors + onlyAfterProposed + onlyIfStopped + rejectValue { + investors[investorIDs[msg.sender]].votedForEmergencyWithdrawal = vote; + } + + function proposeEmergencyWithdrawal(address withdrawalAddress) + onlyIfStopped + onlyOwner + rejectValue { + + //Resets previous votes + for (uint i = 1; i <= numInvestors; i++) { + delete investors[i].votedForEmergencyWithdrawal; + } + + proposedWithdrawal = WithdrawalProposal(withdrawalAddress, now); + EmergencyWithdrawalProposed(); + } + + function executeEmergencyWithdrawal() + onlyOwner + onlyAfterProposed + onlyIfStopped + onlyIfEmergencyTimeOutHasPassed + rejectValue { + + uint numOfVotesInFavour; + uint amountToWithdrawal = this.balance; + + for (uint i = 1; i <= numInvestors; i++) { + if (investors[i].votedForEmergencyWithdrawal == true) { + numOfVotesInFavour++; + delete investors[i].votedForEmergencyWithdrawal; + } + } + + if (numOfVotesInFavour >= emergencyWithdrawalRatio * numInvestors / 100) { + if (!proposedWithdrawal.toAddress.send(this.balance)) { + EmergencyWithdrawalFailed(proposedWithdrawal.toAddress); + } + else { + EmergencyWithdrawalSucceeded(proposedWithdrawal.toAddress, amountToWithdrawal); + } + } + else { + throw; + } + } + +}