├── .gitattributes ├── .gitignore ├── .soliumignore ├── .soliumrc.json ├── .travis.yml ├── Dockerfile ├── Incentive Layer.jpg ├── README.md ├── contracts ├── DepositsManager.sol ├── DisputeResolutionDummy.sol ├── ExchangeRateOracle.sol ├── IDisputeResolutionLayer.sol ├── IGameMaker.sol ├── IncentiveLayer.sol ├── JackpotManager.sol ├── Migrations.sol ├── README.md ├── RewardsManager.sol ├── TRU.sol ├── TestJackpotManager.sol └── TestRewardsManager.sol ├── migrations ├── 1_initial_migration.js ├── 2_deploy_contracts.js └── 3_export.js ├── package-lock.json ├── package.json ├── task_exchange.js ├── test ├── deposits_manager.js ├── helpers │ ├── mineBlocks.js │ └── timeout.js ├── incentive_layer.js ├── jackpot_manager.js ├── oracle.js ├── rewards_manager.js └── timeouts.js └── truffle.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /build 3 | /tmp 4 | */target 5 | */addresses.json 6 | /export -------------------------------------------------------------------------------- /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:recommended", 3 | "plugins": [ 4 | "security" 5 | ], 6 | "rules": { 7 | "quotes": [ 8 | "error", 9 | "double" 10 | ], 11 | "indentation": [ 12 | "error", 13 | 4 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: node_js 4 | node_js: 5 | - "9.4.0" 6 | 7 | matrix: 8 | include: 9 | - os: linux 10 | dist: trusty 11 | before_install: 12 | - npm i -g ganache-cli@6.1.0 13 | - ganache-cli &>/dev/null & 14 | 15 | 16 | install: 17 | - npm install 18 | 19 | script: 20 | - npm run migrate 21 | - npm run test 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:17.04 2 | MAINTAINER Harley Swick 3 | 4 | ENV PATH="${PATH}:/node-v6.11.3-linux-x64/bin" 5 | 6 | RUN apt-get update && \ 7 | apt-get install -y curl && \ 8 | curl -sL https://deb.nodesource.com/setup_6.x | bash - && \ 9 | apt-get install -y nodejs && \ 10 | npm install -g ethereumjs-testrpc 11 | 12 | RUN apt-get install -y git && \ 13 | npm install truffle@v4.0.0-beta.0 -g && \ 14 | git clone https://github.com/TrueBitFoundation/truebit-contracts && \ 15 | cd truebit-contracts && \ 16 | truffle test -------------------------------------------------------------------------------- /Incentive Layer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrueBitFoundation/incentive-layer/1ec5f3af3dcc0007fd02a9bc8ecfb9a241767d9b/Incentive Layer.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Truebit Incentive Layer 2 | 3 |

4 | 5 |

