Skip to content

Commit 646fcd9

Browse files
authored
Colin/deposit priority (#90)
* updated deposit priority, added test * formatting * finalizing fix provided by eric, tx test provided by kevin * deleted unnecessary comments * updated comments * forogot to save all comments from last commit * updated assert comment
1 parent 7404701 commit 646fcd9

File tree

3 files changed

+111
-21
lines changed

3 files changed

+111
-21
lines changed

contracts/PlasmaMVP.sol

+16-20
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ contract PlasmaMVP {
152152

153153
uint amount = deposits[nonce].amount;
154154
address owner = deposits[nonce].owner;
155-
depositExitQueue.insert(nonce);
155+
uint256 priority = block.timestamp << 128 | nonce;
156+
depositExitQueue.insert(priority);
156157
depositExits[nonce] = exit({
157158
owner: owner,
158159
amount: amount,
@@ -408,15 +409,12 @@ contract PlasmaMVP {
408409
uint256 priority = queue[0];
409410
exit memory currentExit;
410411
uint256 position;
411-
if (isDeposits) {
412-
currentExit = depositExits[priority];
413-
} else {
414-
// retrieve the right 128 bits from the priority to obtain the position
415-
assembly {
416-
position := and(priority, div(not(0x0), exp(256, 16)))
417-
}
418-
currentExit = txExits[position];
419-
}
412+
// retrieve the right 128 bits from the priority to obtain the position
413+
assembly {
414+
position := and(priority, div(not(0x0), exp(256, 16)))
415+
}
416+
417+
currentExit = isDeposits ? depositExits[position] : txExits[position];
420418

421419
/*
422420
* Conditions:
@@ -439,7 +437,7 @@ contract PlasmaMVP {
439437
totalWithdrawBalance = totalWithdrawBalance.add(amountToAdd);
440438

441439
if (isDeposits)
442-
depositExits[priority].state = ExitState.Finalized;
440+
depositExits[position].state = ExitState.Finalized;
443441
else
444442
txExits[position].state = ExitState.Finalized;
445443

@@ -456,15 +454,13 @@ contract PlasmaMVP {
456454

457455
// move onto the next oldest exit
458456
priority = queue[0];
459-
if (isDeposits) {
460-
currentExit = depositExits[priority];
461-
} else {
462-
// retrieve the right 128 bits from the priority to obtain the position
463-
assembly {
464-
position := and(priority, div(not(0x0), exp(256, 16)))
465-
}
466-
currentExit = txExits[position];
467-
}
457+
458+
// retrieve the right 128 bits from the priority to obtain the position
459+
assembly {
460+
position := and(priority, div(not(0x0), exp(256, 16)))
461+
}
462+
463+
currentExit = isDeposits ? depositExits[position] : txExits[position];
468464
}
469465
}
470466

test/plasmamvp/deposits.js

+38
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,42 @@ contract('[PlasmaMVP] Deposits', async (accounts) => {
224224
if (!err)
225225
assert.fail("operator challenged exit with correct committed fee");
226226
});
227+
228+
it("Attempts a withdrawal delay attack on exiting deposits", async () => {
229+
230+
/* 1. Start exit for nonce_2 (newest)
231+
* 2. Start exit for nonce_1, nonce_0 in the same eth block
232+
* 3. Check exit ordering: nonce_2, nonce_0, nonce_1
233+
*/
234+
235+
let nonce_0 = (await instance.depositNonce.call()).toNumber();
236+
await instance.deposit(accounts[2], {from: accounts[2], value: 100});
237+
238+
let nonce_1 = (await instance.depositNonce.call()).toNumber();
239+
await instance.deposit(accounts[2], {from: accounts[2], value: 100});
240+
241+
let nonce_2 = (await instance.depositNonce.call()).toNumber();
242+
await instance.deposit(accounts[2], {from: accounts[2], value: 100});
243+
244+
// exit nonce_2
245+
await instance.startDepositExit(nonce_2, 0, {from: accounts[2], value: minExitBond});
246+
247+
// first exit should be in a different eth block
248+
fastForward(10);
249+
250+
// exit nonce_1 then nonce_0 in the same ethereum block
251+
async function exits(nonce_1, nonce_0) {
252+
let p1 = instance.startDepositExit(nonce_1, 0, {from: accounts[2], value: minExitBond});
253+
let p2 = instance.startDepositExit(nonce_0, 0, {from: accounts[2], value: minExitBond});
254+
return Promise.all([p1, p2]);
255+
}
256+
await exits(nonce_1, nonce_0);
257+
258+
fastForward(one_week);
259+
let depositExits = await instance.finalizeDepositExits({from: authority});
260+
assert.equal(depositExits.logs[0].args.position.toString(), [0, 0, 0, nonce_2].toString(), "nonce_2 was not finalized first");
261+
262+
assert.equal(depositExits.logs[2].args.position.toString(), [0, 0, 0, nonce_0].toString(), "nonce_0 was not finalized second");
263+
assert.equal(depositExits.logs[4].args.position.toString(), [0, 0, 0, nonce_1].toString(), "nonce_1 was not finalized last");
264+
});
227265
});

test/plasmamvp/transactions.js

+57-1
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ contract('[PlasmaMVP] Transactions', async (accounts) => {
278278
let secondOutput = [txPos[0], txPos[1], 1, 0];
279279
await instance.startTransactionExit(secondOutput, toHex(txBytes), toHex(proof),
280280
toHex(confirmSignatures), 0, {from: authority, value: minExitBond});
281-
281+
282282
// operator will challenge with second output
283283
let err;
284284
[err] = await catchError(instance.challengeFeeMismatch(secondOutput, txPos2, toHex(txBytes2), proof2));
@@ -738,4 +738,60 @@ contract('[PlasmaMVP] Transactions', async (accounts) => {
738738
exit = await instance.txExits.call(position);
739739
assert.equal(exit[4].toNumber(), 1, "exit has been challenged or finalized");
740740
});
741+
742+
it("Does finalize two transaction exits if given enough gas", async () => {
743+
depositNonce = (await instance.depositNonce.call()).toNumber();
744+
await instance.deposit(authority, {from: authority, value: amount});
745+
746+
// deposit is the first input. authority sends entire deposit to accounts[1]
747+
let txList2 = Array(17).fill(0);
748+
txList2[3] = depositNonce; txList2[12] = accounts[1]; txList2[13] = amount;
749+
let txHash2 = web3.sha3(RLP.encode(txList2).toString('hex'), {encoding: 'hex'});
750+
751+
let sigs2 = [toHex(await web3.eth.sign(authority, txHash2)), toHex(Buffer.alloc(65).toString('hex'))];
752+
753+
txBytes2 = [txList2, sigs2];
754+
txBytes2 = RLP.encode(txBytes2).toString('hex');
755+
756+
// submit the block
757+
let merkleHash2 = sha256String(txBytes2);
758+
let merkleRoot2, proof2;
759+
[merkleRoot2, proof2] = generateMerkleRootAndProof([merkleHash2], 0);
760+
let blockNum2 = (await instance.lastCommittedBlock.call()).toNumber() + 1;
761+
await instance.submitBlock([toHex(merkleRoot2)], [1], [0], blockNum2, {from: authority});
762+
763+
// construct the confirm signature
764+
let confirmHash2 = sha256String(merkleHash2 + merkleRoot2.slice(2));
765+
confirmSignatures2 = await web3.eth.sign(authority, confirmHash2);
766+
767+
txPos2 = [blockNum2, 0, 0];
768+
769+
// Start txn exit on both utxos
770+
await instance.startTransactionExit(txPos,
771+
toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0,
772+
{from: accounts[1], value: minExitBond});
773+
774+
await instance.startTransactionExit(txPos2,
775+
toHex(txBytes2), toHex(proof2), toHex(confirmSignatures2), 0,
776+
{from: accounts[1], value: minExitBond});
777+
778+
fastForward(one_week + 1000);
779+
780+
// Provide enough gas for both txns to be finalized
781+
await instance.finalizeTransactionExits({gas: 200000});
782+
783+
// The both utxo should have been exited correctly
784+
let balance = (await instance.balanceOf.call(accounts[1])).toNumber();
785+
assert.equal(balance, 2 * (amount + minExitBond));
786+
787+
// Verify that txn1 has been exited
788+
let position = 1000000*txPos[0];
789+
let exit = await instance.txExits.call(position);
790+
assert.equal(exit[4].toNumber(), 3, "exit's state not set to finalized");
791+
792+
// Verify that txn2 has been exited
793+
position = 1000000*txPos2[0];
794+
exit = await instance.txExits.call(position);
795+
assert.equal(exit[4].toNumber(), 3, "exit's state not set to finalized");
796+
});
741797
});

0 commit comments

Comments
 (0)