Skip to content
This repository has been archived by the owner on Jun 13, 2019. It is now read-only.

Fix deposit flaw #98

Merged
merged 2 commits into from
Oct 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions integration_tests/features/exit_deposit_flow.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Feature: Exit deposit flow

Scenario: userA deposits to plasma cash and then tries to exit the deposit
Given userA deposits 1 eth in plasma cash
When userA starts to exit 1 eth from plasma cash
Then root chain got the start-deposit-exit record
When two weeks have passed from depositing exit
And userA finalize the deposit exit
Then userA has around 100 eth in root chain after exit
3 changes: 0 additions & 3 deletions integration_tests/features/steps/basic_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ def userA_deposit_some_eth_to_plasma(context, amount):
client.deposit(amount=amount, currency=eth_currency)
time.sleep(5)

operator = Client(container.get_root_chain(), container.get_child_chain_client(), operator_key)
operator.submit_block()


@then('userA has around {amount:d} eth in root chain')
def userA_has_around_some_amount_of_eth_in_root_chain(context, amount):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ def userA_deposits_some_amount_of_eth_in_plasma_cash(context, amount):
client.deposit(amount=amount, currency=eth_currency)
time.sleep(5)

operator = Client(container.get_root_chain(), container.get_child_chain_client(), operator_key)
operator.submit_block()


@given('userA transfers {amount:d} eth to userB')
def userA_transfers_some_eth_to_userB(context, amount):
Expand All @@ -52,9 +49,13 @@ def userA_tries_to_double_spend_some_eth_to_userC(context, amount):
invalid_tx_merkle = SparseMerkleTree(257, {uid: invalid_tx.merkle_hash})

root_chain = container.get_root_chain()
root_chain.functions.submitBlock(invalid_tx_merkle.root, TRANSFER_TX_2_BLOCK).transact({
'from': operator
})
root_chain.functions.submitBlock(
invalid_tx_merkle.root,
TRANSFER_TX_2_BLOCK,
False,
b'',
b''
).transact({'from': operator})


@when('userC starts to exit {amount:d} eth from plasma cash')
Expand Down
3 changes: 0 additions & 3 deletions integration_tests/features/steps/challenge_history_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ def userA_deposits_some_amount_of_eth_in_plasma_cash(context, amount):
client.deposit(amount=amount, currency=eth_currency)
time.sleep(5)

operator = Client(container.get_root_chain(), container.get_child_chain_client(), operator_key)
operator.submit_block()


@given('userA transfers {amount:d} eth to userB')
def userA_transfers_some_eth_to_userB(context, amount):
Expand Down
3 changes: 0 additions & 3 deletions integration_tests/features/steps/challenge_spent_coin_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ def userA_deposits_some_amount_of_eth_in_plasma_cash(context, amount):
client.deposit(amount=amount, currency=eth_currency)
time.sleep(5)

operator = Client(container.get_root_chain(), container.get_child_chain_client(), operator_key)
operator.submit_block()


@given('userA transfers {amount:d} eth to userB')
def userA_transfers_some_eth_to_userB(context, amount):
Expand Down
61 changes: 61 additions & 0 deletions integration_tests/features/steps/exit_deposit_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import time

from behave import given, then, when
from web3.auto import w3

from integration_tests.features.utils import has_value
from plasma_cash.client.client import Client
from plasma_cash.dependency_config import container

userA = '0xb83e232458A092696bE9717045d9A605FB0FEc2b'
operator_key = '0xa18969817c2cefadf52b93eb20f917dce760ce13b2ac9025e0361ad1e7a1d448'
userA_key = '0xe4807cf08191b310fe1821e6e5397727ee6bc694e92e25115eca40114e3a4e6b'
eth_currency = '0x0000000000000000000000000000000000000000'
uid = 1693390459388381052156419331572168595237271043726428428352746834777341368960

