├── .gitignore
├── .gitmodules
├── README.md
├── build
├── .keep
└── abi
├── contracts
├── Bits.sol
├── BuyoutsProcessor.sol
├── ByteSlice.sol
├── Conversion.sol
├── LimboExitGame.sol
├── Migrations.sol
├── PlasmaBlockStorage.sol
├── PlasmaChallenges.sol
├── PlasmaParent.sol
├── PlasmaTransactionLibrary.sol
├── PriorityQueue.sol
├── RLP.sol
├── SafeMath.sol
├── Structures.sol
└── TXTester.sol
├── helpers
├── assertHelpers.js
├── details.js
├── expectThrow.js
├── increaseTime.js
└── start.sh
├── migrations
├── 1_initial_migration.js
└── 2_deploy_plasma.js
├── package-lock.json
├── package.json
├── test
├── blockSubmissionFunctions.js
├── buyout.js
├── createBlock.js
├── createTransaction.js
├── deploy.js
├── depositWithdrawFunctions.js
├── exitFunctions.js
├── fullExitProcedure.js
├── keys.js
├── limboExitFunctions.js
├── priorityQueue.js
├── testInvalidBlockChallenges.js
├── transactionSerialization.js
└── utils.js
└── truffle.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | /build/contracts
3 | node_modules
4 | .vscode
5 | .DS_Store
6 | .idea
7 | !/build/.keep
8 | !/build/abi
9 | build/details*
10 | .node-*
11 | .node*
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib"]
2 | path = lib
3 | url = https://github.com/matterinc/PlasmaTransactionJS.git
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | Contract •
10 | TX & Block RLP •
11 | API •
12 | JS Lib •
13 | Swift Lib •
14 | Block Explorer •
15 | Web App •
16 | iOS App
17 |
18 |
19 | # Plasma Parent Contract
20 |
21 |
22 |
23 | - [Description](#description)
24 | - [Transaction structure](#transaction-structure)
25 | * [Input](#input)
26 | * [Output](#output)
27 | * [Transaction](#transaction)
28 | * [Signed transaction](#signed-transaction)
29 | - [Block structure](#block-structure)
30 | * [Block header](#block-header)
31 | * [Block](#block)
32 | - [This contract differs from Minimal Viable Plasma in the following:](#this-contract-differs-from-minimal-viable-plasma-in-the-following)
33 | - [Implemented functionality:](#implemented-functionality)
34 | - [List of intended challenges and tests](#list-of-intended-challenges-and-tests)
35 | - [Getting started](#getting-started)
36 | * [Download dependecies:](#download-dependecies)
37 | - [Contribution](#contribution)
38 | - [Authors](#authors)
39 | - [Further work](#further-work)
40 | - [License](#license)
41 |
42 |
43 |
44 | ## Description
45 |
46 | This contract is used to maintain the integrity and correctness of Plasma by providing deposits, exits and limbo exits mechanism. It's based on a construction called More Viable Plasma, where in case of exit a priority of the transaction in the exit queue (so, priority of all outputs of this transaction) is assigned as the "age of the yongest input". In case of this implementation age is determined as a big-endian number made from byte concatenation of `BlockNumber|TransactionNumber|OutputNumber` of the UTXO being spent by the input. So age is first determined by smalled block number, then smaller transaction number in block, and then smaller output number in transaction. Priority is determined as the largest age and lower is better. Priority for exit purposes is capped and can not be better (smaller) than the age of block at `-1 week` timestamp.
47 |
48 | To demonstrate why such priority is enough for proper limbo exit game (when block withholding happens and it's unknown what's happening in Plasma, so exits are done without a Merkle proof of inclusion in block) on can imagine a following situation:
49 | - An operator does something malicious in block number `N` (like spend someone's else UTXO without knowing a private key) and withholds a block, but still publishes a header
50 | - An operator can not start an exit directly from the block number `N` as he will be challenged by demonstrating a mismatch between transaction input and original UTXOs
51 | - So an operator will have to make another block `N+1` (and withhold it) to start exits from it. There he puts a transaction that references an invalid transaction that was produced in block number `N`
52 | - In this case a priority of malicious exiting transaction will be based on a block number `N`
53 | - Any valid transaction from users (that can not be trivially challenged, since full information for a blocks `< N` is available) would have it's inputs from block with a number ` input
173 | - [x] should allow successful exit
174 | - [x] Challenges
175 | - [x] Transaction in block references the future
176 | - [x] Transaction is malformed (balance breaking)
177 | - [x] Double spend
178 | - [x] Spend without owner signature
179 | - [x] UTXO amount is not equal to input amount
180 | - [x] UTXO was successfully withdrawn and than spent in Plasma
181 |
182 | ## Getting started
183 |
184 | ### Download dependecies:
185 |
186 | ```
187 | git submodule init
188 | git submodule update --recursive
189 | ```
190 |
191 | ## Contribution
192 |
193 | Everyone is welcome to spot mistakes in the logic of this contract as number of provided functions is substantial. If you find a potential error or security loophole (one that would allow Plasma operator or user to break the normal operation and not being caught) - please open an issue.
194 |
195 | ## Authors
196 |
197 | Alex Vlasov, [@shamatar](https://github.com/shamatar), alex.m.vlasov@gmail.com
198 |
199 | ## Further work
200 |
201 | Make optimizations for reduced gas costs for most of the functions.
202 |
203 | ## License
204 |
205 | All source code and information in this repository is available under the Apache License 2.0 license.
206 |
--------------------------------------------------------------------------------
/build/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matter-labs-archive/PlasmaContract/a1e96489a2b2c05c43a4d614ff40177e9ed03e42/build/.keep
--------------------------------------------------------------------------------
/build/abi:
--------------------------------------------------------------------------------
1 | {"abi":[{"constant":true,"inputs":[],"name":"buyoutProcessorContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"WithdrawCollateral","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"plasmaErrorFound","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"lastValidBlock","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DepositWithdrawDelay","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ExitDelay","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"blockStorage","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"operatorsBond","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes22"}],"name":"succesfulExits","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"limboExitContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"depositRecords","outputs":[{"name":"from","type":"address"},{"name":"status","type":"uint8"},{"name":"hasCollateral","type":"bool"},{"name":"amount","type":"uint256"},{"name":"withdrawStartedAt","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DepositWithdrawCollateral","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes22"}],"name":"exitBuyoutOffers","outputs":[{"name":"amount","type":"uint256"},{"name":"from","type":"address"},{"name":"accepted","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"challengesContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes22"}],"name":"exitRecords","outputs":[{"name":"transactionRef","type":"bytes32"},{"name":"amount","type":"uint256"},{"name":"owner","type":"address"},{"name":"timePublished","type":"uint64"},{"name":"blockNumber","type":"uint32"},{"name":"transactionNumber","type":"uint32"},{"name":"outputNumber","type":"uint8"},{"name":"isValid","type":"bool"},{"name":"isLimbo","type":"bool"},{"name":"priority","type":"uint72"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"name":"allDepositRecordsForUser","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"depositCounter","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"name":"allExitsForUser","outputs":[{"name":"","type":"bytes22"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"LimboChallangesDelay","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"exitQueue","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_priorityQueue","type":"address"},{"name":"_blockStorage","type":"address"}],"payable":true,"stateMutability":"payable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_lastValidBlockNumber","type":"uint256"}],"name":"ErrorFoundEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_amount","type":"uint256"},{"indexed":true,"name":"_depositIndex","type":"uint256"}],"name":"DepositEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_depositIndex","type":"uint256"}],"name":"DepositWithdrawStartedEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_depositIndex","type":"uint256"}],"name":"DepositWithdrawChallengedEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_depositIndex","type":"uint256"}],"name":"DepositWithdrawCompletedEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_hash","type":"bytes32"},{"indexed":false,"name":"_data","type":"bytes"}],"name":"TransactionPublished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_partialHash","type":"bytes22"}],"name":"ExitRecordCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_partialHash","type":"bytes22"}],"name":"ExitChallenged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_index","type":"uint64"}],"name":"TransactionIsPublished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":false,"name":"_priority","type":"uint72"},{"indexed":true,"name":"_index","type":"uint72"},{"indexed":true,"name":"_partialHash","type":"bytes22"}],"name":"ExitStartedEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_priority","type":"uint72"},{"indexed":true,"name":"_partialHash","type":"bytes22"}],"name":"LimboExitStartedEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_partialHash","type":"bytes22"},{"indexed":true,"name":"_from","type":"address"},{"indexed":false,"name":"_challengeNumber","type":"uint8"},{"indexed":false,"name":"_inputNumber","type":"uint8"}],"name":"LimboExitChallengePublished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_partialHash","type":"bytes22"},{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_buyoutAmount","type":"uint256"}],"name":"ExitBuyoutOffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_partialHash","type":"bytes22"},{"indexed":true,"name":"_from","type":"address"}],"name":"ExitBuyoutAccepted","type":"event"},{"constant":false,"inputs":[{"name":"_op","type":"address"},{"name":"_status","type":"uint256"}],"name":"setOperator","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_buyouts","type":"address"}],"name":"allowDeposits","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_challenger","type":"address"}],"name":"allowChallenges","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_limboExiter","type":"address"}],"name":"allowLimboExits","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_headers","type":"bytes"}],"name":"submitBlockHeaders","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"lastBlockNumber","outputs":[{"name":"blockNumber","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"hashOfLastSubmittedBlock","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"incrementWeekOldCounter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_plasmaBlockNumber","type":"uint32"},{"name":"_outputNumber","type":"uint8"},{"name":"_plasmaTransaction","type":"bytes"},{"name":"_merkleProof","type":"bytes"}],"name":"startExit","outputs":[{"name":"success","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_exitRecordHash","type":"bytes22"},{"name":"_plasmaBlockNumber","type":"uint32"},{"name":"_plasmaTransaction","type":"bytes"},{"name":"_merkleProof","type":"bytes"},{"name":"_inputNumber","type":"uint8"}],"name":"challengeNormalExitByShowingExitBeingSpent","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_originalTransaction","type":"bytes"},{"name":"_originalInputNumber","type":"uint8"},{"name":"_exitRecordHash","type":"bytes22"},{"name":"_plasmaBlockNumber","type":"uint32"},{"name":"_plasmaTransaction","type":"bytes"},{"name":"_merkleProof","type":"bytes"},{"name":"_inputNumber","type":"uint8"}],"name":"challengeNormalExitByShowingAnInputDoubleSpend","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_originalTransaction","type":"bytes"},{"name":"_originalInputNumber","type":"uint8"},{"name":"_exitRecordHash","type":"bytes22"},{"name":"_plasmaBlockNumber","type":"uint32"},{"name":"_plasmaTransaction","type":"bytes"},{"name":"_merkleProof","type":"bytes"},{"name":"_outputNumber","type":"uint8"}],"name":"challengeNormalExitByShowingMismatchedInput","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_numOfExits","type":"uint256"}],"name":"finalizeExits","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_plasmaTransaction","type":"bytes"}],"name":"isWellFormedTransaction","outputs":[{"name":"isWellFormed","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[{"name":"success","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_for","type":"address"}],"name":"depositFor","outputs":[{"name":"success","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_index","type":"bytes22"},{"name":"_beneficiary","type":"address"}],"name":"offerOutputBuyout","outputs":[{"name":"success","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_index","type":"bytes22"}],"name":"acceptBuyoutOffer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_index","type":"bytes22"}],"name":"returnExpiredBuyoutOffer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_index","type":"bytes22"},{"name":"_amount","type":"uint256"},{"name":"_beneficiary","type":"address"},{"name":"v","type":"uint8"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"}],"name":"publishPreacceptedBuyout","outputs":[{"name":"success","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"depositIndex","type":"uint256"}],"name":"startDepositWithdraw","outputs":[{"name":"success","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"depositIndex","type":"uint256"}],"name":"finalizeDepositWithdraw","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"depositIndex","type":"uint256"},{"name":"_plasmaBlockNumber","type":"uint32"},{"name":"_plasmaTransaction","type":"bytes"},{"name":"_merkleProof","type":"bytes"}],"name":"challengeDepositWithdraw","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_plasmaBlockNumber1","type":"uint32"},{"name":"_inputNumber1","type":"uint8"},{"name":"_plasmaTransaction1","type":"bytes"},{"name":"_merkleProof1","type":"bytes"},{"name":"_plasmaBlockNumber2","type":"uint32"},{"name":"_inputNumber2","type":"uint8"},{"name":"_plasmaTransaction2","type":"bytes"},{"name":"_merkleProof2","type":"bytes"}],"name":"proveDoubleSpend","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_plasmaBlockNumber","type":"uint32"},{"name":"_plasmaTransaction","type":"bytes"},{"name":"_merkleProof","type":"bytes"},{"name":"_originatingPlasmaTransaction","type":"bytes"},{"name":"_originatingMerkleProof","type":"bytes"},{"name":"_inputNumber","type":"uint8"}],"name":"proveSpendAndWithdraw","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_plasmaBlockNumber","type":"uint32"},{"name":"_plasmaTransaction","type":"bytes"},{"name":"_merkleProof","type":"bytes"}],"name":"proveInvalidDeposit","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_plasmaBlockNumber1","type":"uint32"},{"name":"_plasmaTransaction1","type":"bytes"},{"name":"_merkleProof1","type":"bytes"},{"name":"_plasmaBlockNumber2","type":"uint32"},{"name":"_plasmaTransaction2","type":"bytes"},{"name":"_merkleProof2","type":"bytes"}],"name":"proveDoubleFunding","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_plasmaBlockNumber","type":"uint32"},{"name":"_plasmaInputNumberInTx","type":"uint8"},{"name":"_plasmaTransaction","type":"bytes"},{"name":"_merkleProof","type":"bytes"}],"name":"proveReferencingInvalidBlock","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_plasmaBlockNumber","type":"uint32"},{"name":"_plasmaTransaction","type":"bytes"},{"name":"_merkleProof","type":"bytes"},{"name":"_originatingPlasmaBlockNumber","type":"uint32"},{"name":"_originatingPlasmaTransaction","type":"bytes"},{"name":"_originatingMerkleProof","type":"bytes"},{"name":"_inputOfInterest","type":"uint256"}],"name":"proveBalanceOrOwnershipBreakingBetweenInputAndOutput","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]}
--------------------------------------------------------------------------------
/contracts/Bits.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.25;
2 |
3 |
4 | library SmallBits {
5 |
6 | uint8 constant internal ONE = uint8(1);
7 | uint8 constant internal ONES = uint8(~0);
8 |
9 | // Sets the bit at the given 'index' in 'self' to '1'.
10 | // Returns the modified value.
11 | function setBit(uint8 self, uint8 index) internal pure returns (uint8) {
12 | return self | ONE << index;
13 | }
14 |
15 | // Sets the bit at the given 'index' in 'self' to '0'.
16 | // Returns the modified value.
17 | function clearBit(uint8 self, uint8 index) internal pure returns (uint8) {
18 | return self & ~(ONE << index);
19 | }
20 |
21 | // Sets the bit at the given 'index' in 'self' to:
22 | // '1' - if the bit is '0'
23 | // '0' - if the bit is '1'
24 | // Returns the modified value.
25 | function toggleBit(uint self, uint8 index) internal pure returns (uint) {
26 | return self ^ ONE << index;
27 | }
28 |
29 | // Get the value of the bit at the given 'index' in 'self'.
30 | function bit(uint self, uint8 index) internal pure returns (uint8) {
31 | return uint8(self >> index & 1);
32 | }
33 |
34 | // Check if the bit at the given 'index' in 'self' is set.
35 | // Returns:
36 | // 'true' - if the value of the bit is '1'
37 | // 'false' - if the value of the bit is '0'
38 | function bitSet(uint self, uint8 index) internal pure returns (bool) {
39 | return self >> index & 1 == 1;
40 | }
41 |
42 | // Checks if the bit at the given 'index' in 'self' is equal to the corresponding
43 | // bit in 'other'.
44 | // Returns:
45 | // 'true' - if both bits are '0' or both bits are '1'
46 | // 'false' - otherwise
47 | function bitEqual(uint self, uint other, uint8 index) internal pure returns (bool) {
48 | return (self ^ other) >> index & 1 == 0;
49 | }
50 |
51 | // Get the bitwise NOT of the bit at the given 'index' in 'self'.
52 | function bitNot(uint self, uint8 index) internal pure returns (uint8) {
53 | return uint8(1 - (self >> index & 1));
54 | }
55 |
56 | // Computes the bitwise AND of the bit at the given 'index' in 'self', and the
57 | // corresponding bit in 'other', and returns the value.
58 | function bitAnd(uint self, uint other, uint8 index) internal pure returns (uint8) {
59 | return uint8((self & other) >> index & 1);
60 | }
61 |
62 | // Computes the bitwise OR of the bit at the given 'index' in 'self', and the
63 | // corresponding bit in 'other', and returns the value.
64 | function bitOr(uint self, uint other, uint8 index) internal pure returns (uint8) {
65 | return uint8((self | other) >> index & 1);
66 | }
67 |
68 | // Computes the bitwise XOR of the bit at the given 'index' in 'self', and the
69 | // corresponding bit in 'other', and returns the value.
70 | function bitXor(uint self, uint other, uint8 index) internal pure returns (uint8) {
71 | return uint8((self ^ other) >> index & 1);
72 | }
73 |
74 | // Gets 'numBits' consecutive bits from 'self', starting from the bit at 'startIndex'.
75 | // Returns the bits as a 'uint'.
76 | // Requires that:
77 | // - '0 < numBits <= 256'
78 | // - 'startIndex < 256'
79 | // - 'numBits + startIndex <= 256'
80 | function bits(uint self, uint8 startIndex, uint16 numBits) internal pure returns (uint) {
81 | require(0 < numBits && startIndex < 256 && startIndex + numBits <= 256);
82 | return self >> startIndex & ONES >> 256 - numBits;
83 | }
84 |
85 | // Computes the index of the highest bit set in 'self'.
86 | // Returns the highest bit set as an 'uint8'.
87 | // Requires that 'self != 0'.
88 | function highestBitSet(uint self) internal pure returns (uint8 highest) {
89 | require(self != 0);
90 | uint val = self;
91 | for (uint8 i = 128; i >= 1; i >>= 1) {
92 | if (val & (ONE << i) - 1 << i != 0) {
93 | highest += i;
94 | val >>= i;
95 | }
96 | }
97 | }
98 |
99 | // Computes the index of the lowest bit set in 'self'.
100 | // Returns the lowest bit set as an 'uint8'.
101 | // Requires that 'self != 0'.
102 | function lowestBitSet(uint self) internal pure returns (uint8 lowest) {
103 | require(self != 0);
104 | uint val = self;
105 | for (uint8 i = 128; i >= 1; i >>= 1) {
106 | if (val & (ONE << i) - 1 == 0) {
107 | lowest += i;
108 | val >>= i;
109 | }
110 | }
111 | }
112 |
113 | }
--------------------------------------------------------------------------------
/contracts/BuyoutsProcessor.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.25;
2 |
3 | import {PlasmaTransactionLibrary} from "./PlasmaTransactionLibrary.sol";
4 | import {PlasmaBlockStorageInterface} from "./PlasmaBlockStorage.sol";
5 | import {PriorityQueueInterface} from "./PriorityQueue.sol";
6 | import {StructuresLibrary} from "./Structures.sol";
7 | import {SafeMath} from "./SafeMath.sol";
8 |
9 | contract PlasmaBuyoutProcessor {
10 | // begining of storage declaration
11 |
12 | bool public plasmaErrorFound;
13 | uint32 public lastValidBlock;
14 | uint256 public operatorsBond;
15 |
16 | PriorityQueueInterface public exitQueue;
17 | PlasmaBlockStorageInterface public blockStorage;
18 | address public challengesContract;
19 | address public limboExitContract;
20 | address public buyoutProcessorContract;
21 | address public owner = msg.sender;
22 |
23 | uint256 public depositCounter;
24 |
25 | uint256 public constant DepositWithdrawCollateral = 50000000000000000;
26 | uint256 public constant WithdrawCollateral = 50000000000000000;
27 | uint256 public constant DepositWithdrawDelay = (72 hours);
28 | uint256 public constant LimboChallangesDelay = (72 hours);
29 | uint256 public constant ExitDelay = (168 hours);
30 |
31 | uint256 constant TxTypeNull = 0;
32 | uint256 constant TxTypeSplit = 1;
33 | uint256 constant TxTypeMerge = 2;
34 | uint256 constant TxTypeFund = 4;
35 |
36 | // deposits
37 |
38 | uint8 constant DepositStatusNoRecord = 0; // no deposit
39 | uint8 constant DepositStatusDeposited = 1; // deposit has happened
40 | uint8 constant DepositStatusWithdrawStarted = 2; // user withdraws a deposit
41 | uint8 constant DepositStatusWithdrawCompleted = 3; // used has withdrawn a deposit
42 | uint8 constant DepositStatusDepositConfirmed = 4; // a transaction with a deposit was posted
43 |
44 | struct DepositRecord {
45 | address from;
46 | uint8 status;
47 | bool hasCollateral;
48 | uint256 amount;
49 | uint256 withdrawStartedAt;
50 | }
51 |
52 | mapping(uint256 => DepositRecord) public depositRecords;
53 | mapping(address => uint256[]) public allDepositRecordsForUser;
54 |
55 | struct ExitBuyoutOffer {
56 | uint256 amount;
57 | address from;
58 | bool accepted;
59 | }
60 |
61 | mapping(address => bytes22[]) public allExitsForUser;
62 | mapping(bytes22 => ExitBuyoutOffer) public exitBuyoutOffers;
63 |
64 | mapping(bytes22 => StructuresLibrary.ExitRecord) public exitRecords;
65 | mapping(bytes22 => StructuresLibrary.LimboData) limboExitsData;
66 | mapping(bytes22 => bool) public succesfulExits;
67 |
68 | event ErrorFoundEvent(uint256 indexed _lastValidBlockNumber);
69 |
70 | event DepositEvent(address indexed _from, uint256 indexed _amount, uint256 indexed _depositIndex);
71 | event DepositWithdrawStartedEvent(uint256 indexed _depositIndex);
72 | event DepositWithdrawChallengedEvent(uint256 indexed _depositIndex);
73 | event DepositWithdrawCompletedEvent(uint256 indexed _depositIndex);
74 |
75 | event TransactionPublished(bytes32 indexed _hash, bytes _data);
76 | event ExitRecordCreated(bytes22 indexed _partialHash);
77 | event ExitChallenged(bytes22 indexed _partialHash);
78 | event TransactionIsPublished(uint64 indexed _index);
79 | event ExitStartedEvent(address indexed _from, uint72 _priority, uint72 indexed _index, bytes22 indexed _partialHash);
80 |
81 | event LimboExitStartedEvent(address indexed _from, uint72 indexed _priority, bytes22 indexed _partialHash);
82 | event LimboExitChallengePublished(bytes22 indexed _partialHash, address indexed _from, uint8 _challengeNumber, uint8 _inputNumber);
83 | event ExitBuyoutOffered(bytes22 indexed _partialHash, address indexed _from, uint256 indexed _buyoutAmount);
84 | event ExitBuyoutAccepted(bytes22 indexed _partialHash, address indexed _from);
85 | // end of storage declarations ---------------------------
86 |
87 | constructor() public {
88 | }
89 | // ----------------------------------
90 |
91 | // Deposit related functions
92 |
93 | function deposit() payable public returns (bool success) {
94 | return depositFor(msg.sender);
95 | }
96 |
97 | function depositFor(address _for) payable public returns (bool success) {
98 | require(msg.value > 0);
99 | require(!plasmaErrorFound);
100 | uint256 size;
101 | assembly {
102 | size := extcodesize(_for)
103 | }
104 | if (size > 0) {
105 | revert("No deposits to the contracts!");
106 | }
107 | uint256 depositIndex = depositCounter;
108 | DepositRecord storage record = depositRecords[depositIndex];
109 | require(record.status == DepositStatusNoRecord);
110 | record.from = _for;
111 | record.amount = msg.value;
112 | record.status = DepositStatusDeposited;
113 | depositCounter = depositCounter + 1;
114 | emit DepositEvent(_for, msg.value, depositIndex);
115 | allDepositRecordsForUser[_for].push(depositIndex);
116 | return true;
117 | }
118 |
119 | function offerOutputBuyout(bytes22 _index, address _beneficiary) public payable returns (bool success) {
120 | require(msg.value > 0);
121 | require(_beneficiary != address(0));
122 | StructuresLibrary.ExitRecord storage exitRecord = exitRecords[_index];
123 | require(exitRecord.isValid);
124 | ExitBuyoutOffer storage offer = exitBuyoutOffers[_index];
125 | emit ExitBuyoutOffered(_index, _beneficiary, msg.value);
126 | require(!offer.accepted);
127 | address oldFrom = offer.from;
128 | uint256 oldAmount = offer.amount;
129 | require(msg.value > oldAmount);
130 | offer.from = _beneficiary;
131 | offer.amount = msg.value;
132 | if (oldFrom != address(0)) {
133 | oldFrom.transfer(oldAmount);
134 | }
135 | return true;
136 | }
137 |
138 | function acceptBuyoutOffer(bytes22 _index) public returns (bool success) {
139 | StructuresLibrary.ExitRecord storage exitRecord = exitRecords[_index];
140 | // this simple require solves accepting the already exited transaciton and validity :)
141 | require(exitRecord.isValid);
142 | ExitBuyoutOffer storage offer = exitBuyoutOffers[_index];
143 | require(offer.from != address(0));
144 | require(!offer.accepted);
145 | address oldBeneficiary = exitRecord.owner;
146 | uint256 offerAmount = offer.amount;
147 | offer.accepted = true;
148 | emit ExitBuyoutAccepted(_index, offer.from);
149 | oldBeneficiary.transfer(offerAmount);
150 | return true;
151 | }
152 |
153 | function returnExpiredBuyoutOffer(bytes22 _index) public returns (bool success) {
154 | ExitBuyoutOffer storage offer = exitBuyoutOffers[_index];
155 | require(!offer.accepted);
156 | // require(record.status != WithdrawStatusStarted || (block.timestamp >= record.timestamp + WithdrawDelay));
157 | address oldFrom = offer.from;
158 | uint256 oldAmount = offer.amount;
159 | require(msg.sender == oldFrom);
160 | delete exitBuyoutOffers[_index];
161 | oldFrom.transfer(oldAmount);
162 | return true;
163 | }
164 |
165 | function publishPreacceptedBuyout(
166 | bytes22 _index,
167 | uint256 _amount,
168 | address _beneficiary,
169 | uint8 v,
170 | bytes32 r,
171 | bytes32 s
172 | ) public payable returns (bool success) {
173 | StructuresLibrary.ExitRecord storage exitRecord = exitRecords[_index];
174 | require(exitRecord.isValid, "Exit should be valid to accept a buyout");
175 | ExitBuyoutOffer storage offer = exitBuyoutOffers[_index];
176 | require(!offer.accepted, "Offer should not be prefiously accepted");
177 | bytes memory PersonalMessagePrefixBytes = "\x19Ethereum Signed Message:\n74"; // 22 of index + 32 of amount + 20 of address
178 | address signer = ecrecover(keccak256(abi.encodePacked(PersonalMessagePrefixBytes, _index, _amount, _beneficiary)), v, r, s);
179 | require(signer == exitRecord.owner, "Acceptance signer should be record owner");
180 | require(msg.value >= _amount, "Need to send at least the agreed amount");
181 | address oldFrom = offer.from;
182 | uint256 oldAmount = offer.amount;
183 | require(msg.value > oldAmount, "Should send more than any previous offer");
184 | offer.from = _beneficiary;
185 | offer.amount = msg.value;
186 | if (oldFrom != address(0)) {
187 | oldFrom.transfer(oldAmount);
188 | }
189 | offer.accepted = true;
190 | exitRecord.owner.transfer(_amount);
191 | emit ExitBuyoutAccepted(_index, _beneficiary);
192 | return true;
193 | }
194 |
195 | // ----------------------------------
196 |
197 | function() external payable{
198 | address callee = challengesContract;
199 | assembly {
200 | let memoryPointer := mload(0x40)
201 | calldatacopy(memoryPointer, 0, calldatasize)
202 | let newFreeMemoryPointer := add(memoryPointer, calldatasize)
203 | mstore(0x40, newFreeMemoryPointer)
204 | let retVal := delegatecall(sub(gas, 2000), callee, memoryPointer, calldatasize, newFreeMemoryPointer, 0x40)
205 | let retDataSize := returndatasize
206 | returndatacopy(newFreeMemoryPointer, 0, retDataSize)
207 | switch retVal case 0 { revert(0,0) } default { return(newFreeMemoryPointer, retDataSize) }
208 | }
209 | }
210 |
211 | }
212 |
--------------------------------------------------------------------------------
/contracts/ByteSlice.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.25;
2 |
3 | library ByteSlice {
4 |
5 | struct Slice {
6 | uint256 _unsafe_memPtr; // Memory address of the first byte.
7 | uint256 _unsafe_length; // Length.
8 | }
9 |
10 | /// @dev Converts bytes to a slice.
11 | /// @param self The bytes.
12 | /// @return A slice.
13 | function slice(bytes memory self) internal pure returns (Slice memory newSlice) {
14 | assembly {
15 | let length := mload(self)
16 | let memPtr := add(self, 0x20)
17 | mstore(newSlice, mul(memPtr, iszero(iszero(length))))
18 | mstore(add(newSlice, 0x20), length)
19 | }
20 | }
21 |
22 | // /// @dev Converts bytes to a slice from the given starting position.
23 | // /// 'startpos' <= 'len(slice)'
24 | // /// @param self The bytes.
25 | // /// @param startpos The starting position.
26 | // /// @return A slice.
27 | // function slice(bytes memory self, uint256 startpos) internal pure returns (Slice memory) {
28 | // return slice(slice(self), startpos);
29 | // }
30 |
31 | // /// @dev Converts bytes to a slice from the given starting position.
32 | // /// -len(slice) <= 'startpos' <= 'len(slice)'
33 | // /// @param self The bytes.
34 | // /// @param startpos The starting position.
35 | // /// @return A slice.
36 | // function slice(bytes memory self, int startpos) internal pure returns (Slice memory) {
37 | // return slice(slice(self), startpos);
38 | // }
39 |
40 | // /// @dev Converts bytes to a slice from the given starting-position, and end-position.
41 | // /// 'startpos <= len(slice) and startpos <= endpos'
42 | // /// 'endpos <= len(slice)'
43 | // /// @param self The bytes.
44 | // /// @param startpos The starting position.
45 | // /// @param endpos The end position.
46 | // /// @return A slice.
47 | // function slice(bytes memory self, uint256 startpos, uint256 endpos) internal pure returns (Slice memory) {
48 | // return slice(slice(self), startpos, endpos);
49 | // }
50 |
51 | // /// @dev Converts bytes to a slice from the given starting-position, and end-position.
52 | // /// Warning: higher cost then using unsigned integers.
53 | // /// @param self The bytes.
54 | // /// @param startpos The starting position.
55 | // /// @param endpos The end position.
56 | // /// @return A slice.
57 | // function slice(bytes memory self, int startpos, int endpos) internal pure returns (Slice memory) {
58 | // return slice(slice(self), startpos, endpos);
59 | // }
60 |
61 | /// @dev Get the length of the slice (in bytes).
62 | /// @param self The slice.
63 | /// @return the length.
64 | function len(Slice memory self) internal pure returns (uint256) {
65 | return self._unsafe_length;
66 | }
67 |
68 | // /// @dev Returns the byte from the backing array at a given index.
69 | // /// The function will throw unless 'index < len(slice)'
70 | // /// @param self The slice.
71 | // /// @param index The index.
72 | // /// @return The byte at that index.
73 | // function at(Slice memory self, uint256 index) internal pure returns (byte b) {
74 | // if (index >= self._unsafe_length)
75 | // revert();
76 | // uint256 bb;
77 | // assembly {
78 | // // Get byte at index, and format to 'byte' variable.
79 | // bb := byte(0, mload(add(mload(self), index)))
80 | // }
81 | // b = byte(bb);
82 | // }
83 |
84 | // /// @dev Returns the byte from the backing array at a given index.
85 | // /// The function will throw unless '-len(self) <= index < len(self)'.
86 | // /// @param self The slice.
87 | // /// @param index The index.
88 | // /// @return The byte at that index.
89 | // function at(Slice memory self, int index) internal pure returns (byte b) {
90 | // if (index >= 0)
91 | // return at(self, uint256(index));
92 | // uint256 iAbs = uint256(-index);
93 | // if (iAbs > self._unsafe_length)
94 | // revert();
95 | // return at(self, self._unsafe_length - iAbs);
96 | // }
97 |
98 | // /// @dev Set the byte at the given index.
99 | // /// The function will throw unless 'index < len(slice)'
100 | // /// @param self The slice.
101 | // /// @param index The index.
102 | // /// @return The byte at that index.
103 | // function set(Slice memory self, uint256 index, byte b) internal pure {
104 | // if (index >= self._unsafe_length)
105 | // revert();
106 | // assembly {
107 | // mstore8(add(mload(self), index), byte(0, b))
108 | // }
109 | // }
110 |
111 | // /// @dev Set the byte at the given index.
112 | // /// The function will throw unless '-len(self) <= index < len(self)'.
113 | // /// @param self The slice.
114 | // /// @param index The index.
115 | // /// @return The byte at that index.
116 | // function set(Slice memory self, int index, byte b) internal pure {
117 | // if (index >= 0)
118 | // return set(self, uint256(index), b);
119 | // uint256 iAbs = uint256(-index);
120 | // if (iAbs > self._unsafe_length)
121 | // revert();
122 | // return set(self, self._unsafe_length - iAbs, b);
123 | // }
124 |
125 | /// @dev Creates a copy of the slice.
126 | /// @param self The slice.
127 | /// @return the new reference.
128 | function slice(Slice memory self) internal pure returns (Slice memory newSlice) {
129 | newSlice._unsafe_memPtr = self._unsafe_memPtr;
130 | newSlice._unsafe_length = self._unsafe_length;
131 | }
132 |
133 | // /// @dev Create a new slice from the given starting position.
134 | // /// 'startpos' <= 'len(slice)'
135 | // /// @param self The slice.
136 | // /// @param startpos The starting position.
137 | // /// @return The new slice.
138 | // function slice(Slice memory self, uint256 startpos) internal pure returns (Slice memory newSlice) {
139 | // uint256 length = self._unsafe_length;
140 | // if (startpos > length)
141 | // revert();
142 | // assembly {
143 | // length := sub(length, startpos)
144 | // let newMemPtr := mul(add(mload(self), startpos), iszero(iszero(length)))
145 | // mstore(newSlice, newMemPtr)
146 | // mstore(add(newSlice, 0x20), length)
147 | // }
148 | // }
149 |
150 | // /// @dev Create a new slice from the given starting position.
151 | // /// -len(slice) <= 'startpos' <= 'len(slice)'
152 | // /// @param self The slice.
153 | // /// @param startpos The starting position.
154 | // /// @return The new slice.
155 | // function slice(Slice memory self, int startpos) internal pure returns (Slice memory newSlice) {
156 | // uint256 startpos_;
157 | // uint256 length = self._unsafe_length;
158 | // if (startpos >= 0) {
159 | // startpos_ = uint256(startpos);
160 | // if (startpos_ > length)
161 | // revert();
162 | // } else {
163 | // startpos_ = uint256(-startpos);
164 | // if (startpos_ > length)
165 | // revert();
166 | // startpos_ = length - startpos_;
167 | // }
168 | // assembly {
169 | // length := sub(length, startpos_)
170 | // let newMemPtr := mul(add(mload(self), startpos_), iszero(iszero(length)))
171 | // mstore(newSlice, newMemPtr)
172 | // mstore(add(newSlice, 0x20), length)
173 | // }
174 | // }
175 |
176 | /// @dev Create a new slice from a given slice, starting-position, and end-position.
177 | /// 'startpos <= len(slice) and startpos <= endpos'
178 | /// 'endpos <= len(slice)'
179 | /// @param self The slice.
180 | /// @param startpos The starting position.
181 | /// @param endpos The end position.
182 | /// @return the new slice.
183 | function slice(Slice memory self, uint256 startpos, uint256 endpos) internal pure returns (Slice memory newSlice) {
184 | uint256 length = self._unsafe_length;
185 | if (startpos > length || endpos > length || startpos > endpos)
186 | revert();
187 | assembly {
188 | length := sub(endpos, startpos)
189 | let newMemPtr := mul(add(mload(self), startpos), iszero(iszero(length)))
190 | mstore(newSlice, newMemPtr)
191 | mstore(add(newSlice, 0x20), length)
192 | }
193 | }
194 |
195 | /// Same as new(Slice memory, uint256, uint256) but allows for negative indices.
196 | /// Warning: higher cost then using unsigned integers.
197 | /// @param self The slice.
198 | /// @param startpos The starting position.
199 | /// @param endpos The end position.
200 | /// @return The new slice.
201 | function slice(Slice memory self, int startpos, int endpos) internal pure returns (Slice memory newSlice) {
202 | // Don't allow slice on bytes of length 0.
203 | uint256 startpos_;
204 | uint256 endpos_;
205 | uint256 length = self._unsafe_length;
206 | if (startpos < 0) {
207 | startpos_ = uint256(-startpos);
208 | if (startpos_ > length)
209 | revert();
210 | startpos_ = length - startpos_;
211 | }
212 | else {
213 | startpos_ = uint256(startpos);
214 | if (startpos_ > length)
215 | revert();
216 | }
217 | if (endpos < 0) {
218 | endpos_ = uint256(-endpos);
219 | if (endpos_ > length)
220 | revert();
221 | endpos_ = length - endpos_;
222 | }
223 | else {
224 | endpos_ = uint256(endpos);
225 | if (endpos_ > length)
226 | revert();
227 | }
228 | if(startpos_ > endpos_)
229 | revert();
230 | assembly {
231 | length := sub(endpos_, startpos_)
232 | let newMemPtr := mul(add(mload(self), startpos_), iszero(iszero(length)))
233 | mstore(newSlice, newMemPtr)
234 | mstore(add(newSlice, 0x20), length)
235 | }
236 | }
237 |
238 | /// @dev Creates a 'bytes memory' variable from a slice, copying the data.
239 | /// Bytes are copied from the memory address 'self._unsafe_memPtr'.
240 | /// The number of bytes copied is 'self._unsafe_length'.
241 | /// @param self The slice.
242 | /// @return The bytes variable.
243 | function toBytes(Slice memory self) internal constant returns (bytes memory bts) {
244 | uint256 length = self._unsafe_length;
245 | if (length == 0)
246 | return;
247 | uint256 memPtr = self._unsafe_memPtr;
248 | bts = new bytes(length);
249 | // We can do word-by-word copying since 'bts' was the last thing to be
250 | // allocated. Just overwrite any excess bytes at the end with zeroes.
251 | assembly {
252 | let i := 0
253 | let btsOffset := add(bts, 0x20)
254 | let words := div(add(length, 31), 32)
255 | tag_loop:
256 | jumpi(end, gt(i, words))
257 | {
258 | let offset := mul(i, 32)
259 | mstore(add(btsOffset, offset), mload(add(memPtr, offset)))
260 | i := add(i, 1)
261 | }
262 | jump(tag_loop)
263 | end:
264 | mstore(add(add(bts, length), 0x20), 0)
265 | }
266 | }
267 |
268 | /// @dev Creates an ascii-encoded 'string' variable from a slice, copying the data.
269 | /// Bytes are copied from the memory address 'self._unsafe_memPtr'.
270 | /// The number of bytes copied is 'self._unsafe_length'.
271 | /// @param self The slice.
272 | /// @return The bytes variable.
273 | function toAscii(Slice memory self) internal view returns (string memory str) {
274 | return string(toBytes(self));
275 | }
276 |
277 | /// @dev Check if two slices are equal.
278 | /// @param self The slice.
279 | /// @param other The other slice.
280 | /// @return True if both slices point to the same memory address, and has the same length.
281 | function equals(Slice memory self, Slice memory other) internal pure returns (bool) {
282 | return (
283 | self._unsafe_length == other._unsafe_length &&
284 | self._unsafe_memPtr == other._unsafe_memPtr
285 | );
286 | }
287 |
288 | function toUint(Slice memory self) internal pure returns (uint256 data) {
289 | uint256 sliceLength = self._unsafe_length;
290 | uint256 rStartPos = self._unsafe_memPtr;
291 | if (sliceLength > 32 || sliceLength == 0)
292 | revert();
293 | assembly {
294 | data := div(mload(rStartPos), exp(256, sub(32, sliceLength)))
295 | }
296 | }
297 |
298 | function toBytes32(Slice memory self) internal pure returns (bytes32 data) {
299 | uint256 sliceLength = self._unsafe_length;
300 | uint256 rStartPos = self._unsafe_memPtr;
301 | if (sliceLength != 32)
302 | revert();
303 | assembly {
304 | data := mload(rStartPos)
305 | }
306 | }
307 |
308 | }
--------------------------------------------------------------------------------
/contracts/Conversion.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.25;
2 |
3 | library Conversion {
4 |
5 | function uintToBytes(uint256 self) internal pure returns (bytes memory s) {
6 | uint256 maxlength = 100;
7 | bytes memory reversed = new bytes(maxlength);
8 | uint256 i = 0;
9 | while (self != 0) {
10 | uint256 remainder = self % 10;
11 | self = self / 10;
12 | reversed[i++] = byte(48 + remainder);
13 | }
14 | s = new bytes(i);
15 | for (uint256 j = 0; j < i; j++) {
16 | s[j] = reversed[i - 1 - j];
17 | }
18 | return s;
19 | }
20 | }
--------------------------------------------------------------------------------
/contracts/Migrations.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.25;
2 |
3 | contract Migrations {
4 | address public owner;
5 | uint public last_completed_migration;
6 |
7 | modifier restricted() {
8 | if (msg.sender == owner) _;
9 | }
10 |
11 | constructor() public {
12 | owner = msg.sender;
13 | }
14 |
15 | function setCompleted(uint completed) public restricted {
16 | last_completed_migration = completed;
17 | }
18 |
19 | function upgrade(address new_address) public restricted {
20 | Migrations upgraded = Migrations(new_address);
21 | upgraded.setCompleted(last_completed_migration);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/contracts/PlasmaBlockStorage.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.25;
2 |
3 | import {Conversion} from "./Conversion.sol";
4 | import {ByteSlice} from "./ByteSlice.sol";
5 |
6 | interface PlasmaBlockStorageInterface {
7 | function isOperator(address _operator) external view returns (bool);
8 | function canSignBlocks(address _operator) external view returns (bool);
9 | function setOperator(address _op, uint256 _status) external returns (bool success);
10 | function lastBlockNumber() external view returns(uint256);
11 | function hashOfLastSubmittedBlock() external view returns(bytes32);
12 | function weekOldBlockNumber() external view returns(uint256);
13 | function submitBlockHeaders(bytes _headers) external returns (bool success);
14 | // function storeBlock(uint256 _blockNumber, uint256 _numberOfTransactions, bytes32 _merkleRoot) external returns (bool success);
15 | // function storeBlocks(uint256[] _blockNumbers, uint256[] _numbersOfTransactions, bytes32[] _merkleRoots) external returns (bool success);
16 | function getBlockInformation(uint32 _blockNumber) external view returns (uint256 submittedAt, uint32 numberOfTransactions, bytes32 merkleRoot);
17 | function getMerkleRoot(uint32 _blockNumber) external view returns (bytes32 merkleRoot);
18 | function incrementWeekOldCounter() external;
19 | function getSubmissionTime(uint32 _blockNumber) external view returns (uint256 submittedAt);
20 | function getNumberOfTransactions(uint32 _blockNumber) external view returns (uint32 numberOfTransaction);
21 | }
22 |
23 | contract PlasmaBlockStorage {
24 | using ByteSlice for bytes;
25 | using ByteSlice for ByteSlice.Slice;
26 | using Conversion for uint256;
27 | address public owner;
28 |
29 | mapping(address => OperatorStatus) public operators;
30 | enum OperatorStatus {Null, CanSignTXes, CanSignBlocks}
31 |
32 | uint256 public lastBlockNumber;
33 | uint256 public weekOldBlockNumber;
34 | uint256 public blockHeaderLength = 137;
35 | bytes32 public hashOfLastSubmittedBlock = keccak256(abi.encodePacked(PersonalMessagePrefixBytes,"6","Matter"));
36 |
37 | uint256 constant SignatureLength = 65;
38 | uint256 constant BlockNumberLength = 4;
39 | uint256 constant TxNumberLength = 4;
40 | uint256 constant TxTypeLength = 1;
41 | uint256 constant TxOutputNumberLength = 1;
42 | uint256 constant PreviousHashLength = 32;
43 | uint256 constant MerkleRootHashLength = 32;
44 | bytes constant PersonalMessagePrefixBytes = "\x19Ethereum Signed Message:\n";
45 | uint256 constant PreviousBlockPersonalHashLength = BlockNumberLength +
46 | TxNumberLength +
47 | PreviousHashLength +
48 | MerkleRootHashLength +
49 | SignatureLength;
50 | uint256 constant NewBlockPersonalHashLength = BlockNumberLength +
51 | TxNumberLength +
52 | PreviousHashLength +
53 | MerkleRootHashLength;
54 |
55 | struct BlockInformation {
56 | uint32 numberOfTransactions;
57 | uint64 submittedAt;
58 | bytes32 merkleRootHash;
59 | }
60 |
61 | mapping (uint256 => BlockInformation) public blocks;
62 | event BlockHeaderSubmitted(uint256 indexed _blockNumber, bytes32 indexed _merkleRoot);
63 |
64 | constructor() public {
65 | owner = msg.sender;
66 | blocks[weekOldBlockNumber].submittedAt = uint64(block.timestamp);
67 | }
68 |
69 | modifier onlyOwner() {
70 | require(msg.sender == owner);
71 | _;
72 | }
73 |
74 | function setOwner(address _newOwner) onlyOwner public {
75 | require(_newOwner != address(0));
76 | owner = _newOwner;
77 | }
78 |
79 | function setOperator(address _op, uint256 _status) public returns (bool success) {
80 | require(msg.sender == owner);
81 | OperatorStatus stat = operators[_op];
82 | OperatorStatus newStat = OperatorStatus(_status);
83 | if (stat == OperatorStatus.Null) {
84 | operators[_op] = newStat;
85 | return true;
86 | } else if (stat == OperatorStatus.CanSignTXes) {
87 | require(newStat == OperatorStatus.CanSignBlocks);
88 | operators[_op] = newStat;
89 | return true;
90 | } else if (stat == OperatorStatus.CanSignBlocks) {
91 | require(newStat == OperatorStatus.CanSignTXes);
92 | operators[_op] = newStat;
93 | return true;
94 | }
95 | revert();
96 | }
97 |
98 | function isOperator(address _operator) public view returns (bool) {
99 | OperatorStatus stat = operators[_operator];
100 | return stat != OperatorStatus.Null;
101 | }
102 |
103 | function canSignBlocks(address _operator) public view returns (bool) {
104 | OperatorStatus stat = operators[_operator];
105 | return stat == OperatorStatus.CanSignBlocks;
106 | }
107 |
108 | function incrementWeekOldCounter() onlyOwner public {
109 | uint256 ts = block.timestamp - (1 weeks);
110 | uint256 localCounter = weekOldBlockNumber;
111 | while (uint256(blocks[localCounter].submittedAt) <= ts) {
112 | if (blocks[localCounter].submittedAt == 0) {
113 | break;
114 | }
115 | localCounter++;
116 | }
117 | if (localCounter != weekOldBlockNumber) {
118 | weekOldBlockNumber = localCounter - 1;
119 | }
120 | }
121 |
122 | function submitBlockHeaders(bytes _headers) onlyOwner public returns (bool success) {
123 | require(_headers.length % blockHeaderLength == 0);
124 | ByteSlice.Slice memory slice = _headers.slice();
125 | ByteSlice.Slice memory reusableSlice;
126 | uint256[] memory reusableSpace = new uint256[](5);
127 | bytes32 lastBlockHash = hashOfLastSubmittedBlock;
128 | uint256 _lastBlockNumber = lastBlockNumber;
129 | for (uint256 i = 0; i < _headers.length/blockHeaderLength; i++) {
130 | reusableSlice = slice.slice(i*blockHeaderLength, (i+1)*blockHeaderLength);
131 | reusableSpace[0] = 0;
132 | reusableSpace[1] = BlockNumberLength;
133 | reusableSpace[2] = reusableSlice.slice(reusableSpace[0],reusableSpace[1]).toUint(); //blockNumber
134 | require(reusableSpace[2] == _lastBlockNumber+1+i);
135 | reusableSpace[0] = reusableSpace[1];
136 | reusableSpace[1] += TxNumberLength;
137 | reusableSpace[3] = reusableSlice.slice(reusableSpace[0],reusableSpace[1]).toUint(); //numberOfTransactions
138 | reusableSpace[0] = reusableSpace[1];
139 | reusableSpace[1] += PreviousHashLength;
140 | bytes32 previousBlockHash = reusableSlice.slice(reusableSpace[0],reusableSpace[1]).toBytes32();
141 | require(previousBlockHash == lastBlockHash);
142 | reusableSpace[0] = reusableSpace[1];
143 | reusableSpace[1] += MerkleRootHashLength;
144 | bytes32 merkleRootHash = reusableSlice.slice(reusableSpace[0],reusableSpace[1]).toBytes32();
145 | reusableSpace[0] = reusableSpace[1];
146 | reusableSpace[1] += 1;
147 | reusableSpace[4] = reusableSlice.slice(reusableSpace[0],reusableSpace[1]).toUint();
148 | if (reusableSpace[4] < 27) {
149 | reusableSpace[4] = reusableSpace[4]+27;
150 | }
151 | reusableSpace[0] = reusableSpace[1];
152 | reusableSpace[1] += 32;
153 | bytes32 r = reusableSlice.slice(reusableSpace[0],reusableSpace[1]).toBytes32();
154 | reusableSpace[0] = reusableSpace[1];
155 | reusableSpace[1] += 32;
156 | bytes32 s = reusableSlice.slice(reusableSpace[0],reusableSpace[1]).toBytes32();
157 | bytes32 newBlockHash = keccak256(abi.encodePacked(PersonalMessagePrefixBytes, NewBlockPersonalHashLength.uintToBytes(), uint32(reusableSpace[2]), uint32(reusableSpace[3]), previousBlockHash, merkleRootHash));
158 | address signer = ecrecover(newBlockHash, uint8(reusableSpace[4]), r, s);
159 | require(canSignBlocks(signer));
160 | lastBlockHash = keccak256(abi.encodePacked(PersonalMessagePrefixBytes, PreviousBlockPersonalHashLength.uintToBytes(), reusableSlice.toBytes()));
161 | storeBlock(reusableSpace[2], reusableSpace[3], merkleRootHash, i);
162 | }
163 | hashOfLastSubmittedBlock = lastBlockHash;
164 | return true;
165 | }
166 |
167 |
168 | function storeBlock(uint256 _blockNumber, uint256 _numberOfTransactions, bytes32 _merkleRoot, uint256 _timeOffset) internal returns (bool success) {
169 | incrementWeekOldCounter();
170 | require(_blockNumber == lastBlockNumber + 1);
171 | BlockInformation storage newBlockInformation = blocks[_blockNumber];
172 | newBlockInformation.merkleRootHash = _merkleRoot;
173 | newBlockInformation.submittedAt = uint64(block.timestamp + _timeOffset);
174 | newBlockInformation.numberOfTransactions = uint32(_numberOfTransactions);
175 | lastBlockNumber = _blockNumber;
176 | emit BlockHeaderSubmitted(_blockNumber, _merkleRoot);
177 | return true;
178 | }
179 |
180 | // function storeBlocks(uint256[] _blockNumbers, uint256[] _numbersOfTransactions, bytes32[] _merkleRoots) public returns (bool success) {
181 | // require(_blockNumbers.length == _merkleRoots.length);
182 | // require(_blockNumbers.length == _numbersOfTransactions.length);
183 | // require(_blockNumbers.length != 0);
184 | // incrementWeekOldCounter();
185 |
186 | // uint256 currentCounter = lastBlockNumber;
187 | // for (uint256 i = 0; i < _blockNumbers.length; i++) {
188 | // require(_blockNumbers[i] == currentCounter + 1);
189 | // currentCounter = _blockNumbers[i];
190 | // BlockInformation storage newBlockInformation = blocks[currentCounter];
191 | // newBlockInformation.merkleRootHash = _merkleRoots[i];
192 | // newBlockInformation.submittedAt = uint192(block.timestamp + i);
193 | // newBlockInformation.numberOfTransactions = uint32(_numbersOfTransactions[i]);
194 | // emit BlockHeaderSubmitted(_blockNumbers[i], _merkleRoots[i]);
195 | // }
196 | // lastBlockNumber = currentCounter;
197 | // return true;
198 | // }
199 |
200 | function getBlockInformation(uint32 _blockNumber) public view returns (uint256 submittedAt, uint32 numberOfTransactions, bytes32 merkleRoot) {
201 | BlockInformation storage blockInformation = blocks[uint256(_blockNumber)];
202 | return (blockInformation.submittedAt, blockInformation.numberOfTransactions, blockInformation.merkleRootHash);
203 | }
204 |
205 | function getMerkleRoot(uint32 _blockNumber) public view returns (bytes32 merkleRoot) {
206 | BlockInformation storage blockInformation = blocks[uint256(_blockNumber)];
207 | return blockInformation.merkleRootHash;
208 | }
209 |
210 | function getSubmissionTime(uint32 _blockNumber) public view returns (uint256 submittedAt) {
211 | BlockInformation storage blockInformation = blocks[uint256(_blockNumber)];
212 | return uint256(blockInformation.submittedAt);
213 | }
214 |
215 | function getNumberOfTransactions(uint32 _blockNumber) public view returns (uint32 numberOfTransaction) {
216 | BlockInformation storage blockInformation = blocks[uint256(_blockNumber)];
217 | return blockInformation.numberOfTransactions;
218 | }
219 | }
--------------------------------------------------------------------------------
/contracts/PlasmaTransactionLibrary.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.25;
2 |
3 | import {RLP} from "./RLP.sol";
4 | import {Conversion} from "./Conversion.sol";
5 |
6 | library PlasmaTransactionLibrary {
7 |
8 | using RLP for RLP.RLPItem;
9 | using RLP for RLP.Iterator;
10 | using RLP for bytes;
11 | using Conversion for uint256;
12 |
13 | uint256 public constant BlockNumberLength = 4;
14 | uint256 public constant TxNumberLength = 4;
15 | uint256 public constant TxTypeLength = 1;
16 | uint256 public constant TxOutputNumberLength = 1;
17 |
18 | uint256 constant TxTypeNull = 0;
19 | uint256 constant TxTypeSplit = 1;
20 | uint256 constant TxTypeMerge = 2;
21 | uint256 constant TxTypeFund = 4;
22 |
23 | struct TransactionInput {
24 | uint32 blockNumber;
25 | uint32 txNumberInBlock;
26 | uint8 outputNumberInTX;
27 | uint256 amount;
28 | }
29 |
30 | struct TransactionOutput {
31 | address recipient;
32 | uint8 outputNumberInTX;
33 | uint256 amount;
34 | }
35 |
36 | struct PlasmaTransaction {
37 | uint32 txNumberInBlock;
38 | uint8 txType;
39 | TransactionInput[] inputs;
40 | TransactionOutput[] outputs;
41 | address sender;
42 | bool isWellFormed;
43 | }
44 |
45 | bytes constant PersonalMessagePrefixBytes = "\x19Ethereum Signed Message:\n";
46 |
47 | function createPersonalMessageTypeHash(bytes memory message) internal pure returns (bytes32 msgHash) {
48 | bytes memory lengthBytes = message.length.uintToBytes();
49 | return keccak256(abi.encodePacked(PersonalMessagePrefixBytes, lengthBytes, message));
50 | }
51 |
52 | function checkForInclusionIntoBlock(bytes32 _merkleRoot, bytes _plasmaTransaction, bytes _merkleProof) internal pure returns (bool included, uint256 txNumber) {
53 | (included, txNumber) = checkProof(_merkleRoot, _plasmaTransaction, _merkleProof, true);
54 | return (included, txNumber);
55 | }
56 |
57 | function checkProof(bytes32 root, bytes data, bytes proof, bool convertToMessageHash) internal pure returns (bool included, uint256 txNumber) {
58 | bytes32 h;
59 | if (convertToMessageHash) {
60 | h = createPersonalMessageTypeHash(data);
61 | } else {
62 | h = keccak256(data);
63 | }
64 | bytes32 elProvided;
65 | uint8 rightElementProvided;
66 | uint32 loc;
67 | uint32 elLoc;
68 | uint256 transactionNumber;
69 | uint256 treeLevel;
70 | for (uint32 i = 32; i <= uint32(proof.length); i += 33) {
71 | assembly {
72 | loc := proof
73 | elLoc := add(loc, add(i, 1))
74 | elProvided := mload(elLoc)
75 | }
76 | rightElementProvided = uint8(bytes1(0xff)&proof[i-32]);
77 | if (rightElementProvided > 0) {
78 | h = keccak256(abi.encodePacked(h, elProvided));
79 | } else {
80 | transactionNumber += 1 << treeLevel;
81 | h = keccak256(abi.encodePacked(elProvided, h));
82 | }
83 | treeLevel++;
84 | }
85 | if (h == root) {
86 | return (true, transactionNumber);
87 | }
88 | return (false, 0);
89 | }
90 |
91 | // function plasmaTransactionFromBytes(bytes _rawTX) internal view returns (PlasmaTransaction memory TX) {
92 | // RLP.RLPItem memory item = _rawTX.toRLPItem();
93 | // if (!item._validate()) {
94 | // return constructEmptyTransaction();
95 | // }
96 | // if (!item.isList()) {
97 | // return constructEmptyTransaction();
98 | // }
99 | // uint256 numItems = item.items();
100 | // if (numItems != 2) {
101 | // return constructEmptyTransaction();
102 | // }
103 | // RLP.Iterator memory iter = item.iterator();
104 | // item = iter.next();
105 | // (uint256 numInBlock, bool valid) = item.toUint(TxNumberLength);
106 | // if (!valid) {
107 | // return constructEmptyTransaction();
108 | // }
109 | // item = iter.next();
110 | // TX = signedPlasmaTransactionFromRLPItem(item);
111 | // if (!TX.isWellFormed) {
112 | // return constructEmptyTransaction();
113 | // }
114 | // TX.txNumberInBlock = uint32(numInBlock);
115 | // return TX;
116 | // }
117 |
118 | function signedPlasmaTransactionFromBytes(bytes _rawLimboTX) internal view returns (PlasmaTransaction memory TX) {
119 | RLP.RLPItem memory item = _rawLimboTX.toRLPItem();
120 | if (!item._validate()) {
121 | return constructEmptyTransaction();
122 | }
123 | TX = signedPlasmaTransactionFromRLPItem(item);
124 | if (!TX.isWellFormed) {
125 | return constructEmptyTransaction();
126 | }
127 | return TX;
128 | }
129 |
130 | function signedPlasmaTransactionFromRLPItem(RLP.RLPItem memory _item) internal view returns (PlasmaTransaction memory TX) {
131 | if (!_item.isList()) {
132 | return constructEmptyTransaction();
133 | }
134 | uint256 numItems = _item.items();
135 | if (numItems != 4) {
136 | return constructEmptyTransaction();
137 | }
138 | RLP.Iterator memory iter = _item.iterator();
139 | RLP.RLPItem memory item = iter.next();
140 | bytes memory rawSignedPart = item.toBytes();
141 | bytes32 persMessageHashWithoutNumber = createPersonalMessageTypeHash(rawSignedPart);
142 | TX = plasmaTransactionFromRLPItem(item);
143 | if (!TX.isWellFormed) {
144 | return constructEmptyTransaction();
145 | }
146 | item = iter.next();
147 | (uint256 v_tmp, bool valid) = item.toUint(1);
148 | uint8 v = uint8(v_tmp);
149 | if (!valid) {
150 | return constructEmptyTransaction();
151 | }
152 | item = iter.next();
153 | bytes32 r;
154 | (r, valid) = item.toBytes32();
155 | if (!valid) {
156 | return constructEmptyTransaction();
157 | }
158 | item = iter.next();
159 | bytes32 s;
160 | (s, valid) = item.toBytes32();
161 | if (!valid) {
162 | return constructEmptyTransaction();
163 | }
164 | TX.sender = ecrecover(persMessageHashWithoutNumber, v, r, s);
165 | if (TX.sender == address(0)) {
166 | return constructEmptyTransaction();
167 | }
168 | return TX;
169 | }
170 |
171 | function plasmaTransactionFromRLPItem(RLP.RLPItem memory _item) internal pure returns (PlasmaTransaction memory TX) {
172 | if (!_item.isList()) {
173 | return constructEmptyTransaction();
174 | }
175 | uint256 numItems = _item.items();
176 | if (numItems != 3) {
177 | return constructEmptyTransaction();
178 | }
179 | RLP.Iterator memory iter = _item.iterator();
180 | if (!iter.hasNext()) {
181 | return constructEmptyTransaction();
182 | }
183 | RLP.RLPItem memory item = iter.next(); // transaction type
184 | bool reusableValidFlag = false;
185 | uint256[] memory reusableSpace = new uint256[](7);
186 | (reusableSpace[0], reusableValidFlag) = item.toUint(TxTypeLength); //hardcode can be used
187 | uint256 txType = reusableSpace[0];
188 | if (!(txType == TxTypeFund || txType == TxTypeSplit || txType == TxTypeMerge) ) {
189 | return constructEmptyTransaction();
190 | }
191 | if (!reusableValidFlag) {
192 | return constructEmptyTransaction();
193 | }
194 | item = iter.next();
195 | if (!item.isList()) {
196 | return constructEmptyTransaction();
197 | }
198 | numItems = item.items();
199 | if (numItems == 0) {
200 | return constructEmptyTransaction();
201 | }
202 | RLP.Iterator memory reusableIterator = item.iterator();
203 | RLP.Iterator memory reusableIteratorPerItem;
204 | TransactionInput[] memory inputs = new TransactionInput[](numItems);
205 | reusableSpace[1] = 0;
206 | while (reusableIterator.hasNext()) { // go over the inputs
207 | item = reusableIterator.next();
208 | if (!item.isList()) {
209 | return constructEmptyTransaction();
210 | }
211 | numItems = item.items();
212 | if (numItems != 4) {
213 | return constructEmptyTransaction();
214 | }
215 | reusableIteratorPerItem = item.iterator();
216 | (reusableSpace[2], reusableValidFlag) = reusableIteratorPerItem.next().toUint(BlockNumberLength); // block number
217 | if (!reusableValidFlag) {
218 | return constructEmptyTransaction();
219 | }
220 | (reusableSpace[3], reusableValidFlag) = reusableIteratorPerItem.next().toUint(TxNumberLength); // tx number in block
221 | if (!reusableValidFlag) {
222 | return constructEmptyTransaction();
223 | }
224 | (reusableSpace[4], reusableValidFlag) = reusableIteratorPerItem.next().toUint(TxOutputNumberLength); //tx output number in tx
225 | if (!reusableValidFlag) {
226 | return constructEmptyTransaction();
227 | }
228 | (reusableSpace[5], reusableValidFlag) = reusableIteratorPerItem.next().toUint(32); //tx amount
229 | if (!reusableValidFlag) {
230 | return constructEmptyTransaction();
231 | }
232 | TransactionInput memory input = TransactionInput({
233 | blockNumber: uint32(reusableSpace[2]),
234 | txNumberInBlock: uint32(reusableSpace[3]),
235 | outputNumberInTX: uint8(reusableSpace[4]),
236 | amount: reusableSpace[5]
237 | });
238 | inputs[reusableSpace[1]] = input;
239 | reusableSpace[1]++;
240 | } // now we have completed parsing all the inputs
241 | if (!iter.hasNext()) {
242 | return constructEmptyTransaction();
243 | }
244 | item = iter.next();
245 | if(!item.isList()){
246 | return constructEmptyTransaction();
247 | }
248 | reusableIterator = item.iterator();
249 | numItems = item.items();
250 | if (numItems == 0) {
251 | return constructEmptyTransaction();
252 | }
253 | TransactionOutput[] memory outputs = new TransactionOutput[](numItems);
254 | reusableSpace[1] = 0;
255 | address reusableRecipient;
256 | while (reusableIterator.hasNext()) { // go over outputs
257 | item = reusableIterator.next();
258 | if (!item.isList()) {
259 | return constructEmptyTransaction();
260 | }
261 | numItems = item.items();
262 | if (numItems != 3) {
263 | return constructEmptyTransaction();
264 | }
265 | reusableIteratorPerItem = item.iterator();
266 | if (!reusableIteratorPerItem.hasNext()) {
267 | return constructEmptyTransaction();
268 | }
269 | (reusableSpace[2], reusableValidFlag) = reusableIteratorPerItem.next().toUint(TxOutputNumberLength); // output numbber
270 | if (!reusableValidFlag) {
271 | return constructEmptyTransaction();
272 | }
273 | (reusableRecipient, reusableValidFlag) = reusableIteratorPerItem.next().toAddress(); // recipient
274 | if (!reusableValidFlag) {
275 | return constructEmptyTransaction();
276 | }
277 | (reusableSpace[3], reusableValidFlag) = reusableIteratorPerItem.next().toUint(32); //amount
278 | if (!reusableValidFlag) {
279 | return constructEmptyTransaction();
280 | }
281 | TransactionOutput memory output = TransactionOutput({
282 | outputNumberInTX: uint8(reusableSpace[2]),
283 | recipient: reusableRecipient,
284 | amount: reusableSpace[3]
285 | });
286 | outputs[reusableSpace[1]] = output;
287 | reusableSpace[1]++;
288 | }
289 | TX = PlasmaTransaction({
290 | txNumberInBlock: 0,
291 | txType: uint8(reusableSpace[0]),
292 | inputs: inputs,
293 | outputs: outputs,
294 | sender: address(0),
295 | isWellFormed: true
296 | });
297 | return TX;
298 | }
299 |
300 | function makeTransactionIndex(uint32 _blockNumber, uint32 _txNumberInBlock) internal pure returns (uint64 index) {
301 | index += (uint64(_blockNumber) << (TxNumberLength*8));
302 | index += uint64(_txNumberInBlock);
303 | return index;
304 | }
305 |
306 | function parseTransactionIndex(uint64 _index) internal pure returns (uint32 blockNumber, uint32 txNumberInBlock) {
307 | uint64 idx = _index;
308 | txNumberInBlock = uint32(idx % (uint64(1) << TxNumberLength*8));
309 | idx = idx >> (TxNumberLength*8);
310 | blockNumber = uint32(idx);
311 | return (blockNumber, txNumberInBlock);
312 | }
313 |
314 | function makeInputOrOutputIndex(uint32 _blockNumber, uint32 _txNumberInBlock, uint8 _outputNumberInTX) internal pure returns (uint72 index) {
315 | index += ( uint72(_blockNumber) << ((TxNumberLength + TxOutputNumberLength)*8) );
316 | index += ( uint72(_txNumberInBlock) << (TxOutputNumberLength*8) );
317 | index += uint72(_outputNumberInTX);
318 | return index;
319 | }
320 |
321 | function parseInputOrOutputIndex(uint72 _index) internal pure returns (uint32 blockNumber, uint32 txNumberInBlock, uint8 outputNumber) {
322 | uint256 idx = _index;
323 | outputNumber = uint8(idx % (uint72(1) << TxOutputNumberLength*8));
324 | idx = idx >> (TxOutputNumberLength*8);
325 | txNumberInBlock = uint32(idx % (uint72(1) << TxNumberLength*8));
326 | idx = idx >> (TxNumberLength*8);
327 | blockNumber = uint32(idx % (uint72(1) << BlockNumberLength*8));
328 | return (blockNumber, txNumberInBlock, outputNumber);
329 | }
330 |
331 | function constructEmptyTransaction() internal pure returns (PlasmaTransaction memory TX) {
332 | TransactionInput[] memory inputs = new TransactionInput[](0);
333 | TransactionOutput[] memory outputs = new TransactionOutput[](0);
334 | TX = PlasmaTransaction({
335 | txNumberInBlock: 0,
336 | txType: 0,
337 | sender: address(0),
338 | inputs: inputs,
339 | outputs: outputs,
340 | isWellFormed: false
341 | });
342 | }
343 | }
--------------------------------------------------------------------------------
/contracts/PriorityQueue.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.25;
2 |
3 | // original source from https://github.com/DavidKnott
4 | // https://github.com/omisego/plasma-mvp/blob/master/plasma/root_chain/contracts/RootChain/RootChain.sol
5 |
6 | import {SafeMath} from "./SafeMath.sol";
7 |
8 | interface PriorityQueueInterface {
9 | function insert(uint72 _priority, bytes22 _partialHash) external;
10 | function minChild(uint256 i) view external returns (uint256);
11 | function getMin() external view returns (bytes22 partialHash);
12 | function delMin() external returns (bytes22 partialHash);
13 | function currentSize() external returns (uint256);
14 | }
15 |
16 | contract PriorityQueue {
17 | using SafeMath for uint256;
18 | /*
19 | * Modifiers
20 | */
21 | modifier onlyOwner() {
22 | require(msg.sender == owner);
23 | _;
24 | }
25 |
26 | function setOwner (address _newOwner) onlyOwner public {
27 | require(_newOwner != address(0));
28 | owner = _newOwner;
29 | }
30 |
31 | /*
32 | * Storage
33 | */
34 |
35 | struct QueueItem {
36 | uint72 priority;
37 | bytes22 partialHash;
38 | // 31 bytes
39 | }
40 |
41 | address public owner;
42 | QueueItem[] public heapList;
43 | uint256 public currentSize;
44 |
45 | constructor () public
46 | {
47 | owner = msg.sender;
48 | QueueItem memory item = QueueItem({
49 | priority: 0,
50 | partialHash: bytes22(0)
51 | });
52 | heapList.push(item);
53 | currentSize = 0;
54 | }
55 |
56 | function insert(uint72 _priority, bytes22 _index)
57 | public
58 | onlyOwner
59 | {
60 | heapList.push(QueueItem({
61 | priority: _priority,
62 | partialHash: _index
63 | }));
64 | currentSize = currentSize.add(1);
65 | percUp(currentSize);
66 | }
67 |
68 | function minChild(uint256 i)
69 | public
70 | view
71 | returns (uint256)
72 | {
73 | if (i.mul(2).add(1) > currentSize) {
74 | return i.mul(2);
75 | } else {
76 | if (heapList[i.mul(2)].priority < heapList[i.mul(2).add(1)].priority) {
77 | return i.mul(2);
78 | } else {
79 | return i.mul(2).add(1);
80 | }
81 | }
82 | }
83 |
84 | function getMin()
85 | public
86 | view
87 | returns (bytes22 index)
88 | {
89 | return heapList[1].partialHash;
90 | }
91 |
92 | function delMin()
93 | public
94 | onlyOwner
95 | returns (bytes22 partialHash)
96 | {
97 | require(currentSize > 0);
98 | partialHash = heapList[1].partialHash;
99 | heapList[1] = heapList[currentSize];
100 | delete heapList[currentSize];
101 | currentSize = currentSize.sub(1);
102 | percDown(1);
103 | return partialHash;
104 | }
105 |
106 | function percUp(uint256 j)
107 | private
108 | {
109 | uint256 i = j;
110 | QueueItem memory tmp;
111 | while (i.div(2) > 0) {
112 | if (heapList[i].priority < heapList[i.div(2)].priority) {
113 | tmp = heapList[i.div(2)];
114 | heapList[i.div(2)] = heapList[i];
115 | heapList[i] = tmp;
116 | }
117 | i = i.div(2);
118 | }
119 | }
120 |
121 | function percDown(uint256 j)
122 | private
123 | {
124 | uint256 i = j;
125 | QueueItem memory tmp;
126 | while (i.mul(2) <= currentSize) {
127 | uint256 mc = minChild(i);
128 | if (heapList[i].priority > heapList[mc].priority) {
129 | tmp = heapList[i];
130 | heapList[i] = heapList[mc];
131 | heapList[mc] = tmp;
132 | }
133 | i = mc;
134 | }
135 | }
136 | }
--------------------------------------------------------------------------------
/contracts/RLP.sol:
--------------------------------------------------------------------------------
1 | /**
2 | * @title RLPReader
3 | *
4 | * RLPReader is used to read and parse RLP encoded data in memory.
5 | *
6 | * @author Andreas Olofsson (androlo1980@gmail.com)
7 | * updated by
8 | * @author Alex Vlasov (alex.m.vlasov@gmail.com)
9 | */
10 | pragma solidity ^0.4.25;
11 |
12 | library RLP {
13 |
14 | uint256 constant DATA_SHORT_START = 0x80;
15 | uint256 constant DATA_LONG_START = 0xB8;
16 | uint256 constant LIST_SHORT_START = 0xC0;
17 | uint256 constant LIST_LONG_START = 0xF8;
18 |
19 | uint256 constant DATA_LONG_OFFSET = 0xB7;
20 | uint256 constant LIST_LONG_OFFSET = 0xF7;
21 |
22 |
23 | struct RLPItem {
24 | uint256 _unsafe_memPtr; // Pointer to the RLP-encoded bytes.
25 | uint256 _unsafe_length; // Number of bytes. This is the full length of the string.
26 | }
27 |
28 | struct Iterator {
29 | RLPItem _unsafe_item; // Item that's being iterated over.
30 | uint256 _unsafe_nextPtr; // Position of the next item in the list.
31 | }
32 |
33 | /* Iterator */
34 |
35 | function next(Iterator memory self) internal pure returns (RLPItem memory subItem) {
36 | if (hasNext(self)) {
37 | uint256 ptr = self._unsafe_nextPtr;
38 | uint256 itemLength = _itemLength(ptr);
39 | subItem._unsafe_memPtr = ptr;
40 | subItem._unsafe_length = itemLength;
41 | self._unsafe_nextPtr = ptr + itemLength;
42 | }
43 | else
44 | revert();
45 | }
46 |
47 | function next(Iterator memory self, bool strict) internal pure returns (RLPItem memory subItem) {
48 | subItem = next(self);
49 | if (strict && !_validate(subItem))
50 | revert();
51 | return;
52 | }
53 |
54 | function hasNext(Iterator memory self) internal pure returns (bool) {
55 | RLPItem memory item = self._unsafe_item;
56 | return self._unsafe_nextPtr < item._unsafe_memPtr + item._unsafe_length;
57 | }
58 |
59 | /* RLPItem */
60 |
61 | /// @dev Creates an RLPItem from an array of RLP encoded bytes.
62 | /// @param self The RLP encoded bytes.
63 | /// @return An RLPItem
64 | function toRLPItem(bytes memory self) internal pure returns (RLPItem memory) {
65 | uint256 len = self.length;
66 | if (len == 0) {
67 | return RLPItem(0, 0);
68 | }
69 | uint256 memPtr;
70 | assembly {
71 | memPtr := add(self, 0x20)
72 | }
73 | return RLPItem(memPtr, len);
74 | }
75 |
76 | /// @dev Creates an RLPItem from an array of RLP encoded bytes.
77 | /// @param self The RLP encoded bytes.
78 | /// @param strict Will revert() if the data is not RLP encoded.
79 | /// @return An RLPItem
80 | function toRLPItem(bytes memory self, bool strict) internal pure returns (RLPItem memory) {
81 | RLPItem memory item = toRLPItem(self);
82 | if (strict) {
83 | uint256 len = self.length;
84 | if (_payloadOffset(item) > len)
85 | revert();
86 | if (_itemLength(item._unsafe_memPtr) != len)
87 | revert();
88 | if (!_validate(item))
89 | revert();
90 | }
91 | return item;
92 | }
93 |
94 | /// @dev Check if the RLP item is null.
95 | /// @param self The RLP item.
96 | /// @return 'true' if the item is null.
97 | function isNull(RLPItem memory self) internal pure returns (bool ret) {
98 | return self._unsafe_length == 0;
99 | }
100 |
101 | /// @dev Check if the RLP item is a list.
102 | /// @param self The RLP item.
103 | /// @return 'true' if the item is a list.
104 | function isList(RLPItem memory self) internal pure returns (bool ret) {
105 | if (self._unsafe_length == 0)
106 | return false;
107 | uint256 memPtr = self._unsafe_memPtr;
108 | assembly {
109 | ret := iszero(lt(byte(0, mload(memPtr)), 0xC0))
110 | }
111 | }
112 |
113 | /// @dev Check if the RLP item is data.
114 | /// @param self The RLP item.
115 | /// @return 'true' if the item is data.
116 | function isData(RLPItem memory self) internal pure returns (bool ret) {
117 | if (self._unsafe_length == 0)
118 | return false;
119 | uint256 memPtr = self._unsafe_memPtr;
120 | assembly {
121 | ret := lt(byte(0, mload(memPtr)), 0xC0)
122 | }
123 | }
124 |
125 | /// @dev Check if the RLP item is empty (string or list).
126 | /// @param self The RLP item.
127 | /// @return 'true' if the item is null.
128 | function isEmpty(RLPItem memory self) internal pure returns (bool ret) {
129 | if (isNull(self))
130 | return false;
131 | uint256 b0;
132 | uint256 memPtr = self._unsafe_memPtr;
133 | assembly {
134 | b0 := byte(0, mload(memPtr))
135 | }
136 | return (b0 == DATA_SHORT_START || b0 == LIST_SHORT_START);
137 | }
138 |
139 | /// @dev Get the number of items in an RLP encoded list.
140 | /// @param self The RLP item.
141 | /// @return The number of items.
142 | function items(RLPItem memory self) internal pure returns (uint) {
143 | if (!isList(self))
144 | return 0;
145 | uint256 b0;
146 | uint256 memPtr = self._unsafe_memPtr;
147 | assembly {
148 | b0 := byte(0, mload(memPtr))
149 | }
150 | uint256 pos = memPtr + _payloadOffset(self);
151 | uint256 last = memPtr + self._unsafe_length - 1;
152 | uint256 itms;
153 | while(pos <= last) {
154 | pos += _itemLength(pos);
155 | itms++;
156 | }
157 | return itms;
158 | }
159 |
160 | /// @dev Create an iterator.
161 | /// @param self The RLP item.
162 | /// @return An 'Iterator' over the item.
163 | function iterator(RLPItem memory self) internal pure returns (Iterator memory it) {
164 | if (!isList(self))
165 | return;
166 | // revert();
167 | uint256 ptr = self._unsafe_memPtr + _payloadOffset(self);
168 | it._unsafe_item = self;
169 | it._unsafe_nextPtr = ptr;
170 | }
171 |
172 | /// @dev Return the RLP encoded bytes.
173 | /// @param self The RLPItem.
174 | /// @return The bytes.
175 | function toBytes(RLPItem memory self) internal view returns (bytes memory bts) {
176 | uint256 len = self._unsafe_length;
177 | if (len == 0)
178 | return;
179 | bts = new bytes(len);
180 | _copyToBytes(self._unsafe_memPtr, bts, len);
181 | }
182 |
183 | /// @dev Decode an RLPItem into bytes. This will not work if the
184 | /// RLPItem is a list.
185 | /// @param self The RLPItem.
186 | /// @return The decoded string.
187 | function toData(RLPItem memory self) internal view returns (bytes memory bts) {
188 | if (!isData(self))
189 | return;
190 | (uint256 rStartPos, uint256 len) = _decode(self);
191 | bts = new bytes(len);
192 | _copyToBytes(rStartPos, bts, len);
193 | }
194 |
195 | /// @dev Get the list of sub-items from an RLP encoded list.
196 | /// Warning: This is inefficient, as it requires that the list is read twice.
197 | /// @param self The RLP item.
198 | /// @return Array of RLPItems.
199 | function toList(RLPItem memory self) internal pure returns (RLPItem[] memory list) {
200 | if (!isList(self))
201 | return;
202 | // revert();
203 | uint256 numItems = items(self);
204 | list = new RLPItem[](numItems);
205 | Iterator memory it = iterator(self);
206 | uint256 idx;
207 | while(hasNext(it)) {
208 | list[idx] = next(it);
209 | idx++;
210 | }
211 | }
212 |
213 | /// @dev Decode an RLPItem into an ascii string. This will not work if the
214 | /// RLPItem is a list.
215 | /// @param self The RLPItem.
216 | /// @return The decoded string.
217 | function toAscii(RLPItem memory self) internal view returns (string memory str) {
218 | if (!isData(self))
219 | return;
220 | // revert();
221 | (uint256 rStartPos, uint256 len) = _decode(self);
222 | bytes memory bts = new bytes(len);
223 | _copyToBytes(rStartPos, bts, len);
224 | str = string(bts);
225 | }
226 |
227 | /// @dev Decode an RLPItem into a uint. This will not work if the
228 | /// RLPItem is a list.
229 | /// @param self The RLPItem.
230 | /// @return The decoded string.
231 | function toUint(RLPItem memory self, uint256 maxLength) internal pure returns (uint256 data, bool valid) {
232 | if (!isData(self)) {
233 | return (0, false);
234 | }
235 | (uint256 rStartPos, uint256 len) = _decode(self);
236 | if (len > 32 || len == 0 || len > maxLength) {
237 | return (0, false);
238 | }
239 | assembly {
240 | data := div(mload(rStartPos), exp(256, sub(32, len)))
241 | }
242 | return (data, true);
243 | }
244 |
245 | /// @dev Decode an RLPItem into a boolean. This will not work if the
246 | /// RLPItem is a list.
247 | /// @param self The RLPItem.
248 | /// @return The decoded string.
249 | function toBool(RLPItem memory self) internal pure returns (bool data, bool valid) {
250 | if (!isData(self))
251 | return;
252 | (uint256 rStartPos, uint256 len) = _decode(self);
253 | if (len != 1)
254 | return;
255 | uint256 temp;
256 | assembly {
257 | temp := byte(0, mload(rStartPos))
258 | }
259 | if (temp > 1) {
260 | return;
261 | } else if (temp == 1) {
262 | data = true;
263 | } else {
264 | data = false;
265 | }
266 | return (data, true);
267 | }
268 |
269 | /// @dev Decode an RLPItem into a byte. This will not work if the
270 | /// RLPItem is a list.
271 | /// @param self The RLPItem.
272 | /// @return The decoded string.
273 | function toByte(RLPItem memory self) internal pure returns (byte data, bool valid) {
274 | if (!isData(self))
275 | return;
276 | (uint256 rStartPos, uint256 len) = _decode(self);
277 | if (len != 1)
278 | return;
279 | uint256 temp;
280 | assembly {
281 | temp := byte(0, mload(rStartPos))
282 | }
283 | return (byte(temp), true);
284 | }
285 |
286 | /// @dev Decode an RLPItem into an int. This will not work if the
287 | /// RLPItem is a list.
288 | /// @param self The RLPItem.
289 | /// @return The decoded string.
290 | function toInt(RLPItem memory self, uint256 maxLength) internal pure returns (int256 data, bool valid) {
291 | (uint256 num, bool val) = toUint(self, maxLength);
292 | if (!val) {
293 | return;
294 | }
295 | return (int256(num), val);
296 | }
297 |
298 | /// @dev Decode an RLPItem into a bytes32. This will not work if the
299 | /// RLPItem is a list.
300 | /// @param self The RLPItem.
301 | /// @return The decoded string.
302 | function toBytes32(RLPItem memory self) internal pure returns (bytes32 data, bool valid) {
303 | (uint256 num, bool val) = toUint(self, 32);
304 | if (!val) {
305 | return;
306 | }
307 | return (bytes32(num), val);
308 | }
309 |
310 | /// @dev Decode an RLPItem into an address. This will not work if the
311 | /// RLPItem is a list.
312 | /// @param self The RLPItem.
313 | /// @return The decoded string.
314 | function toAddress(RLPItem memory self) internal pure returns (address data, bool valid) {
315 | if (!isData(self))
316 | return;
317 | (uint256 rStartPos, uint256 len) = _decode(self);
318 | if (len != 20)
319 | return;
320 | assembly {
321 | data := div(mload(rStartPos), exp(256, 12))
322 | }
323 | return (data, true);
324 | }
325 |
326 | // Get the payload offset.
327 | function _payloadOffset(RLPItem memory self) private pure returns (uint) {
328 | if (self._unsafe_length == 0)
329 | return 0;
330 | uint256 b0;
331 | uint256 memPtr = self._unsafe_memPtr;
332 | assembly {
333 | b0 := byte(0, mload(memPtr))
334 | }
335 | if (b0 < DATA_SHORT_START)
336 | return 0;
337 | if (b0 < DATA_LONG_START || (b0 >= LIST_SHORT_START && b0 < LIST_LONG_START))
338 | return 1;
339 | if (b0 < LIST_SHORT_START)
340 | return b0 - DATA_LONG_OFFSET + 1;
341 | return b0 - LIST_LONG_OFFSET + 1;
342 | }
343 |
344 | // Get the full length of an RLP item.
345 | function _itemLength(uint256 memPtr) private pure returns (uint256 len) {
346 | uint256 b0;
347 | assembly {
348 | b0 := byte(0, mload(memPtr))
349 | }
350 | if (b0 < DATA_SHORT_START)
351 | len = 1;
352 | else if (b0 < DATA_LONG_START)
353 | len = b0 - DATA_SHORT_START + 1;
354 | else if (b0 < LIST_SHORT_START) {
355 | assembly {
356 | let bLen := sub(b0, 0xB7) // bytes length (DATA_LONG_OFFSET)
357 | let dLen := div(mload(add(memPtr, 1)), exp(256, sub(32, bLen))) // data length
358 | len := add(1, add(bLen, dLen)) // total length
359 | }
360 | }
361 | else if (b0 < LIST_LONG_START)
362 | len = b0 - LIST_SHORT_START + 1;
363 | else {
364 | assembly {
365 | let bLen := sub(b0, 0xF7) // bytes length (LIST_LONG_OFFSET)
366 | let dLen := div(mload(add(memPtr, 1)), exp(256, sub(32, bLen))) // data length
367 | len := add(1, add(bLen, dLen)) // total length
368 | }
369 | }
370 | }
371 |
372 | // Get start position and length of the data.
373 | function _decode(RLPItem memory self) private pure returns (uint256 memPtr, uint256 len) {
374 | if (!isData(self))
375 | return;
376 | uint256 b0;
377 | uint256 start = self._unsafe_memPtr;
378 | assembly {
379 | b0 := byte(0, mload(start))
380 | }
381 | if (b0 < DATA_SHORT_START) {
382 | memPtr = start;
383 | len = 1;
384 | return;
385 | }
386 | if (b0 < DATA_LONG_START) {
387 | len = self._unsafe_length - 1;
388 | memPtr = start + 1;
389 | } else {
390 | uint256 bLen;
391 | assembly {
392 | bLen := sub(b0, 0xB7) // DATA_LONG_OFFSET
393 | }
394 | len = self._unsafe_length - 1 - bLen;
395 | memPtr = start + bLen + 1;
396 | }
397 | return;
398 | }
399 |
400 | // Assumes that enough memory has been allocated to store in target.
401 | function _copyToBytes(uint256 btsPtr, bytes memory tgt, uint256 btsLen) private view {
402 | // Exploiting the fact that 'tgt' was the last thing to be allocated,
403 | // we can write entire words, and just overwrite any excess.
404 | assembly {
405 | {
406 | let i := 0 // Start at arr + 0x20
407 | let words := div(add(btsLen, 31), 32)
408 | let rOffset := btsPtr
409 | let wOffset := add(tgt, 0x20)
410 | tag_loop:
411 | jumpi(end, eq(i, words))
412 | {
413 | let offset := mul(i, 0x20)
414 | mstore(add(wOffset, offset), mload(add(rOffset, offset)))
415 | i := add(i, 1)
416 | }
417 | jump(tag_loop)
418 | end:
419 | mstore(add(tgt, add(0x20, mload(tgt))), 0)
420 | }
421 | }
422 | }
423 |
424 | // Check that an RLP item is valid.
425 | function _validate(RLPItem memory self) internal pure returns (bool ret) {
426 | // Check that RLP is well-formed.
427 | uint256 b0;
428 | uint256 b1;
429 | uint256 memPtr = self._unsafe_memPtr;
430 | assembly {
431 | b0 := byte(0, mload(memPtr))
432 | b1 := byte(1, mload(memPtr))
433 | }
434 | if (b0 == DATA_SHORT_START + 1 && b1 < DATA_SHORT_START)
435 | return false;
436 | return true;
437 | }
438 | }
--------------------------------------------------------------------------------
/contracts/SafeMath.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.25;
2 |
3 | library SafeMath {
4 | function mul(uint256 a, uint256 b) internal pure returns (uint256) {
5 | uint256 c = a * b;
6 | assert(a == 0 || c / a == b);
7 | return c;
8 | }
9 |
10 | function div(uint256 a, uint256 b) internal pure returns (uint256) {
11 | uint256 c = a / b;
12 | return c;
13 | }
14 |
15 | function sub(uint256 a, uint256 b) internal pure returns (uint256) {
16 | assert(b <= a);
17 | return a - b;
18 | }
19 |
20 | function add(uint256 a, uint256 b) internal pure returns (uint256) {
21 | uint256 c = a + b;
22 | assert(c >= a);
23 | return c;
24 | }
25 | }
--------------------------------------------------------------------------------
/contracts/Structures.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.25;
2 |
3 | library StructuresLibrary {
4 | struct ExitRecord {
5 | bytes32 transactionRef;
6 | //32 bytes
7 | uint256 amount;
8 | // 64 bytes
9 | address owner;
10 | uint64 timePublished;
11 | uint32 blockNumber;
12 | // 96 bytes
13 | uint32 transactionNumber;
14 | uint8 outputNumber;
15 | bool isValid;
16 | bool isLimbo;
17 | // 96 + 7 bytes
18 | uint72 priority;
19 | // 96 + 16 bytes
20 | }
21 |
22 | struct LimboData {
23 | LimboInputChallenge[] inputChallenges;
24 | LimboOutput[] outputs;
25 | }
26 |
27 | struct LimboInputChallenge {
28 | address from;
29 | uint8 inputNumber;
30 | bool resolved;
31 | }
32 |
33 | struct LimboOutput {
34 | uint256 amount;
35 | address owner;
36 | bool isPegged;
37 | }
38 |
39 | function getCompactExitRecordCommitment(ExitRecord self) internal pure returns(bytes22 commitment) {
40 | return bytes22(keccak256(abi.encodePacked(self.transactionRef, self.blockNumber, self.transactionNumber, self.outputNumber, self.isLimbo)));
41 | }
42 |
43 | function isFullyResolved(LimboData storage self) internal view returns (bool resolved) {
44 | for (uint8 i = 0; i < self.inputChallenges.length; i++) {
45 | if (self.inputChallenges[i].resolved == false) {
46 | return false;
47 | }
48 | }
49 | return true;
50 | }
51 |
52 | function addOutput(LimboData storage self, address _owner, uint256 _amount, bool _pegged) internal {
53 | LimboOutput memory newOutput;
54 | newOutput.owner = _owner;
55 | newOutput.amount = _amount;
56 | newOutput.isPegged = _pegged;
57 | self.outputs.push(newOutput);
58 | }
59 | }
--------------------------------------------------------------------------------
/contracts/TXTester.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.25;
2 |
3 | import {PlasmaTransactionLibrary} from "./PlasmaTransactionLibrary.sol";
4 |
5 | contract TXTester {
6 | constructor() public {
7 |
8 | }
9 |
10 | function parseFromBlock(bytes _plasmaTransaction, bytes _merkleProof, bytes32 _merkleRoot) public view returns (uint32 txNum, uint8 txType, uint numIns, uint numOuts, address sender, bool isWellFormed ) {
11 | PlasmaTransactionLibrary.PlasmaTransaction memory TX = PlasmaTransactionLibrary.signedPlasmaTransactionFromBytes(_plasmaTransaction);
12 | (bool included, uint256 transactionNumber) = PlasmaTransactionLibrary.checkForInclusionIntoBlock(_merkleRoot, _plasmaTransaction, _merkleProof);
13 | require(included);
14 | TX.txNumberInBlock = uint32(transactionNumber);
15 | return (TX.txNumberInBlock, TX.txType, TX.inputs.length, TX.outputs.length, TX.sender, TX.isWellFormed);
16 | }
17 |
18 | function parseFromBlockLimited(bytes _plasmaTransaction, bytes _merkleProof, bytes32 _merkleRoot) public pure returns (bool included, uint256 transactionNumber) {
19 | (included, transactionNumber) = PlasmaTransactionLibrary.checkForInclusionIntoBlock(_merkleRoot, _plasmaTransaction, _merkleProof);
20 | return;
21 | }
22 |
23 | function parseTransaction(bytes _plasmaTransaction) public view returns (uint32 txNum, uint8 txType, uint numIns, uint numOuts, address sender, bool isWellFormed ) {
24 | PlasmaTransactionLibrary.PlasmaTransaction memory TX = PlasmaTransactionLibrary.signedPlasmaTransactionFromBytes(_plasmaTransaction);
25 | return (TX.txNumberInBlock, TX.txType, TX.inputs.length, TX.outputs.length, TX.sender, TX.isWellFormed);
26 | }
27 |
28 | function getInputInfo(bytes _plasmaTransaction, uint8 _inputNumber) public view returns (uint32 blockNumber, uint32 txNumberInBlock, uint8 outputNumberInTx, uint amount) {
29 | PlasmaTransactionLibrary.PlasmaTransaction memory TX = PlasmaTransactionLibrary.signedPlasmaTransactionFromBytes(_plasmaTransaction);
30 | PlasmaTransactionLibrary.TransactionInput memory input = TX.inputs[_inputNumber];
31 | return (input.blockNumber, input.txNumberInBlock, input.outputNumberInTX, input.amount);
32 | }
33 |
34 | function getOutputInfo(bytes _plasmaTransaction, uint8 _outputNumber) public view returns (uint8 outputNumberInTx, address recipient, uint amount) {
35 | PlasmaTransactionLibrary.PlasmaTransaction memory TX = PlasmaTransactionLibrary.signedPlasmaTransactionFromBytes(_plasmaTransaction);
36 | PlasmaTransactionLibrary.TransactionOutput memory output = TX.outputs[_outputNumber];
37 | return (output.outputNumberInTX, output.recipient, output.amount);
38 | }
39 | }
--------------------------------------------------------------------------------
/helpers/assertHelpers.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | init: function() {
4 | if (assert !== undefined) {
5 | assert.web3Events = function(observedTransactionResult, expectedEvents, message) {
6 | let entries = observedTransactionResult.logs.map(function(logEntry) {
7 | return {
8 | event: logEntry.event,
9 | args: Object.keys(logEntry.args).reduce(function(previous, current) {
10 | previous[current] =
11 | (typeof logEntry.args[current].toNumber === 'function')
12 | ? logEntry.args[current].toNumber()
13 | : logEntry.args[current];
14 | return previous;
15 | }, {})
16 | }
17 | });
18 | assert.deepEqual(entries, expectedEvents, message);
19 | };
20 |
21 | assert.web3Event = function(observedTransactionResult, expectedEvent, message) {
22 | assert.web3Events(observedTransactionResult, [expectedEvent], message);
23 | };
24 | }
25 |
26 | if (expect !== undefined) {
27 | expect.web3Events = function(observedTransactionResult, expectedEvents, message) {
28 | let entries = observedTransactionResult.logs.map(function(logEntry) {
29 | return {
30 | event: logEntry.event,
31 | args: Object.keys(logEntry.args).reduce(function(previous, current) {
32 | previous[current] =
33 | (typeof logEntry.args[current].toNumber === 'function')
34 | ? logEntry.args[current].toNumber()
35 | : logEntry.args[current];
36 | return previous;
37 | }, {})
38 | }
39 | });
40 | // expect({a: 1}).to.deep.equal({a: 1});
41 | // expect.deepEqual(entries, expectedEvents, message);
42 | expect(entries).to.deep.equal(expectedEvents);
43 | };
44 |
45 | expect.web3Event = function(observedTransactionResult, expectedEvent, message) {
46 | expect.web3Events(observedTransactionResult, [expectedEvent], message);
47 | };
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/helpers/details.js:
--------------------------------------------------------------------------------
1 | let fs = require('fs');
2 | let contract = require("../build/contracts/PlasmaParent.json");
3 | let details = {error: false, address: contract.networks[4].address, abi: contract.abi};
4 | fs.writeFile("../build/details", JSON.stringify(details), err => {
5 | if (err) throw err;
6 | console.log('Complete. Contract address: ' + details.address);
7 | }
8 | );
9 |
--------------------------------------------------------------------------------
/helpers/expectThrow.js:
--------------------------------------------------------------------------------
1 | module.exports = async function expectThrow(promise) {
2 | try {
3 | await promise;
4 | } catch (error) {
5 | // TODO: Check jump destination to destinguish between a throw
6 | // and an actual invalid jump.
7 | const invalidJump = error.message.search('invalid JUMP') >= 0;
8 | // TODO: When we contract A calls contract B, and B throws, instead
9 | // of an 'invalid jump', we get an 'out of gas' error. How do
10 | // we distinguish this from an actual out of gas event? (The
11 | // testrpc log actually show an 'invalid jump' event.)
12 | const outOfGas = error.message.search('out of gas') >= 0;
13 | // General revert
14 | const revert = error.message.search("VM Exception while processing transaction: revert") >= 0;
15 | assert(
16 | invalidJump || outOfGas || revert,
17 | "Expected throw, got '" + error + "' instead",
18 | );
19 | return;
20 | }
21 | assert.fail('Expected throw not received');
22 | };
--------------------------------------------------------------------------------
/helpers/increaseTime.js:
--------------------------------------------------------------------------------
1 | const increaseTime = async function(addSeconds) {
2 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [addSeconds], id: 0})
3 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 1})
4 | }
5 |
6 | const increaseBlockCounter = async function(addBlocks) {
7 | for (let i = 0; i < addBlocks; i++) {
8 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: i})
9 | }
10 | }
11 |
12 | module.exports.increaseTime = {increaseTime, increaseBlockCounter};
--------------------------------------------------------------------------------
/helpers/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ganache-cli --gasLimit=7e7 --defaultBalanceEther=10000 -m 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'
4 |
--------------------------------------------------------------------------------
/migrations/1_initial_migration.js:
--------------------------------------------------------------------------------
1 | var Migrations = artifacts.require("Migrations.sol");
2 |
3 | module.exports = function(deployer, network, accounts) {
4 | return;
5 | deployer.deploy(Migrations);
6 | };
7 |
--------------------------------------------------------------------------------
/migrations/2_deploy_plasma.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const PlasmaParent = artifacts.require('PlasmaParent');
3 | const PriorityQueue = artifacts.require('PriorityQueue');
4 | const BlockStorage = artifacts.require("PlasmaBlockStorage");
5 | const Challenger = artifacts.require("PlasmaChallenges");
6 | const BuyoutProcessor = artifacts.require("PlasmaBuyoutProcessor");
7 | // const LimboExitGame = artifacts.require("PlasmaLimboExitGame");
8 | const assert = require('assert');
9 | const _ = require('lodash');
10 |
11 | var nonce;
12 |
13 | function sleep(ms) {
14 | return new Promise(resolve => setTimeout(resolve, ms));
15 | }
16 |
17 | let blockSignerAddress = "0x627306090abab3a6e1400e9345bc60c78a8bef57"
18 | let operatorsCollateral = "1000000000000000000" // 1 ETH
19 |
20 | module.exports = function(deployer, network, accounts) {
21 |
22 | async function waitForNonceUpdate() {
23 | console.log("Waiting for nonce update")
24 | await sleep(60000)
25 | let newNonce = await getNonce(operator)
26 | while (newNonce <= nonce) {
27 | await sleep(60000)
28 | newNonce = await getNonce(operator)
29 | }
30 | }
31 |
32 | function getNonce(account) {
33 | return new Promise(function(resolve, reject) {
34 | web3.eth.getTransactionCount(account, function(error, nonce) {
35 | if (error !== null) {
36 | reject(error)
37 | }
38 | resolve(nonce)
39 | })
40 | });
41 | }
42 |
43 | const operator = accounts[0];
44 | try {
45 | let env = process.env;
46 | if (env.NODE_ENV !== 'production') {
47 | require('dotenv').load();
48 | }
49 | const blockSignerAddressCandidate = env.BLOCK_SIGNER_ADDRESS
50 | if (blockSignerAddressCandidate !== undefined && blockSignerAddressCandidate !== "") {
51 | blockSignerAddress = blockSignerAddressCandidate;
52 | }
53 | }
54 | catch(error) {
55 | console.log(error)
56 | }
57 | console.log("Block signer = " + blockSignerAddress);
58 | console.log("Operator's bond = " + operatorsCollateral + " wei");
59 |
60 | (async () => {
61 | nonce = await getNonce(operator);
62 | await deployer.deploy(BlockStorage, {from: operator, gas: 2500000});
63 | let storage = await BlockStorage.deployed();
64 | console.log("Storage was deployed at " + storage.address);
65 | await waitForNonceUpdate();
66 |
67 | await deployer.deploy(PriorityQueue, {from: operator, gas: 1500000});
68 | let queue = await PriorityQueue.deployed();
69 | console.log("Queue was deployed at " + queue.address);
70 | await waitForNonceUpdate();
71 |
72 | await deployer.deploy(PlasmaParent, queue.address, storage.address, {from: operator, value: operatorsCollateral});
73 | let parent = await PlasmaParent.deployed();
74 | console.log("Parent was deployed at " + parent.address);
75 | await waitForNonceUpdate();
76 |
77 | // let contractBalance = await web3.eth.getBalance(parent.address);
78 | // assert(contractBalance.toString(10) === operatorsCollateral);
79 |
80 | await storage.setOwner(parent.address, {from: operator, gas: 50000});
81 | console.log("Has set storage owner")
82 | await waitForNonceUpdate();
83 |
84 | await queue.setOwner(parent.address, {from: operator, gas: 50000});
85 | console.log("Has set queue owner")
86 | await waitForNonceUpdate();
87 |
88 | await deployer.deploy(BuyoutProcessor, {from: operator, gas: 2500000});
89 | let buyoutProcessor = await BuyoutProcessor.deployed();
90 | console.log("Buyout processor was deployed at " + buyoutProcessor.address);
91 | await waitForNonceUpdate();
92 |
93 | await deployer.deploy(Challenger, {from: operator, gas: 5500000});
94 | let challenger = await Challenger.deployed();
95 | console.log("Challenge processor was deployed at " + challenger.address);
96 | await waitForNonceUpdate();
97 |
98 | // await deployer.deploy(LimboExitGame, {from: operator});
99 | // let limboExitGame = await LimboExitGame.deployed();
100 |
101 | await parent.allowDeposits(buyoutProcessor.address, {from: operator, gas: 50000})
102 | await waitForNonceUpdate();
103 |
104 | await parent.allowChallenges(challenger.address, {from: operator, gas: 50000});
105 | await waitForNonceUpdate();
106 |
107 | // await parent.allowLimboExits(limboExitGame.address, {from: operator})
108 | // await waitForNonceUpdate();
109 |
110 | await parent.setOperator(blockSignerAddress, 2, {from: operator, gas: 50000});
111 | await waitForNonceUpdate();
112 |
113 | const canSignBlocks = await storage.canSignBlocks(blockSignerAddress);
114 | assert(canSignBlocks);
115 |
116 | const buyoutProcessorAddress = await parent.buyoutProcessorContract();
117 | assert(buyoutProcessorAddress === buyoutProcessor.address);
118 |
119 | const challengesAddress = await parent.challengesContract();
120 | assert(challengesAddress === challenger.address);
121 |
122 | // const limboExitsAddress = await parent.limboExitContract();
123 | // assert(limboExitsAddress === limboExitGame.address);
124 |
125 | let parentAbi = parent.abi;
126 | let buyoutAbi = buyoutProcessor.abi;
127 | let challengerAbi = challenger.abi;
128 | // let limboExitAbi = limboExitGame.abi;
129 |
130 |
131 | const mergedABI = _.uniqBy([...parentAbi, ...buyoutAbi, ...challengerAbi], a => a.name || a.type);
132 |
133 | // const mergedABI = _.uniqBy([...parentAbi, ...buyoutAbi, ...challengerAbi, ...limboExitAbi], a => a.name || a.type);
134 | // due to async contract address is not saved in not saved in json by truffle
135 | // so we need to generate details file from within migration
136 | let details = {error: false, address: parent.address, abi: mergedABI};
137 | fs.writeFileSync("build/details" + network, JSON.stringify(details));
138 | let abiOnly = {abi: mergedABI}
139 | fs.writeFileSync("build/abi", JSON.stringify(abiOnly));
140 | if (fs.existsSync("/data/shared")) {
141 | fs.writeFileSync("/data/shared/details", JSON.stringify(details));
142 | fs.writeFileSync("/data/shared/abi", JSON.stringify(abiOnly));
143 | }
144 | console.log('Complete. Contract address: ' + parent.address);
145 | })();
146 | };
147 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "PlasmaParentContract",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "deploy-rinkeby": "truffle migrate --network rinkeby",
6 | "test": "node $NODE_DEBUG_OPTION node_modules/.bin/truffle test"
7 | },
8 | "dependencies": {
9 | "dotenv": "^5.0.1",
10 | "ethereumjs-util": "^5.2.0",
11 | "is-buffer": "^2.0.2",
12 | "js-sha3": "^0.7.0",
13 | "lodash": "^4.17.10",
14 | "mocha": "^5.2.0",
15 | "truffle": "^4.1.3",
16 | "truffle-core": "^4.1.3",
17 | "truffle-hdwallet-provider": "0.0.3",
18 | "truffle-wallet-provider": "0.0.5",
19 | "web3": "^1.0.0-beta.36"
20 | },
21 | "devDependencies": {
22 | "truffle-artifactor": "^3.0.7",
23 | "truffle-contract": "^3.0.6",
24 | "truffle-test-utils": "0.1.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/buyout.js:
--------------------------------------------------------------------------------
1 | const util = require("util");
2 | const ethUtil = require('ethereumjs-util')
3 | const BN = ethUtil.BN;
4 | const t = require('truffle-test-utils')
5 | t.init()
6 | const expectThrow = require("../helpers/expectThrow");
7 | const {addresses, keys} = require("./keys.js");
8 | const {createTransaction, parseTransactionIndex} = require("./createTransaction");
9 | const {createBlock, createMerkleTree} = require("./createBlock");
10 | const testUtils = require('./utils');
11 | const deploy = require("./deploy");
12 |
13 | const increaseTime = async function(addSeconds) {
14 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [addSeconds], id: 0})
15 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 1})
16 | }
17 |
18 | const {
19 | TxTypeFund,
20 | TxTypeMerge,
21 | TxTypeSplit} = require("../lib/Tx/RLPtx.js");
22 |
23 | contract('Plasma buyout procedure', async (accounts) => {
24 |
25 | const operatorAddress = accounts[0];
26 | const operatorKey = keys[0];
27 |
28 | let queue;
29 | let plasma;
30 | let storage;
31 | let firstHash;
32 |
33 | const operator = accounts[0];
34 |
35 | const alice = addresses[2];
36 | const aliceKey = keys[2];
37 | const bob = addresses[3];
38 | const bobKey = keys[3];
39 |
40 | beforeEach(async () => {
41 | const result = await deploy(operator, operatorAddress);
42 | ({plasma, firstHash, queue, storage} = result);
43 | })
44 |
45 | it('should send an offer and accept it', async () => {
46 | // deposit to prevent stopping
47 |
48 | const withdrawCollateral = await plasma.WithdrawCollateral();
49 | await plasma.deposit({from: alice, value: "100"});
50 |
51 | const allTXes = [];
52 | const fundTX = createTransaction(TxTypeFund, 0,
53 | [{
54 | blockNumber: 0,
55 | txNumberInBlock: 0,
56 | outputNumberInTransaction: 0,
57 | amount: 0
58 | }],
59 | [{
60 | amount: 100,
61 | to: alice
62 | }],
63 | operatorKey
64 | )
65 | allTXes.push(fundTX)
66 | const block = createBlock(1, allTXes.length, firstHash, allTXes, operatorKey)
67 | await testUtils.submitBlock(plasma, block);
68 |
69 | const proofObject = block.getProofForTransactionByNumber(0);
70 | const {proof, tx} = proofObject;
71 | let submissionReceipt = await plasma.startExit(
72 | 1, 0, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof),
73 | {from: alice, value: withdrawCollateral}
74 | )
75 |
76 | const exitRecordHash = submissionReceipt.logs[2].args._partialHash;
77 | const exitRecord = await plasma.exitRecords(exitRecordHash);
78 | const txHash = ethUtil.bufferToHex(ethUtil.sha3(proofObject.tx.serialize()))
79 |
80 | assert(exitRecord[0] === txHash);
81 | assert(exitRecord[1].toNumber() === 100)
82 | assert(exitRecord[2] === alice);
83 | assert(exitRecord[4].toString(10) === "1")
84 | assert(exitRecord[5].toNumber() === 0)
85 | assert(exitRecord[6].toNumber() === 0)
86 | assert(exitRecord[7] === true)
87 | assert(exitRecord[8] === false)
88 |
89 | //now lets offer a buyoyt for half of the amount
90 | // offerOutputBuyout(bytes22 _index)
91 | submissionReceipt = await plasma.offerOutputBuyout(exitRecordHash, bob, {from: bob, value: 50})
92 | assert(submissionReceipt.logs.length == 1);
93 | let offer = await plasma.exitBuyoutOffers(exitRecordHash);
94 | assert(offer[1] === bob);
95 | assert(offer[0].toString(10) === "50");
96 | assert(!offer[2]);
97 |
98 | let oldBalanceAlice = await web3.eth.getBalance(alice);
99 | submissionReceipt = await plasma.acceptBuyoutOffer(exitRecordHash, {from: alice, gasPrice: 0});
100 | let newBalanceAlice = await web3.eth.getBalance(alice);
101 |
102 | assert(newBalanceAlice.gt(oldBalanceAlice));
103 |
104 | offer = await plasma.exitBuyoutOffers(exitRecordHash);
105 | assert(offer[1] === bob);
106 | assert(offer[0].toString(10) === "50");
107 | assert(offer[2]);
108 |
109 | const delay = await plasma.ExitDelay();
110 | await increaseTime(delay.toNumber() + 1);
111 |
112 | let oldBalanceBob = await web3.eth.getBalance(bob);
113 | oldBalanceAlice = newBalanceAlice
114 | submissionReceipt = await plasma.finalizeExits(1, {from: operator});
115 | let newBalanceBob = await web3.eth.getBalance(bob);
116 | assert(newBalanceBob.gt(oldBalanceBob));
117 |
118 | newBalanceAlice = await web3.eth.getBalance(alice);
119 | assert(newBalanceAlice.eq(oldBalanceAlice));
120 | })
121 |
122 |
123 | it('should send a presigned offer', async () => {
124 | // deposit to prevent stopping
125 |
126 | const withdrawCollateral = await plasma.WithdrawCollateral();
127 | await plasma.deposit({from: alice, value: "100"});
128 |
129 | const allTXes = [];
130 | const fundTX = createTransaction(TxTypeFund, 0,
131 | [{
132 | blockNumber: 0,
133 | txNumberInBlock: 0,
134 | outputNumberInTransaction: 0,
135 | amount: 0
136 | }],
137 | [{
138 | amount: 100,
139 | to: alice
140 | }],
141 | operatorKey
142 | )
143 | allTXes.push(fundTX)
144 | const block = createBlock(1, allTXes.length, firstHash, allTXes, operatorKey)
145 | await testUtils.submitBlock(plasma, block);
146 |
147 | const proofObject = block.getProofForTransactionByNumber(0);
148 | const {proof, tx} = proofObject;
149 | let submissionReceipt = await plasma.startExit(
150 | 1, 0, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof),
151 | {from: alice, value: withdrawCollateral}
152 | )
153 |
154 | const exitRecordHash = submissionReceipt.logs[2].args._partialHash;
155 | const exitRecord = await plasma.exitRecords(exitRecordHash);
156 | const txHash = ethUtil.bufferToHex(ethUtil.sha3(proofObject.tx.serialize()))
157 |
158 | assert(exitRecord[0] === txHash);
159 | assert(exitRecord[1].toNumber() === 100)
160 | assert(exitRecord[2] === alice);
161 | assert(exitRecord[4].toString(10) === "1")
162 | assert(exitRecord[5].toNumber() === 0)
163 | assert(exitRecord[6].toNumber() === 0)
164 | assert(exitRecord[7] === true)
165 | assert(exitRecord[8] === false)
166 |
167 | //now lets offer a buyoyt for half of the amount
168 | // function publishPreacceptedBuyout(
169 | // bytes22 _index,
170 | // uint256 _amount,
171 | // address _beneficiary,
172 | // uint8 v,
173 | // bytes32 r,
174 | // bytes32 s
175 | // )
176 |
177 | const valueBuffer = (new BN(50)).toBuffer("be", 32)
178 | const dataToSign = Buffer.concat([ethUtil.toBuffer(exitRecordHash), valueBuffer, ethUtil.toBuffer(bob)])
179 | const hashToSign = ethUtil.hashPersonalMessage(dataToSign)
180 | const signature = ethUtil.ecsign(hashToSign, aliceKey)
181 | const {v, r, s} = signature
182 | let oldBalanceAlice = await web3.eth.getBalance(alice);
183 | submissionReceipt = await plasma.publishPreacceptedBuyout(
184 | exitRecordHash,
185 | 50,
186 | bob,
187 | v,
188 | ethUtil.bufferToHex(r),
189 | ethUtil.bufferToHex(s),
190 | {from: bob, value: 50})
191 | assert(submissionReceipt.logs.length == 1);
192 | let offer = await plasma.exitBuyoutOffers(exitRecordHash);
193 | assert(offer[1] === bob);
194 | assert(offer[0].toString(10) === "50");
195 | assert(offer[2]);
196 |
197 | let newBalanceAlice = await web3.eth.getBalance(alice);
198 |
199 | assert(newBalanceAlice.gt(oldBalanceAlice));
200 |
201 | const delay = await plasma.ExitDelay();
202 | await increaseTime(delay.toNumber() + 1);
203 |
204 | let oldBalanceBob = await web3.eth.getBalance(bob);
205 | oldBalanceAlice = newBalanceAlice
206 | submissionReceipt = await plasma.finalizeExits(1, {from: operator});
207 | let newBalanceBob = await web3.eth.getBalance(bob);
208 | assert(newBalanceBob.gt(oldBalanceBob));
209 |
210 | newBalanceAlice = await web3.eth.getBalance(alice);
211 | assert(newBalanceAlice.eq(oldBalanceAlice));
212 | })
213 |
214 | it('should allow returning funds for expired offer', async () => {
215 | // deposit to prevent stopping
216 |
217 | const withdrawCollateral = await plasma.WithdrawCollateral();
218 | await plasma.deposit({from: alice, value: "100"});
219 |
220 | const allTXes = [];
221 | const fundTX = createTransaction(TxTypeFund, 0,
222 | [{
223 | blockNumber: 0,
224 | txNumberInBlock: 0,
225 | outputNumberInTransaction: 0,
226 | amount: 0
227 | }],
228 | [{
229 | amount: 100,
230 | to: alice
231 | }],
232 | operatorKey
233 | )
234 | allTXes.push(fundTX)
235 | const block = createBlock(1, allTXes.length, firstHash, allTXes, operatorKey)
236 | await testUtils.submitBlock(plasma, block);
237 |
238 | const proofObject = block.getProofForTransactionByNumber(0);
239 | const {proof, tx} = proofObject;
240 | let submissionReceipt = await plasma.startExit(
241 | 1, 0, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof),
242 | {from: alice, value: withdrawCollateral}
243 | )
244 |
245 | const exitRecordHash = submissionReceipt.logs[2].args._partialHash;
246 | const exitRecord = await plasma.exitRecords(exitRecordHash);
247 | const txHash = ethUtil.bufferToHex(ethUtil.sha3(proofObject.tx.serialize()))
248 |
249 | assert(exitRecord[0] === txHash);
250 | assert(exitRecord[1].toNumber() === 100)
251 | assert(exitRecord[2] === alice);
252 | assert(exitRecord[4].toString(10) === "1")
253 | assert(exitRecord[5].toNumber() === 0)
254 | assert(exitRecord[6].toNumber() === 0)
255 | assert(exitRecord[7] === true)
256 | assert(exitRecord[8] === false)
257 |
258 | //now lets offer a buyoyt for half of the amount
259 | // offerOutputBuyout(bytes22 _index)
260 | submissionReceipt = await plasma.offerOutputBuyout(exitRecordHash, bob, {from: bob, value: 50})
261 | assert(submissionReceipt.logs.length == 1);
262 | let offer = await plasma.exitBuyoutOffers(exitRecordHash);
263 | assert(offer[1] === bob);
264 | assert(offer[0].toString(10) === "50");
265 | assert(!offer[2]);
266 |
267 | let oldBalanceBob = await web3.eth.getBalance(bob);
268 | submissionReceipt = await plasma.returnExpiredBuyoutOffer(exitRecordHash, {from: bob, gasPrice: 0});
269 | let newBalanceBob = await web3.eth.getBalance(bob);
270 |
271 | assert(newBalanceBob.gt(oldBalanceBob));
272 | await expectThrow(plasma.acceptBuyoutOffer(exitRecordHash, {from: alice, gasPrice: 0}));
273 | offer = await plasma.exitBuyoutOffers(exitRecordHash);
274 | assert(offer[0].toString(10) === "0");
275 | assert(!offer[2]);
276 |
277 | const delay = await plasma.ExitDelay();
278 | await increaseTime(delay.toNumber() + 1);
279 |
280 | let oldBalanceAlice = await web3.eth.getBalance(alice);
281 | oldBalanceBob = newBalanceBob
282 | submissionReceipt = await plasma.finalizeExits(1, {from: operator});
283 | let newBalanceAlice = await web3.eth.getBalance(alice);
284 | newBalanceBob = await web3.eth.getBalance(bob);
285 | assert(newBalanceBob.eq(oldBalanceBob));
286 | assert(newBalanceAlice.gt(oldBalanceAlice));
287 | })
288 |
289 | it('should not allow accepting for already exited transaction', async () => {
290 | // deposit to prevent stopping
291 |
292 | const withdrawCollateral = await plasma.WithdrawCollateral();
293 | await plasma.deposit({from: alice, value: "100"});
294 |
295 | const allTXes = [];
296 | const fundTX = createTransaction(TxTypeFund, 0,
297 | [{
298 | blockNumber: 0,
299 | txNumberInBlock: 0,
300 | outputNumberInTransaction: 0,
301 | amount: 0
302 | }],
303 | [{
304 | amount: 100,
305 | to: alice
306 | }],
307 | operatorKey
308 | )
309 | allTXes.push(fundTX)
310 | const block = createBlock(1, allTXes.length, firstHash, allTXes, operatorKey)
311 | await testUtils.submitBlock(plasma, block);
312 |
313 | const proofObject = block.getProofForTransactionByNumber(0);
314 | const {proof, tx} = proofObject;
315 | let submissionReceipt = await plasma.startExit(
316 | 1, 0, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof),
317 | {from: alice, value: withdrawCollateral}
318 | )
319 |
320 | const exitRecordHash = submissionReceipt.logs[2].args._partialHash;
321 | const exitRecord = await plasma.exitRecords(exitRecordHash);
322 | const txHash = ethUtil.bufferToHex(ethUtil.sha3(proofObject.tx.serialize()))
323 |
324 | assert(exitRecord[0] === txHash);
325 | assert(exitRecord[1].toNumber() === 100)
326 | assert(exitRecord[2] === alice);
327 | assert(exitRecord[4].toString(10) === "1")
328 | assert(exitRecord[5].toNumber() === 0)
329 | assert(exitRecord[6].toNumber() === 0)
330 | assert(exitRecord[7] === true)
331 | assert(exitRecord[8] === false)
332 |
333 | //now lets offer a buyoyt for half of the amount
334 | // offerOutputBuyout(bytes22 _index)
335 | submissionReceipt = await plasma.offerOutputBuyout(exitRecordHash, bob, {from: bob, value: 50})
336 | assert(submissionReceipt.logs.length == 1);
337 | let offer = await plasma.exitBuyoutOffers(exitRecordHash);
338 | assert(offer[1] === bob);
339 | assert(offer[0].toString(10) === "50");
340 | assert(!offer[2]);
341 |
342 | const delay = await plasma.ExitDelay();
343 | await increaseTime(delay.toNumber() + 1);
344 |
345 | submissionReceipt = await plasma.finalizeExits(1, {from: operator});
346 |
347 | await expectThrow(plasma.acceptBuyoutOffer(exitRecordHash, {from: alice, gasPrice: 0}));
348 | })
349 |
350 | it('should not allow to offer for already exited transaction', async () => {
351 | // deposit to prevent stopping
352 |
353 | const withdrawCollateral = await plasma.WithdrawCollateral();
354 | await plasma.deposit({from: alice, value: "100"});
355 |
356 | const allTXes = [];
357 | const fundTX = createTransaction(TxTypeFund, 0,
358 | [{
359 | blockNumber: 0,
360 | txNumberInBlock: 0,
361 | outputNumberInTransaction: 0,
362 | amount: 0
363 | }],
364 | [{
365 | amount: 100,
366 | to: alice
367 | }],
368 | operatorKey
369 | )
370 | allTXes.push(fundTX)
371 | const block = createBlock(1, allTXes.length, firstHash, allTXes, operatorKey)
372 | await testUtils.submitBlock(plasma, block);
373 |
374 | const proofObject = block.getProofForTransactionByNumber(0);
375 | const {proof, tx} = proofObject;
376 | let submissionReceipt = await plasma.startExit(
377 | 1, 0, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof),
378 | {from: alice, value: withdrawCollateral}
379 | )
380 |
381 | const exitRecordHash = submissionReceipt.logs[2].args._partialHash;
382 | const exitRecord = await plasma.exitRecords(exitRecordHash);
383 | const txHash = ethUtil.bufferToHex(ethUtil.sha3(proofObject.tx.serialize()))
384 |
385 | assert(exitRecord[0] === txHash);
386 | assert(exitRecord[1].toNumber() === 100)
387 | assert(exitRecord[2] === alice);
388 | assert(exitRecord[4].toString(10) === "1")
389 | assert(exitRecord[5].toNumber() === 0)
390 | assert(exitRecord[6].toNumber() === 0)
391 | assert(exitRecord[7] === true)
392 | assert(exitRecord[8] === false)
393 |
394 | const delay = await plasma.ExitDelay();
395 | await increaseTime(delay.toNumber() + 1);
396 |
397 | submissionReceipt = await plasma.finalizeExits(1, {from: operator});
398 | await expectThrow(plasma.offerOutputBuyout(exitRecordHash, bob, {from: bob, value: 50}))
399 | })
400 |
401 | })
402 |
403 |
--------------------------------------------------------------------------------
/test/createBlock.js:
--------------------------------------------------------------------------------
1 | const {Block} = require("../lib/Block/RLPblock");
2 | const ethUtil = require("ethereumjs-util");
3 | const BN = ethUtil.BN;
4 | const MerkleTools = require("../lib/merkle-tools");
5 | const {EmptyTransactionBuffer} = require("../lib/Tx/RLPtxWithSignature")
6 |
7 | function createBlock(blockNumber, numberOfTransactions, previousHash, transactions, privateKey) {
8 | const params = {
9 | blockNumber : (new BN(blockNumber)).toBuffer("be", 4),
10 | transactions : transactions,
11 | parentHash : ethUtil.toBuffer(previousHash),
12 | }
13 | const block = new Block(params)
14 | block.numberOfTransactions = (new BN(numberOfTransactions)).toBuffer("be",4)
15 | block.sign(privateKey);
16 | return block
17 | }
18 |
19 | function createMerkleTree(dataArray) {
20 | const treeOptions = {
21 | hashType: 'sha3'
22 | }
23 |
24 | const merkleTree = new MerkleTools(treeOptions)
25 | for (let i = 0; i < dataArray.length; i++) {
26 | const txHash = ethUtil.hashPersonalMessage(dataArray[i]);
27 | merkleTree.addLeaf(txHash);
28 | }
29 | merkleTree.makePlasmaTree(EmptyTransactionBuffer);
30 | return merkleTree
31 | }
32 |
33 | module.exports = {createBlock, createMerkleTree}
--------------------------------------------------------------------------------
/test/createTransaction.js:
--------------------------------------------------------------------------------
1 | const {PlasmaTransaction,
2 | TxTypeFund,
3 | TxTypeMerge,
4 | TxTypeSplit} = require("../lib/Tx/RLPtx.js");
5 |
6 | const {PlasmaTransactionWithSignature} = require("../lib/Tx/RLPtxWithSignature");
7 |
8 | const {TransactionInput} = require("../lib/Tx/RLPinput");
9 | const {TransactionOutput} = require("../lib/Tx/RLPoutput");
10 |
11 | const ethUtil = require("ethereumjs-util");
12 | const BN = ethUtil.BN;
13 |
14 | function createTransaction(transactionType, transactionNumber, inputs, outputs, privateKey) {
15 | const allInputs = [];
16 | const allOutputs = [];
17 |
18 | for (const input of inputs) {
19 | const inp = new TransactionInput({
20 | blockNumber: (new BN(input.blockNumber)).toBuffer("be",4),
21 | txNumberInBlock: (new BN(input.txNumberInBlock)).toBuffer("be",4),
22 | outputNumberInTransaction: (new BN(input.outputNumberInTransaction)).toBuffer("be",1),
23 | amountBuffer: (new BN(input.amount)).toBuffer("be",32)
24 | })
25 | allInputs.push(inp)
26 | }
27 | let outputCounter = 0
28 | for (const output of outputs) {
29 | const out = new TransactionOutput({
30 | outputNum: (new BN(outputCounter)).toBuffer("be", 1),
31 | amountBuffer: (new BN(output.amount)).toBuffer("be",32),
32 | to: ethUtil.toBuffer(output.to)
33 | })
34 | allOutputs.push(out)
35 | outputCounter++;
36 | }
37 |
38 | const plasmaTransaction = new PlasmaTransaction({
39 | transactionType: (new BN(transactionType)).toBuffer("be",1),
40 | inputs: allInputs,
41 | outputs: allOutputs,
42 | })
43 |
44 | // const hash = plasmaTransaction.hash();
45 | // const signature = ethUtil.ecsign(hash, privateKey)
46 | // const sig = ethUtil.toRpcSig(signature.v, signature.r, signature.s)
47 | const sigTX = new PlasmaTransactionWithSignature({
48 | transaction: plasmaTransaction
49 | });
50 | sigTX.sign(privateKey);
51 |
52 | return sigTX;
53 | }
54 |
55 | // function createTransaction(transactionType, transactionNumber, inputs, outputs, privateKey) {
56 | // const allInputs = [];
57 | // const allOutputs = [];
58 |
59 | // for (const input of inputs) {
60 | // const inp = new TransactionInput({
61 | // blockNumber: (new BN(input.blockNumber)).toBuffer("be",4),
62 | // txNumberInBlock: (new BN(input.txNumberInBlock)).toBuffer("be",4),
63 | // outputNumberInTransaction: (new BN(input.outputNumberInTransaction)).toBuffer("be",1),
64 | // amountBuffer: (new BN(input.amount)).toBuffer("be",32)
65 | // })
66 | // allInputs.push(inp)
67 | // }
68 |
69 | // for (const output of outputs) {
70 | // const out = new TransactionOutput({
71 | // amountBuffer: (new BN(output.amount)).toBuffer("be",32),
72 | // to: ethUtil.toBuffer(output.to)
73 | // })
74 | // allOutputs.push(out)
75 | // }
76 |
77 | // const plasmaTransaction = new PlasmaTransaction({
78 | // transactionType: (new BN(transactionType)).toBuffer("be",1),
79 | // inputs: allInputs,
80 | // outputs: allOutputs,
81 | // })
82 |
83 | // // const hash = plasmaTransaction.hash();
84 | // // const signature = ethUtil.ecsign(hash, privateKey)
85 | // // const sig = ethUtil.toRpcSig(signature.v, signature.r, signature.s)
86 | // const sigTX = new PlasmaTransactionWithSignature({
87 | // transaction: plasmaTransaction
88 | // });
89 | // sigTX.sign(privateKey);
90 |
91 | // // sigTX.serializeSignature(sig)
92 |
93 | // const numberedTX = new PlasmaTransactionWithNumberAndSignature({
94 | // transactionNumberInBlock: (new BN(transactionNumber)).toBuffer("be", 4),
95 | // signedTransaction: sigTX
96 | // })
97 | // return numberedTX
98 | // }
99 |
100 | function parseTransactionIndex(index) {
101 | let idx = new BN(index)
102 | const ONE = new BN(1);
103 | let outputNumber = idx.mod(ONE.ushln(8));
104 | idx = idx.ushrn(8);
105 | const txNumber = idx.mod(ONE.ushln(32));
106 | idx = idx.ushrn(32);
107 | const blockNumber = idx.mod(ONE.ushln(32));
108 | return {blockNumber, txNumber, outputNumber}
109 | }
110 |
111 | module.exports = {createTransaction, parseTransactionIndex};
--------------------------------------------------------------------------------
/test/deploy.js:
--------------------------------------------------------------------------------
1 | const PlasmaParent = artifacts.require('PlasmaParent');
2 | const PriorityQueue = artifacts.require('PriorityQueue');
3 | const BlockStorage = artifacts.require("PlasmaBlockStorage");
4 | const Challenger = artifacts.require("PlasmaChallenges");
5 | const BuyoutProcessor = artifacts.require("PlasmaBuyoutProcessor");
6 | const LimboExitGame = artifacts.require("PlasmaLimboExitGame");
7 |
8 | const assert = require("assert");
9 | const truffleContract = require("truffle-contract");
10 | const _ = require("lodash");
11 |
12 | console.log("Parent bytecode size = " + (PlasmaParent.bytecode.length -2)/2);
13 | console.log("Buyouts processor bytecode size = " + (BuyoutProcessor.bytecode.length -2)/2);
14 | console.log("Challenger bytecode size = " + (Challenger.bytecode.length -2)/2);
15 | console.log("Limbo exit game bytecode length = " + (LimboExitGame.bytecode.length -2)/2);
16 |
17 | async function deploy(operator, operatorAddress) {
18 | const storage = await BlockStorage.new({from: operator})
19 | const queue = await PriorityQueue.new({from: operator})
20 | const parent = await PlasmaParent.new(queue.address, storage.address, {from: operator, value: "10000000000000000000"})
21 | await storage.setOwner(parent.address, {from: operator})
22 | await queue.setOwner(parent.address, {from: operator})
23 |
24 | const buyoutProcessor = await BuyoutProcessor.new({from: operator});
25 | const challenger = await Challenger.new({from: operator});
26 | const limboExitGame = await LimboExitGame.new({from: operator});
27 |
28 | await parent.allowDeposits(buyoutProcessor.address, {from: operator})
29 | await parent.allowChallenges(challenger.address, {from: operator});
30 | await parent.allowLimboExits(limboExitGame.address, {from: operator})
31 | await parent.setOperator(operatorAddress, 2, {from: operator});
32 |
33 | const canSignBlocks = await storage.canSignBlocks(operator);
34 | assert(canSignBlocks);
35 |
36 | const buyoutProcessorAddress = await parent.buyoutProcessorContract();
37 | assert(buyoutProcessorAddress === buyoutProcessor.address);
38 |
39 | const challengesAddress = await parent.challengesContract();
40 | assert(challengesAddress === challenger.address);
41 |
42 | const limboExitGameAddress = await parent.limboExitContract();
43 | assert(limboExitGameAddress == limboExitGame.address);
44 |
45 | let parentAbi = parent.abi;
46 | let buyoutAbi = buyoutProcessor.abi;
47 | let challengerAbi = challenger.abi;
48 | let limboAbi = limboExitGame.abi;
49 |
50 | const mergedABI = _.uniqBy([...parentAbi, ...buyoutAbi, ...challengerAbi, ...limboAbi], a => a.name || a.type);
51 |
52 | const plasmaMergedContract = truffleContract({abi: mergedABI}, {gas: 4700000});
53 | plasmaMergedContract.setProvider(web3.currentProvider)
54 | plasmaMergedContract.defaults({from: operator, gas: 1000000});
55 | const plasma = plasmaMergedContract.at(parent.address);
56 | const firstHash = await plasma.hashOfLastSubmittedBlock();
57 |
58 | return {plasma, firstHash, queue, storage}
59 | }
60 |
61 | module.exports = deploy
--------------------------------------------------------------------------------
/test/depositWithdrawFunctions.js:
--------------------------------------------------------------------------------
1 | const util = require("util");
2 | const ethUtil = require('ethereumjs-util');
3 | // const BN = ethUtil.BN;
4 | var BN;
5 | const t = require('truffle-test-utils');
6 | t.init();
7 | const expectThrow = require("../helpers/expectThrow");
8 | const {addresses, keys} = require("./keys");
9 | const {createTransaction} = require("./createTransaction");
10 | const {createBlock, createMerkleTree} = require("./createBlock");
11 | const testUtils = require('./utils');
12 | const deploy = require("./deploy");
13 |
14 | const {
15 | TxTypeFund,
16 | TxTypeMerge,
17 | TxTypeSplit} = require("../lib/Tx/RLPtx");
18 |
19 | // const Web3 = require("web3");
20 |
21 | const increaseTime = async function(addSeconds) {
22 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [addSeconds], id: 0});
23 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 1})
24 | };
25 |
26 | contract('Deposit withdraw functions', async (accounts) => {
27 | BN = web3.BigNumber;
28 | const operatorAddress = accounts[0];
29 | const operatorKey = keys[0];
30 |
31 | let queue;
32 | let plasma;
33 | let storage;
34 | let firstHash;
35 |
36 | const operator = accounts[0];
37 |
38 | const alice = addresses[2];
39 | const aliceKey = keys[2];
40 | const bob = addresses[3];
41 | const bobKey = keys[3];
42 |
43 | beforeEach(async () => {
44 | const result = await deploy(operator, operatorAddress);
45 | ({plasma, firstHash, queue, storage} = result);
46 | })
47 |
48 | it('should emit deposit event', async () => {
49 | const depositAmount = 42;
50 | // const depositedBefore = await plasma.totalAmountDeposited();
51 | let receipt = await plasma.deposit({from: alice, value: depositAmount});
52 | // const depositIndex = testUtils.depositIndex(receipt.receipt.blockNumber);
53 | const depositIndex = new web3.BigNumber(0);
54 | // const depositedAfter = await plasma.totalAmountDeposited();
55 | await testUtils.expectEvents(plasma, receipt.receipt.blockNumber, 'DepositEvent', {_from: alice, _amount: depositAmount, _depositIndex: depositIndex.toNumber()});
56 | // assert.equal(depositedAfter.toNumber(), depositedBefore.toNumber() + depositAmount, 'Deposit counter should increase');
57 | });
58 |
59 | it('should allow deposit withdraw process', async () => {
60 | let receipt = await plasma.deposit({from: alice, value: 314});
61 | // const depositIndex = testUtils.depositIndex(receipt.receipt.blockNumber);
62 | const depositIndex = new web3.BigNumber(0);
63 | const depositWithdrawCollateral = await plasma.DepositWithdrawCollateral();
64 | receipt = await plasma.startDepositWithdraw(depositIndex.toString(), {from: alice, value: depositWithdrawCollateral.toString()});
65 | await testUtils.expectEvents(plasma, receipt.receipt.blockNumber, 'DepositWithdrawStartedEvent', {_depositIndex: depositIndex.toNumber()});
66 | });
67 |
68 | it('should require bond for deposit withdraw start', async () => {
69 | const receipt = await plasma.deposit({from: alice, value: 314});
70 | // const depositIndex = testUtils.depositIndex(receipt.receipt.blockNumber);
71 | const depositIndex = new web3.BigNumber(0);
72 | const promise = plasma.startDepositWithdraw(depositIndex, {from: alice, value: 0});
73 | // Will also fail if contract's bond constant is set to 0
74 | await expectThrow(promise);
75 | });
76 |
77 | it('should not allow early deposit withdraw', async () => {
78 | let receipt = await plasma.deposit({from: alice, value: 314});
79 | const depositIndex = new web3.BigNumber(0);
80 | // const depositIndex = testUtils.depositIndex(receipt.receipt.blockNumber);
81 |
82 | const depositWithdrawCollateral = await plasma.DepositWithdrawCollateral();
83 | await plasma.startDepositWithdraw(depositIndex.toString(), {from: alice, value: depositWithdrawCollateral.toString()});
84 |
85 | const promise = plasma.finalizeDepositWithdraw(depositIndex.toString(), {from: alice});
86 | await expectThrow(promise);
87 | });
88 |
89 | it('should allow successful deposit withdraw', async () => {
90 | const depositAmount = new BN(314);
91 | let receipt = await plasma.deposit({from: alice, value: depositAmount.toString()});
92 | const depositIndex = new web3.BigNumber(0);
93 | // const depositIndex = testUtils.depositIndex(receipt.receipt.blockNumber);
94 |
95 | const depositWithdrawCollateral = await plasma.DepositWithdrawCollateral();
96 | await plasma.startDepositWithdraw(depositIndex.toString(), {from: alice, value: depositWithdrawCollateral.toString()});
97 |
98 | const delay = await plasma.DepositWithdrawDelay();
99 | await increaseTime(delay.toNumber() + 1);
100 | const balanceBefore = await web3.eth.getBalance(alice);
101 | // const depositedBefore = await plasma.totalAmountDeposited();
102 | receipt = await plasma.finalizeDepositWithdraw(depositIndex.toString(), {from: alice, gasPrice: web3.eth.gasPrice});
103 | // const depositedAfter = await plasma.totalAmountDeposited();
104 | const balanceAfter = await web3.eth.getBalance(alice);
105 | await testUtils.expectEvents(plasma, receipt.receipt.blockNumber, 'DepositWithdrawCompletedEvent', {_depositIndex: depositIndex.toNumber()});
106 |
107 | assert(balanceAfter.gt(balanceBefore));
108 | const expectedBalance = balanceBefore
109 | .add(depositAmount)
110 | .add(depositWithdrawCollateral)
111 | .sub(web3.eth.gasPrice.mul(receipt.receipt.gasUsed));
112 | assert.equal(balanceAfter.toString(), expectedBalance.toString(), 'Balance not equal');
113 | // assert.equal(depositedAfter.toString(), (depositedBefore - depositAmount).toString(), 'Deposit counter should decrease');
114 | });
115 |
116 | it('should respond to deposit withdraw challenge', async () => {
117 | const depositAmount = new BN(42);
118 | let receipt = await plasma.deposit({from: alice, value: depositAmount.toString()});
119 | const depositIndex = new web3.BigNumber(0);
120 | // const depositIndex = testUtils.depositIndex(receipt.receipt.blockNumber);
121 |
122 | const tx = createTransaction(TxTypeFund, 0, [{
123 | blockNumber: 0,
124 | txNumberInBlock: 0,
125 | outputNumberInTransaction: 0,
126 | amount: depositIndex.toString(10)
127 | }], [{
128 | amount: depositAmount.toString(10),
129 | to: alice
130 | }],
131 | operatorKey
132 | );
133 | const block = createBlock(1, 1, firstHash, [tx], operatorKey);
134 | await testUtils.submitBlock(plasma, block);
135 | const proof = block.merkleTree.getProof(0, true);
136 |
137 | const depositWithdrawCollateral = await plasma.DepositWithdrawCollateral();
138 | await plasma.startDepositWithdraw(depositIndex, {from: alice, value: depositWithdrawCollateral});
139 |
140 | const balanceBefore = await web3.eth.getBalance(bob);
141 | receipt = await plasma.challengeDepositWithdraw(depositIndex.toString(), 1, ethUtil.bufferToHex(tx.rlpEncode()), ethUtil.bufferToHex(proof), {from: bob, gasPrice: web3.eth.gasPrice});
142 | const balanceAfter = await web3.eth.getBalance(bob);
143 | await testUtils.expectEvents(plasma, receipt.receipt.blockNumber, 'DepositWithdrawChallengedEvent', {_depositIndex: depositIndex.toNumber()});
144 |
145 | const expectedBalance = balanceBefore
146 | .add(depositWithdrawCollateral)
147 | .sub(web3.eth.gasPrice.mul(receipt.receipt.gasUsed));
148 | assert.equal(balanceAfter.toString(), expectedBalance.toString(), 'Balance not equal');
149 | });
150 |
151 | it('should stop Plasma on funding without deposit', async () => {
152 | const depositAmount = new BN(42);
153 | const tx = createTransaction(TxTypeFund, 0, [{
154 | blockNumber: 0,
155 | txNumberInBlock: 0,
156 | outputNumberInTransaction: 0,
157 | amount: 1
158 | }], [{
159 | amount: depositAmount.toString(),
160 | to: alice
161 | }],
162 | operatorKey
163 | );
164 | const block = createBlock(1, 1, firstHash, [tx], operatorKey);
165 | await testUtils.submitBlock(plasma, block);
166 | const proof = block.merkleTree.getProof(0, true);
167 |
168 | const balanceBefore = await web3.eth.getBalance(bob);
169 | const receipt = await plasma.proveInvalidDeposit(1, ethUtil.bufferToHex(tx.rlpEncode()),
170 | ethUtil.bufferToHex(proof), {from: bob, gasPrice: web3.eth.gasPrice});
171 | const balanceAfter = await web3.eth.getBalance(bob);
172 |
173 | assert.equal(true, await plasma.plasmaErrorFound());
174 | // const two = new BN(2);
175 | // const bond = await plasma.operatorsBond();
176 | assert(balanceAfter.gt(balanceBefore));
177 | // const expectedBalance = balanceBefore
178 | // .add(bond.div(two))
179 | // .sub(web3.eth.gasPrice.mul(receipt.receipt.gasUsed));
180 | // assert.equal(balanceAfter.toString(), expectedBalance.toString(), 'Balance not equal');
181 | });
182 |
183 | it('should stop Plasma on double funding', async () => {
184 | const depositAmount = new BN(42);
185 | let receipt = await plasma.deposit({from: alice, value: depositAmount.toString()});
186 | const depositIndex = new web3.BigNumber(0);
187 | // const depositIndex = testUtils.depositIndex(receipt.receipt.blockNumber);
188 |
189 | const tx1 = createTransaction(TxTypeFund, 0, [{
190 | blockNumber: 0,
191 | txNumberInBlock: 0,
192 | outputNumberInTransaction: 0,
193 | amount: depositIndex.toString()
194 | }], [{
195 | amount: depositAmount.toString(),
196 | to: alice
197 | }],
198 | operatorKey
199 | );
200 | const tx2 = createTransaction(TxTypeFund, 1, [{
201 | blockNumber: 0,
202 | txNumberInBlock: 0,
203 | outputNumberInTransaction: 0,
204 | amount: depositIndex.toString()
205 | }], [{
206 | amount: depositAmount.toString(),
207 | to: alice
208 | }],
209 | operatorKey
210 | );
211 | const block = createBlock(1, 1, firstHash, [tx1, tx2], operatorKey);
212 | await testUtils.submitBlock(plasma, block);
213 | const proof1 = block.merkleTree.getProof(0, true);
214 | const proof2 = block.merkleTree.getProof(1, true);
215 |
216 | const balanceBefore = await web3.eth.getBalance(bob);
217 | receipt = await plasma.proveDoubleFunding(
218 | 1, ethUtil.bufferToHex(tx1.rlpEncode()), ethUtil.bufferToHex(Buffer.concat(proof1)),
219 | 1, ethUtil.bufferToHex(tx2.rlpEncode()), ethUtil.bufferToHex(Buffer.concat(proof2)),
220 | {from: bob, gasPrice: web3.eth.gasPrice});
221 | const balanceAfter = await web3.eth.getBalance(bob);
222 |
223 | assert.equal(true, await plasma.plasmaErrorFound());
224 | assert(balanceAfter.gt(balanceBefore));
225 |
226 | // const bond = await plasma.operatorsBond();
227 | // const expectedBalance = balanceBefore
228 | // .add(bond.div(2))
229 | // .sub(web3.eth.gasPrice.mul(receipt.receipt.gasUsed));
230 | // assert.equal(balanceAfter.toString(), expectedBalance.toString(), 'Balance not equal');
231 | });
232 |
233 | });
234 |
--------------------------------------------------------------------------------
/test/fullExitProcedure.js:
--------------------------------------------------------------------------------
1 | const util = require("util");
2 | const ethUtil = require('ethereumjs-util')
3 | const BN = ethUtil.BN;
4 | const t = require('truffle-test-utils')
5 | t.init()
6 | const expectThrow = require("../helpers/expectThrow");
7 | const {addresses, keys} = require("./keys");
8 | const {createTransaction, parseTransactionIndex} = require("./createTransaction");
9 | const {createBlock, createMerkleTree} = require("./createBlock");
10 | const testUtils = require('./utils');
11 | const deploy = require("./deploy");
12 |
13 | const increaseTime = async function(addSeconds) {
14 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [addSeconds], id: 0})
15 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 1})
16 | }
17 |
18 | const {
19 | TxTypeFund,
20 | TxTypeMerge,
21 | TxTypeSplit} = require("../lib/Tx/RLPtx");
22 |
23 | contract('PlasmaParent exit procedure', async (accounts) => {
24 | const operatorAddress = accounts[0];
25 | const operatorKey = keys[0];
26 |
27 | let queue;
28 | let plasma;
29 | let storage;
30 | let firstHash;
31 |
32 | const operator = accounts[0];
33 |
34 | const alice = addresses[2];
35 | const aliceKey = keys[2];
36 | const bob = addresses[3];
37 | const bobKey = keys[3];
38 |
39 | beforeEach(async () => {
40 | const result = await deploy(operator, operatorAddress);
41 | ({plasma, firstHash, queue, storage} = result);
42 | })
43 |
44 | it('Simulate exit procedure', async () => {
45 | const withdrawCollateral = await plasma.WithdrawCollateral();
46 | await plasma.deposit({from: alice, value: "100"});
47 |
48 | const allTXes = [];
49 | const fundTX = createTransaction(TxTypeFund, 0,
50 | [{
51 | blockNumber: 0,
52 | txNumberInBlock: 0,
53 | outputNumberInTransaction: 0,
54 | amount: 0
55 | }],
56 | [{
57 | amount: 100,
58 | to: alice
59 | }],
60 | operatorKey
61 | )
62 | allTXes.push(fundTX)
63 | const block = createBlock(1, allTXes.length, firstHash, allTXes, operatorKey)
64 | await testUtils.submitBlock(plasma, block);
65 |
66 | const proofObject = block.getProofForTransactionByNumber(0);
67 | const {proof, tx} = proofObject;
68 | let submissionReceipt = await plasma.startExit(
69 | 1, 0, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof),
70 | {from: alice, value: withdrawCollateral}
71 | )
72 | console.log("Single exit gas price for exiting a deposit transaction is " + submissionReceipt.receipt.gasUsed)
73 | // struct ExitRecord {
74 | // bytes32 transactionRef;
75 | // //32 bytes
76 | // uint256 amount;
77 | // // 64 bytes
78 | // address owner;
79 | // uint64 timePublished;
80 | // uint32 blockNumber;
81 | // // 96 bytes
82 | // uint32 transactionNumber;
83 | // uint8 outputNumber;
84 | // bool isValid;
85 | // bool isLimbo;
86 | // // 96 + 7 bytes
87 | // }
88 | const transactionPublishedEvent = submissionReceipt.logs[0]
89 | const txHashFromEvent = transactionPublishedEvent.args._hash;
90 | const txDataFromEvent = transactionPublishedEvent.args._data;
91 | const exitRecordHash = submissionReceipt.logs[2].args._partialHash;
92 | const exitRecord = await plasma.exitRecords(exitRecordHash);
93 | const txData = ethUtil.bufferToHex(tx.serialize())
94 | const txHash = ethUtil.bufferToHex(ethUtil.sha3(proofObject.tx.serialize()))
95 |
96 | assert(exitRecord[0] === txHash);
97 | assert(exitRecord[1].toNumber() === 100)
98 | assert(exitRecord[2] === alice);
99 | assert(exitRecord[4].toString(10) === "1")
100 | assert(exitRecord[5].toNumber() === 0)
101 | assert(exitRecord[6].toNumber() === 0)
102 | assert(exitRecord[7] === true)
103 | assert(exitRecord[8] === false)
104 |
105 | assert(txHash === txHashFromEvent);
106 | assert(txData === txDataFromEvent);
107 |
108 | let oldBalance = await web3.eth.getBalance(alice);
109 |
110 | size = await queue.currentSize();
111 | assert(size.toString(10) === "1");
112 |
113 | minimalItem = await queue.getMin();
114 | assert(minimalItem === exitRecordHash);
115 |
116 | let exitDelay = await plasma.ExitDelay()
117 | await increaseTime(exitDelay.toNumber() + 1)
118 |
119 | submissionReceipt = await plasma.finalizeExits(1);
120 | console.log("One item in the queue finalization gas = " + submissionReceipt.receipt.gasUsed)
121 | let newBalance = await web3.eth.getBalance(alice);
122 | assert(newBalance.gt(oldBalance));
123 | let succesfulExit = await plasma.succesfulExits(exitRecordHash);
124 | assert(succesfulExit);
125 | size = await queue.currentSize();
126 | assert(size.toString(10) === "0");
127 |
128 | })
129 |
130 | it('Simulate exit procedure with invalid (spent) transaction in queue before valid', async () => {
131 | const withdrawCollateral = await plasma.WithdrawCollateral();
132 | await plasma.deposit({from: alice, value: "100"});
133 |
134 | const allTXes = [];
135 | const fundTX = createTransaction(TxTypeFund, 0,
136 | [{
137 | blockNumber: 0,
138 | txNumberInBlock: 0,
139 | outputNumberInTransaction: 0,
140 | amount: 0
141 | }],
142 | [{
143 | amount: 100,
144 | to: alice
145 | }],
146 | operatorKey
147 | )
148 | allTXes.push(fundTX)
149 | const block = createBlock(1, allTXes.length, firstHash, allTXes, operatorKey)
150 | await testUtils.submitBlock(plasma, block);
151 | let proofObject = block.getProofForTransactionByNumber(0);
152 | let {proof, tx} = proofObject;
153 | let submissionReceipt = await plasma.startExit(
154 | 1, 0, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof),
155 | {from: alice, value: withdrawCollateral}
156 | )
157 | const transactionPublishedEvent = submissionReceipt.logs[0]
158 | const txHashFromEvent = transactionPublishedEvent.args._hash;
159 | const txDataFromEvent = transactionPublishedEvent.args._data;
160 | const alicePriority = submissionReceipt.logs[1].args._priority;
161 |
162 | let exitRecordHash = submissionReceipt.logs[2].args._partialHash;
163 | let exitRecord = await plasma.exitRecords(exitRecordHash);
164 | let txData = ethUtil.bufferToHex(tx.serialize())
165 | let txHash = ethUtil.bufferToHex(ethUtil.sha3(proofObject.tx.serialize()))
166 |
167 | assert(exitRecord[0] === txHash);
168 | assert(exitRecord[1].toNumber() === 100)
169 | assert(exitRecord[2] === alice);
170 | assert(exitRecord[4].toString(10) === "1")
171 | assert(exitRecord[5].toNumber() === 0)
172 | assert(exitRecord[6].toNumber() === 0)
173 | assert(exitRecord[7] === true)
174 | assert(exitRecord[8] === false)
175 |
176 | assert(txHash === txHashFromEvent);
177 | assert(txData === txDataFromEvent);
178 |
179 | let nextHash = await plasma.hashOfLastSubmittedBlock();
180 | const spendingTX = createTransaction(TxTypeSplit, 0,
181 | [{
182 | blockNumber: 1,
183 | txNumberInBlock: 0,
184 | outputNumberInTransaction: 0,
185 | amount: 100
186 | }],
187 | [{
188 | amount: 100,
189 | to: bob
190 | }],
191 | aliceKey
192 | )
193 | const block2 = createBlock(2, 1, nextHash, [spendingTX], operatorKey)
194 | await testUtils.submitBlock(plasma, block2);
195 |
196 | proofObject = block2.getProofForTransactionByNumber(0);
197 | ({proof, tx} = proofObject);
198 |
199 | submissionReceipt = await plasma.challengeNormalExitByShowingExitBeingSpent(
200 | exitRecordHash, 2, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof), 0
201 | )
202 |
203 | exitRecordHash = submissionReceipt.logs[0].args._partialHash;
204 | exitRecord = await plasma.exitRecords(exitRecordHash);
205 | const aliceHash = exitRecordHash;
206 | assert(exitRecord[0] === txHash);
207 | assert(exitRecord[1].toNumber() === 100)
208 | assert(exitRecord[2] === alice);
209 | assert(exitRecord[4].toString(10) === "1")
210 | assert(exitRecord[5].toNumber() === 0)
211 | assert(exitRecord[6].toNumber() === 0)
212 | assert(exitRecord[7] === false)
213 | assert(exitRecord[8] === false)
214 |
215 | assert(txHash === txHashFromEvent);
216 | assert(txData === txDataFromEvent);
217 |
218 | submissionReceipt = await plasma.startExit( 2, 0, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof),
219 | {from: bob, value: withdrawCollateral});
220 |
221 | exitRecordHash = submissionReceipt.logs[2].args._partialHash;
222 | exitRecord = await plasma.exitRecords(exitRecordHash);
223 | const bobPriority = submissionReceipt.logs[1].args._priority;
224 |
225 | assert(bobPriority.gt(alicePriority));
226 | txData = ethUtil.bufferToHex(tx.serialize())
227 | txHash = ethUtil.bufferToHex(ethUtil.sha3(proofObject.tx.serialize()))
228 |
229 | const bobHash = exitRecordHash;
230 | assert(exitRecord[0] === txHash);
231 | assert(exitRecord[1].toNumber() === 100)
232 | assert(exitRecord[2] === bob);
233 | assert(exitRecord[4].toString(10) === "2")
234 | assert(exitRecord[5].toNumber() === 0)
235 | assert(exitRecord[6].toNumber() === 0)
236 | assert(exitRecord[7] === true)
237 | assert(exitRecord[8] === false)
238 |
239 | let oldBalanceAlice = await web3.eth.getBalance(alice);
240 | let oldBalanceBob = await web3.eth.getBalance(bob);
241 |
242 | size = await queue.currentSize();
243 | assert(size.toString(10) === "2");
244 |
245 | minimalItem = await queue.getMin();
246 | assert(minimalItem === aliceHash);
247 |
248 | let exitDelay = await plasma.ExitDelay()
249 | await increaseTime(exitDelay.toNumber() + 1)
250 |
251 | submissionReceipt = await plasma.finalizeExits(2);
252 | console.log("Two items in the queue finalization gas = " + submissionReceipt.receipt.gasUsed)
253 | let newBalanceAlice = await web3.eth.getBalance(alice);
254 | let newBalanceBob = await web3.eth.getBalance(bob);
255 | assert(newBalanceAlice.eq(oldBalanceAlice));
256 | assert(newBalanceBob.gt(oldBalanceBob));
257 | let succesfulExit = await plasma.succesfulExits(bobHash);
258 | assert(succesfulExit);
259 | succesfulExit = await plasma.succesfulExits(aliceHash);
260 | assert(!succesfulExit);
261 | size = await queue.currentSize();
262 | assert(size.toString(10) === "0");
263 |
264 | })
265 |
266 | it('Should pop only matured items from the exit queue', async () => {
267 | const withdrawCollateral = await plasma.WithdrawCollateral();
268 | await plasma.deposit({from: alice, value: "100"});
269 |
270 | const allTXes = [];
271 | const fundTX = createTransaction(TxTypeFund, 0,
272 | [{
273 | blockNumber: 0,
274 | txNumberInBlock: 0,
275 | outputNumberInTransaction: 0,
276 | amount: 0
277 | }],
278 | [{
279 | amount: 100,
280 | to: alice
281 | }],
282 | operatorKey
283 | )
284 | allTXes.push(fundTX)
285 | const block = createBlock(1, allTXes.length, firstHash, allTXes, operatorKey)
286 | await testUtils.submitBlock(plasma, block);
287 | let proofObject = block.getProofForTransactionByNumber(0);
288 | let {proof, tx} = proofObject;
289 | let submissionReceipt = await plasma.startExit(
290 | 1, 0, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof),
291 | {from: alice, value: withdrawCollateral}
292 | )
293 | const transactionPublishedEvent = submissionReceipt.logs[0]
294 | const txHashFromEvent = transactionPublishedEvent.args._hash;
295 | const txDataFromEvent = transactionPublishedEvent.args._data;
296 | const alicePriority = submissionReceipt.logs[1].args._priority;
297 |
298 | let exitRecordHash = submissionReceipt.logs[2].args._partialHash;
299 | const aliceHash = exitRecordHash;
300 | let exitRecord = await plasma.exitRecords(exitRecordHash);
301 | let txData = ethUtil.bufferToHex(tx.serialize())
302 | let txHash = ethUtil.bufferToHex(ethUtil.sha3(proofObject.tx.serialize()))
303 |
304 | assert(exitRecord[0] === txHash);
305 | assert(exitRecord[1].toNumber() === 100)
306 | assert(exitRecord[2] === alice);
307 | assert(exitRecord[4].toString(10) === "1")
308 | assert(exitRecord[5].toNumber() === 0)
309 | assert(exitRecord[6].toNumber() === 0)
310 | assert(exitRecord[7] === true)
311 | assert(exitRecord[8] === false)
312 |
313 | assert(txHash === txHashFromEvent);
314 | assert(txData === txDataFromEvent);
315 |
316 | let nextHash = await plasma.hashOfLastSubmittedBlock();
317 | const spendingTX = createTransaction(TxTypeSplit, 0,
318 | [{
319 | blockNumber: 1,
320 | txNumberInBlock: 0,
321 | outputNumberInTransaction: 0,
322 | amount: 100
323 | }],
324 | [{
325 | amount: 100,
326 | to: bob
327 | }],
328 | aliceKey
329 | )
330 | const block2 = createBlock(2, 1, nextHash, [spendingTX], operatorKey)
331 | await testUtils.submitBlock(plasma, block2);
332 |
333 | proofObject = block2.getProofForTransactionByNumber(0);
334 | ({proof, tx} = proofObject);
335 |
336 | let exitDelay = await plasma.ExitDelay()
337 | await increaseTime(Math.floor(exitDelay.toNumber()/2))
338 |
339 | submissionReceipt = await plasma.startExit( 2, 0, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof),
340 | {from: bob, value: withdrawCollateral});
341 |
342 | exitRecordHash = submissionReceipt.logs[2].args._partialHash;
343 | exitRecord = await plasma.exitRecords(exitRecordHash);
344 | const bobPriority = submissionReceipt.logs[1].args._priority;
345 |
346 | assert(bobPriority.gt(alicePriority));
347 | txData = ethUtil.bufferToHex(tx.serialize())
348 | txHash = ethUtil.bufferToHex(ethUtil.sha3(proofObject.tx.serialize()))
349 |
350 | const bobHash = exitRecordHash;
351 | assert(exitRecord[0] === txHash);
352 | assert(exitRecord[1].toNumber() === 100)
353 | assert(exitRecord[2] === bob);
354 | assert(exitRecord[4].toString(10) === "2")
355 | assert(exitRecord[5].toNumber() === 0)
356 | assert(exitRecord[6].toNumber() === 0)
357 | assert(exitRecord[7] === true)
358 | assert(exitRecord[8] === false)
359 |
360 | await expectThrow(plasma.finalizeExits(100));
361 |
362 | await increaseTime(Math.floor(exitDelay.toNumber()/2) + 100)
363 |
364 | let oldBalanceAlice = await web3.eth.getBalance(alice);
365 | let oldBalanceBob = await web3.eth.getBalance(bob);
366 |
367 | size = await queue.currentSize();
368 | assert(size.toString(10) === "2");
369 |
370 | minimalItem = await queue.getMin();
371 | assert(minimalItem === aliceHash);
372 |
373 | submissionReceipt = await plasma.finalizeExits(100);
374 | await expectThrow(plasma.finalizeExits(100));
375 | let newBalanceAlice = await web3.eth.getBalance(alice);
376 | let newBalanceBob = await web3.eth.getBalance(bob);
377 | assert(newBalanceAlice.gt(oldBalanceAlice));
378 | assert(newBalanceBob.eq(oldBalanceBob));
379 | let succesfulExit = await plasma.succesfulExits(bobHash);
380 | assert(!succesfulExit);
381 | succesfulExit = await plasma.succesfulExits(aliceHash);
382 | assert(succesfulExit);
383 | size = await queue.currentSize();
384 | assert(size.toString(10) === "1");
385 | minimalItem = await queue.getMin();
386 | assert(minimalItem === bobHash);
387 |
388 | await increaseTime(exitDelay.toNumber())
389 | submissionReceipt = await plasma.finalizeExits(100);
390 | newBalanceBob = await web3.eth.getBalance(bob);
391 | assert(newBalanceBob.gt(oldBalanceBob));
392 | succesfulExit = await plasma.succesfulExits(bobHash);
393 | assert(succesfulExit);
394 | })
395 | })
396 |
397 | function prettyPrint(res) {
398 | for (let field of res) {
399 | console.log(field.toString(10));
400 | }
401 | }
402 |
403 |
--------------------------------------------------------------------------------
/test/keys.js:
--------------------------------------------------------------------------------
1 | const addresses = [
2 | "0x627306090abab3a6e1400e9345bc60c78a8bef57",
3 | "0xf17f52151ebef6c7334fad080c5704d77216b732",
4 | "0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef",
5 | "0x821aea9a577a9b44299b9c15c88cf3087f3b5544",
6 | "0x0d1d4e623d10f9fba5db95830f7d3839406c6af2",
7 | "0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e",
8 | "0x2191ef87e392377ec08e7c08eb105ef5448eced5",
9 | "0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5",
10 | "0x6330a553fc93768f612722bb8c2ec78ac90b3bbc",
11 | "0x5aeda56215b167893e80b4fe645ba6d5bab767de",
12 | ];
13 |
14 | const keys = [
15 | Buffer.from("c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", "hex"),
16 | Buffer.from("ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f", "hex"),
17 | Buffer.from("0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1", "hex"),
18 | Buffer.from("c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c", "hex"),
19 | Buffer.from("388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418", "hex"),
20 | Buffer.from("659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63", "hex"),
21 | Buffer.from("82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8", "hex"),
22 | Buffer.from("aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7", "hex"),
23 | Buffer.from("0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4", "hex"),
24 | Buffer.from("8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5", "hex"),
25 | ];
26 |
27 | module.exports = {
28 | addresses, keys
29 | };
--------------------------------------------------------------------------------
/test/priorityQueue.js:
--------------------------------------------------------------------------------
1 | const PriorityQueue = artifacts.require('PriorityQueue');
2 | const util = require("util");
3 | const ethUtil = require('ethereumjs-util')
4 | const BN = ethUtil.BN;
5 | const t = require('truffle-test-utils')
6 | t.init()
7 | const expectThrow = require("../helpers/expectThrow");
8 | const {addresses, keys} = require("./keys.js");
9 | const crypto = require("crypto");
10 | const exitPartialHashSize = 22;
11 | // const Web3 = require("web3");
12 |
13 | const increaseTime = async function(addSeconds) {
14 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [addSeconds], id: 0})
15 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 1})
16 | }
17 |
18 | const {
19 | TxTypeFund,
20 | TxTypeMerge,
21 | TxTypeSplit} = require("../lib/Tx/RLPtx.js");
22 |
23 | contract('Priority queue', async (accounts) => {
24 | let priorityQueue;
25 |
26 | const operator = accounts[0];
27 |
28 | beforeEach(async () => {
29 | priorityQueue = await PriorityQueue.new({from: operator});
30 | })
31 |
32 | it('should insert one item into queue', async () => {
33 | let priority = new BN(1);
34 | let hash = ethUtil.bufferToHex(crypto.randomBytes(exitPartialHashSize));
35 | let size = await priorityQueue.currentSize();
36 | assert(size.toString(10) === "0", "queue size should be zero at deployment");
37 | let submissionReceipt = await priorityQueue.insert([priority], hash);
38 | console.log("Inserting one item to the queue gas price = " + submissionReceipt.receipt.gasUsed);
39 | let minimalItem = await priorityQueue.getMin();
40 | assert(minimalItem === hash, "inserted hash should match");
41 | size = await priorityQueue.currentSize();
42 | assert(size.toString(10) === "1", "queue size should increase");
43 | });
44 |
45 | it('should insert two items into queue', async () => {
46 | let priority = new BN(1);
47 | let hash = ethUtil.bufferToHex(crypto.randomBytes(exitPartialHashSize));
48 | let submissionReceipt = await priorityQueue.insert([priority], hash);
49 | let size = await priorityQueue.currentSize();
50 | assert(size.toString(10) === "1");
51 | priority = new BN(2);
52 | let anotherHash = ethUtil.bufferToHex(crypto.randomBytes(exitPartialHashSize));
53 | submissionReceipt = await priorityQueue.insert([priority], anotherHash);
54 | size = await priorityQueue.currentSize();
55 | assert(size.toString(10) === "2");
56 | let minimalItem = await priorityQueue.getMin();
57 | assert(minimalItem == hash);
58 | });
59 |
60 | it('should insert two items into queue with the same priority', async () => {
61 | let priority = new BN(1);
62 | let hash = ethUtil.bufferToHex(crypto.randomBytes(exitPartialHashSize));
63 | let submissionReceipt = await priorityQueue.insert([priority], hash);
64 | let size = await priorityQueue.currentSize();
65 | assert(size.toString(10) === "1");
66 | priority = new BN(1);
67 | let anotherHash = ethUtil.bufferToHex(crypto.randomBytes(exitPartialHashSize));
68 | submissionReceipt = await priorityQueue.insert([priority], anotherHash);
69 | size = await priorityQueue.currentSize();
70 | assert(size.toString(10) === "2");
71 | let minimalItem = await priorityQueue.getMin();
72 | assert(minimalItem == hash);
73 | });
74 |
75 |
76 | // not usable for heap queue
77 | // it('should insert many items and find position', async () => {
78 | // let maxSize = 100
79 | // let mod = new BN(maxSize/2);
80 | // let p;
81 | // let h;
82 | // for (let i = 0; i < maxSize; i++) {
83 | // let priority = new BN(crypto.randomBytes(1));
84 | // let hash = ethUtil.bufferToHex(crypto.randomBytes(exitPartialHashSize));
85 | // await priorityQueue.insert([priority], hash);
86 | // if (i == maxSize/2) {
87 | // p = priority
88 | // h = hash
89 | // }
90 | // }
91 | // let size = await priorityQueue.currentSize();
92 | // assert(size.toString(10) === "" + maxSize);
93 | // let position = await priorityQueue.getEstimateQueuePositionForPriority([p]);
94 | // let item = await priorityQueue.heapList(position)
95 | // assert(item[0].toNumber() >= p.toNumber())
96 | // // assert(item[0].toString(10) === p.toString(10))
97 | // // assert(item[1] === h);
98 | // });
99 |
100 | it('should insert many items and pop with checking priority', async () => {
101 | let maxSize = 100
102 | for (let i = 0; i < maxSize; i++) {
103 | let priority = new BN(crypto.randomBytes(1));
104 | let hash = ethUtil.bufferToHex(crypto.randomBytes(exitPartialHashSize));
105 | await priorityQueue.insert([priority], hash);
106 | }
107 | let size = await priorityQueue.currentSize();
108 | assert(size.toString(10) === "" + maxSize);
109 | let prevPrior = 0
110 | for (let i = 0; i < maxSize; i++) {
111 | let item = await priorityQueue.heapList(1);
112 | assert(item[0].toNumber() >= prevPrior);
113 | prevPrior = item[0].toNumber();
114 | await priorityQueue.delMin()
115 | }
116 | });
117 | })
--------------------------------------------------------------------------------
/test/testInvalidBlockChallenges.js:
--------------------------------------------------------------------------------
1 | const util = require("util");
2 | const ethUtil = require('ethereumjs-util')
3 | const BN = ethUtil.BN;
4 | const t = require('truffle-test-utils')
5 | t.init()
6 | const expectThrow = require("../helpers/expectThrow");
7 | const {addresses, keys} = require("./keys.js");
8 | const {createTransaction, parseTransactionIndex} = require("./createTransaction");
9 | const {createBlock, createMerkleTree} = require("./createBlock");
10 | const testUtils = require('./utils');
11 | const deploy = require("./deploy");
12 |
13 | // const Web3 = require("web3");
14 |
15 | const increaseTime = async function(addSeconds) {
16 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [addSeconds], id: 0})
17 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 1})
18 | }
19 |
20 | const {
21 | TxTypeFund,
22 | TxTypeMerge,
23 | TxTypeSplit} = require("../lib/Tx/RLPtx.js");
24 |
25 | contract('PlasmaParent invalid block challenges', async (accounts) => {
26 |
27 | const operatorAddress = accounts[0];
28 | const operatorKey = keys[0];
29 |
30 | let queue;
31 | let plasma;
32 | let storage;
33 | let firstHash;
34 |
35 | const operator = accounts[0];
36 |
37 | const alice = addresses[2];
38 | const aliceKey = keys[2];
39 | const bob = addresses[3];
40 | const bobKey = keys[3];
41 |
42 | beforeEach(async () => {
43 | const result = await deploy(operator, operatorAddress);
44 | ({plasma, firstHash, queue, storage} = result);
45 | })
46 |
47 | it('Transaction in block references the future', async () => {
48 | const tx = createTransaction(TxTypeSplit, 0,
49 | [{
50 | blockNumber: 100,
51 | txNumberInBlock: 0,
52 | outputNumberInTransaction: 0,
53 | amount: 10
54 | }],
55 | [{
56 | amount: 10,
57 | to: alice
58 | }],
59 | aliceKey
60 | )
61 | let block = createBlock(1, 1, firstHash, [tx], operatorKey)
62 | const reencodedTX = tx.serialize();
63 | const proof = block.merkleTree.getProof(0, true);
64 | const blockOneArray = block.serialize();
65 | const blockOne = Buffer.concat(blockOneArray);
66 | const blockOneHeader = blockOne.slice(0,137);
67 | const deserialization = ethUtil.rlp.decode(blockOneArray[7]);
68 | let lastBlockNumber = await plasma.lastBlockNumber()
69 | let lastBlockHash = await plasma.hashOfLastSubmittedBlock()
70 | lastBlockHash = await plasma.hashOfLastSubmittedBlock()
71 | assert(lastBlockNumber.toString() == "0");
72 | let submissionReceipt = await plasma.submitBlockHeaders(ethUtil.bufferToHex(blockOneHeader));
73 | lastBlockNumber = await plasma.lastBlockNumber();
74 | assert(lastBlockNumber.toString() == "1");
75 | await testUtils.expectEvents(
76 | storage,
77 | submissionReceipt.receipt.blockNumber,
78 | 'BlockHeaderSubmitted',
79 | {_blockNumber: 1, _merkleRoot: ethUtil.bufferToHex(block.header.merkleRootHash)}
80 | );
81 | let bl = await storage.blocks(1);
82 | assert(bl[2] == ethUtil.bufferToHex(block.header.merkleRootHash));
83 | // let root = await storage.getMerkleRoot(1);
84 | // assert(root = ethUtil.bufferToHex(ethUtil.hashPersonalMessage(reencodedTX)));
85 | submissionReceipt = await plasma.proveReferencingInvalidBlock(1, 0, ethUtil.bufferToHex(reencodedTX), ethUtil.bufferToHex(proof));
86 | const plasmaIsStopped = await plasma.plasmaErrorFound();
87 | assert(plasmaIsStopped);
88 | })
89 |
90 | it('Spend without owner signature', async () => {
91 | // first we fund Alice with something
92 |
93 | let tx = createTransaction(TxTypeFund, 0,
94 | [{
95 | blockNumber: 0,
96 | txNumberInBlock: 0,
97 | outputNumberInTransaction: 0,
98 | amount: 1
99 | }],
100 | [{
101 | amount: 100,
102 | to: alice
103 | }],
104 | operatorKey
105 | )
106 | let block = createBlock(1, 1, firstHash, [tx], operatorKey)
107 | const reencodedTX = tx.serialize();
108 | const proof = block.merkleTree.getProof(0, true);
109 | let blockArray = block.serialize();
110 | let blockHeader = Buffer.concat(blockArray).slice(0,137);
111 | let deserialization = ethUtil.rlp.decode(blockArray[7]);
112 | let lastBlockNumber = await plasma.lastBlockNumber()
113 | assert(lastBlockNumber.toString() == "0");
114 | let submissionReceipt = await plasma.submitBlockHeaders(ethUtil.bufferToHex(blockHeader));
115 | lastBlockNumber = await plasma.lastBlockNumber();
116 | assert(lastBlockNumber.toString() == "1");
117 | let allEvents = storage.allEvents({fromBlock: submissionReceipt.receipt.blockNumber, toBlock: submissionReceipt.receipt.blockNumber});
118 | let get = util.promisify(allEvents.get.bind(allEvents))
119 | let evs = await get()
120 | assert.web3Event({logs: evs}, {
121 | event: 'BlockHeaderSubmitted',
122 | args: {_blockNumber: 1,
123 | _merkleRoot: ethUtil.bufferToHex(block.header.merkleRootHash)}
124 | }, 'The event is emitted');
125 | let bl = await storage.blocks(1);
126 | assert(bl[2] == ethUtil.bufferToHex(block.header.merkleRootHash));
127 |
128 | // than we spend an output, but now Bob signs instead of Alice
129 |
130 | const newHash = await plasma.hashOfLastSubmittedBlock();
131 | const tx2 = createTransaction(TxTypeSplit, 0,
132 | [{
133 | blockNumber: 1,
134 | txNumberInBlock: 0,
135 | outputNumberInTransaction: 0,
136 | amount: 100
137 | }],
138 | [{
139 | amount: 100,
140 | to: bob
141 | }],
142 | bobKey
143 | )
144 | block = createBlock(2, 1, newHash, [tx2], operatorKey)
145 | const reencodedTX2 = tx2.serialize();
146 | const proof2 = block.merkleTree.getProof(0, true);
147 | blockArray = block.serialize();
148 | blockHeader = Buffer.concat(blockArray).slice(0,137);
149 | deserialization = ethUtil.rlp.decode(blockArray[7]);
150 | submissionReceipt = await plasma.submitBlockHeaders(ethUtil.bufferToHex(blockHeader));
151 | lastBlockNumber = await plasma.lastBlockNumber();
152 |
153 | allEvents = storage.allEvents({fromBlock: submissionReceipt.receipt.blockNumber, toBlock: submissionReceipt.receipt.blockNumber});
154 | get = util.promisify(allEvents.get.bind(allEvents))
155 | evs = await get()
156 | assert.web3Event({logs: evs}, {
157 | event: 'BlockHeaderSubmitted',
158 | args: {_blockNumber: 2,
159 | _merkleRoot: ethUtil.bufferToHex(block.header.merkleRootHash)}
160 | }, 'The event is emitted');
161 |
162 | bl = await storage.blocks(2);
163 | assert(bl[2] == ethUtil.bufferToHex(block.header.merkleRootHash));
164 | // uint32 _plasmaBlockNumber, //references and proves ownership on withdraw transaction
165 | // uint32 _plasmaTxNumInBlock,
166 | // bytes _plasmaTransaction,
167 | // bytes _merkleProof,
168 | // uint32 _originatingPlasmaBlockNumber, //references and proves ownership on output of original transaction
169 | // uint32 _originatingPlasmaTxNumInBlock,
170 | // bytes _originatingPlasmaTransaction,
171 | // bytes _originatingMerkleProof,
172 | // uint256 _inputOfInterest
173 |
174 | submissionReceipt = await plasma.proveBalanceOrOwnershipBreakingBetweenInputAndOutput(
175 | 2, ethUtil.bufferToHex(reencodedTX2), ethUtil.bufferToHex(proof2),
176 | 1, ethUtil.bufferToHex(reencodedTX), ethUtil.bufferToHex(proof),
177 | 0);
178 | const plasmaIsStopped = await plasma.plasmaErrorFound();
179 | assert(plasmaIsStopped);
180 | })
181 |
182 | it('UTXO amount is not equal to input amount', async () => {
183 | // first we fund Alice with something
184 |
185 | let tx = createTransaction(TxTypeFund, 0,
186 | [{
187 | blockNumber: 0,
188 | txNumberInBlock: 0,
189 | outputNumberInTransaction: 0,
190 | amount: 1
191 | }],
192 | [{
193 | amount: 100,
194 | to: alice
195 | }],
196 | operatorKey
197 | )
198 | let block = createBlock(1, 1, firstHash, [tx], operatorKey)
199 | const reencodedTX = tx.serialize();
200 | const proof = block.merkleTree.getProof(0, true);
201 | let blockArray = block.serialize();
202 | let blockHeader = Buffer.concat(blockArray).slice(0,137);
203 | let deserialization = ethUtil.rlp.decode(blockArray[7]);
204 | let lastBlockNumber = await plasma.lastBlockNumber()
205 | assert(lastBlockNumber.toString() == "0");
206 | let submissionReceipt = await plasma.submitBlockHeaders(ethUtil.bufferToHex(blockHeader));
207 | lastBlockNumber = await plasma.lastBlockNumber();
208 | assert(lastBlockNumber.toString() == "1");
209 | let allEvents = storage.allEvents({fromBlock: submissionReceipt.receipt.blockNumber, toBlock: submissionReceipt.receipt.blockNumber});
210 | let get = util.promisify(allEvents.get.bind(allEvents))
211 | let evs = await get()
212 | assert.web3Event({logs: evs}, {
213 | event: 'BlockHeaderSubmitted',
214 | args: {_blockNumber: 1,
215 | _merkleRoot: ethUtil.bufferToHex(block.header.merkleRootHash)}
216 | }, 'The event is emitted');
217 | let bl = await storage.blocks(1);
218 | assert(bl[2] == ethUtil.bufferToHex(block.header.merkleRootHash));
219 |
220 | // than we spend an output, but now Bob signs instead of Alice
221 |
222 | const newHash = await plasma.hashOfLastSubmittedBlock();
223 | const tx2 = createTransaction(TxTypeSplit, 0,
224 | [{
225 | blockNumber: 1,
226 | txNumberInBlock: 0,
227 | outputNumberInTransaction: 0,
228 | amount: 1000
229 | }],
230 | [{
231 | amount: 1000,
232 | to: bob
233 | }],
234 | aliceKey
235 | )
236 | block = createBlock(2, 1, newHash, [tx2], operatorKey)
237 | const reencodedTX2 = tx2.serialize();
238 | const proof2 = block.merkleTree.getProof(0, true);
239 | blockArray = block.serialize();
240 | blockHeader = Buffer.concat(blockArray).slice(0,137);
241 | deserialization = ethUtil.rlp.decode(blockArray[7]);
242 | submissionReceipt = await plasma.submitBlockHeaders(ethUtil.bufferToHex(blockHeader));
243 | lastBlockNumber = await plasma.lastBlockNumber();
244 |
245 | allEvents = storage.allEvents({fromBlock: submissionReceipt.receipt.blockNumber, toBlock: submissionReceipt.receipt.blockNumber});
246 | get = util.promisify(allEvents.get.bind(allEvents))
247 | evs = await get()
248 | assert.web3Event({logs: evs}, {
249 | event: 'BlockHeaderSubmitted',
250 | args: {_blockNumber: 2,
251 | _merkleRoot: ethUtil.bufferToHex(block.header.merkleRootHash)}
252 | }, 'The event is emitted');
253 |
254 | bl = await storage.blocks(2);
255 | assert(bl[2] == ethUtil.bufferToHex(block.header.merkleRootHash));
256 | // uint32 _plasmaBlockNumber, //references and proves ownership on withdraw transaction
257 | // uint32 _plasmaTxNumInBlock,
258 | // bytes _plasmaTransaction,
259 | // bytes _merkleProof,
260 | // uint32 _originatingPlasmaBlockNumber, //references and proves ownership on output of original transaction
261 | // uint32 _originatingPlasmaTxNumInBlock,
262 | // bytes _originatingPlasmaTransaction,
263 | // bytes _originatingMerkleProof,
264 | // uint256 _inputOfInterest
265 |
266 | submissionReceipt = await plasma.proveBalanceOrOwnershipBreakingBetweenInputAndOutput(
267 | 2, ethUtil.bufferToHex(reencodedTX2), ethUtil.bufferToHex(proof2),
268 | 1, ethUtil.bufferToHex(reencodedTX), ethUtil.bufferToHex(proof),
269 | 0);
270 | const plasmaIsStopped = await plasma.plasmaErrorFound();
271 | assert(plasmaIsStopped);
272 | })
273 |
274 | it('Double spend', async () => {
275 | // first we fund Alice with something
276 |
277 | let tx = createTransaction(TxTypeFund, 0,
278 | [{
279 | blockNumber: 0,
280 | txNumberInBlock: 0,
281 | outputNumberInTransaction: 0,
282 | amount: 1
283 | }],
284 | [{
285 | amount: 100,
286 | to: alice
287 | }],
288 | operatorKey
289 | )
290 | let block = createBlock(1, 1, firstHash, [tx], operatorKey)
291 | const reencodedTX = tx.serialize();
292 | const proof = block.merkleTree.getProof(0, true);
293 | let blockArray = block.serialize();
294 | let blockHeader = Buffer.concat(blockArray).slice(0,137);
295 | let deserialization = ethUtil.rlp.decode(blockArray[7]);
296 | let lastBlockNumber = await plasma.lastBlockNumber()
297 | assert(lastBlockNumber.toString() == "0");
298 | let submissionReceipt = await plasma.submitBlockHeaders(ethUtil.bufferToHex(blockHeader));
299 | lastBlockNumber = await plasma.lastBlockNumber();
300 | assert(lastBlockNumber.toString() == "1");
301 | let allEvents = storage.allEvents({fromBlock: submissionReceipt.receipt.blockNumber, toBlock: submissionReceipt.receipt.blockNumber});
302 | let get = util.promisify(allEvents.get.bind(allEvents))
303 | let evs = await get()
304 | assert.web3Event({logs: evs}, {
305 | event: 'BlockHeaderSubmitted',
306 | args: {_blockNumber: 1,
307 | _merkleRoot: ethUtil.bufferToHex(block.header.merkleRootHash)}
308 | }, 'The event is emitted');
309 | let bl = await storage.blocks(1);
310 | assert(bl[2] == ethUtil.bufferToHex(block.header.merkleRootHash));
311 |
312 | // than we spend an output, but now Bob signs instead of Alice
313 |
314 | const newHash = await plasma.hashOfLastSubmittedBlock();
315 | const tx2 = createTransaction(TxTypeSplit, 0,
316 | [{
317 | blockNumber: 1,
318 | txNumberInBlock: 0,
319 | outputNumberInTransaction: 0,
320 | amount: 100
321 | }],
322 | [{
323 | amount: 100,
324 | to: bob
325 | }],
326 | aliceKey
327 | )
328 | const tx3 = createTransaction(TxTypeSplit, 1,
329 | [{
330 | blockNumber: 1,
331 | txNumberInBlock: 0,
332 | outputNumberInTransaction: 0,
333 | amount: 100
334 | }],
335 | [{
336 | amount: 100,
337 | to: bob
338 | }],
339 | aliceKey
340 | )
341 | block = createBlock(2, 2, newHash, [tx2, tx3], operatorKey)
342 | const reencodedTX2 = tx2.serialize();
343 | const proof2 = Buffer.concat(block.merkleTree.getProof(0, true));
344 |
345 | const reencodedTX3 = tx3.serialize();
346 | const proof3 = Buffer.concat(block.merkleTree.getProof(1, true));
347 |
348 | blockArray = block.serialize();
349 | blockHeader = Buffer.concat(blockArray).slice(0,137);
350 | deserialization = ethUtil.rlp.decode(blockArray[7]);
351 | submissionReceipt = await plasma.submitBlockHeaders(ethUtil.bufferToHex(blockHeader));
352 | lastBlockNumber = await plasma.lastBlockNumber();
353 |
354 | allEvents = storage.allEvents({fromBlock: submissionReceipt.receipt.blockNumber, toBlock: submissionReceipt.receipt.blockNumber});
355 | get = util.promisify(allEvents.get.bind(allEvents))
356 | evs = await get()
357 | assert.web3Event({logs: evs}, {
358 | event: 'BlockHeaderSubmitted',
359 | args: {_blockNumber: 2,
360 | _merkleRoot: ethUtil.bufferToHex(block.header.merkleRootHash)}
361 | }, 'The event is emitted');
362 |
363 | bl = await storage.blocks(2);
364 | assert(bl[2] == ethUtil.bufferToHex(block.header.merkleRootHash));
365 | // function proveDoubleSpend(uint32 _plasmaBlockNumber1, //references and proves transaction number 1
366 | // uint32 _plasmaTxNumInBlock1,
367 | // uint8 _inputNumber1,
368 | // bytes _plasmaTransaction1,
369 | // bytes _merkleProof1,
370 | // uint32 _plasmaBlockNumber2, //references and proves transaction number 2
371 | // uint32 _plasmaTxNumInBlock2,
372 | // uint8 _inputNumber2,
373 | // bytes _plasmaTransaction2,
374 | // bytes _merkleProof2)
375 |
376 | submissionReceipt = await plasma.proveDoubleSpend(
377 | 2, 0, ethUtil.bufferToHex(reencodedTX2), ethUtil.bufferToHex(proof2),
378 | 2, 0, ethUtil.bufferToHex(reencodedTX3), ethUtil.bufferToHex(proof3));
379 | const plasmaIsStopped = await plasma.plasmaErrorFound();
380 | assert(plasmaIsStopped);
381 | })
382 |
383 | it('UTXO was successfully withdrawn and than spent in Plasma', async () => {
384 | const withdrawCollateral = await plasma.WithdrawCollateral();
385 | await plasma.deposit({from: alice, value: "100"});
386 |
387 | const allTXes = [];
388 | const fundTX = createTransaction(TxTypeFund, 0,
389 | [{
390 | blockNumber: 0,
391 | txNumberInBlock: 0,
392 | outputNumberInTransaction: 0,
393 | amount: 0
394 | }],
395 | [{
396 | amount: 100,
397 | to: alice
398 | }],
399 | operatorKey
400 | )
401 | allTXes.push(fundTX)
402 | const block = createBlock(1, allTXes.length, firstHash, allTXes, operatorKey)
403 | await testUtils.submitBlock(plasma, block);
404 |
405 | let proofObject = block.getProofForTransactionByNumber(0);
406 | let {proof, tx} = proofObject;
407 | let submissionReceipt = await plasma.startExit(
408 | 1, 0, ethUtil.bufferToHex(tx.serialize()), ethUtil.bufferToHex(proof),
409 | {from: alice, value: withdrawCollateral}
410 | )
411 | console.log("Single exit gas price for exiting a deposit transaction is " + submissionReceipt.receipt.gasUsed)
412 |
413 | const transactionPublishedEvent = submissionReceipt.logs[0]
414 | const txHashFromEvent = transactionPublishedEvent.args._hash;
415 | const txDataFromEvent = transactionPublishedEvent.args._data;
416 | let exitRecordHash = submissionReceipt.logs[2].args._partialHash;
417 | let exitRecord = await plasma.exitRecords(exitRecordHash);
418 | const txData = ethUtil.bufferToHex(tx.serialize())
419 | const txHash = ethUtil.bufferToHex(ethUtil.sha3(proofObject.tx.serialize()))
420 |
421 | assert(exitRecord[0] === txHash);
422 | assert(exitRecord[1].toNumber() === 100)
423 | assert(exitRecord[2] === alice);
424 | assert(exitRecord[4].toString(10) === "1")
425 | assert(exitRecord[5].toNumber() === 0)
426 | assert(exitRecord[6].toNumber() === 0)
427 | assert(exitRecord[7] === true)
428 | assert(exitRecord[8] === false)
429 |
430 | assert(txHash === txHashFromEvent);
431 | assert(txData === txDataFromEvent);
432 |
433 | let nextHash = await plasma.hashOfLastSubmittedBlock();
434 | const txToSpend = allTXes[0];
435 | const spendingTX = createTransaction(TxTypeSplit, 0,
436 | [{
437 | blockNumber: 1,
438 | txNumberInBlock: 0,
439 | outputNumberInTransaction: 0,
440 | amount: 100
441 | }],
442 | [{
443 | amount: 100,
444 | to: alice
445 | }],
446 | aliceKey
447 | )
448 | const block2 = createBlock(2, 1, nextHash, [spendingTX], operatorKey)
449 | await testUtils.submitBlock(plasma, block2);
450 |
451 | let proofObject2 = block2.getProofForTransactionByNumber(0);
452 |
453 | let exitDelay = await plasma.ExitDelay()
454 | await increaseTime(exitDelay.toNumber() + 1)
455 |
456 | submissionReceipt = await plasma.finalizeExits(2);
457 |
458 | // function proveSpendAndWithdraw(
459 | // uint32 _plasmaBlockNumber, //references and proves ownership on withdraw transaction
460 | // bytes _plasmaTransaction,
461 | // bytes _merkleProof,
462 | // bytes _originatingPlasmaTransaction,
463 | // bytes _originatingMerkleProof,
464 | // uint8 _inputNumber,
465 | // bytes22 _partialHash)
466 |
467 | submissionReceipt = await plasma.proveSpendAndWithdraw(2,
468 | ethUtil.bufferToHex(proofObject2.tx.serialize()),
469 | ethUtil.bufferToHex(proofObject2.proof),
470 | ethUtil.bufferToHex(tx.serialize()),
471 | ethUtil.bufferToHex(proof),
472 | 0, {from: bob});
473 | const plasmaIsStopped = await plasma.plasmaErrorFound();
474 | assert(plasmaIsStopped);
475 |
476 | })
477 |
478 | })
--------------------------------------------------------------------------------
/test/transactionSerialization.js:
--------------------------------------------------------------------------------
1 | const TXTester = artifacts.require("TXTester");
2 | const util = require("util");
3 | const ethUtil = require('ethereumjs-util')
4 | const BN = ethUtil.BN;
5 | const t = require('truffle-test-utils')
6 | t.init()
7 | const expectThrow = require("../helpers/expectThrow");
8 | const {addresses, keys} = require("./keys.js");
9 | const {createTransaction} = require("./createTransaction");
10 | const {createBlock, createMerkleTree} = require("./createBlock");
11 | const {PlasmaTransactionWithSignature} = require("../lib/Tx/RLPtxWithSignature");
12 | const testUtils = require('./utils');
13 |
14 | // const Web3 = require("web3");
15 |
16 | const increaseTime = async function(addSeconds) {
17 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [addSeconds], id: 0})
18 | await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 1})
19 | }
20 |
21 | const {
22 | TxTypeFund,
23 | TxTypeMerge,
24 | TxTypeSplit} = require("../lib/Tx/RLPtx.js");
25 |
26 | contract('Transaction deserialization tester', async (accounts) => {
27 |
28 | const operatorAddress = accounts[0];
29 | const operatorKey = keys[0];
30 | let txTester;
31 |
32 | const operator = accounts[0];
33 |
34 | const alice = addresses[2];
35 | const aliceKey = keys[2];
36 | const bob = addresses[3];
37 | const bobKey = keys[3];
38 |
39 | let firstHash;
40 |
41 | beforeEach(async () => {
42 | txTester = await TXTester.new({from: operator});
43 | })
44 |
45 | it('should encode and decode the transaction locally', () => {
46 | const tx = createTransaction(TxTypeSplit, 100,
47 | [{
48 | blockNumber: 1,
49 | txNumberInBlock: 200,
50 | outputNumberInTransaction: 0,
51 | amount: 10
52 | }],
53 | [{
54 | amount: 10,
55 | to: alice
56 | }],
57 | aliceKey
58 | )
59 | const reencodedTX = tx.serialize();
60 | const parsedTX = new PlasmaTransactionWithSignature(reencodedTX);
61 | assert(ethUtil.bufferToHex(parsedTX.from) == alice);
62 | })
63 |
64 | it('should give proper information about the TX', async () => {
65 | const tx = createTransaction(TxTypeSplit, 100,
66 | [{
67 | blockNumber: 1,
68 | txNumberInBlock: 200,
69 | outputNumberInTransaction: 0,
70 | amount: 10
71 | }],
72 | [{
73 | amount: 10,
74 | to: alice
75 | }],
76 | aliceKey
77 | )
78 | const reencodedTX = tx.serialize();
79 | const info = await txTester.parseTransaction(ethUtil.bufferToHex(reencodedTX));
80 | const txNumberInBlock = info[0].toNumber();
81 | const txType = info[1].toNumber();
82 | const inputsLength = info[2].toNumber();
83 | const outputsLength = info[3].toNumber();
84 | const sender = info[4];
85 | const isWellFormed = info[5];
86 | assert(isWellFormed);
87 | assert(sender === alice);
88 | assert(txType === TxTypeSplit);
89 | assert(inputsLength === 1);
90 | assert(outputsLength === 1);
91 | // assert(txNumberInBlock === 100);
92 | });
93 |
94 | it('should give proper information about the TX input', async () => {
95 | const tx = createTransaction(TxTypeSplit, 0,
96 | [{
97 | blockNumber: 1,
98 | txNumberInBlock: 200,
99 | outputNumberInTransaction: 0,
100 | amount: 10
101 | }],
102 | [{
103 | amount: 10,
104 | to: alice
105 | }],
106 | aliceKey
107 | )
108 | const reencodedTX = tx.serialize();
109 | const info = await txTester.getInputInfo(ethUtil.bufferToHex(reencodedTX), 0);
110 | const blockNumber = info[0].toNumber();
111 | const txNumberInBlock = info[1].toNumber();
112 | const outputNumber = info[2].toNumber();
113 | const amount = info[3].toString(10);
114 | assert(blockNumber === 1);
115 | // assert(txNumberInBlock === 200);
116 | assert(outputNumber === 0);
117 | assert(amount === ""+10);
118 | });
119 |
120 | it('should give proper information about the TX output', async () => {
121 | const tx = createTransaction(TxTypeSplit, 0,
122 | [{
123 | blockNumber: 1,
124 | txNumberInBlock: 200,
125 | outputNumberInTransaction: 0,
126 | amount: 10
127 | }],
128 | [{
129 | amount: 10,
130 | to: bob
131 | }],
132 | aliceKey
133 | )
134 | const reencodedTX = tx.serialize();
135 | const info = await txTester.getOutputInfo(ethUtil.bufferToHex(reencodedTX), 0);
136 | const outputNumber = info[0].toNumber();
137 | const recipient = info[1];
138 | const amount = info[2].toString(10);
139 | assert(outputNumber === 0);
140 | assert(recipient === bob);
141 | assert(amount === ""+10);
142 | });
143 |
144 | it('should give proper information about the TX in block', async () => {
145 | const tx = createTransaction(TxTypeSplit, 100,
146 | [{
147 | blockNumber: 1,
148 | txNumberInBlock: 200,
149 | outputNumberInTransaction: 0,
150 | amount: 10
151 | }],
152 | [{
153 | amount: 10,
154 | to: alice
155 | }],
156 | aliceKey
157 | )
158 | const tx2 = createTransaction(TxTypeSplit, 100,
159 | [{
160 | blockNumber: 1,
161 | txNumberInBlock: 100,
162 | outputNumberInTransaction: 0,
163 | amount: 10
164 | }],
165 | [{
166 | amount: 10,
167 | to: alice
168 | }],
169 | aliceKey
170 | )
171 | const block = createBlock(2, 2, Buffer.alloc(32), [tx, tx2], operatorKey);
172 | const reencodedTX2 = tx2.serialize();
173 | let proof = block.getProofForTransaction(tx.serialize());
174 | let proof2 = block.getProofForTransaction(tx2.serialize());
175 | proof = proof.proof
176 | proof2 = proof2.proof
177 | assert(!proof2.equals(proof));
178 | const root = block.getMerkleHash();
179 | const info = await txTester.parseFromBlock(ethUtil.bufferToHex(reencodedTX2), ethUtil.bufferToHex(proof2), ethUtil.bufferToHex(root));
180 | const txNumberInBlock = info[0].toNumber();
181 | const txType = info[1].toNumber();
182 | const inputsLength = info[2].toNumber();
183 | const outputsLength = info[3].toNumber();
184 | const sender = info[4];
185 | const isWellFormed = info[5];
186 | assert(txNumberInBlock === 1);
187 | assert(isWellFormed);
188 | assert(sender === alice);
189 | assert(txType === TxTypeSplit);
190 | assert(inputsLength === 1);
191 | assert(outputsLength === 1);
192 | });
193 |
194 | it('should parse deeped block', async () => {
195 | const tx = createTransaction(TxTypeSplit, 100,
196 | [{
197 | blockNumber: 1,
198 | txNumberInBlock: 200,
199 | outputNumberInTransaction: 0,
200 | amount: 10
201 | }],
202 | [{
203 | amount: 10,
204 | to: alice
205 | }],
206 | aliceKey
207 | )
208 | const tx2 = createTransaction(TxTypeSplit, 100,
209 | [{
210 | blockNumber: 1,
211 | txNumberInBlock: 100,
212 | outputNumberInTransaction: 0,
213 | amount: 10
214 | }],
215 | [{
216 | amount: 10,
217 | to: alice
218 | }],
219 | aliceKey
220 | )
221 | const tx3 = createTransaction(TxTypeSplit, 100,
222 | [{
223 | blockNumber: 1,
224 | txNumberInBlock: 300,
225 | outputNumberInTransaction: 0,
226 | amount: 100
227 | }],
228 | [{
229 | amount: 100,
230 | to: alice
231 | }],
232 | aliceKey
233 | )
234 | const block = createBlock(2, 2, Buffer.alloc(32), [tx, tx2, tx3], operatorKey);
235 | const reencodedTX3 = tx3.serialize();
236 | let proof = block.getProofForTransaction(tx.serialize());
237 | let proof2 = block.getProofForTransaction(tx2.serialize());
238 | let proof3 = block.getProofForTransaction(tx3.serialize());
239 | proof = proof.proof
240 | proof2 = proof2.proof
241 | proof3 = proof3.proof
242 | assert(!proof2.equals(proof));
243 | const proof3copy = Buffer.concat(block.merkleTree.getProof(2, true));
244 | assert(Buffer(proof3copy).equals(Buffer(proof3)));
245 | const root = block.getMerkleHash();
246 | const info = await txTester.parseFromBlock(ethUtil.bufferToHex(reencodedTX3), ethUtil.bufferToHex(proof3), ethUtil.bufferToHex(root));
247 | // console.log(ethUtil.bufferToHex(reencodedTX3));
248 | // console.log(ethUtil.bufferToHex(proof3));
249 | // console.log(ethUtil.bufferToHex(root));
250 | const txNumberInBlock = info[0].toNumber();
251 | const txType = info[1].toNumber();
252 | const inputsLength = info[2].toNumber();
253 | const outputsLength = info[3].toNumber();
254 | const sender = info[4];
255 | const isWellFormed = info[5];
256 | assert(txNumberInBlock === 2);
257 | assert(isWellFormed);
258 | assert(sender === alice);
259 | assert(txType === TxTypeSplit);
260 | assert(inputsLength === 1);
261 | assert(outputsLength === 1);
262 | });
263 |
264 | it('should parse deeped block 2', async () => {
265 | const tx = createTransaction(TxTypeSplit, 100,
266 | [{
267 | blockNumber: 1,
268 | txNumberInBlock: 200,
269 | outputNumberInTransaction: 0,
270 | amount: 10
271 | }],
272 | [{
273 | amount: 10,
274 | to: alice
275 | }],
276 | aliceKey
277 | )
278 | const tx2 = createTransaction(TxTypeSplit, 100,
279 | [{
280 | blockNumber: 1,
281 | txNumberInBlock: 100,
282 | outputNumberInTransaction: 0,
283 | amount: 10
284 | }],
285 | [{
286 | amount: 10,
287 | to: alice
288 | }],
289 | aliceKey
290 | )
291 | const tx3 = createTransaction(TxTypeSplit, 100,
292 | [{
293 | blockNumber: 1,
294 | txNumberInBlock: 300,
295 | outputNumberInTransaction: 0,
296 | amount: 100
297 | }],
298 | [{
299 | amount: 100,
300 | to: alice
301 | }],
302 | aliceKey
303 | )
304 | const block = createBlock(2, 2, Buffer.alloc(32), [tx, tx2, tx3], operatorKey);
305 | const reencodedTX3 = tx3.serialize();
306 | let proof = block.getProofForTransaction(tx.serialize());
307 | let proof2 = block.getProofForTransaction(tx2.serialize());
308 | let proof3 = block.getProofForTransaction(tx3.serialize());
309 | proof = proof.proof
310 | proof2 = proof2.proof
311 | proof3 = proof3.proof
312 | assert(!proof2.equals(proof));
313 | const proof3copy = Buffer.concat(block.merkleTree.getProof(2, true));
314 | assert(Buffer(proof3copy).equals(Buffer(proof3)));
315 | const root = block.getMerkleHash();
316 | const info = await txTester.parseFromBlockLimited(ethUtil.bufferToHex(reencodedTX3), ethUtil.bufferToHex(proof3), ethUtil.bufferToHex(root));
317 | // console.log(ethUtil.bufferToHex(reencodedTX3));
318 | // console.log(ethUtil.bufferToHex(proof3));
319 | // console.log(ethUtil.bufferToHex(root));
320 | const txNumberInBlock = info[1].toNumber();
321 | assert(info[0]);
322 | assert(txNumberInBlock === 2);
323 | });
324 |
325 |
326 |
327 | })
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | const util = require("util");
2 | const {Block} = require("../lib/Block/RLPblock");
3 | const ethUtil = require('ethereumjs-util');
4 |
5 | module.exports = {
6 | /**
7 | * Asserts that even is emitted
8 | *
9 | * @param contract A contract instance to check
10 | * @param {int} blockNumber Block number to look at
11 | * @param {(string|Array)} event Event name or an array of events
12 | * @param {Object} args Optional event args
13 | * @returns {Promise}
14 | */
15 | async expectEvents(contract, blockNumber, event, args = {}) {
16 | let allEvents = contract.allEvents({fromBlock: blockNumber, toBlock: blockNumber});
17 | let get = util.promisify(allEvents.get.bind(allEvents));
18 | let evs = await get();
19 | assert.web3Events({logs: evs}, Array.isArray(event) ? event : [{event, args}], 'The event is emitted');
20 | },
21 |
22 | /**
23 | * Submit a block header to the Plasma contract
24 | *
25 | * @param plasma The Plasma contract
26 | * @param {Block} block A block to submit
27 | * @returns {Promise}
28 | */
29 | async submitBlock(plasma, block) {
30 | const blockHeader = Buffer.concat(block.serialize()).slice(0,137);
31 | await plasma.submitBlockHeaders(ethUtil.bufferToHex(blockHeader));
32 | },
33 |
34 | depositIndex(blockNumber) {
35 | return (new web3.BigNumber(blockNumber)).mul(new web3.BigNumber(2).pow(32));
36 | },
37 |
38 |
39 | async checkPlasmaStopped() {
40 |
41 | }
42 | };
--------------------------------------------------------------------------------
/truffle.js:
--------------------------------------------------------------------------------
1 | const env = process.env;
2 | // don't load .env file in prod
3 |
4 | module.exports = {
5 | networks: {
6 | mainnet: {
7 | provider: function() {
8 | if (env.NODE_ENV !== 'production') {
9 | require('dotenv').load();
10 | }
11 | let WalletProvider = require("truffle-wallet-provider");
12 | let NonceTrackerSubprovider = require("web3-provider-engine/subproviders/nonce-tracker");
13 | let walletEth = require('ethereumjs-wallet').fromPrivateKey(Buffer.from(env.ETH_KEY, 'hex'));
14 | let wallet = new WalletProvider(walletEth, "https://mainnet.infura.io/" + env.INFURA_TOKEN)
15 | let nonceTracker = new NonceTrackerSubprovider()
16 | wallet.engine._providers.unshift(nonceTracker)
17 | nonceTracker.setEngine(wallet.engine)
18 | return wallet
19 | },
20 | network_id: 1,
21 | gasPrice: 5000000000
22 | // gas: 7000000,
23 | },
24 | mainnet2: {
25 | provider: function() {
26 | if (env.NODE_ENV !== 'production') {
27 | require('dotenv').load();
28 | }
29 | let HDWalletProvider = require("truffle-hdwallet-provider");
30 | return new HDWalletProvider(env.ETH_MNEMONIC, "https://mainnet.infura.io/" + env.INFURA_TOKEN, 0)
31 | },
32 | network_id: 1
33 | },
34 | rinkeby: {
35 | provider: function() {
36 | if (env.NODE_ENV !== 'production') {
37 | require('dotenv').load();
38 | }
39 | let WalletProvider = require("truffle-wallet-provider");
40 | let wallet = require('ethereumjs-wallet').fromPrivateKey(Buffer.from(env.ETH_KEY, 'hex'));
41 | return new WalletProvider(wallet, "https://rinkeby.infura.io/" + env.INFURA_TOKEN)
42 | },
43 | network_id: 4,
44 | gasPrice: 1000000000
45 | // gas: 7000000,
46 | },
47 | rinkeby2: {
48 | provider: function() {
49 | if (env.NODE_ENV !== 'production') {
50 | require('dotenv').load();
51 | }
52 | let HDWalletProvider = require("truffle-hdwallet-provider");
53 | return new HDWalletProvider(env.ETH_MNEMONIC, "https://rinkeby.infura.io/" + env.INFURA_TOKEN, 0)
54 | },
55 | network_id: 4
56 | },
57 | ganache: {
58 | host: "127.0.0.1",
59 | gas: 7000000,
60 | port: 7545,
61 | network_id: "*", // Match any network id,
62 | },
63 | development: {
64 | host: '127.0.0.1',
65 | gas: 7000000,
66 | port: 8545,
67 | network_id: '*' // Match any network id
68 | },
69 | cli: {
70 | host: '127.0.0.1',
71 | gas: 7000000,
72 | port: 9545,
73 | network_id: '*' // Match any network id
74 | }
75 | },
76 | };
77 |
--------------------------------------------------------------------------------