def participate_account(account_address, deployed_contracts) -> List: """ Make the given account participate. :param account_address: address to the account to participate :param deployed_contracts: instances of the deployed contracts :return: list of coins """ plasma_address = deployed_contracts.plasma_instance.address # transfering erc20 tokens to account fro plasma participation erc20_transfer(account_address, w3.toWei(ETHER_ALLOC, ETHER_NAME), deployed_contracts.erc20_instance) # approve tokens to plasma address so when participate function is called the # tokens will be transferred to the plasma address erc20_approve(plasma_address, account_address, w3.toWei(ETHER_ALLOC, ETHER_NAME), deployed_contracts.erc20_instance) # allocate 10 tokens to plasma tokens = participate(deployed_contracts, account_address, TOKEN_ALLOC, COIN_DENOMINATION) return tokens
def test_out_of_funds_participate(setup): """ user owns 10 dock tokens in decimals and approves 5 dock tokens to plasma plasma user wants to mint(participate) 2 times to plasma with coin denomination 5 he can't do that since the amount approved is 5 dock tokens in decimals """ accounts, deployed_contracts = setup peter_addr = accounts[1].address oscar_addr = accounts[3].address plasma_address = deployed_contracts.plasma_instance.address helpers.utils.erc20_transfer(oscar_addr, w3.toWei(10, ETHER_NAME), deployed_contracts.erc20_instance) helpers.utils.erc20_approve(plasma_address, peter_addr, w3.toWei(5, ETHER_NAME), deployed_contracts.erc20_instance) denomination = w3.toWei(5, ETHER_NAME) with pytest.raises(Exception): utils.participate(deployed_contracts, oscar_addr, 2, denomination)
def generate_tx(token_id, prev_block, denomination, addr_to, addr_from) -> Dict: """ Generate tx that will be used off-chain to initiate transactions which will also be used to exit or challenge a coin :param token_id: id of the token we want to generate a tx for :param prev_block: prevBlock of the token. :param denomination: token denomination :param addr_to: address were the token is being sent :param addr_from: address that initiates the transaction :return: Dictionary with signature and rlp encoded transaction. """ tx = rlp.encode([token_id, prev_block, denomination, bytes.fromhex(addr_to[2:])]) if prev_block == 0: # This is a deposit transaction. # the kecca256 of token id since this is a deposit. tx_hash = w3.soliditySha3(['uint64'], [token_id]) w3.personal.unlockAccount(addr_from, DEFAULT_PASSWORD) signature = w3.eth.sign(addr_from, data=tx_hash) tx = { "signature": signature, "tx": tx } return tx else: # Else the token was transferred before off-chain. # the hash of the tx encoded bytes. tx_hash = w3.soliditySha3(['bytes'], [tx]) w3.personal.unlockAccount(addr_from, DEFAULT_PASSWORD) signature = w3.eth.sign(addr_from, data=tx_hash) tx = { "signature": signature, "tx": tx, "tx_hash": tx_hash } return tx
def test_unauthorized_user_submition(setup): accounts, deployed_contracts = setup alice_addr = accounts[1].address plasma_instance = deployed_contracts.plasma_instance root = w3.soliditySha3(['string'], ['test']) with pytest.raises(Exception): args = (0, root) kwargs = {'from': alice_addr} fn_submit = plasma_instance.functions.submitBlock(*args) kwargs['gas'] = fn_submit.estimateGas() w3.personal.unlockAccount(alice_addr, DEFAULT_PASSWORD) tx_hash = fn_submit.transact(kwargs) assert w3.eth.waitForTransactionReceipt(tx_hash).status
def test_unsuccessful_block_submition(setup): ''' fails as block number provided is not greater than current block on smart contract ''' _, deployed_contracts = setup plasma_instance = deployed_contracts.plasma_instance root = w3.soliditySha3(['string'], ['test']) with pytest.raises(Exception): args = (0, root) kwargs = {'from': w3.eth.accounts[0]} w3.personal.unlockAccount(w3.eth.defaultAccount, '') fn_submit = plasma_instance.functions.submitBlock(*args) kwargs['gas'] = fn_submit.estimateGas(kwargs) tx_hash = fn_submit.transact(kwargs) assert w3.eth.waitForTransactionReceipt(tx_hash).status
def generate_dummy_tx() -> List: """ Generate dummy transactions to be included in a sparse merkle tree. Used so the sparse merkle tree doesn't have only one transaction. :return: List with id and transaction hash. """ uid = random.getrandbits(64) prevBlock = 5 deno = 20 owner = bytes.fromhex("30e3862ceb1a9b8b227bd2a53948c2ba2f1aa54a") tx = rlp.encode([uid, prevBlock, deno, owner]) tx_hash = HexBytes( w3.soliditySha3(['bytes'], [tx]) # dummy hash(leaf) ) tx = [uid, tx_hash] return tx
def test_successful_block_submition(setup): ''' successful submission of a block on-chain sparse_merkle_tree not included since the test is done just to see if values are being stored as expected. ''' _, deployed_contracts = setup plasma_instance = deployed_contracts.plasma_instance # dummy root hash root = w3.soliditySha3(['string'], ['test']) args = (1_000, root) kwargs = {'from': w3.eth.accounts[0]} w3.personal.unlockAccount(w3.eth.defaultAccount, '') fn_submit = plasma_instance.functions.submitBlock(*args) kwargs['gas'] = fn_submit.estimateGas(kwargs) tx_hash = fn_submit.transact(kwargs) assert w3.eth.waitForTransactionReceipt(tx_hash).status root_on_contract = plasma_instance.functions.childChain(1_000).call() assert root_on_contract[0] == root
def test_unsuccessful_participate(setup): """ user owns and approves tokens to plasma contract and should not be able to create DOCK token with different denomination """ accounts, deployed_contracts = setup alice_addr = accounts[2].address wrong_denomination = 6_000 assert wrong_denomination != COIN_DENOMINATION plasma_address = deployed_contracts.plasma_instance.address helpers.utils.erc20_transfer(alice_addr, COIN_DENOMINATION, deployed_contracts.erc20_instance) helpers.utils.erc20_approve(plasma_address, alice_addr, COIN_DENOMINATION, deployed_contracts.erc20_instance) new_denomination = w3.toWei(wrong_denomination, ETHER_NAME) with pytest.raises(Exception): utils.participate(deployed_contracts, alice_addr, 1, new_denomination)
def test_challenge_5(setup_participate): ''' A group of participants gathered together with operator try to exit alice legitimately owned coin... bob in colaboration with operator pretends he received the coin from alice and includes it in a block... bob sends coin to oscar... oscar sends coin to charlie... charlie sends coin to peter... peter start exit... One of the group members challenges peter... alice also challenges with her legit deposit transaction... ''' accounts, deployed_contracts, coins = setup_participate alice_addr = accounts[1].address bob_addr = accounts[2].address oscar_addr = accounts[3].address charlie_addr = accounts[4].address peter_addr = accounts[5].address # deposit tx of alice / legit token_uid = next(COIN_COUNTER) previous_block = 0 args = (coins[token_uid], previous_block, COIN_DENOMINATION, alice_addr, alice_addr) alice_alice = helpers.utils.generate_tx(*args) # Loid pretends she received coin from alice previous_block = deployed_contracts.plasma_instance.functions.getPlasmaCoin(coins[token_uid]).call()[1] args = (coins[token_uid], previous_block, COIN_DENOMINATION, bob_addr, bob_addr) alice_bob = helpers.utils.generate_tx(*args) # plasma block is generated and submited to mainnet block_height = next(BLOCK_COUNTER) args = (deployed_contracts.plasma_instance, coins[token_uid], alice_bob["tx_hash"], block_height) alice_bob["proof"], alice_bob["block_number"] = helpers.utils.generate_block(*args) # bob sends coin to oscar bob_oscar = helpers.utils.generate_tx( coins[token_uid], alice_bob["block_number"], COIN_DENOMINATION, oscar_addr, bob_addr) # plasma block is generated and submited to mainnet block_height = next(BLOCK_COUNTER) args = (deployed_contracts.plasma_instance, coins[token_uid], bob_oscar["tx_hash"], block_height) bob_oscar["proof"], bob_oscar["block_number"] = helpers.utils.generate_block(*args) # oscar sends coin to charlie oscar_charlie = helpers.utils.generate_tx( coins[token_uid], bob_oscar["block_number"], COIN_DENOMINATION, charlie_addr, oscar_addr) # plasma block is generated and submited to mainnet block_height = next(BLOCK_COUNTER) args = (deployed_contracts.plasma_instance, coins[token_uid], oscar_charlie["tx_hash"], block_height) oscar_charlie["proof"], oscar_charlie["block_number"] = helpers.utils.generate_block(*args) # charlie sends coin to peter charlie_peter = helpers.utils.generate_tx( coins[token_uid], oscar_charlie["block_number"], COIN_DENOMINATION, peter_addr, charlie_addr) # plasma block is generated and submited to mainnet block_height = next(BLOCK_COUNTER) args = (deployed_contracts.plasma_instance, coins[token_uid], charlie_peter["tx_hash"], block_height) charlie_peter["proof"], charlie_peter["block_number"] = helpers.utils.generate_block(*args) # peter starts exits events.start_exit( deployed_contracts.plasma_instance, coins[token_uid], oscar_charlie["tx"], charlie_peter["tx"], oscar_charlie["proof"], charlie_peter["proof"], charlie_peter["signature"], [oscar_charlie["block_number"], charlie_peter["block_number"]], peter_addr ) # oscar challenges helpers.events.challenge_before( deployed_contracts.plasma_instance, coins[token_uid], bob_oscar["tx"], bob_oscar["proof"], bob_oscar["signature"], 18000, oscar_addr, bob_oscar["tx_hash"] ) # alice also challenges with her valid deposit tx helpers.events.challenge_before( deployed_contracts.plasma_instance, coins[token_uid], alice_alice["tx"], '0x', alice_alice["signature"], 8, alice_addr, w3.soliditySha3(['uint64'], [coins[token_uid]]) ) # charlie tries to respond challenges / can respond to first challenge but # not to alice since it is the valid one. helpers.events.respond_challenge_before( deployed_contracts.plasma_instance, coins[token_uid], bob_oscar["tx_hash"], oscar_charlie["block_number"], oscar_charlie["tx"], oscar_charlie["proof"], oscar_charlie["signature"], charlie_addr ) # alice finishes exit since she owns the coin helpers.events.finish_challenge_exit(deployed_contracts.plasma_instance, coins[token_uid], alice_addr) coin_struct = deployed_contracts.plasma_instance.functions.getPlasmaCoin(coins[token_uid]).call() assert coin_struct[4] == 0
def test_challenge_1(setup_participate): ''' alice legitimately has deposited and owns a coin bob in colludes with the operator to pretend she received a coin from alice operator includes fake tx in block bob transfers coin to oscar and oscar tries to exit ''' accounts, deployed_contracts, coins = setup_participate alice_addr = accounts[1].address bob_addr = accounts[2].address oscar_addr = accounts[3].address # deposit tx of alice / legit token_uid = next(COIN_COUNTER) previous_block = 0 args = (coins[token_uid], previous_block, 20, alice_addr, alice_addr) alice_alice = helpers.utils.generate_tx(*args) # invalid tx...bob pretending she received coin from alice previous_block = deployed_contracts.plasma_instance.functions.getPlasmaCoin(coins[token_uid]).call()[1] args = (coins[token_uid], previous_block, 20, bob_addr, bob_addr) alice_bob = helpers.utils.generate_tx(*args) # plasma block is generated and submited to mainnet block_height = next(BLOCK_COUNTER) args = (deployed_contracts.plasma_instance, coins[token_uid], alice_bob["tx_hash"], block_height) alice_bob["proof"], alice_bob["block_number"] = helpers.utils.generate_block(*args) # bob pretending to receive the coin sends it to oscar bob_oscar = helpers.utils.generate_tx( coins[token_uid], alice_bob["block_number"], COIN_DENOMINATION, oscar_addr, bob_addr, ) # plasma block is generated and submited to mainnet block_height = next(BLOCK_COUNTER) args = (deployed_contracts.plasma_instance, coins[token_uid], bob_oscar["tx_hash"], block_height) bob_oscar["proof"], bob_oscar["block_number"] = helpers.utils.generate_block(*args) # oscar exits w3.personal.unlockAccount(oscar_addr, DEFAULT_PASSWORD) events.start_exit( deployed_contracts.plasma_instance, coins[token_uid], alice_bob["tx"], bob_oscar["tx"], alice_bob["proof"], bob_oscar["proof"], bob_oscar["signature"], [alice_bob["block_number"], bob_oscar["block_number"]], oscar_addr ) # alice challenges with his deposit/valid tx. helpers.events.challenge_before( deployed_contracts.plasma_instance, coins[token_uid], alice_alice["tx"], '0x', alice_alice["signature"], 4, alice_addr, # since it is a deposit tx from alice the tx_hash is the keccak256 of # token uid. w3.soliditySha3(['uint64'], [coins[token_uid]]) ) helpers.events.finish_challenge_exit(deployed_contracts.plasma_instance, coins[token_uid], alice_addr) coin_struct = deployed_contracts.plasma_instance.functions.getPlasmaCoin(coins[token_uid]).call() assert coin_struct[4] == 0
def finish_exit(deployed_contracts, coin_id, address): ''' finishExit calling PlasmaContract finalizeExit function. coinI_i : ID of the coin that user wants to finish exit. address : address of the user that finishes exit. NOTE: gas estimation will not work so we're defaulting to a tad below block size limit ''' # getting the erc20_instance set by deployer. erc20_instance = deployed_contracts.erc20_instance # getting the erc721_instance set by deployer. erc721_instance = deployed_contracts.erc721_instance # time required to wait for MATURITY_PERIOD(on mainnet = 1 week / 5 days) time.sleep(3) """ Note : estimateGas is not working as it should in the case of calling finalizeExit() function. """ args = (coin_id,) kwargs = {'from': address, 'gas': BGS_LIMIT} w3.personal.unlockAccount(address, DEFAULT_PASSWORD) fn_finalized_x = deployed_contracts.plasma_instance.functions.finalizeExit(*args) tx_hash = fn_finalized_x.transact(kwargs) assert w3.eth.waitForTransactionReceipt(tx_hash).status # calling ownerOfToken function on DockPlasmaToken. new_owner = erc721_instance.functions.ownerOfToken(coin_id).call() assert new_owner == address # calling the exit function on PlasmaContract and validate exit_struct = deployed_contracts.plasma_instance.functions.getExit(coin_id).call() assert exit_struct[0] == '0x0000000000000000000000000000000000000000' assert exit_struct[1] == 0 assert exit_struct[2] == 0 assert exit_struct[3] == 2 # calling balances instance of Balance struct on PlasmaContract and validate balance = deployed_contracts.plasma_instance.functions.balances(address).call() expected = DEFAULT_BOND assert balance[0] == 0 assert balance[1] == expected # check gas used kwargs = {'from': address} w3.personal.unlockAccount(address, DEFAULT_PASSWORD) fn_withdraw = deployed_contracts.plasma_instance.functions.withdrawBonds() kwargs['gas'] = fn_withdraw.estimateGas(kwargs) tx_hash = fn_withdraw.transact(kwargs) assert w3.eth.waitForTransactionReceipt(tx_hash).status # calling balances instance of Balance struct on PlasmaContract. balance = deployed_contracts.plasma_instance.functions.balances(address).call() expected = w3.toWei(0, 'ether') assert balance[0] == 0 assert balance[1] == expected # validate balance(s) of ERC20DockToken contract. kwargs = {'from': address} balanceBefore = erc20_instance.functions.balanceOf(address).call() w3.personal.unlockAccount(address, DEFAULT_PASSWORD) fn_withdraw = deployed_contracts.plasma_instance.functions.withdraw(coin_id) kwargs['gas'] = fn_withdraw.estimateGas(kwargs) tx_hash = fn_withdraw.transact(kwargs) assert w3.eth.waitForTransactionReceipt(tx_hash).status # validate balance of ERC20DockToken contract. balanceAfter = erc20_instance.functions.balanceOf(address).call() assert balanceAfter == balanceBefore + COIN_DENOMINATION