DEPOSIT_TX_BLOCK = 1
TRANSFER_TX_1_BLOCK = 2


@given('userA deposits {amount:d} eth in plasma cash')
def userA_deposits_some_amount_of_eth_in_plasma_cash(context, amount):
client = Client(container.get_root_chain(), container.get_child_chain_client(), userA_key)
client.deposit(amount=amount, currency=eth_currency)
time.sleep(5)


@when('userA starts to exit {amount:d} eth from plasma cash')
def userA_starts_to_exit_deposit_from_plasma_cash(context, amount):
client = Client(container.get_root_chain(), container.get_child_chain_client(), userA_key)
client.start_deposit_exit(uid, tx_blk_num=DEPOSIT_TX_BLOCK)
time.sleep(5)

operator = Client(container.get_root_chain(), container.get_child_chain_client(), operator_key)
operator.submit_block()


@then('root chain got the start-deposit-exit record')
def root_chain_got_the_start_deposit_exit_record(context):
root_chain = container.get_root_chain()
assert has_value(root_chain.functions.exits(uid).call({'from': userA}))


@when('two weeks have passed from depositing exit')
def two_week_passed(context):
TWO_WEEK_SECOND = 60 * 60 * 24 * 14
for provider in w3.providers:
provider.make_request('evm_increaseTime', TWO_WEEK_SECOND)


@when('userA finalize the deposit exit')
def userA_finalize_exit(context):
client = Client(container.get_root_chain(), container.get_child_chain_client(), userA_key)
client.finalize_exit(uid)
time.sleep(5)


@then('userA has around {amount:d} eth in root chain after exit')
def userB_successfully_exit_from_root_chain_after_exit(context, amount):
balance = w3.eth.getBalance(userA)
assert_msg = 'balance: {} is not in around: {}'.format(w3.fromWei(balance, 'ether'), amount)
assert w3.toWei(amount - 0.05, 'ether') <= balance <= w3.toWei(amount, 'ether'), assert_msg
26 changes: 18 additions & 8 deletions plasma_cash/child_chain/child_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ethereum import utils
from web3.auto import w3

from plasma_cash.utils.utils import get_sender
from plasma_cash.utils.utils import get_sender, sign

from .block import Block
from .event import emit
Expand Down Expand Up @@ -53,26 +53,36 @@ def apply_deposit(self, depositor, amount, uid):
if not self.current_block.get_tx_by_uid(uid):
deposit_tx = Transaction(0, uid, amount, new_owner)
self.current_block.add_tx(deposit_tx)
sig = sign(self.current_block.hash, self.key)
self.submit_block(sig.hex(), True, uid)
return deposit_tx.hash

err_msg = 'deposit of uid: {} is already applied previously'.format(uid)
raise DepositAlreadyAppliedException(err_msg)

def submit_block(self, sig):
def submit_block(self, sig, isDepositBlock=False, uid=None):
signature = bytes.fromhex(sig)
if (signature == b'\x00' * 65 or
get_sender(self.current_block.hash, signature) != self.authority):
raise InvalidBlockSignatureException('failed to submit a block')

merkle_hash = self.current_block.merklize_transaction_set()
deposit_tx, deposit_tx_proof = b'', b''
if uid is not None:
deposit_tx = self.current_block.get_tx_by_uid(uid)
deposit_tx_proof = self.current_block.merkle.create_merkle_proof(uid)

authority_address = w3.toChecksumAddress('0x' + self.authority.hex())
tx = (self.root_chain.functions
.submitBlock(merkle_hash, self.current_block_number)
.buildTransaction({
'from': authority_address,
'nonce': w3.eth.getTransactionCount(authority_address, 'pending')
}))
tx = self.root_chain.functions.submitBlock(
merkle_hash,
self.current_block_number,
isDepositBlock,
rlp.encode(deposit_tx),
deposit_tx_proof
).buildTransaction({
'from': authority_address,
'nonce': w3.eth.getTransactionCount(authority_address, 'pending')
})

