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

Commit

Permalink
Fix deposit flaw
Browse files Browse the repository at this point in the history
  • Loading branch information
bun919tw authored and boolafish committed Oct 26, 2018
1 parent 8c6bcda commit 93bc2a8
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 29 deletions.
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

0 comments on commit 93bc2a8

Please sign in to comment.