Skip to content

Commit

Permalink
Merge branch 'master' into pgebal/add_transaction_fees_to_integration…
Browse files Browse the repository at this point in the history
…_docs
  • Loading branch information
pgebal committed Jan 9, 2020
2 parents 01926f7 + 0e27fe6 commit c24786c
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 4 deletions.
3 changes: 3 additions & 0 deletions plasma_framework/contracts/src/utils/Merkle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ library Merkle {
require(proof.length != 0, "Merkle proof must not be empty");
require(proof.length % 32 == 0, "Length of Merkle proof must be a multiple of 32");

// see https://github.com/omisego/plasma-contracts/issues/546
require(index < 2**(proof.length/32), "Index does not match the length of the proof");

bytes32 proofElement;
bytes32 computedHash = keccak256(abi.encodePacked(LEAF_SALT, leaf));
uint256 j = index;
Expand Down
27 changes: 25 additions & 2 deletions plasma_framework/docs/integration-docs/integration-doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ GenericTransaction is based on [Wire Transaction format](https://docs.google.com
A GenericTransaction is:

```
transaction::= txType [input] [output] txData metaData [witness]
transaction::= txType [input] [output] txData metaData
```

Where
Expand All @@ -90,13 +90,36 @@ outputType ::= uint256
outputData ::= undefined, to be defined by concrete transaction types
txData ::= undefined, to be defined by concrete transaction types
metaData ::= bytes32
witness ::= bytes
```

The current implementation supports only the `Payment` transaction type.

Support for additional transaction types, such as ERC721, is reserved for future development.

### Signed transaction format (child chain and watcher only)
A note on encoding of signed transactions (transactions including witnesses) when handled in the [child chain and watcher implementations](github.com/omisego/elixir-omg).

Signed transactions are:

```
signedTransaction ::= [signature] rawTransaction
```

Where
```
signature ::= bytes
rawTransaction ::= transaction
```

and are transferred and stored RLP-encoded.

Child chain and watcher both expect, for a signed transaction to be valid, that:
- every input has a corresponding signature by the respective output's owner (`outputGuard`)
- every signature is a 65-byte long binary

**NOTE** Only signature witnesses are operative now, because only payment transactions are supported.
A signature is a specific form of a `witness` and is called as such throughout this document.

## Payment transaction format
Payment transactions are used to transfer fungible tokens, such as ETH and ERC20 tokens. A Payment transaction's output is as described in [FungibleTokenOutputModel](../contracts/FungibleTokenOutputModel.md)

Expand Down
5 changes: 5 additions & 0 deletions plasma_framework/python_tests/testlang/testlang.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,3 +484,8 @@ def get_in_flight_exit(self, in_flight_tx_id):
exit_id = self.root_chain.getInFlightExitId(in_flight_tx.encoded)
exit_info = self.root_chain.inFlightExits(exit_id)
return InFlightExit(self.root_chain, in_flight_tx, *exit_info)

def delete_in_flight_exit(self, in_flight_tx_id):
in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
exit_id = self.root_chain.getInFlightExitId(in_flight_tx.encoded)
self.root_chain.deleteNonPiggybackedInFlightExit(exit_id)
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import pytest
from eth_tester.exceptions import TransactionFailed
from plasma_core.constants import NULL_ADDRESS, NULL_ADDRESS_HEX, MIN_EXIT_PERIOD

FIRST_PERIOD_OVER = MIN_EXIT_PERIOD + 1


def test_delete_in_flight_exit_delete_exit_data_and_returns_bond(testlang):
owner, amount = testlang.accounts[0], 100
deposit_id = testlang.deposit(owner, amount)
spend_id = testlang.spend_utxo([deposit_id], [owner], outputs=[(owner.address, NULL_ADDRESS, amount)])

testlang.start_in_flight_exit(spend_id)
testlang.forward_timestamp(FIRST_PERIOD_OVER)

pre_balance = testlang.get_balance(owner)
testlang.delete_in_flight_exit(spend_id)
post_balance = testlang.get_balance(owner)

assert post_balance == pre_balance + testlang.root_chain.inFlightExitBond()

in_flight_exit = testlang.get_in_flight_exit(spend_id)
assert in_flight_exit.exit_start_timestamp == 0
assert in_flight_exit.exit_map == 0
assert in_flight_exit.bond_owner == NULL_ADDRESS_HEX
assert in_flight_exit.oldest_competitor == 0


def test_can_restart_exit_after_deletion(testlang):
owner, amount = testlang.accounts[0], 100
deposit_id = testlang.deposit(owner, amount)
spend_id = testlang.spend_utxo([deposit_id], [owner], outputs=[(owner.address, NULL_ADDRESS, amount)])

testlang.start_in_flight_exit(spend_id)
testlang.forward_timestamp(FIRST_PERIOD_OVER)
testlang.delete_in_flight_exit(spend_id)
testlang.start_in_flight_exit(spend_id)


def test_piggyback_on_deleted_exit_fails(testlang):
owner, amount = testlang.accounts[0], 100
deposit_id = testlang.deposit(owner, amount)
spend_id = testlang.spend_utxo([deposit_id], [owner], outputs=[(owner.address, NULL_ADDRESS, amount)])

testlang.start_in_flight_exit(spend_id)
testlang.forward_timestamp(FIRST_PERIOD_OVER)
testlang.delete_in_flight_exit(spend_id)

with pytest.raises(TransactionFailed):
testlang.piggyback_in_flight_exit_input(spend_id, 0, owner)

with pytest.raises(TransactionFailed):
testlang.piggyback_in_flight_exit_output(spend_id, 0, owner)


def test_deletion_fails_when_exit_is_in_first_phase(testlang):
owner, amount = testlang.accounts[0], 100
deposit_id = testlang.deposit(owner, amount)
spend_id = testlang.spend_utxo([deposit_id], [owner], outputs=[(owner.address, NULL_ADDRESS, amount)])

testlang.start_in_flight_exit(spend_id)

with pytest.raises(TransactionFailed):
testlang.delete_in_flight_exit(spend_id)


def test_deletion_fails_when_already_piggybacked(testlang):
owner, amount = testlang.accounts[0], 100
deposit_id = testlang.deposit(owner, amount)
spend_id = testlang.spend_utxo([deposit_id], [owner], outputs=[(owner.address, NULL_ADDRESS, amount)])

testlang.start_in_flight_exit(spend_id)
testlang.piggyback_in_flight_exit_input(spend_id, 0, owner)
testlang.forward_timestamp(FIRST_PERIOD_OVER)

with pytest.raises(TransactionFailed):
testlang.delete_in_flight_exit(spend_id)
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,22 @@ def test_process_exits_in_flight_exit_should_succeed(testlang):
assert testlang.get_balance(owner) == expected_balance


def test_in_flight_exit_is_not_processed_before_exit_period_passes(testlang, plasma_framework):
owner, amount = testlang.accounts[0], 100
deposit_id = testlang.deposit(owner, amount)
spend_id = testlang.spend_utxo([deposit_id], [owner], outputs=[(owner.address, NULL_ADDRESS, 100)])
testlang.start_in_flight_exit(spend_id)
testlang.piggyback_in_flight_exit_output(spend_id, 0, owner)
testlang.piggyback_in_flight_exit_input(spend_id, 0, owner)
testlang.forward_timestamp(MIN_EXIT_PERIOD)

pre_balance = testlang.get_balance(plasma_framework.eth_vault)
testlang.process_exits(NULL_ADDRESS, 0, 100)
post_balance = testlang.get_balance(plasma_framework.eth_vault)

assert pre_balance == post_balance


def test_finalize_exits_for_erc20_should_succeed(testlang, plasma_framework, token):
owner, amount = testlang.accounts[0], 100
assert plasma_framework.hasExitQueue(plasma_framework.erc20_vault_id, token.address)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ def processExits(self, token, top_exit_id, exits_to_process, vault_id=None):

return self.plasma_framework.processExits(vault_id, token, top_exit_id, exits_to_process)

def deleteNonPiggybackedInFlightExit(self, exit_id):
return self.payment_exit_game.deleteNonPiggybackedInFlightExit(exit_id)

def getInFlightExitId(self, tx):
return self.payment_exit_game.getInFlightExitId(tx)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,6 @@ contract('PaymentExitGame - Standard Exit - End to End Tests', ([_deployer, _mai
});
});


describe('Given Alice deposited with ERC20 token', () => {
before(async () => {
await this.erc20.transfer(alice, DEPOSIT_VALUE, { from: richFather });
Expand Down
2 changes: 1 addition & 1 deletion plasma_framework/test/helpers/merkle.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class MerkleNode {

class MerkleTree {
constructor(leaves, height = 0) {
const minHeightForLeaves = parseInt(Math.log2(leaves.length), 10) + 1;
const minHeightForLeaves = leaves.length === 1 ? 1 : parseInt(Math.ceil(Math.log2(leaves.length)), 10);

if (height === 0) {
this.height = minHeightForLeaves;
Expand Down
19 changes: 19 additions & 0 deletions plasma_framework/test/src/utils/Merkle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,25 @@ contract('Merkle', () => {
'Merkle proof must not be empty',
);
});

it('should revert when index exceeds max possible size according to proof data', async () => {
const leafIndex = 0;
const leafData = web3.utils.sha3(leaves[leafIndex]);
const proof = this.merkleTree.getInclusionProof(leaves[leafIndex]);
const rootHash = this.merkleTree.root;

const invalidIndex = maxSize;

await expectRevert(
this.merkleContract.checkMembership(
leafData,
invalidIndex,
rootHash,
proof,
),
'Index does not match the length of the proof',
);
});
});

it('check membership for tree where some leaves are empty', async () => {
Expand Down

0 comments on commit c24786c

Please sign in to comment.