signed = w3.eth.account.signTransaction(tx, self.key)
w3.eth.sendRawTransaction(signed.rawTransaction)
Expand Down
18 changes: 18 additions & 0 deletions plasma_cash/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ def start_exit(self, uid, prev_tx_blk_num, tx_blk_num):
).buildTransaction({'from': self.address})
self._sign_and_send_tx(tx)

def abort_deposit(self, uid):
tx = self.root_chain.functions.abortDeposit(uid).buildTransaction({'from': self.address})
self._sign_and_send_tx(tx)

def start_deposit_exit(self, uid, tx_blk_num):
block = self.get_block(tx_blk_num)

tx = block.get_tx_by_uid(uid)
block.merklize_transaction_set()
tx_proof = block.merkle.create_merkle_proof(uid)

tx = self.root_chain.functions.startDepositExit(
rlp.encode(tx),
tx_proof,
tx_blk_num
).buildTransaction({'from': self.address})
self._sign_and_send_tx(tx)

def challenge_exit(self, uid, tx_blk_num):
block = self.get_block(tx_blk_num)

Expand Down
70 changes: 66 additions & 4 deletions plasma_cash/root_chain/contracts/RootChain/RootChain.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ contract RootChain {
uint public depositCount;
uint public currentBlkNum;
mapping(uint => bytes32) public childChain;
mapping(uint => uint) public wallet;
mapping(uint => funds) public wallet;
mapping(uint => exit) public exits;
mapping(uint => Challenge.challenge[]) public challenges;

Expand All @@ -36,6 +36,13 @@ contract RootChain {
address owner;
}

struct funds {
bool hasValue;
bool isConfirmed;
uint amount;
address depositor;
}

/*
* Modifiers
*/
Expand All @@ -56,10 +63,24 @@ contract RootChain {
// @dev Allows Plasma chain operator to submit block root
// @param blkRoot The root of a child chain block
// @param blknum The child chain block number
function submitBlock(bytes32 blkRoot, uint blknum)
function submitBlock(
bytes32 blkRoot,
uint blknum,
bool isDepositBlock,
bytes depositTx,
bytes depositTxProof
)
public
isAuthority
{
if (isDepositBlock) {
Transaction.Tx memory txObj = depositTx.createTx();
bytes32 merkleHash = keccak256(depositTx);
// Check if the deposit is aborted
require(wallet[txObj.uid].hasValue);
require(merkleHash.checkMembership(txObj.uid, blkRoot, depositTxProof));
wallet[txObj.uid].isConfirmed = true;
}
require(currentBlkNum + 1 == blknum);
childChain[blknum] = blkRoot;
currentBlkNum += 1;
Expand All @@ -78,12 +99,53 @@ contract RootChain {
require(amount * 10**18 == msg.value);
}
uint uid = uint256(keccak256(currency, msg.sender, depositCount));
wallet[uid] = amount;
wallet[uid] = funds({
hasValue: true,
isConfirmed: false,
amount: amount,
depositor: msg.sender
});
depositCount += 1;
emit Deposit(msg.sender, amount, uid);
return uid;
}

// @dev Abort an deposit which is not included in child chain
// @param uid The id to specify the deposit
function abortDeposit(uint uid) public {
require(!wallet[uid].isConfirmed);
require(wallet[uid].depositor == msg.sender);

msg.sender.transfer(wallet[uid].amount*10**18);
delete wallet[uid].hasValue;
}

// @dev Starts to exit a deposit transaction
// @param tx The transaction in bytes that user wants to exit
// @param txProof The merkle proof of the tx
// @param txBlkNum The block number of the tx
function startDepositExit(bytes tx, bytes txProof, uint txBlkNum) public {
Transaction.Tx memory txObj = tx.createTx();
require(txObj.prevBlock == 0);
require(msg.sender == txObj.newOwner);

bytes32 merkleHash = keccak256(tx);
bytes32 root = childChain[txBlkNum];
require(merkleHash.checkMembership(txObj.uid, root, txProof));

// Record the exit tx.
require(!exits[txObj.uid].hasValue);
exits[txObj.uid] = exit({
hasValue: true,
exitTime: block.timestamp + 2 weeks,
exitTxBlkNum: txBlkNum,
exitTx: tx,
txBeforeExitTxBlkNum: 0,
txBeforeExitTx: "",
owner: msg.sender
});
}

// @dev Starts to exit a transaction
// @param prevTx The previous transaction in bytes of the transaction that user wants to exit
// @param prevTxProof The merkle proof of the prevTx
Expand Down Expand Up @@ -211,7 +273,7 @@ contract RootChain {
require(!challenges[uid][i].hasValue);
}

exits[uid].owner.transfer(wallet[uid]*10**18);
exits[uid].owner.transfer(wallet[uid].amount*10**18);
delete exits[uid].hasValue;
}

Expand Down
14 changes: 13 additions & 1 deletion unit_tests/child_chain/test_child_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ def test_apply_deposit(self, child_chain):
DUMMY_AMOUNT = 123
DUMMY_UID = 0
DUMMY_ADDR = b'\xfd\x02\xec\xeeby~u\xd8k\xcf\xf1d.\xb0\x84J\xfb(\xc7'
DUMMY_SIG = ('e79be9e20e121a8447b845c1b95b30b9bc4ed33db1de8e0f2c4401f56660506b7' +
'f67dde4068f0a3a3763ef15d0c86988db8bbdaddfa9f42a36a9721349433e051b')
DUMMY_IS_DEPOSIT_BLOCK = True

(when(child_chain)
.submit_block(DUMMY_SIG, DUMMY_IS_DEPOSIT_BLOCK, DUMMY_UID)
.thenReturn(None))
tx_hash = child_chain.apply_deposit(DUMMY_ADDR, DUMMY_AMOUNT, DUMMY_UID)

tx = child_chain.current_block.transaction_set[0]
Expand All @@ -73,7 +79,13 @@ def test_apply_deposit_should_fail_when_is_already_applied(self, child_chain, ro
DUMMY_AMOUNT = 123
DUMMY_UID = 0
DUMMY_ADDR = b'\xfd\x02\xec\xeeby~u\xd8k\xcf\xf1d.\xb0\x84J\xfb(\xc7'
DUMMY_SIG = ('e79be9e20e121a8447b845c1b95b30b9bc4ed33db1de8e0f2c4401f56660506b7' +
'f67dde4068f0a3a3763ef15d0c86988db8bbdaddfa9f42a36a9721349433e051b')
DUMMY_IS_DEPOSIT_BLOCK = True

(when(child_chain)
.submit_block(DUMMY_SIG, DUMMY_IS_DEPOSIT_BLOCK, DUMMY_UID)
.thenReturn(None))
child_chain.apply_deposit(DUMMY_ADDR, DUMMY_AMOUNT, DUMMY_UID)
with pytest.raises(DepositAlreadyAppliedException):
child_chain.apply_deposit(DUMMY_ADDR, DUMMY_AMOUNT, DUMMY_UID)
Expand All @@ -88,7 +100,7 @@ def test_submit_block(self, child_chain, root_chain):
block = child_chain.current_block
when(child_chain.current_block).merklize_transaction_set().thenReturn(DUMMY_MERKLE)
(when(root_chain.functions)
.submitBlock(DUMMY_MERKLE, block_number)
.submitBlock(DUMMY_MERKLE, block_number, ANY, ANY, ANY)
.thenReturn(MOCK_FUNCTION))
when(MOCK_FUNCTION).buildTransaction(ANY).thenReturn(DUMMY_TX)
(when('plasma_cash.child_chain.child_chain')
Expand Down
Loading