6 | 7 | This repo hosts the smart contract code related to the Truebit incentive layer. We have a contract called IncentiveLayer that inherits from the DepositsManager contract. Which handles operations like keeping track of a balance, submitting a deposit, bonding a deposit, slashing deposit, etc. Users can submit deposits to these contracts which will effectively start an account on the IncentiveLayer. Once a user has an account they have the ability to create, solve, or verify tasks. A creator of a task is known as a Task Giver. Solvers solve tasks, and Verifiers verify tasks. The data that each of these agents interact with is contained in a struct. The lifecycle of a task is implemented as a finite state machine. 8 | 9 | # Install 10 | This is a truffle codebase (see http://truffleframework.com/docs). 11 | 12 | `npm install truffle -g` to install truffle 13 | 14 | `npm install` to install needed dependencies locally 15 | 16 | In a separate tab on the command line run `ganache-cli` 17 | 18 | Then `truffle migrate --reset` to deploy the contracts 19 | 20 | `truffle test` to run tests 21 | 22 | # TaskGiver 23 | This is a user that creates a new task. They exchange Ether for computing tasks like running some C code. Alice can send out a task to be solved with the `createTask` method. That method creates a Task, an ID for that Task, and stores the information in a mapping. A TaskCreated event is broadcasted over the network. 24 | 25 | # Solver 26 | Once a solver is chosen for a task, it solves the task by running the code in an offchain WASM interpreter. It then submits a hash of that solution to be verified. If challenged, the solver and the verifier play the verification game. 27 | 28 | # Verifier 29 | Verifiers can challenge a solution submitted and win jackpots. 30 | 31 | ## Task 32 | ``` 33 | struct Task { 34 | address owner; 35 | address selectedSolver; 36 | uint minDeposit; 37 | uint reward; 38 | bytes32 taskData; 39 | mapping(address => bytes32) challenges; 40 | State state; 41 | bytes32 blockhash; 42 | bytes32 randomBitsHash; 43 | uint numBlocks; 44 | uint taskCreationBlockNumber; 45 | mapping(address => uint) bondedDeposits; 46 | uint randomBits; 47 | uint finalityCode; //0 => not finalized, 1 => finalized, 2 => forced error occurred 48 | uint jackpotID; 49 | uint initialReward; 50 | } 51 | ``` 52 | 53 | A task is identified with an unisgned integer functioning as its ID. In the code, this is referred to as taskID. 54 | 55 | `owner:` is the address of the task giver. 56 | 57 | `selectedSolver`: address of the selectedSolver (first solver to register for task). 58 | 59 | `minDeposit`: the minimum deposit required to participate in task actions. 60 | 61 | `reward`: amount of reward given for completing the task 62 | 63 | `taskData`: the initial data for the task, or possibly an IPFS address. 64 | 65 | `challenges`: is a map that relates addresses of verifiers to their intent hash. 66 | 67 | `state`: is an enum representing the different states of the task. 68 | 69 | `blockhash`: the hash of the previous block upon registering for a task. 70 | 71 | `randomBitsHash`: the hash of the random bits upon registering for a task. 72 | 73 | `numBlocks`: the number of blocks to adjust for task difficulty. 74 | 75 | `taskCreationBlockNumber`: block number upon creating the task. 76 | 77 | `bondedDeposits`: Addresses of people with bonded deposits and amount deposited. 78 | 79 | `randomBits`: this is the original random bits that is eventually revealed. 80 | 81 | `finalityCode`: this is an encoding to determine the final state of the task 82 | 83 | `jackpotID`: the version of the jackpot associated with the task because jackpot versions can change over time. 84 | 85 | `initialReward`: this value is stored to compare what amount should be received by a particular user. 86 | 87 | ## Posting a deposit 88 | 89 | In order to participate in the Truebit protocol a client must first submit a deposit. This deposit can be used for multiple tasks as only the minDeposit for a task will be bonded. 90 | `incentiveLayer.makeDeposit({from: taskGiver, value: 1000});` 91 | 92 | ## Finite State Machine 93 | 94 | ```javascript 95 | var minDeposit = 5000; 96 | var taskData = 0x0; 97 | var numBlocks = 5;//For timeout 98 | var task_giver = accounts[0]; 99 | var solver = accounts[1]; 100 | var verifier = accounts[2]; 101 | ``` 102 | 103 | The states of the task will change after certain functions are called. 104 | 105 | A task has been created with an ID and is stored on the TBIncentiveLayer contract. This also has the side effect of bonding the taskGiver's deposit. 106 | ```javascript 107 | incentiveLayer.createTask(minDeposit, reward, taskData, numBlocks, {from: task_giver}); 108 | ``` 109 | 110 | The Finite State Machine (FSM) is in State 0: Task Initialized. In this state a solver can register to solve a task. This state is open until a solver registers or a given timeout has ended. Solver submits task ID and hash of random bits. This also has the side effect of bonding the solver's deposit. 111 | 112 | ```javascript 113 | incentiveLayer.registerForTask(taskID, web3.utils.soliditySha3(12345), {from: solver}); 114 | ``` 115 | 116 | Now the FSM is in State 1: SolverSelected 117 | 118 | A solver is selected based on who registers for task. This can also be thought of as whichever solver's call to this function gets put on the main chain first. Now that the solver has been selected, they have a given time period to solve a task and submit a correct and incorrect solution. If they do not perform this action within the given time period than they are penalized. 119 | 120 | ```javascript 121 | incentiveLayer.commitSolution(taskID, web3.utils.soliditySha3(0x0), web3.utils.soliditySha3(0x12345), {from: solver}); 122 | ``` 123 | 124 | Once the solution is commiteed we enter State 2: SolutionCommitted. It is now possible to post challenges 125 | 126 | Notice that two solution hashes are input. This is done because one of the hashes is for a forced error and another is for the real solution. At this point only the Solver is aware which solutionHash is 'correct'. 127 | 128 | Now that a solution is committed there is a time period for challenges to be accepted. These challenges will come from verifiers. It is assumed at least one challenge will come from a solver. This also has the effect of bonding the verifier's deposit. Once a given time period ends the task giver will call this function to change the state of the task. Only the task giver (owner of task) is allowed to call this function. 129 | 130 | ```javascript 131 | incentiveLayer.commitChallenge(taskID, minDeposit, intentHash, {from: verifier}); 132 | 133 | incentiveLayer.changeTaskState(taskID, 3, {from: task_giver}); 134 | ``` 135 | 136 | State 3: ChallengesAccepted - is the time period where the verifiers that have committed challenges reveal which solution they believe is correct. Solution0 or Solution1 by revealing either 0 or 1, anything else is an intent to challenge both solutions. Then after a given time period the task giver changes the state to 4. 137 | 138 | ```javascript 139 | incentiveLayer.revealIntent(taskID, 0, {from: verifier}); 140 | 141 | incentiveLayer.changeTaskState(taskID, 4, {from: task_giver}); 142 | ``` 143 | 144 | State 4: IntentsRevealed is the state where the solver is allowed to reveal which solution hash is the correct solution hash. This is denoted by submitting a boolean value. true means solutionHash0 is correct, and thus solutionHash1 is incorrect. Submitting false has the opposite effect. This is important because it determines who eventually will participate in the verification game later. The solver also reveals the random number submitted previously which determines whether a forced error is in effect or not. 145 | 146 | ```javascript 147 | incentiveLayer.revealSolution(taskID, true, 12345, {from: solver}); 148 | ``` 149 | 150 | We are now in State 5: SolutionRevealed 151 | 152 | After the solution is revealed the task giver can initiate the verification game and then verify the solution. 153 | 154 | ```javascript 155 | incentiveLayer.verifySolution(taskID, 12345, {from: task_giver}); 156 | ``` 157 | 158 | This will eventually include calls to the dispute resolution layer where the actual verification games will take place. 159 | 160 | Once the verification games are over the task is finalized and we are in the final stage of the lifecycle of a task State 6: TaskFinalized. 161 | 162 | If for any reason there was a timeout triggered in the intermediate states a task can end up in the TaskTimeout state. 163 | -------------------------------------------------------------------------------- /contracts/DepositsManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "./TRU.sol"; 5 | 6 | contract DepositsManager { 7 | using SafeMath for uint; 8 | 9 | mapping(address => uint) public deposits; 10 | uint public jackpot; 11 | address public owner; 12 | TRU public token; 13 | 14 | event DepositMade(address who, uint amount); 15 | event DepositWithdrawn(address who, uint amount); 16 | 17 | // @dev – the constructor 18 | constructor(address _tru) public { 19 | owner = msg.sender; 20 | token = TRU(_tru); 21 | } 22 | 23 | // @dev - fallback does nothing since we only accept TRU tokens 24 | function () public payable { 25 | revert(); 26 | } 27 | 28 | // @dev – returns an account's deposit 29 | // @param who – the account's address. 30 | // @return – the account's deposit. 31 | function getDeposit(address who) view public returns (uint) { 32 | return deposits[who]; 33 | } 34 | 35 | // @dev - allows a user to deposit TRU tokens 36 | // @return - the uer's update deposit amount 37 | function makeDeposit(uint _deposit) public payable returns (uint) { 38 | require(token.allowance(msg.sender, address(this)) >= _deposit); 39 | token.transferFrom(msg.sender, address(this), _deposit); 40 | 41 | deposits[msg.sender] = deposits[msg.sender].add(_deposit); 42 | emit DepositMade(msg.sender, _deposit); 43 | return deposits[msg.sender]; 44 | } 45 | 46 | // @dev - allows a user to withdraw TRU from their deposit 47 | // @param amount - how much TRU to withdraw 48 | // @return - the user's updated deposit 49 | function withdrawDeposit(uint amount) public returns (uint) { 50 | require(deposits[msg.sender] >= amount); 51 | 52 | deposits[msg.sender] = deposits[msg.sender].sub(amount); 53 | token.transfer(msg.sender, amount); 54 | 55 | emit DepositWithdrawn(msg.sender, amount); 56 | return deposits[msg.sender]; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /contracts/DisputeResolutionDummy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./IDisputeResolutionLayer.sol"; 4 | import "./IGameMaker.sol"; 5 | 6 | contract DisputeResolutionLayerDummy is IDisputeResolutionLayer, IGameMaker { 7 | 8 | enum State { Uninitialized, Challenged, Unresolved, SolverWon, ChallengerWon } 9 | 10 | struct Game { 11 | address solver; 12 | address verifier; 13 | State status; 14 | } 15 | 16 | mapping(bytes32 => Game) private games; 17 | 18 | function commitChallenge(address solver, address verifier, bytes32 spec) external returns (bytes32 gameId) { 19 | gameId = keccak256(abi.encodePacked(solver, verifier, spec)); 20 | Game storage g = games[gameId]; 21 | g.solver = solver; 22 | g.verifier = verifier; 23 | g.status = State.SolverWon; 24 | } 25 | 26 | function status(bytes32 gameId) external view returns (uint) { 27 | return uint(games[gameId].status); 28 | } 29 | 30 | struct Game2 { 31 | uint taskID; 32 | address solver; 33 | address verifier; 34 | bytes32 startStateHash; 35 | bytes32 endStateHash; 36 | uint size; 37 | uint timeout; 38 | } 39 | 40 | mapping(bytes32 => Game2) private games2; 41 | 42 | function make(uint taskID, address solver, address verifier, bytes32 startStateHash, bytes32 endStateHash, uint256 size, uint timeout) external returns (bytes32) { 43 | bytes32 gameID = keccak256(abi.encodePacked(solver, verifier, startStateHash, endStateHash, size, timeout)); 44 | Game2 storage g = games2[gameID]; 45 | g.taskID = taskID; 46 | g.solver = solver; 47 | g.verifier = verifier; 48 | g.startStateHash = startStateHash; 49 | g.endStateHash = endStateHash; 50 | g.size = size; 51 | g.timeout = timeout; 52 | return gameID; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contracts/ExchangeRateOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | 5 | contract ExchangeRateOracle is Ownable { 6 | uint public constant priceOfCyclemUSD = 1; 7 | uint public TRUperUSD; 8 | uint public priceOfCycleTRU; 9 | 10 | event ExchangeRateUpdate(uint indexed TRUperUSD, address owner); 11 | 12 | function updateExchangeRate (uint _TRUperUSD) public onlyOwner { 13 | require(_TRUperUSD!= 0); 14 | TRUperUSD = _TRUperUSD; 15 | priceOfCycleTRU = TRUperUSD * priceOfCyclemUSD / 1000; 16 | emit ExchangeRateUpdate(TRUperUSD, owner); 17 | } 18 | 19 | function getMinDeposit (uint taskDifficulty) public view returns (uint) { 20 | return taskDifficulty * priceOfCycleTRU; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/IDisputeResolutionLayer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | interface IDisputeResolutionLayer { 4 | function status(bytes32 id) external view returns (uint); //returns State enum 5 | } 6 | -------------------------------------------------------------------------------- /contracts/IGameMaker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | interface IGameMaker { 4 | function make(uint taskID, address solver, address verifier, bytes32 startStateHash, bytes32 endStateHash, uint256 size, uint timeout) external returns (bytes32); 5 | } 6 | -------------------------------------------------------------------------------- /contracts/IncentiveLayer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./DepositsManager.sol"; 4 | import "./JackpotManager.sol"; 5 | import "./TRU.sol"; 6 | import "./ExchangeRateOracle.sol"; 7 | import "./RewardsManager.sol"; 8 | 9 | import "./IGameMaker.sol"; 10 | import "./IDisputeResolutionLayer.sol"; 11 | 12 | contract IncentiveLayer is JackpotManager, DepositsManager, RewardsManager { 13 | 14 | uint private numTasks = 0; 15 | uint private forcedErrorThreshold = 42; 16 | uint private taxMultiplier = 5; 17 | 18 | uint constant TIMEOUT = 100; 19 | 20 | enum CodeType { 21 | WAST, 22 | WASM, 23 | INTERNAL 24 | } 25 | 26 | enum StorageType { 27 | IPFS, 28 | BLOCKCHAIN 29 | } 30 | 31 | struct VMParameters { 32 | uint8 stackSize; 33 | uint8 memorySize; 34 | uint8 callSize; 35 | uint8 globalsSize; 36 | uint8 tableSize; 37 | } 38 | 39 | event DepositBonded(uint taskID, address account, uint amount); 40 | event DepositUnbonded(uint taskID, address account, uint amount); 41 | event BondedDepositMovedToJackpot(uint taskID, address account, uint amount); 42 | event TaskCreated(uint taskID, uint minDeposit, uint blockNumber, uint reward, uint tax, CodeType codeType, StorageType storageType, string storageAddress); 43 | event SolverSelected(uint indexed taskID, address solver, bytes32 taskData, uint minDeposit, bytes32 randomBitsHash); 44 | event SolutionsCommitted(uint taskID, uint minDeposit, CodeType codeType, StorageType storageType, string storageAddress); 45 | event SolutionRevealed(uint taskID, uint randomBits); 46 | event TaskStateChange(uint taskID, uint state); 47 | event VerificationCommitted(address verifier, uint jackpotID, uint solutionID, uint index); 48 | event SolverDepositBurned(address solver, uint taskID); 49 | event VerificationGame(address indexed solver, uint currentChallenger); 50 | event PayReward(address indexed solver, uint reward); 51 | 52 | enum State { TaskInitialized, SolverSelected, SolutionComitted, ChallengesAccepted, IntentsRevealed, SolutionRevealed, TaskFinalized, TaskTimeout } 53 | enum Status { Uninitialized, Challenged, Unresolved, SolverWon, ChallengerWon }//For dispute resolution 54 | 55 | struct Task { 56 | address owner; 57 | address selectedSolver; 58 | uint minDeposit; 59 | uint reward; 60 | uint tax; 61 | bytes32 initTaskHash; 62 | mapping(address => bytes32) challenges; 63 | State state; 64 | bytes32 blockhash; 65 | bytes32 randomBitsHash; 66 | uint taskCreationBlockNumber; 67 | mapping(address => uint) bondedDeposits; 68 | uint randomBits; 69 | uint finalityCode; // 0 => not finalized, 1 => finalized, 2 => forced error occurred 70 | uint jackpotID; 71 | uint initialReward; 72 | CodeType codeType; 73 | StorageType storageType; 74 | string storageAddress; 75 | } 76 | 77 | struct Solution { 78 | bytes32 solutionHash0; 79 | bytes32 solutionHash1; 80 | bool solution0Correct; 81 | address[] solution0Challengers; 82 | address[] solution1Challengers; 83 | address[] allChallengers; 84 | uint currentChallenger; 85 | bool solverConvicted; 86 | bytes32 currentGame; 87 | } 88 | 89 | mapping(uint => Task) private tasks; 90 | mapping(uint => Solution) private solutions; 91 | mapping(uint => VMParameters) private vmParams; 92 | 93 | ExchangeRateOracle oracle; 94 | address disputeResolutionLayer; //using address type because in some cases it is IGameMaker, and others IDisputeResolutionLayer 95 | 96 | constructor (address _TRU, address _exchangeRateOracle, address _disputeResolutionLayer) 97 | DepositsManager(_TRU) 98 | JackpotManager(_TRU) 99 | RewardsManager(_TRU) 100 | public 101 | { 102 | disputeResolutionLayer = _disputeResolutionLayer; 103 | oracle = ExchangeRateOracle(_exchangeRateOracle); 104 | } 105 | 106 | // @dev - private method to check if the denoted amount of blocks have been mined (time has passed). 107 | // @param taskID - the task id. 108 | // @param numBlocks - the difficulty weight for the task 109 | // @return - boolean 110 | function stateChangeTimeoutReached(uint taskID) private view returns (bool) { 111 | Task storage t = tasks[taskID]; 112 | return block.number.sub(t.taskCreationBlockNumber) >= TIMEOUT; 113 | } 114 | 115 | // @dev – locks up part of the a user's deposit into a task. 116 | // @param taskID – the task id. 117 | // @param account – the user's address. 118 | // @param amount – the amount of deposit to lock up. 119 | // @return – the user's deposit bonded for the task. 120 | function bondDeposit(uint taskID, address account, uint amount) private returns (uint) { 121 | Task storage task = tasks[taskID]; 122 | require(deposits[msg.sender] >= amount); 123 | deposits[account] = deposits[account].sub(amount); 124 | task.bondedDeposits[account] = task.bondedDeposits[account].add(amount); 125 | emit DepositBonded(taskID, account, amount); 126 | return task.bondedDeposits[account]; 127 | } 128 | 129 | // @dev – unlocks a user's bonded deposits from a task. 130 | // @param taskID – the task id. 131 | // @param account – the user's address. 132 | // @return – the user's deposit which was unbonded from the task. 133 | function unbondDeposit(uint taskID) public returns (uint) { 134 | Task storage task = tasks[taskID]; 135 | require(task.state == State.TaskFinalized || task.state == State.TaskTimeout); 136 | uint bondedDeposit = task.bondedDeposits[msg.sender]; 137 | delete task.bondedDeposits[msg.sender]; 138 | deposits[msg.sender] = deposits[msg.sender].add(bondedDeposit); 139 | emit DepositUnbonded(taskID, msg.sender, bondedDeposit); 140 | 141 | return bondedDeposit; 142 | } 143 | 144 | // @dev – punishes a user by moving their bonded deposits for a task into the jackpot. 145 | // @param taskID – the task id. 146 | // @param account – the user's address. 147 | // @return – the updated jackpot amount. 148 | function moveBondedDepositToJackpot(uint taskID, address account) private returns (uint) { 149 | Task storage task = tasks[taskID]; 150 | uint bondedDeposit = task.bondedDeposits[account]; 151 | delete task.bondedDeposits[account]; 152 | jackpot = jackpot.add(bondedDeposit); 153 | emit BondedDepositMovedToJackpot(taskID, account, bondedDeposit); 154 | 155 | return bondedDeposit; 156 | } 157 | 158 | // @dev – returns the user's bonded deposits for a task. 159 | // @param taskID – the task id. 160 | // @param account – the user's address. 161 | // @return – the user's bonded deposits for a task. 162 | function getBondedDeposit(uint taskID, address account) constant public returns (uint) { 163 | return tasks[taskID].bondedDeposits[account]; 164 | } 165 | 166 | function defaultParameters(uint taskID) internal { 167 | VMParameters storage params = vmParams[taskID]; 168 | params.stackSize = 14; 169 | params.memorySize = 16; 170 | params.globalsSize = 8; 171 | params.tableSize = 8; 172 | params.callSize = 10; 173 | } 174 | 175 | // @dev – taskGiver creates tasks to be solved. 176 | // @param minDeposit – the minimum deposit required for engaging with a task as a solver or verifier. 177 | // @param reward - the payout given to solver 178 | // @param taskData – tbd. could be hash of the wasm file on a filesystem. 179 | // @param numBlocks – the number of blocks to adjust for task difficulty 180 | // @return – boolean 181 | function createTask(bytes32 initTaskHash, CodeType codeType, StorageType storageType, string storageAddress, uint maxDifficulty, uint reward) public returns (bool) { 182 | // Get minDeposit required by task 183 | uint minDeposit = oracle.getMinDeposit(maxDifficulty); 184 | require(minDeposit > 0); 185 | // require(deposits[msg.sender] >= (reward + (minDeposit * taxMultiplier))); 186 | require(deposits[msg.sender] >= (minDeposit * taxMultiplier)); 187 | 188 | Task storage t = tasks[numTasks]; 189 | t.owner = msg.sender; 190 | t.minDeposit = minDeposit; 191 | depositReward(numTasks, reward); 192 | t.reward = reward; 193 | // deposits[msg.sender] = deposits[msg.sender].sub(reward); 194 | 195 | t.tax = minDeposit * taxMultiplier; 196 | t.initTaskHash = initTaskHash; 197 | t.taskCreationBlockNumber = block.number; 198 | t.initialReward = minDeposit; 199 | t.codeType = codeType; 200 | t.storageType = storageType; 201 | t.storageAddress = storageAddress; 202 | defaultParameters(numTasks); 203 | 204 | // LOOK AT: May be some problem if tax amount is also not bonded 205 | // but still submitted through makeDeposit. For example, 206 | // if the task giver decides to bond the deposit and the 207 | 208 | // tax can not be collected. Perhaps another bonding 209 | // structure to escrow the taxes. 210 | log0(keccak256(abi.encodePacked(msg.sender))); // possible bug if log is after event 211 | emit TaskCreated(numTasks, minDeposit, t.taskCreationBlockNumber, t.reward, t.tax, t.codeType, t.storageType, t.storageAddress); 212 | 213 | numTasks.add(1); 214 | return true; 215 | } 216 | 217 | // @dev – changes a tasks state. 218 | // @param taskID – the task id. 219 | // @param newSate – the new state. 220 | // @return – boolean 221 | function changeTaskState(uint taskID, uint newState) public returns (bool) { 222 | Task storage t = tasks[taskID]; 223 | 224 | //TODO: Add this back in 225 | //require(stateChangeTimeoutReached(taskID)); 226 | 227 | t.state = State(newState); 228 | emit TaskStateChange(taskID, newState); 229 | return true; 230 | } 231 | 232 | // @dev – solver registers for tasks, if first to register than automatically selected solver 233 | // 0 -> 1 234 | // @param taskID – the task id. 235 | // @param randomBitsHash – hash of random bits to commit to task 236 | // @return – boolean 237 | function registerForTask(uint taskID, bytes32 randomBitsHash) public returns(bool) { 238 | Task storage t = tasks[taskID]; 239 | 240 | require(!(t.owner == 0x0)); 241 | require(t.state == State.TaskInitialized); 242 | require(t.selectedSolver == 0x0); 243 | 244 | bondDeposit(taskID, msg.sender, t.minDeposit); 245 | t.selectedSolver = msg.sender; 246 | t.randomBitsHash = randomBitsHash; 247 | t.blockhash = blockhash(block.number.add(1)); 248 | t.state = State.SolverSelected; 249 | 250 | // Burn task giver's taxes now that someone has claimed the task 251 | deposits[t.owner] = deposits[t.owner].sub(t.tax); 252 | token.burn(t.tax); 253 | 254 | emit SolverSelected(taskID, msg.sender, t.initTaskHash, t.minDeposit, t.randomBitsHash); 255 | return true; 256 | } 257 | 258 | 259 | // @dev – new solver registers for task if penalize old one, don't burn tokens twice 260 | // 0 -> 1 261 | // @param taskID – the task id. 262 | // @param randomBitsHash – hash of random bits to commit to task 263 | // @return – boolean 264 | function registerNewSolver(uint taskID, bytes32 randomBitsHash) public returns(bool) { 265 | Task storage t = tasks[taskID]; 266 | 267 | require(!(t.owner == 0x0)); 268 | require(t.state == State.TaskInitialized); 269 | require(t.selectedSolver == 0x0); 270 | 271 | bondDeposit(taskID, msg.sender, t.minDeposit); 272 | t.selectedSolver = msg.sender; 273 | t.randomBitsHash = randomBitsHash; 274 | t.blockhash = blockhash(block.number.add(1)); 275 | t.state = State.SolverSelected; 276 | 277 | emit SolverSelected(taskID, msg.sender, t.initTaskHash, t.minDeposit, t.randomBitsHash); 278 | return true; 279 | } 280 | 281 | 282 | // @dev – selected solver submits a solution to the exchange 283 | // 1 -> 2 284 | // @param taskID – the task id. 285 | // @param solutionHash0 – the hash of the solution (could be true or false solution) 286 | // @param solutionHash1 – the hash of the solution (could be true or false solution) 287 | // @return – boolean 288 | function commitSolution(uint taskID, bytes32 solutionHash0, bytes32 solutionHash1) public returns (bool) { 289 | Task storage t = tasks[taskID]; 290 | require(t.selectedSolver == msg.sender); 291 | require(t.state == State.SolverSelected); 292 | require(block.number < t.taskCreationBlockNumber.add(TIMEOUT)); 293 | Solution storage s = solutions[taskID]; 294 | s.solutionHash0 = solutionHash0; 295 | s.solutionHash1 = solutionHash1; 296 | s.solverConvicted = false; 297 | t.state = State.SolutionComitted; 298 | emit SolutionsCommitted(taskID, t.minDeposit, t.codeType, t.storageType, t.storageAddress); 299 | return true; 300 | } 301 | 302 | // @dev – selected solver revealed his random bits prematurely 303 | // @param taskID – The task id. 304 | // @param randomBits – bits whose hash is the commited randomBitsHash of this task 305 | // @return – boolean 306 | function prematureReveal(uint taskID, uint originalRandomBits) public returns (bool) { 307 | Task storage t = tasks[taskID]; 308 | require(t.state == State.SolverSelected); 309 | require(block.number < t.taskCreationBlockNumber.add(TIMEOUT)); 310 | require(t.randomBitsHash == keccak256(abi.encodePacked(originalRandomBits))); 311 | uint bondedDeposit = t.bondedDeposits[t.selectedSolver]; 312 | delete t.bondedDeposits[t.selectedSolver]; 313 | deposits[msg.sender] = deposits[msg.sender].add(bondedDeposit/2); 314 | token.burn(bondedDeposit/2); 315 | emit SolverDepositBurned(t.selectedSolver, taskID); 316 | 317 | // Reset task data to selected another solver 318 | t.state = State.TaskInitialized; 319 | t.selectedSolver = 0x0; 320 | t.taskCreationBlockNumber = block.number; 321 | emit TaskCreated(taskID, t.minDeposit, t.taskCreationBlockNumber, t.reward, 1, t.codeType, t.storageType, t.storageAddress); 322 | 323 | return true; 324 | } 325 | 326 | function taskGiverTimeout(uint taskID) public { 327 | Task storage t = tasks[taskID]; 328 | require(msg.sender == t.owner); 329 | Solution storage s = solutions[taskID]; 330 | require(s.solutionHash0 == 0x0 && s.solutionHash1 == 0x0); 331 | require(block.number > t.taskCreationBlockNumber.add(TIMEOUT)); 332 | moveBondedDepositToJackpot(taskID, t.selectedSolver); 333 | t.state = State.TaskTimeout; 334 | } 335 | 336 | // @dev – verifier submits a challenge to the solution provided for a task 337 | // verifiers can call this until task giver changes state or timeout 338 | // @param taskID – the task id. 339 | // @param intentHash – submit hash of even or odd number to designate which solution is correct/incorrect. 340 | // @return – boolean 341 | function commitChallenge(uint taskID, bytes32 intentHash) public returns (bool) { 342 | Task storage t = tasks[taskID]; 343 | require(t.state == State.SolutionComitted); 344 | 345 | bondDeposit(taskID, msg.sender, t.minDeposit); 346 | t.challenges[msg.sender] = intentHash; 347 | return true; 348 | } 349 | 350 | // @dev – verifiers can call this until task giver changes state or timeout 351 | // @param taskID – the task id. 352 | // @param intent – submit 0 to challenge solution0, 1 to challenge solution1, anything else challenges both 353 | // @return – boolean 354 | function revealIntent(uint taskID, uint intent) public returns (bool) { 355 | require(tasks[taskID].challenges[msg.sender] == keccak256(abi.encodePacked(intent))); 356 | require(tasks[taskID].state == State.ChallengesAccepted); 357 | uint solution = 0; 358 | uint position = 0; 359 | if (intent == 0) { // Intent determines which solution the verifier is betting is deemed incorrect 360 | position = solutions[taskID].solution0Challengers.length; 361 | solutions[taskID].solution0Challengers.push(msg.sender); 362 | } else if (intent == 1) { 363 | position = solutions[taskID].solution1Challengers.length; 364 | solutions[taskID].solution1Challengers.push(msg.sender); 365 | solution = 1; 366 | } 367 | position = solutions[taskID].allChallengers.length; 368 | solutions[taskID].allChallengers.push(msg.sender); 369 | 370 | delete tasks[taskID].challenges[msg.sender]; 371 | emit VerificationCommitted(msg.sender, tasks[taskID].jackpotID, solution, position); 372 | return true; 373 | } 374 | 375 | // @dev – solver reveals which solution they say is the correct one 376 | // 4 -> 5 377 | // @param taskID – the task id. 378 | // @param solution0Correct – determines if solution0Hash is the correct solution 379 | // @param originalRandomBits – original random bits for sake of commitment. 380 | // @return – boolean 381 | function revealSolution(uint taskID, bool solution0Correct, uint originalRandomBits) public { 382 | Task storage t = tasks[taskID]; 383 | require(t.randomBitsHash == keccak256(abi.encodePacked(originalRandomBits))); 384 | require(t.state == State.IntentsRevealed); 385 | require(t.selectedSolver == msg.sender); 386 | solutions[taskID].solution0Correct = solution0Correct; 387 | t.state = State.SolutionRevealed; 388 | t.randomBits = originalRandomBits; 389 | if (isForcedError(originalRandomBits)) { // this if statement will make this function tricky to test 390 | rewardJackpot(taskID); 391 | t.finalityCode = 2; 392 | t.state = State.TaskFinalized; 393 | } else { 394 | emit SolutionRevealed(taskID, originalRandomBits); 395 | } 396 | } 397 | 398 | function isForcedError(uint randomBits) internal view returns (bool) { 399 | return (uint(keccak256(abi.encodePacked(randomBits, blockhash(block.number)))) < forcedErrorThreshold); 400 | } 401 | 402 | function rewardJackpot(uint taskID) internal { 403 | Task storage t = tasks[taskID]; 404 | Solution storage s = solutions[taskID]; 405 | t.jackpotID = setJackpotReceivers(s.allChallengers); 406 | 407 | payReward(taskID, t.owner);//Still compensating solver even though solution wasn't thoroughly verified, task giver recommended to not use solution 408 | } 409 | 410 | // verifier should be responsible for calling this first 411 | function runVerificationGame(uint taskID) public { 412 | Task storage t = tasks[taskID]; 413 | require(t.state == State.SolutionRevealed); 414 | Solution storage s = solutions[taskID]; 415 | if (s.solution0Correct) { 416 | verificationGame(taskID, t.selectedSolver, s.solution0Challengers[s.currentChallenger], s.solutionHash0); 417 | } else { 418 | verificationGame(taskID, t.selectedSolver, s.solution1Challengers[s.currentChallenger], s.solutionHash1); 419 | } 420 | s.currentChallenger = s.currentChallenger + 1; 421 | emit VerificationGame(t.selectedSolver, s.currentChallenger); 422 | } 423 | 424 | function verificationGame(uint taskID, address solver, address challenger, bytes32 solutionHash) internal { 425 | Task storage t = tasks[taskID]; 426 | uint size = 1; 427 | bytes32 gameID = IGameMaker(disputeResolutionLayer).make(taskID, solver, challenger, t.initTaskHash, solutionHash, 1, TIMEOUT); 428 | solutions[taskID].currentGame = gameID; 429 | } 430 | 431 | function finalizeTask(uint taskID) public { 432 | Task storage t = tasks[taskID]; 433 | Solution storage s = solutions[taskID]; 434 | 435 | require(s.currentChallenger >= s.solution0Challengers.length 436 | || s.currentChallenger >= s.solution1Challengers.length 437 | && IDisputeResolutionLayer(disputeResolutionLayer).status(s.currentGame) == uint(Status.SolverWon)); 438 | 439 | t.state = State.TaskFinalized; 440 | t.finalityCode = 1; // Task has been completed 441 | 442 | payReward(taskID, t.selectedSolver); 443 | } 444 | 445 | function getTaskFinality(uint taskID) public view returns (uint) { 446 | return tasks[taskID].finalityCode; 447 | } 448 | 449 | function getVMParameters(uint taskID) public view returns (uint8, uint8, uint8, uint8, uint8) { 450 | VMParameters storage params = vmParams[taskID]; 451 | return (params.stackSize, params.memorySize, params.globalsSize, params.tableSize, params.callSize); 452 | } 453 | 454 | function getTaskInfo(uint taskID) public view returns (address, bytes32, CodeType, StorageType, string, uint) { 455 | Task storage t = tasks[taskID]; 456 | return (t.owner, t.initTaskHash, t.codeType, t.storageType, t.storageAddress, taskID); 457 | } 458 | 459 | function getSolutionInfo(uint taskID) public view returns(uint, bytes32, bytes32, bytes32, CodeType, StorageType, string, address) { 460 | Task storage t = tasks[taskID]; 461 | Solution storage s = solutions[taskID]; 462 | return (taskID, s.solutionHash0, s.solutionHash1, t.initTaskHash, t.codeType, t.storageType, t.storageAddress, t.selectedSolver); 463 | } 464 | 465 | } 466 | -------------------------------------------------------------------------------- /contracts/JackpotManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "./TRU.sol"; 5 | 6 | contract JackpotManager { 7 | using SafeMath for uint; 8 | 9 | struct Jackpot { 10 | uint finalAmount; 11 | uint amount; 12 | address[] challengers; 13 | uint redeemedCount; 14 | } 15 | 16 | mapping(uint => Jackpot) jackpots;//keeps track of versions of jackpots 17 | 18 | uint internal currentJackpotID; 19 | TRU public token; 20 | 21 | event JackpotIncreased(uint amount); 22 | 23 | constructor (address _TRU) public { 24 | token = TRU(_TRU); 25 | } 26 | 27 | // @dev – returns the current jackpot 28 | // @return – the jackpot. 29 | function getJackpotAmount() view public returns (uint) { 30 | return jackpots[currentJackpotID].amount; 31 | } 32 | 33 | function getCurrentJackpotID() view public returns (uint) { 34 | return currentJackpotID; 35 | } 36 | 37 | //// @dev – allows a uer to donate to the jackpot. 38 | //// @return – the updated jackpot amount. 39 | //function donateToJackpot() public payable { 40 | // jackpots[currentJackpotID].amount = jackpots[currentJackpotID].amount.add(msg.value); 41 | // emit JackpotIncreased(msg.value); 42 | //} 43 | 44 | function increaseJackpot(uint _amount) public payable { 45 | jackpots[currentJackpotID].amount = jackpots[currentJackpotID].amount.add(_amount); 46 | emit JackpotIncreased(_amount); 47 | } 48 | 49 | function setJackpotReceivers(address[] _challengers) internal returns (uint) { 50 | jackpots[currentJackpotID].finalAmount = jackpots[currentJackpotID].amount; 51 | jackpots[currentJackpotID].challengers = _challengers; 52 | currentJackpotID = currentJackpotID + 1; 53 | return currentJackpotID - 1; 54 | } 55 | 56 | function receiveJackpotPayment(uint jackpotID, uint index) public { 57 | Jackpot storage j = jackpots[jackpotID]; 58 | require(j.challengers[index] == msg.sender); 59 | 60 | uint amount = j.finalAmount.div(2**(index+1)); 61 | //transfer jackpot payment 62 | //msg.sender.transfer(amount); 63 | token.mint(msg.sender, amount); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | 5 | /** 6 | * @title Migrations 7 | * @dev This is a truffle contract, needed for truffle integration, not meant for use by Zeppelin users. 8 | */ 9 | contract Migrations is Ownable { 10 | uint256 public lastCompletedMigration; 11 | 12 | function setCompleted(uint256 completed) onlyOwner public { 13 | lastCompletedMigration = completed; 14 | } 15 | 16 | function upgrade(address newAddress) onlyOwner public { 17 | Migrations upgraded = Migrations(newAddress); 18 | upgraded.setCompleted(lastCompletedMigration); 19 | } 20 | } -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # Contracts 2 | 3 | ## DepositsManager.sol 4 | This contract is essentially described by its name. This contract handles deposits made by stakers in the incentive layer. Users only call `makeDeposit()` or fallback `function ()` in order to become staked. 5 | 6 | ## JackpotManager.sol 7 | Jackpot is donated to through `donateToJackpot() payable`. The function adds to the current `jackpots[currentJackpotID]` and emits a `JackpotIncreased(msg.value)` event. 8 | 9 | Verifiers receive the jackpot by calling the `receiveJackpotPayment(uint jackpotID, uint receiverGroup, uint index)` with the jackpot they are trying to claim and their index in the list of jackpot receivers. In the current implementation the jackpot is split _equally_ among the verifiers that show up. **TODO: change the jackpot to pay out with the exponential drop-off instead to prevent the jackpot being zeroed.** 10 | 11 | 12 | ## IncentiveLayer.sol 13 | This file defines `contract IncentiveLayer is JackpotManager, DepositsManager`. The contract handles all functions of the incentive layer from task givers submitting tasks to starting the dispute resolution layer. A task giver arrives and is forced to have made a deposit through `makeDeposit()` before submitting a task. 14 | The task giver can then call `createTask(uint minDeposit, bytes32 taskData, uint numBlocks) payable` specifying the minimum deposit that all stakers (solvers and verifiers) must make to take part in this task and the number of blocks to adjust for task difficulty. 15 | **TODO: remove the need for task giver to also stake money in order to submit a task. Related to them having to do a state update after commit challenges** 16 | 17 | To become a solver, stakers all try to call `registerForTask(uint taskID, bytes32 randomBitHash)` with only the first staker being chosen. All other calls to the function fail because the requirement of no solver being present is no longer true. Along with registering for the task the staked amount that they have put up is now bonded to this task. This prevents them for registering for multiple tasks with the same deposit. When the computation is complete, the solver calls `commitSolution(uint taskID, bytes32 solutionHash0, bytes32 solutionHash1)` with a commitment to two different answers. One is the right answer and the other is the wrong answer which is used in case of a force error. The task state updates to indicate that the a committed solution was submitted by the solver. 18 | 19 | One the solution is commited to, other stakes can call `commitChallenge(uint taskID, bytes32 intentHash)` in order to register their intent to challenge or not to for a particular task. We want that verifiers also indicated their intention not to challenge so that other verifiers can't monitor the challenges and only show up for forced error. Instead, they see `commitChallenge` calls for every task and have to check every solution. The task giver in this case has to force the state transition of the task once verifiers have challenged. This is why the task giver currently is required to initiate a state transition when verifiers have challenge. 20 | **TODO: remove this requirement.** 21 | 22 | Verifers reveal intent with `revealIntent(uint taskID, uint intent)` which tells which solution they want to challenge, the first or the second. 23 | **TODO: don't know if current implementation forced devlaring intent to not challenge either which might need to be added explicitly for clarity of readers.** 24 | 25 | Intents are revealed and the solutions are also revealed by the solver calling `revealSolution(uint taskID, bool solution0Correct, uint originalRandomBits)`. This function also reveals random bits and checks if a forced error is in effect. If it is in effect then the jackpot is automatically paid out through `rewardJackpot(uint taskID)` to all verifiers that challenged this task. 26 | 27 | Finally, the verification game is called by the verifier, `runVerificationGame(uint taskID)`. Once the game is complete the task giver calls `finalizeTask(uint taskID)`. The function only finalized the task once every challenger has played the verification game with the solver. Only then the task is finalized and the reward is distributed. 28 | 29 | Stakers can finaly call `unbondDeposit` in order to get their stake out of this task to be used in another task. 30 | -------------------------------------------------------------------------------- /contracts/RewardsManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "./TRU.sol"; 5 | 6 | contract RewardsManager { 7 | using SafeMath for uint; 8 | 9 | mapping(uint => uint) public rewards; 10 | address public owner; 11 | TRU public token; 12 | 13 | event RewardDeposit(uint indexed task, address who, uint amount); 14 | event RewardClaimed(uint indexed task, address who, uint amount); 15 | 16 | modifier onlyOwner() { 17 | require(msg.sender == owner); 18 | _; 19 | } 20 | 21 | constructor(address _tru) public { 22 | owner = msg.sender; 23 | token = TRU(_tru); 24 | } 25 | 26 | function getTaskReward(uint taskID) public returns (uint) { 27 | return rewards[taskID]; 28 | } 29 | 30 | function depositReward(uint taskID, uint reward) public returns (bool) { 31 | require(token.allowance(msg.sender, address(this)) >= reward); 32 | token.transferFrom(msg.sender, address(this), reward); 33 | 34 | rewards[taskID] = rewards[taskID].add(reward); 35 | emit RewardDeposit(taskID, msg.sender, reward); 36 | return true; 37 | } 38 | 39 | function payReward(uint taskID, address to) internal returns (bool) { 40 | require(rewards[taskID] > 0); 41 | uint payout = rewards[taskID]; 42 | rewards[taskID] = 0; 43 | 44 | token.transfer(to, payout); 45 | emit RewardClaimed(taskID, to, payout); 46 | return true; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /contracts/TRU.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; 4 | import "zeppelin-solidity/contracts/token/ERC20/BurnableToken.sol"; 5 | 6 | contract TRU is MintableToken, BurnableToken { 7 | string public constant name = "TRU Token"; 8 | string public constant symbol = "TRU"; 9 | uint8 public constant decimals = 8; 10 | 11 | // event Burn(address indexed from, uint256 amount); 12 | 13 | function () public payable { 14 | if (msg.value > 0) { 15 | balances[msg.sender] += msg.value; 16 | totalSupply_ = totalSupply_.add(msg.value); 17 | } 18 | } 19 | 20 | // function burn(address _from, uint _amount) onlyOwner returns (bool) { 21 | // require(balances[_from] >= _amount); 22 | // totalSupply_ = totalSupply_.sub(_amount); 23 | // balances[_from] = balances[_from].sub(_amount); 24 | // emit Burn(_from, _amount); 25 | // return true; 26 | // } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/TestJackpotManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./JackpotManager.sol"; 4 | 5 | contract TestJackpotManager is JackpotManager { 6 | 7 | constructor (address _TRU) JackpotManager(_TRU) { } 8 | 9 | function distributeJackpot(address[] challengers) public { 10 | setJackpotReceivers(challengers); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/TestRewardsManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./RewardsManager.sol"; 4 | 5 | contract TestRewardsManager is RewardsManager { 6 | 7 | constructor (address _tru) RewardsManager(_tru) { } 8 | 9 | function testPayReward(uint taskID, address to) public returns (bool) { 10 | return payReward(taskID, to); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const IncentiveLayer = artifacts.require("./IncentiveLayer.sol") 2 | const TestJackpotManager = artifacts.require("./TestJackpotManager.sol") 3 | const TestRewardsManager = artifacts.require("./TestRewardsManager.sol") 4 | const DisputeResolutionLayerDummy = artifacts.require("./DisputeResolutionLayerDummy.sol") 5 | const TRU = artifacts.require('./TRU.sol') 6 | const ExchangeRateOracle = artifacts.require('./ExchangeRateOracle.sol') 7 | const RewardsManager = artifacts.require('./RewardsManager.sol') 8 | 9 | module.exports = async (deployer, network, accounts) => { 10 | await deployer.deploy(DisputeResolutionLayerDummy) 11 | await deployer.deploy(TRU) 12 | await deployer.deploy(TestRewardsManager, TRU.address) 13 | await deployer.deploy(TestJackpotManager, TRU.address) 14 | await deployer.deploy(ExchangeRateOracle) 15 | await deployer.deploy(IncentiveLayer, TRU.address, ExchangeRateOracle.address, DisputeResolutionLayerDummy.address, {from: accounts[5]}); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /migrations/3_export.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const IncentiveLayer = artifacts.require("./IncentiveLayer.sol") 3 | 4 | module.exports = (deployer, network) => { 5 | // let exportedContracts = {} 6 | // 7 | // let contracts = [TaskExchange] 8 | // 9 | // contracts.forEach((contract) => { 10 | // 11 | // exportedContracts[contract.contractName] = { 12 | // abi: contract.abi, 13 | // address: contract.address 14 | // } 15 | // }) 16 | // 17 | // if (!fs.existsSync(__dirname + "/../export/")){ 18 | // fs.mkdirSync(__dirname + "/../export/") 19 | // } 20 | // 21 | // let path = __dirname + "/../export/" + network+ ".json" 22 | // 23 | // fs.writeFile(path, JSON.stringify(exportedContracts), (e) => {if(e) console.error(e) }) 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "truebit-contracts", 3 | "version": "0.0.1", 4 | "description": "Incentive Layer for TrueBit protocol", 5 | "main": "truffle.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "truffle test", 11 | "migrate": "truffle migrate --reset" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/TrueBitFoundation/truebit-contracts.git" 16 | }, 17 | "author": "TrueBit", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/TrueBitFoundation/truebit-contracts/issues" 21 | }, 22 | "homepage": "https://github.com/TrueBitFoundation/truebit-contracts#readme", 23 | "devDependencies": { 24 | "ganache-cli": "^6.0.3", 25 | "truffle": "^4.1.11", 26 | "web3": "^1.0.0-beta.24" 27 | }, 28 | "dependencies": { 29 | "bignumber.js": "^6.0.0", 30 | "zeppelin-solidity": "^1.6.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /task_exchange.js: -------------------------------------------------------------------------------- 1 | const TaskExchange = artifacts.require('./TaskExchange.sol') 2 | const Web3 = require('web3') 3 | const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) 4 | const DisputeResolutionLayerDummy = artifacts.require('./DisputeResolutionLayerDummy.sol') 5 | 6 | const timeout = require('./helpers/timeout') 7 | const mineBlocks = require('./helpers/mineBlocks') 8 | 9 | const BigNumber = require('bignumber.js') 10 | 11 | contract('TaskExchange', function(accounts) { 12 | let taskExchange, deposit, bond, tx, log, taskID, intent, oldBalance 13 | 14 | const taskGiver = accounts[1] 15 | const solver = accounts[2] 16 | const verifier = accounts[3] 17 | 18 | const minDeposit = 500 19 | const reward = web3.utils.toWei('1', 'ether') 20 | const randomBits = 12345 21 | 22 | context('incentive layer', () => { 23 | 24 | before(async () => { 25 | taskExchange = await TaskExchange.new() 26 | oldBalance = await web3.eth.getBalance(solver) 27 | disputeRes = await DisputeResolutionLayerDummy.at(DisputeResolutionLayerDummy.address) 28 | }) 29 | 30 | it("should have participants make deposits", async () => { 31 | // taskGiver makes a deposit 32 | await taskExchange.makeDeposit({from: taskGiver, value: 1000}) 33 | deposit = await taskExchange.getDeposit.call(taskGiver) 34 | assert(deposit.eq(1000)) 35 | 36 | // to-be solver makes a deposit 37 | await taskExchange.makeDeposit({from: solver, value: 1000}) 38 | deposit = await taskExchange.getDeposit.call(solver) 39 | assert(deposit.eq(1000)) 40 | 41 | // to-be verifier makes a deposit 42 | await taskExchange.makeDeposit({from: verifier, value: 1000}) 43 | deposit = await taskExchange.getDeposit.call(verifier) 44 | assert(deposit.eq(1000)) 45 | }) 46 | 47 | it("should create task", async () => { 48 | // taskGiver creates a task. 49 | // they bond part of their deposit. 50 | tx = await taskExchange.createTask(minDeposit, 0x010203040506070809, [20, 40, 60], 9, DisputeResolutionLayerDummy.address, {from: taskGiver, value: reward}) 51 | bond = await taskExchange.getBondedDeposit.call(0, taskGiver) 52 | assert(bond.eq(500)) 53 | deposit = await taskExchange.getDeposit.call(taskGiver) 54 | assert(deposit.eq(500)) 55 | 56 | log = tx.logs.find(log => log.event === 'DepositBonded') 57 | assert(log.args.taskID.eq(0)) 58 | assert.equal(log.args.account, taskGiver) 59 | assert(log.args.amount.eq(minDeposit)) 60 | 61 | log = tx.logs.find(log => log.event === 'TaskCreated') 62 | assert(log.args.taskID.isZero()) 63 | assert(log.args.minDeposit.eq(minDeposit)) 64 | 65 | taskID = log.args.taskID 66 | }) 67 | 68 | it("should select a solver", async () => { 69 | // solver registers for the task. 70 | // they bond part of their deposit. 71 | tx = await taskExchange.registerForTask(taskID, {from: solver}) 72 | 73 | log = tx.logs.find(log => log.event === 'DepositBonded') 74 | assert(log.args.taskID.eq(taskID)) 75 | assert.equal(log.args.account, solver) 76 | assert(log.args.amount.eq(minDeposit)) 77 | deposit = await taskExchange.getDeposit.call(solver) 78 | assert(deposit.eq(500)) 79 | 80 | log = tx.logs.find(log => log.event === 'SolverSelected') 81 | assert(log.args.taskID.eq(taskID)) 82 | assert.equal(log.args.solver, solver) 83 | assert(log.args.minDeposit.eq(minDeposit)) 84 | }) 85 | 86 | it("should commit a solution", async () => { 87 | // solver commits their solutions. 88 | tx = await taskExchange.commitSolution(taskID, web3.utils.soliditySha3(0x12345), web3.utils.soliditySha3(0x12345), {from: solver}) 89 | log = tx.logs.find(log => log.event === 'SolutionCommitted') 90 | assert(log.args.taskID.eq(taskID)) 91 | assert(log.args.minDeposit.eq(minDeposit)) 92 | }) 93 | 94 | it("should commit to challenge in a verification game", async () => { 95 | // verifier commits a challenge 96 | // they bond part of their deposit. 97 | 98 | tx = await taskExchange.commitChallenge(taskID, {from: verifier}) 99 | log = tx.logs.find(log => log.event === 'DepositBonded') 100 | assert(log.args.taskID.eq(taskID)) 101 | assert.equal(log.args.account, verifier) 102 | assert(log.args.amount.eq(minDeposit)) 103 | deposit = await taskExchange.getDeposit.call(verifier) 104 | assert(deposit.eq(500)) 105 | }) 106 | 107 | it("should convict verifier", async () => { 108 | tx = await taskExchange.convictVerifier(taskID, {from: solver}) 109 | }) 110 | 111 | it("should finalize task", async () => { 112 | await mineBlocks(web3, 65) 113 | 114 | await taskExchange.finalizeTask(taskID, {from: solver}) 115 | }) 116 | 117 | it('should unbond solver deposit', async () => { 118 | 119 | await taskExchange.unbondDeposit(taskID, {from: solver}) 120 | assert((await taskExchange.getDeposit.call(solver)).eq(1500)) 121 | }) 122 | 123 | it('should unbond task giver deposit', async () => { 124 | await taskExchange.unbondDeposit(taskID, {from: taskGiver}) 125 | assert((await taskExchange.getDeposit.call(taskGiver)).eq(1000)) 126 | }) 127 | 128 | //Uncomment out after making deposit amounts larger 129 | it('should be higher than original balance', async () => { 130 | const newBalance = await web3.eth.getBalance(solver) 131 | 132 | //console.log("Old balance: " + oldBalance) 133 | //console.log("New Balance: " + newBalance) 134 | 135 | assert((new BigNumber(oldBalance)).isLessThan(new BigNumber(newBalance))) 136 | }) 137 | }) 138 | }) 139 | -------------------------------------------------------------------------------- /test/deposits_manager.js: -------------------------------------------------------------------------------- 1 | const TRU = artifacts.require('TRU.sol'); 2 | 3 | const ExchangeRateOracle = artifacts.require('./ExchangeRateOracle.sol') 4 | const IncentiveLayer = artifacts.require('IncentiveLayer.sol') 5 | const DisputeResolutionLayer = artifacts.require('./DisputeResolutionLayerDummy.sol') 6 | const Web3 = require('web3'); 7 | const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) 8 | 9 | contract('DepositsManager', function(accounts) { 10 | 11 | let depositsManager, token, oracle, disputeResolutionLayer 12 | 13 | beforeEach(async () => { 14 | token = await TRU.new() 15 | oracle = await ExchangeRateOracle.new() 16 | disputeResolutionLayer = await DisputeResolutionLayer.new({from: accounts[5]}) 17 | depositsManager = await IncentiveLayer.new(token.address, oracle.address, disputeResolutionLayer.address) 18 | // get some tokens and allow transfer 19 | await token.sendTransaction({from: accounts[1], value: web3.utils.toWei('1', 'ether')}) 20 | await token.approve(depositsManager.address, 1000, {from: accounts[1]}) 21 | }) 22 | 23 | 24 | describe('makeDeposit', () => { 25 | it('should make a deposit', async () => { 26 | let tx = await depositsManager.makeDeposit(1000, {from: accounts[1]}) 27 | log = tx.logs.find(log => log.event === 'DepositMade') 28 | assert.equal(log.args.who, accounts[1]) 29 | assert(log.args.amount.eq(1000)) 30 | 31 | const deposit = await depositsManager.getDeposit.call(accounts[1]) 32 | assert(deposit.eq(1000)) 33 | }) 34 | }) 35 | 36 | describe('withdrawDeposit', () => { 37 | it("should withdraw the desired amount from the account's deposit", async () => { 38 | let deposit 39 | 40 | // make a deposit 41 | await depositsManager.makeDeposit(1000, {from: accounts[1]}) 42 | deposit = await depositsManager.getDeposit.call(accounts[1]) 43 | assert(deposit.eq(1000)) 44 | 45 | // withdraw part of the deposit 46 | const tx = await depositsManager.withdrawDeposit(500, {from: accounts[1]}) 47 | log = tx.logs.find(log => log.event === 'DepositWithdrawn') 48 | assert.equal(log.args.who, accounts[1]) 49 | assert(log.args.amount.eq(500)) 50 | 51 | deposit = await depositsManager.getDeposit.call(accounts[1]) 52 | assert(deposit.eq(500)) 53 | }); 54 | 55 | it("should throw an error if withdrawing more than existing deposit", async () => { 56 | let deposit; 57 | 58 | // make a deposit 59 | await depositsManager.makeDeposit(1000, {from: accounts[1]}) 60 | deposit = await depositsManager.getDeposit.call(accounts[1]) 61 | assert(deposit.eq(1000)) 62 | 63 | // withdraw part of the deposit 64 | try { 65 | await depositsManager.withdrawDeposit(2000, {from: accounts[1]}) 66 | } catch(error) { 67 | assert.match(error, /VM Exception [a-zA-Z0-9 ]+/); 68 | } 69 | 70 | // deposit should not have changed. 71 | deposit = await depositsManager.getDeposit.call(accounts[1]) 72 | assert(deposit.eq(1000)) 73 | }) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /test/helpers/mineBlocks.js: -------------------------------------------------------------------------------- 1 | module.exports = async (web3, n) => { 2 | for(let i = 0; i < n; i++) { 3 | await new Promise((resolve, reject) => { 4 | web3.eth.currentProvider.send({ 5 | jsonrpc: '2.0', 6 | method: 'evm_mine', 7 | params: [], 8 | id: 0, 9 | }, () => {resolve()}) 10 | }) 11 | } 12 | } -------------------------------------------------------------------------------- /test/helpers/timeout.js: -------------------------------------------------------------------------------- 1 | module.exports = async (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms)) -------------------------------------------------------------------------------- /test/incentive_layer.js: -------------------------------------------------------------------------------- 1 | const IncentiveLayer = artifacts.require('./IncentiveLayer.sol') 2 | const TRU = artifacts.require('./TRU.sol') 3 | const ExchangeRateOracle = artifacts.require('./ExchangeRateOracle.sol') 4 | 5 | const DisputeResolutionLayer = artifacts.require('./DisputeResolutionLayerDummy.sol') 6 | 7 | const Web3 = require('web3') 8 | const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) 9 | 10 | const timeout = require('./helpers/timeout') 11 | const mineBlocks = require('./helpers/mineBlocks') 12 | 13 | const BigNumber = require('bignumber.js') 14 | 15 | contract('IncentiveLayer', function(accounts) { 16 | 17 | let incentiveLayer, deposit, bond, tx, log, taskID, intent, oldBalance, token, oracle, balance, disputeResolutionLayer 18 | 19 | const taskGiver = accounts[1] 20 | const solver = accounts[2] 21 | const verifier = accounts[3] 22 | const randomUser = accounts[4] 23 | const backupSolver = accounts[5] 24 | const oracleOwner = accounts[6] 25 | 26 | const minDeposit = 100000 27 | const maxDifficulty = 50000 28 | //const reward = web3.utils.toWei('1', 'ether') 29 | const reward = 100000000 30 | const randomBits = 12345 31 | const TRUperUSD = 2000 32 | 33 | context('incentive layer', () => { 34 | 35 | before(async () => { 36 | token = await TRU.new({from: accounts[5]}); 37 | oracle = await ExchangeRateOracle.new({from: oracleOwner}); 38 | 39 | disputeResolutionLayer = await DisputeResolutionLayer.new({from: accounts[5]}) 40 | incentiveLayer = await IncentiveLayer.new(token.address, oracle.address, disputeResolutionLayer.address) 41 | 42 | await token.transferOwnership(incentiveLayer.address, {from: accounts[5]}) 43 | let owner = await token.owner(); 44 | assert.equal(owner, incentiveLayer.address); 45 | 46 | 47 | oldBalance = await web3.eth.getBalance(solver) 48 | 49 | tx = await oracle.updateExchangeRate(TRUperUSD, {from: oracleOwner}) 50 | await token.sendTransaction({from: taskGiver, value: web3.utils.toWei('1', 'ether')}) 51 | await token.sendTransaction({from: solver, value: web3.utils.toWei('1', 'ether')}) 52 | await token.sendTransaction({from: verifier, value: web3.utils.toWei('1', 'ether')}) 53 | 54 | await token.approve(incentiveLayer.address, reward + (minDeposit * 5), {from: taskGiver}) 55 | await token.approve(incentiveLayer.address, minDeposit, {from: solver}) 56 | await token.approve(incentiveLayer.address, minDeposit, {from: verifier}) 57 | 58 | }) 59 | 60 | it("should have participants make deposits", async () => { 61 | // taskGiver makes a deposit to fund taxes 62 | //await incentiveLayer.makeDeposit(reward + (minDeposit * 5), {from: taskGiver}) 63 | await incentiveLayer.makeDeposit(minDeposit * 5, {from: taskGiver}) 64 | deposit = await incentiveLayer.getDeposit.call(taskGiver) 65 | //assert(deposit.eq(reward + (minDeposit * 5))) 66 | assert(deposit.eq(minDeposit * 5)) 67 | let layerBalance = await token.balanceOf(incentiveLayer.address); 68 | assert(layerBalance.eq(minDeposit * 5)); 69 | 70 | // to-be solver makes a deposit 71 | await incentiveLayer.makeDeposit(minDeposit, {from: solver}) 72 | deposit = await incentiveLayer.getDeposit.call(solver) 73 | assert(deposit.eq(minDeposit)) 74 | 75 | // to-be verifier makes a deposit 76 | await incentiveLayer.makeDeposit(minDeposit, {from: verifier}) 77 | deposit = await incentiveLayer.getDeposit.call(verifier) 78 | assert(deposit.eq(minDeposit)) 79 | }) 80 | 81 | it("should create task", async () => { 82 | // taskGiver creates a task. 83 | // they bond part of their deposit. 84 | 85 | tx = await incentiveLayer.createTask(0x0, 0, 0, 0x0, maxDifficulty, reward, {from: taskGiver}) 86 | 87 | //log = tx.logs.find(log => log.event === 'DepositBonded') 88 | //assert(log.args.taskID.eq(0)) 89 | //assert.equal(log.args.account, taskGiver) 90 | //assert(log.args.amount.eq(minDeposit)) 91 | 92 | log = tx.logs.find(log => log.event === 'TaskCreated') 93 | assert(log.args.taskID.isZero()) 94 | assert(log.args.minDeposit.eq(minDeposit)) 95 | 96 | //assert(log.args.blockNumber.eq(5)) 97 | assert(log.args.reward.eq(reward)) 98 | assert(log.args.tax.eq(minDeposit * 5)) 99 | assert(log.args.codeType.eq(0)) 100 | assert(log.args.storageType.eq(0)) 101 | assert(log.args.storageAddress == 0x0) 102 | 103 | deposit = await incentiveLayer.getDeposit.call(taskGiver) 104 | assert(deposit.eq(minDeposit * 5)) 105 | 106 | taskID = log.args.taskID 107 | balance = await incentiveLayer.getTaskReward.call(taskID) 108 | assert(balance.eq(reward)) 109 | }) 110 | 111 | 112 | it("should get vm parameters", async () => { 113 | let p = await incentiveLayer.getVMParameters.call(taskID) 114 | let params = { 115 | stackSize: p[0], 116 | memorySize: p[1], 117 | globalsSize: p[2], 118 | tableSize: p[3], 119 | callSize: p[4] 120 | } 121 | 122 | //Testing for default parameters 123 | assert.equal(params.stackSize, 14) 124 | assert.equal(params.memorySize, 16) 125 | assert.equal(params.globalsSize, 8) 126 | assert.equal(params.tableSize, 8) 127 | assert.equal(params.callSize, 10) 128 | }) 129 | 130 | it("should get task info", async () => { 131 | let t = await incentiveLayer.getTaskInfo.call(taskID) 132 | let taskInfo = { 133 | taskGiver: t[0], 134 | taskInitHash: t[1], 135 | codeType: t[2], 136 | storageType: t[3], 137 | storageAddress: t[4], 138 | taskID: t[5].toNumber() 139 | } 140 | 141 | assert.equal(taskInfo.taskGiver, taskGiver) 142 | assert.equal(taskInfo.taskInitHash, 0x0) 143 | assert.equal(taskInfo.codeType, 0) 144 | assert.equal(taskInfo.storageType, 0) 145 | assert.equal(taskInfo.taskID, taskID) 146 | }) 147 | 148 | it("should select a solver", async () => { 149 | // solver registers for the task. 150 | // they bond part of their deposit. 151 | tx = await incentiveLayer.registerForTask(taskID, web3.utils.soliditySha3(randomBits), {from: solver}) 152 | 153 | log = tx.logs.find(log => log.event === 'DepositBonded') 154 | assert(log.args.taskID.eq(taskID)) 155 | assert.equal(log.args.account, solver) 156 | assert(log.args.amount.eq(minDeposit)) 157 | deposit = await incentiveLayer.getDeposit.call(solver) 158 | assert(deposit.eq(0)) 159 | 160 | log = tx.logs.find(log => log.event === 'SolverSelected') 161 | assert(log.args.taskID.eq(taskID)) 162 | assert.equal(log.args.solver, solver) 163 | assert.equal(log.args.taskData, 0x0) 164 | assert(log.args.minDeposit.eq(minDeposit)) 165 | 166 | assert.equal(log.args.randomBitsHash, web3.utils.soliditySha3(randomBits)) 167 | 168 | deposit = await incentiveLayer.getDeposit.call(taskGiver) 169 | assert(deposit.eq(0)) 170 | }) 171 | 172 | it("should commit a solution", async () => { 173 | // solver commits their solutions. 174 | tx = await incentiveLayer.commitSolution(taskID, web3.utils.soliditySha3(0x0), web3.utils.soliditySha3(0x12345), {from: solver}) 175 | log = tx.logs.find(log => log.event === 'SolutionsCommitted') 176 | assert(log.args.taskID.eq(taskID)) 177 | assert(log.args.minDeposit.eq(minDeposit)) 178 | 179 | assert.equal(log.args.storageAddress, 0x0) 180 | assert.equal(log.args.storageType, 0) 181 | assert.equal(log.args.codeType, 0) 182 | }) 183 | 184 | it("should get solution info", async () => { 185 | let s = await incentiveLayer.getSolutionInfo.call(taskID) 186 | 187 | let solutionInfo = { 188 | taskID: s[0].toNumber(), 189 | solutionHash0: s[1], 190 | solutionHash1: s[2], 191 | taskInitHash: s[3], 192 | codeType: s[4], 193 | storageType: s[5], 194 | storageAddress: s[6], 195 | solver: s[7] 196 | } 197 | 198 | assert.equal(solutionInfo.taskID, taskID) 199 | assert.equal(solutionInfo.solutionHash0, web3.utils.soliditySha3(0x0)) 200 | assert.equal(solutionInfo.solutionHash1, web3.utils.soliditySha3(0x12345)) 201 | assert.equal(solutionInfo.taskInitHash, 0x0) 202 | assert.equal(solutionInfo.codeType, 0) 203 | assert.equal(solutionInfo.storageType, 0) 204 | assert.equal(solutionInfo.storageAddress, 0x0) 205 | assert.equal(solutionInfo.solver, solver) 206 | }) 207 | 208 | it("should commit a challenge", async () => { 209 | // verifier commits a challenge 210 | // they bond part of their deposit. 211 | intent = 0 212 | tx = await incentiveLayer.commitChallenge(taskID, web3.utils.soliditySha3(intent), {from: verifier}) 213 | log = tx.logs.find(log => log.event === 'DepositBonded') 214 | assert(log.args.taskID.eq(taskID)) 215 | assert.equal(log.args.account, verifier) 216 | assert(log.args.amount.eq(minDeposit)) 217 | deposit = await incentiveLayer.getDeposit.call(verifier) 218 | assert(deposit.eq(0)) 219 | }) 220 | 221 | it("should change task state to 3", async () => { 222 | 223 | await mineBlocks(web3, 20) 224 | 225 | // solver triggers task state transition as he wants solution to be finalized 226 | tx = await incentiveLayer.changeTaskState(taskID, 3, {from: solver}) 227 | log = tx.logs.find(log => log.event === 'TaskStateChange') 228 | assert(log.args.taskID.eq(taskID)) 229 | assert(log.args.state.eq(3)) 230 | }) 231 | 232 | it("should reveal intent", async () => { 233 | // state 3: challenges accepted 234 | // verifier reveals their intent 235 | await incentiveLayer.revealIntent(taskID, intent, {from: verifier}) 236 | 237 | await mineBlocks(web3, 10) 238 | 239 | // verifier triggers task state transition as they want to challenge 240 | tx = await incentiveLayer.changeTaskState(taskID, 4, {from: verifier}) 241 | log = tx.logs.find(log => log.event === 'TaskStateChange') 242 | assert(log.args.taskID.eq(taskID)) 243 | assert(log.args.state.eq(4)) 244 | }) 245 | 246 | it("should reveal solution", async () => { 247 | 248 | // state 4: intents revealed 249 | tx = await incentiveLayer.revealSolution(taskID, true, randomBits, {from: solver}) 250 | log = tx.logs.find(log => log.event === 'SolutionRevealed') 251 | if(log) { 252 | assert(log.args.taskID.eq(taskID)) 253 | assert(log.args.randomBits.eq(randomBits)) 254 | } else { 255 | assert((await incentiveLayer.getTaskFinality.call(taskID)).eq(2)) 256 | } 257 | }) 258 | 259 | it('should run verification game', async () => { 260 | tx = await incentiveLayer.runVerificationGame(taskID, {from: verifier}) 261 | 262 | log = tx.logs.find(log => log.event == 'VerificationGame') 263 | assert.equal(log.args.solver, solver) 264 | assert(log.args.currentChallenger.eq(1)) 265 | 266 | oldBalance = await token.balanceOf(solver) 267 | 268 | tx = await incentiveLayer.finalizeTask(taskID, {from: taskGiver}) 269 | 270 | log = tx.logs.find(log => log.event = 'RewardClaimed') 271 | assert(log.args.task.eq(taskID)) 272 | assert.equal(log.args.who, solver) 273 | assert(log.args.amount.eq(reward)) 274 | 275 | newBalance = await token.balanceOf(solver); 276 | assert(newBalance.gt(oldBalance)) 277 | 278 | assert((await incentiveLayer.getTaskFinality.call(taskID)).eq(1)) 279 | }) 280 | 281 | it('should unbond solver deposit', async () => { 282 | await incentiveLayer.unbondDeposit(taskID, {from: solver}) 283 | assert((await incentiveLayer.getDeposit.call(solver)).eq(minDeposit)) 284 | }) 285 | 286 | it('should unbond verifier deposit', async () => { 287 | await incentiveLayer.unbondDeposit(taskID, {from: verifier}) 288 | //assert((await incentiveLayer.getDeposit.call(verifier)).eq(0)) 289 | }) 290 | 291 | it('should be higher than original balance', async () => { 292 | const newBalance = await web3.eth.getBalance(solver) 293 | 294 | assert((new BigNumber(oldBalance)).isLessThan(new BigNumber(newBalance))) 295 | }) 296 | }) 297 | 298 | context('arbit penalty', () => { 299 | before(async () => { 300 | 301 | token = await TRU.new({from: accounts[5]}) 302 | oracle = await ExchangeRateOracle.new({from: oracleOwner}) 303 | disputeResolutionLayer = await DisputeResolutionLayer.new({from: accounts[5]}) 304 | incentiveLayer = await IncentiveLayer.new(token.address, oracle.address, disputeResolutionLayer.address) 305 | await token.transferOwnership(incentiveLayer.address, {from: accounts[5]}) 306 | let owner = await token.owner() 307 | assert.equal(owner, incentiveLayer.address) 308 | 309 | oldBalance = await web3.eth.getBalance(solver) 310 | 311 | tx = await oracle.updateExchangeRate(TRUperUSD, {from: oracleOwner}) 312 | await token.sendTransaction({from: taskGiver, value: web3.utils.toWei('1', 'ether')}) 313 | await token.sendTransaction({from: solver, value: web3.utils.toWei('1', 'ether')}) 314 | await token.sendTransaction({from: verifier, value: web3.utils.toWei('1', 'ether')}) 315 | await token.sendTransaction({from: backupSolver, value: web3.utils.toWei('1', 'ether')}) 316 | 317 | await token.approve(incentiveLayer.address, reward + (minDeposit * 5), {from: taskGiver}) 318 | await token.approve(incentiveLayer.address, minDeposit, {from: solver}) 319 | await token.approve(incentiveLayer.address, minDeposit, {from: verifier}) 320 | await token.approve(incentiveLayer.address, minDeposit, {from: backupSolver}) 321 | 322 | }) 323 | 324 | it("should have participants make deposits", async () => { 325 | // taskGiver makes a deposit to fund taxes 326 | await incentiveLayer.makeDeposit(minDeposit * 5, {from: taskGiver}) 327 | deposit = await incentiveLayer.getDeposit.call(taskGiver) 328 | assert(deposit.eq(minDeposit * 5)) 329 | let layerBalance = await token.balanceOf(incentiveLayer.address); 330 | assert(layerBalance.eq(minDeposit * 5)); 331 | 332 | // to-be solver makes a deposit 333 | await incentiveLayer.makeDeposit(minDeposit, {from: solver}) 334 | deposit = await incentiveLayer.getDeposit.call(solver) 335 | assert(deposit.eq(minDeposit)) 336 | 337 | // to-be verifier makes a deposit 338 | await incentiveLayer.makeDeposit(minDeposit, {from: verifier}) 339 | deposit = await incentiveLayer.getDeposit.call(verifier) 340 | assert(deposit.eq(minDeposit)) 341 | 342 | // to-be backup solver makes a deposit 343 | await incentiveLayer.makeDeposit(minDeposit, {from: backupSolver}) 344 | deposit = await incentiveLayer.getDeposit.call(backupSolver) 345 | assert(deposit.eq(minDeposit)) 346 | }) 347 | 348 | it("should create task", async () => { 349 | // taskGiver creates a task. 350 | // they bond part of their deposit. 351 | 352 | tx = await incentiveLayer.createTask(0x0, 0, 0, 0x0, maxDifficulty, reward, {from: taskGiver}) 353 | //log = tx.logs.find(log => log.event === 'DepositBonded') 354 | //assert(log.args.taskID.eq(0)) 355 | //assert.equal(log.args.account, taskGiver) 356 | //assert(log.args.amount.eq(maxDifficulty * 2)) 357 | 358 | log = tx.logs.find(log => log.event === 'TaskCreated') 359 | assert(log.args.taskID.isZero()) 360 | assert(log.args.minDeposit.eq(maxDifficulty * 2)) 361 | //assert(log.args.blockNumber.eq(5)) 362 | assert(log.args.reward.eq(reward)) 363 | assert(log.args.tax.eq(minDeposit * 5)) 364 | 365 | deposit = await incentiveLayer.getDeposit.call(taskGiver) 366 | assert(deposit.eq(minDeposit * 5)) 367 | 368 | taskID = log.args.taskID 369 | }) 370 | 371 | it("should select a solver", async () => { 372 | // solver registers for the task. 373 | // they bond part of their deposit. 374 | tx = await incentiveLayer.registerForTask(taskID, web3.utils.soliditySha3(randomBits), {from: solver}) 375 | 376 | log = tx.logs.find(log => log.event === 'DepositBonded') 377 | assert(log.args.taskID.eq(taskID)) 378 | assert.equal(log.args.account, solver) 379 | assert(log.args.amount.eq(minDeposit)) 380 | deposit = await incentiveLayer.getDeposit.call(solver) 381 | assert(deposit.eq(0)) 382 | 383 | log = tx.logs.find(log => log.event === 'SolverSelected') 384 | assert(log.args.taskID.eq(taskID)) 385 | assert.equal(log.args.solver, solver) 386 | assert.equal(log.args.taskData, 0x0) 387 | assert(log.args.minDeposit.eq(minDeposit)) 388 | assert.equal(log.args.randomBitsHash, web3.utils.soliditySha3(randomBits)) 389 | 390 | deposit = await incentiveLayer.getDeposit.call(taskGiver) 391 | assert(deposit.eq(0)) 392 | }) 393 | 394 | it("solver penalized for revealling random bits", async () => { 395 | deposit = await incentiveLayer.getBondedDeposit.call(taskID, solver) 396 | assert(deposit.eq(minDeposit)) 397 | 398 | oldDeposit = await incentiveLayer.getDeposit(randomUser) 399 | 400 | // random user submits random bits pre-image 401 | tx = await incentiveLayer.prematureReveal(taskID, randomBits, {from: randomUser}) 402 | 403 | log = tx.logs.find(log => log.event == 'SolverDepositBurned') 404 | assert(log.args.taskID.eq(taskID)) 405 | assert.equal(log.args.solver, solver) 406 | 407 | log = tx.logs.find(log => log.event == 'TaskCreated') 408 | assert(log.args.taskID.isZero()) 409 | assert(log.args.minDeposit.eq(minDeposit)) 410 | 411 | //assert(log.args.blockNumber.eq(5)) 412 | assert(log.args.reward.eq(reward)) 413 | 414 | deposit = await incentiveLayer.getBondedDeposit.call(taskID, solver) 415 | assert(deposit.eq(0)) 416 | 417 | newDeposit = await incentiveLayer.getDeposit.call(randomUser) 418 | assert(newDeposit.gt(oldDeposit)) 419 | }) 420 | 421 | it('new solver should be selected', async () => { 422 | tx = await incentiveLayer.registerNewSolver(taskID, web3.utils.soliditySha3(randomBits), {from: backupSolver}) 423 | log = tx.logs.find(log => log.event === 'DepositBonded') 424 | assert(log.args.taskID.eq(taskID)) 425 | assert.equal(log.args.account, backupSolver) 426 | assert(log.args.amount.eq(minDeposit)) 427 | deposit = await incentiveLayer.getDeposit.call(backupSolver) 428 | assert(deposit.eq(0)) 429 | 430 | log = tx.logs.find(log => log.event === 'SolverSelected') 431 | assert(log.args.taskID.eq(taskID)) 432 | assert.equal(log.args.solver, backupSolver) 433 | assert.equal(log.args.taskData, 0x0) 434 | assert(log.args.minDeposit.eq(minDeposit)) 435 | assert.equal(log.args.randomBitsHash, web3.utils.soliditySha3(randomBits)) 436 | }) 437 | }) 438 | }) 439 | -------------------------------------------------------------------------------- /test/jackpot_manager.js: -------------------------------------------------------------------------------- 1 | const TRU = artifacts.require("TRU.sol"); 2 | const TestJackpotManager = artifacts.require('TestJackpotManager.sol'); 3 | const Web3 = require('web3'); 4 | const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); 5 | 6 | const BigNumber = require('bignumber.js') 7 | 8 | contract('JackpotManager', function(accounts) { 9 | let jackpotManager, oldBalance, newBalance, token, tx 10 | 11 | const donationAmount = web3.utils.toWei('1', 'ether'); 12 | 13 | before(async () => { 14 | token = await TRU.new({from: accounts[5]}) 15 | jackpotManager = await TestJackpotManager.new(token.address) 16 | await token.transferOwnership(jackpotManager.address, {from: accounts[5]}) 17 | }) 18 | 19 | describe('interact with Test Jackpot Manager', () => { 20 | // it('should donate to the jackpot', async () => { 21 | // const tx = await jackpotManager.donateToJackpot({from: accounts[1], value: donationAmount}) 22 | // log = tx.logs.find(log => log.event === 'JackpotIncreased') 23 | // assert(log.args.amount.eq(donationAmount)) 24 | // 25 | // const jackpot = await jackpotManager.getJackpotAmount.call() 26 | // assert(jackpot.eq(donationAmount)) 27 | // }) 28 | it('should donate to the jackpot', async () => { 29 | const tx = await jackpotManager.increaseJackpot(donationAmount, {from: accounts[1]}) 30 | log = tx.logs.find(log => log.event === 'JackpotIncreased') 31 | assert(log.args.amount.eq(donationAmount)) 32 | 33 | const jackpot = await jackpotManager.getJackpotAmount.call() 34 | assert(jackpot.eq(donationAmount)) 35 | }) 36 | 37 | it('should distribute jackpot', async () => { 38 | await jackpotManager.distributeJackpot([accounts[0], accounts[1], accounts[2], accounts[3]]) 39 | 40 | const jackpotID = await jackpotManager.getCurrentJackpotID.call() 41 | 42 | assert(jackpotID.eq(1)) 43 | }) 44 | 45 | it('should be able to receive payment', async () => { 46 | oldBalance = await token.balanceOf(accounts[0]) 47 | tx = await jackpotManager.receiveJackpotPayment(0, 0, {from: accounts[0]}) 48 | newBalance = await token.balanceOf(accounts[0]) 49 | assert((new BigNumber(oldBalance)).isLessThan(new BigNumber(newBalance))) 50 | 51 | oldBalance = await token.balanceOf(accounts[1]) 52 | tx = await jackpotManager.receiveJackpotPayment(0, 1, {from: accounts[1]}) 53 | newBalance = await token.balanceOf(accounts[0]) 54 | assert((new BigNumber(oldBalance)).isLessThan(new BigNumber(newBalance))) 55 | 56 | oldBalance = await token.balanceOf(accounts[2]) 57 | tx = await jackpotManager.receiveJackpotPayment(0, 2, {from: accounts[2]}) 58 | newBalance = await token.balanceOf(accounts[0]) 59 | assert((new BigNumber(oldBalance)).isLessThan(new BigNumber(newBalance))) 60 | 61 | oldBalance = await token.balanceOf(accounts[3]) 62 | tx = await jackpotManager.receiveJackpotPayment(0, 3, {from: accounts[3]}) 63 | newBalance = await token.balanceOf(accounts[0]) 64 | 65 | assert((new BigNumber(oldBalance)).isLessThan(new BigNumber(newBalance))) 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/oracle.js: -------------------------------------------------------------------------------- 1 | const ExchangeRateOracle = artifacts.require('./ExchangeRateOracle.sol') 2 | const Web3 = require('web3') 3 | const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) 4 | 5 | const timeout = require('./helpers/timeout') 6 | const mineBlocks = require('./helpers/mineBlocks') 7 | 8 | const BigNumber = require('bignumber.js') 9 | 10 | contract('ExchangeRateOracle', function(accounts) { 11 | let oracle 12 | 13 | const TRUperUSD = 1000 14 | const owner = accounts[1]; 15 | const notOwner = accounts[2]; 16 | const taskDifficulty = 1000; 17 | const expectedDeposit = 1000; 18 | 19 | before(async () => { 20 | oracle = await ExchangeRateOracle.new({from: owner}) 21 | }) 22 | 23 | it('1000 TRU per USD is 1 TRU per cycle', async () => { 24 | // owner updates exchange rate 25 | tx = await oracle.updateExchangeRate(TRUperUSD, {from: owner}) 26 | 27 | log = tx.logs.find(log => log.event == 'ExchangeRateUpdate') 28 | assert(log.args.TRUperUSD.eq(TRUperUSD)) 29 | assert.equal(log.args.owner, owner) 30 | 31 | // check TRU per cycle 32 | TRUperCycle = await oracle.priceOfCycleTRU.call() 33 | assert(TRUperCycle.eq(1)) 34 | }) 35 | 36 | it('price of task is one to one in TRU', async () => { 37 | minDeposit = await oracle.getMinDeposit(taskDifficulty) 38 | 39 | assert(minDeposit.eq(expectedDeposit)) 40 | }) 41 | 42 | }) 43 | -------------------------------------------------------------------------------- /test/rewards_manager.js: -------------------------------------------------------------------------------- 1 | const TRU = artifacts.require('TRU.sol'); 2 | const RewardsManager = artifacts.require('./TestRewardsManager.sol'); 3 | const Web3 = require('web3'); 4 | const web3 = new Web3(Web3.providers.HttpProvider('http://localhost:8545')); 5 | 6 | contract('RewardsManager', function (accounts) { 7 | let rewardsManager, token, tx, log, oldBalance, newBalance 8 | 9 | const user = accounts[1]; 10 | const depositAmount = 1000; 11 | const taskID = 12345; 12 | const payee = accounts[2]; 13 | 14 | before (async() => { 15 | token = await TRU.new({from: accounts[0]}) 16 | rewardsManager = await RewardsManager.new(token.address, {from: accounts[0]}) 17 | await token.sendTransaction({from: user, value: web3.utils.toWei('1', 'ether')}) 18 | await token.approve(rewardsManager.address, depositAmount, {from: user}) 19 | }) 20 | 21 | 22 | it ('Should make deposit', async () => { 23 | oldBalance = await token.balanceOf(user) 24 | 25 | tx = await rewardsManager.depositReward(taskID, depositAmount, {from: user}) 26 | 27 | log = tx.logs.find(log => log.event == 'RewardDeposit') 28 | assert(log.args.task.eq(taskID)) 29 | assert.equal(log.args.who, user) 30 | assert(log.args.amount.eq(depositAmount)) 31 | 32 | newBalance = await token.balanceOf(user) 33 | 34 | assert(newBalance.lt(oldBalance)) 35 | }) 36 | 37 | it('should pay out reward for task', async () => { 38 | oldBalance = await token.balanceOf(payee) 39 | 40 | tx = await rewardsManager.testPayReward(taskID, payee, {from: accounts[0]}) 41 | 42 | log = tx.logs.find(log => log.event == 'RewardClaimed') 43 | assert(log.args.task.eq(taskID)) 44 | assert.equal(log.args.who, payee) 45 | assert(log.args.amount.eq(depositAmount)) 46 | 47 | newBalance = await token.balanceOf(payee) 48 | 49 | assert(newBalance.gt(oldBalance)) 50 | }) 51 | 52 | }) 53 | -------------------------------------------------------------------------------- /test/timeouts.js: -------------------------------------------------------------------------------- 1 | const TRU = artifacts.require('TRU.sol'); 2 | const IncentiveLayer = artifacts.require('IncentiveLayer.sol') 3 | const ExchangeRateOracle = artifacts.require('ExchangeRateOracle.sol') 4 | const DisputeResolutionLayer = artifacts.require('./DisputeResolutionLayerDummy.sol') 5 | const Web3 = require('web3') 6 | const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) 7 | const mineBlocks = require('./helpers/mineBlocks') 8 | 9 | contract('IncentiveLayer Timeouts', function(accounts) { 10 | 11 | let incentiveLayer, deposit, bond, tx, log, taskID, intent, token, oracle, disputeResolutionLayer 12 | 13 | const taskGiver = accounts[1] 14 | const solver = accounts[2] 15 | const verifier = accounts[3] 16 | 17 | const minDeposit = 500 18 | const maxDifficulty = 500 19 | const reward = 500 20 | const randomBits = 12345 21 | 22 | context('task giver calls timeout on solver for not submitting solution in time', async () => { 23 | before( async ()=> { 24 | token = await TRU.new({from: accounts[5]}) 25 | oracle = await ExchangeRateOracle.new({from: accounts[6]}) 26 | disputeResolutionLayer = await DisputeResolutionLayer.new({from: accounts[6]}) 27 | await oracle.updateExchangeRate(1000, {from: accounts[6]}) 28 | 29 | incentiveLayer = await IncentiveLayer.new(token.address, oracle.address, disputeResolutionLayer.address) 30 | await token.transferOwnership(incentiveLayer.address, {from: accounts[5]}) 31 | 32 | await token.sendTransaction({from: taskGiver, value: web3.utils.toWei('1', 'ether')}) 33 | await token.sendTransaction({from: solver, value: web3.utils.toWei('1', 'ether')}) 34 | await token.sendTransaction({from: verifier, value: web3.utils.toWei('1', 'ether')}) 35 | 36 | await token.approve(incentiveLayer.address, reward + (minDeposit * 2 * 5), {from: taskGiver}) 37 | await token.approve(incentiveLayer.address, minDeposit * 2, {from: solver}) 38 | await token.approve(incentiveLayer.address, minDeposit * 2, {from: verifier}) 39 | 40 | await incentiveLayer.makeDeposit(minDeposit * 2 * 5,{from: taskGiver}) 41 | await incentiveLayer.makeDeposit(minDeposit * 2, {from: solver}) 42 | 43 | }) 44 | 45 | it('should create a task', async () => { 46 | let tx = await incentiveLayer.createTask(0x0, 0, 0, 0x0, maxDifficulty, reward, {from: taskGiver}) 47 | 48 | log = tx.logs.find(log => log.event === 'TaskCreated') 49 | taskID = log.args.taskID.toNumber() 50 | }) 51 | 52 | it('should register for task', async () => { 53 | await incentiveLayer.registerForTask(taskID, web3.utils.soliditySha3(randomBits), {from: solver}) 54 | }) 55 | 56 | it('should transfer solvers funds to jackpot', async () => { 57 | await mineBlocks(web3, 100) 58 | await incentiveLayer.taskGiverTimeout(taskID, {from: taskGiver}) 59 | assert((await incentiveLayer.getDeposit.call(solver)).eq(minDeposit)) 60 | }) 61 | 62 | it('should unbond solver deposit', async () => { 63 | await incentiveLayer.unbondDeposit(taskID, {from: solver}) 64 | assert((await incentiveLayer.getDeposit.call(solver)).eq(minDeposit)) 65 | }) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*", // Match any network id 7 | gas: 6721975 8 | // gas: 4600000 9 | } 10 | } 11 | } 12 | --------------------------------------------------------------------------------