def test_unlock_wrong_locksroot( web3: Web3, token_network: Contract, create_settled_channel: Callable, get_accounts: Callable ) -> None: """ Test unlocking with wrong pending locks """ (A, B) = get_accounts(2) settle_timeout = 8 pending_transfers_tree_A = get_pending_transfers_tree(web3, [1, 3, 5], [], settle_timeout) pending_transfers_tree_A_fake = get_pending_transfers_tree(web3, [1, 3, 6], [], settle_timeout) channel_identifier = create_settled_channel( A, pending_transfers_tree_A.locked_amount, pending_transfers_tree_A.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_A_fake.packed_transfers ).call() # Fails for an empty packed locks with pytest.raises(TransactionFailed): token_network.functions.unlock(channel_identifier, B, A, b"").call() token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_A.packed_transfers ).call_and_transact()
def test_unlock_different_channel_same_participants_fail( web3: Web3, token_network: Contract, get_accounts: Callable, create_settled_channel: Callable, reveal_secrets: Callable, ) -> None: """ Try to confuse unlock() with two channels between the same participants """ (A, B) = get_accounts(2) settle_timeout = 8 # Mock pending transfers data pending_transfers_tree_1 = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) reveal_secrets(A, pending_transfers_tree_1.unlockable) channel_identifier = create_settled_channel( A, pending_transfers_tree_1.locked_amount, pending_transfers_tree_1.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) # The first channel is settled, so we create another one pending_transfers_tree_2 = get_pending_transfers_tree(web3, [3, 5], [2, 4, 3], settle_timeout) reveal_secrets(A, pending_transfers_tree_2.unlockable) channel_identifier2 = create_settled_channel( A, pending_transfers_tree_2.locked_amount, pending_transfers_tree_2.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_2.packed_transfers ).call({"from": A}) with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier2, B, A, pending_transfers_tree_1.packed_transfers ).call({"from": A}) token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_1.packed_transfers ).call_and_transact({"from": A}) token_network.functions.unlock( channel_identifier2, B, A, pending_transfers_tree_2.packed_transfers ).call_and_transact({"from": A})
def test_unlock_twice_fails( web3: Web3, token_network: Contract, get_accounts: Callable, create_settled_channel: Callable, reveal_secrets: Callable, ) -> None: """ The same unlock() call twice do not work """ (A, B) = get_accounts(2) settle_timeout = 8 # Mock pending transfers data pending_transfers_tree_1 = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) reveal_secrets(A, pending_transfers_tree_1.unlockable) # Settle the channel channel_identifier = create_settled_channel( A, pending_transfers_tree_1.locked_amount, pending_transfers_tree_1.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_1.packed_transfers ).call_and_transact({"from": A}) # Calling unlock twice does not work with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_1.packed_transfers ).call({"from": A})
def test_1_item_unlockable( web3: Web3, get_accounts: Callable, token_network_test_utils: Contract, secret_registry_contract: Contract, ) -> None: """ Test getHashRootAndUnlockedAmount() on a single item whose secret has been registered """ A = get_accounts(1)[0] pending_transfers_tree = get_pending_transfers_tree( web3=web3, unlockable_amounts=[6], expired_amounts=[] ) secret_registry_contract.functions.registerSecret( pending_transfers_tree.unlockable[0][TestLockIndex.SECRET] ).call_and_transact({"from": A}) assert ( secret_registry_contract.functions.getSecretRevealBlockHeight( pending_transfers_tree.unlockable[0][TestLockIndex.SECRETHASH] ).call() == web3.eth.blockNumber ) ( locksroot, unlocked_amount, ) = token_network_test_utils.functions.getHashAndUnlockedAmountPublic( pending_transfers_tree.packed_transfers ).call() total_hash = pending_transfers_tree.hash_of_packed_transfers assert locksroot == total_hash assert unlocked_amount == 6
def test_channel_unlock_no_locked_amount_fail( web3: Web3, token_network: Contract, create_settled_channel: Callable, get_accounts: Callable, reveal_secrets: Callable, ) -> None: """ After settleChannel() is called with zero locked amount, unlock() calls fail """ (A, B) = get_accounts(2) settle_timeout = 8 # Mock pending transfers data pending_transfers_tree_A = get_pending_transfers_tree(web3, [2, 5], [4], settle_timeout) reveal_secrets(A, pending_transfers_tree_A.unlockable) channel_identifier = create_settled_channel( A, 0, LOCKSROOT_OF_NO_LOCKS, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout ) with pytest.raises(TransactionFailed): token_network.functions.unlock(channel_identifier, B, A, b"").call() with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_A.packed_transfers ).call()
def test_channel_unlock_smaller_locked_amount( web3: Web3, token_network: Contract, custom_token: Contract, secret_registry_contract: Contract, create_settled_channel: Callable, get_accounts: Callable, reveal_secrets: Callable, ) -> None: """ Test an unlock() call that claims too many tokens When settleChannel() call computes a smaller amount of locked tokens than the following unlock() call, the settleChannel() computation is stronger and the participant receives less tokens. Stealing tokens from other channels is then prevented. """ (A, B) = get_accounts(2) settle_timeout = 8 # Mock pending transfers data pending_transfers_tree_A = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) reveal_secrets(A, pending_transfers_tree_A.unlockable) unlocked_amount = get_unlocked_amount( secret_registry_contract, pending_transfers_tree_A.packed_transfers ) # We settle the channel with a smaller locked amount than we will need for the # actual pending transfers channel_identifier = create_settled_channel( A, pending_transfers_tree_A.locked_amount - 1, pending_transfers_tree_A.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) pre_balance_A = custom_token.functions.balanceOf(A).call() pre_balance_B = custom_token.functions.balanceOf(B).call() balance_contract = custom_token.functions.balanceOf(token_network.address).call() assert balance_contract == pending_transfers_tree_A.locked_amount - 1 # This should pass, even though the locked amount in storage is smaller. # B will receive less tokens. token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_A.packed_transfers ).call_and_transact() balance_A = custom_token.functions.balanceOf(A).call() balance_B = custom_token.functions.balanceOf(B).call() balance_contract = custom_token.functions.balanceOf(token_network.address).call() assert balance_A == ( pre_balance_A + pending_transfers_tree_A.locked_amount - unlocked_amount - 1 ) assert balance_B == pre_balance_B + unlocked_amount assert balance_contract == 0
def test_channel_unlock_with_a_large_expiration( web3: Web3, custom_token: Contract, token_network: Contract, create_channel: Callable, channel_deposit: Callable, get_accounts: Callable, close_and_update_channel: Callable, reveal_secrets: Callable, ) -> None: """ unlock() should still work after a delayed settleChannel() call """ (A, B) = get_accounts(2) settle_timeout = 8 values_A = ChannelValues(deposit=20, transferred=5) values_B = ChannelValues(deposit=30, transferred=40) # Create channel and deposit channel_identifier = create_channel(A, B, settle_timeout)[0] channel_deposit(channel_identifier, A, values_A.deposit, B) channel_deposit(channel_identifier, B, values_B.deposit, A) # Mock pending transfers data with large expiration date pending_transfers_tree = get_pending_transfers_tree( web3, [1, 3, 5], [2, 4], settle_timeout + 100 ) values_B.locksroot = pending_transfers_tree.hash_of_packed_transfers values_B.locked_amounts = LockedAmounts( claimable_locked=get_locked_amount(pending_transfers_tree.transfers) ) # Reveal secrets before settlement window ends reveal_secrets(A, pending_transfers_tree.unlockable) close_and_update_channel(channel_identifier, A, values_A, B, values_B) # Settle channel after a "long" time web3.testing.mine(settle_timeout + 50) call_settle(token_network, channel_identifier, A, values_A, B, values_B) pre_balance_A = custom_token.functions.balanceOf(A).call() pre_balance_B = custom_token.functions.balanceOf(B).call() pre_balance_contract = custom_token.functions.balanceOf(token_network.address).call() # Unlock the tokens must still work token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree.packed_transfers ).call_and_transact() balance_A = custom_token.functions.balanceOf(A).call() balance_B = custom_token.functions.balanceOf(B).call() balance_contract = custom_token.functions.balanceOf(token_network.address).call() assert balance_A == pre_balance_A + 9 assert balance_B == pre_balance_B + 6 assert balance_contract == pre_balance_contract - values_B.locked_amounts.locked
def test_channel_unlock_bigger_locked_amount( web3: Web3, token_network: Contract, custom_token: Contract, secret_registry_contract: Contract, create_settled_channel: Callable, get_accounts: Callable, reveal_secrets: Callable, ) -> None: """ Test an unlock() call that claims too little tokens" When an unlock() call does not contain enough pending locks to claim the locked amount declared in the settleChannel() call, the difference goes to the other party. """ (A, B) = get_accounts(2) settle_timeout = 8 # Mock pending transfers data pending_transfers_tree_A = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) reveal_secrets(A, pending_transfers_tree_A.unlockable) unlocked_amount = get_unlocked_amount( secret_registry_contract, pending_transfers_tree_A.packed_transfers ) # We settle the channel with a bigger locked amount than we will need for the # actual pending transfers channel_identifier = create_settled_channel( A, pending_transfers_tree_A.locked_amount + 1, pending_transfers_tree_A.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) pre_balance_A = custom_token.functions.balanceOf(A).call() pre_balance_B = custom_token.functions.balanceOf(B).call() balance_contract = custom_token.functions.balanceOf(token_network.address).call() assert balance_contract == pending_transfers_tree_A.locked_amount + 1 # This should pass, even though the locked amount in storage is bigger. The rest of the # tokens is sent to A, as tokens corresponding to the locks that could not be unlocked. token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_A.packed_transfers ).call_and_transact() balance_A = custom_token.functions.balanceOf(A).call() balance_B = custom_token.functions.balanceOf(B).call() balance_contract = custom_token.functions.balanceOf(token_network.address).call() assert balance_A == ( pre_balance_A + pending_transfers_tree_A.locked_amount - unlocked_amount + 1 ) assert balance_B == pre_balance_B + unlocked_amount assert balance_contract == 0
def test_odd_even_components( web3: Web3, get_accounts: Callable, token_network_test_utils: Contract, reveal_secrets: Callable, ) -> None: """ Test getHashAndUnlockedAmount() on an odd/even number of locks """ A = get_accounts(1)[0] # Even number of locks pending_transfers_tree = get_pending_transfers_tree(web3, [1, 3, 5], [2, 8, 3]) reveal_secrets(A, pending_transfers_tree.unlockable) ( locksroot, unlocked_amount, ) = token_network_test_utils.functions.getHashAndUnlockedAmountPublic( pending_transfers_tree.packed_transfers ).call() total_hash = pending_transfers_tree.hash_of_packed_transfers assert locksroot == total_hash assert unlocked_amount == 9 # Odd number of locks pending_transfers_tree = get_pending_transfers_tree(web3, [1, 3, 5], [2, 8]) reveal_secrets(A, pending_transfers_tree.unlockable) ( locksroot, unlocked_amount, ) = token_network_test_utils.functions.getHashAndUnlockedAmountPublic( pending_transfers_tree.packed_transfers ).call() total_hash = pending_transfers_tree.hash_of_packed_transfers assert locksroot == total_hash assert unlocked_amount == 9
def test_channel_unlock_bigger_unlocked_amount( web3: Web3, token_network: Contract, custom_token: Contract, secret_registry_contract: Contract, create_settled_channel: Callable, get_accounts: Callable, reveal_secrets: Callable, ) -> None: """ unlock() transfers not more than the locked amount for more expensive unlock() demands """ (A, B) = get_accounts(2) settle_timeout = 8 # Mock pending transfers data pending_transfers_tree_A = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) reveal_secrets(A, pending_transfers_tree_A.unlockable) unlocked_amount = get_unlocked_amount( secret_registry_contract, pending_transfers_tree_A.packed_transfers ) assert unlocked_amount < pending_transfers_tree_A.locked_amount # We settle the channel with a smaller locked amount than the amount that can be unlocked channel_identifier = create_settled_channel( A, unlocked_amount - 1, pending_transfers_tree_A.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) pre_balance_A = custom_token.functions.balanceOf(A).call() pre_balance_B = custom_token.functions.balanceOf(B).call() balance_contract = custom_token.functions.balanceOf(token_network.address).call() assert balance_contract == unlocked_amount - 1 # This should pass, even though the locked amount in storage is smaller. # B will receive the entire locked amount, corresponding to the locks that have been unlocked # and A will receive nothing. token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_A.packed_transfers ).call_and_transact() balance_A = custom_token.functions.balanceOf(A).call() balance_B = custom_token.functions.balanceOf(B).call() balance_contract = custom_token.functions.balanceOf(token_network.address).call() assert balance_A == pre_balance_A assert balance_B == pre_balance_B + unlocked_amount - 1 assert balance_contract == 0
def test_channel_unlock_unregistered_locks( web3: Web3, token_network: Contract, get_accounts: Callable, create_channel_and_deposit: Callable, withdraw_channel: Callable, close_and_update_channel: Callable, custom_token: Contract, ) -> None: """ unlock() should refund tokens locked by secrets not registered before settlement """ (A, B) = get_accounts(2) settle_timeout = TEST_SETTLE_TIMEOUT_MIN pending_transfers_tree = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) locked_A = pending_transfers_tree.locked_amount (vals_A, vals_B) = ( ChannelValues( deposit=35, withdrawn=10, transferred=0, locked_amounts=LockedAmounts(claimable_locked=locked_A), ), ChannelValues(deposit=40, withdrawn=10, transferred=20), ) vals_A.locksroot = pending_transfers_tree.hash_of_packed_transfers vals_B.locksroot = fake_bytes(32, "03") channel_identifier = create_channel_and_deposit(A, B, vals_A.deposit, vals_B.deposit) withdraw_channel(channel_identifier, A, vals_A.withdrawn, B) withdraw_channel(channel_identifier, B, vals_B.withdrawn, A) close_and_update_channel(channel_identifier, A, vals_A, B, vals_B) # Secret hasn't been registered before settlement timeout web3.testing.mine(TEST_SETTLE_TIMEOUT_MIN + 1) call_settle(token_network, channel_identifier, A, vals_A, B, vals_B) # Someone unlocks A's pending transfers - all tokens should be refunded token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree.packed_transfers ).call_and_transact({"from": A}) # A gets back locked tokens assert ( custom_token.functions.balanceOf(A).call() == vals_A.deposit - vals_A.transferred + vals_B.transferred )
def test_unlock_tampered_proof_fails( web3: Web3, token_network: Contract, get_accounts: Callable, create_settled_channel: Callable, reveal_secrets: Callable, ) -> None: """ unlock() should fail when the submitted proofs are tampered """ (A, B) = get_accounts(2) settle_timeout = 8 # Mock pending transfers data pending_transfers_tree = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) reveal_secrets(A, pending_transfers_tree.unlockable) # Settle the channel channel_identifier = create_settled_channel( A, pending_transfers_tree.locked_amount, pending_transfers_tree.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) # Unlock with tampered locks does not work types = ["uint256", "uint256", "bytes32"] for index in range(len(pending_transfers_tree.transfers)): pending_transfers = list(pending_transfers_tree.transfers) pending_transfers[index][2:] = random_secret() packed_transfers_tampered = get_packed_transfers(tuple(pending_transfers), types) with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, B, A, packed_transfers_tampered ).call({"from": A}) # Unlock with correct pending locks does work token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree.packed_transfers ).call_and_transact({"from": A})
def test_get_hash_length_fail( web3: Web3, get_accounts: Callable, token_network_test_utils: Contract, secret_registry_contract: Contract, ) -> None: """ Test getHashAndUnlockedAmount() on inputs of irregular lengths """ network_utils = token_network_test_utils A = get_accounts(1)[0] pending_transfers_tree = get_pending_transfers_tree(web3, [2, 3, 6], [5]) secret_registry_contract.functions.registerSecret( pending_transfers_tree.unlockable[0][TestLockIndex.SECRET] ).call_and_transact({"from": A}) assert ( secret_registry_contract.functions.getSecretRevealBlockHeight( pending_transfers_tree.unlockable[0][TestLockIndex.SECRETHASH] ).call() == web3.eth.blockNumber ) packed = pending_transfers_tree.packed_transfers # packed length must be a multiple of 96 with pytest.raises(TransactionFailed): network_utils.functions.getHashAndUnlockedAmountPublic(packed[0:-1]).call() # last lock only contains expiration + locked_amount with pytest.raises(TransactionFailed): network_utils.functions.getHashAndUnlockedAmountPublic(packed[0:-32]).call() # last lock only contains expiration with pytest.raises(TransactionFailed): network_utils.functions.getHashAndUnlockedAmountPublic(packed[0:-64]).call() assert len(packed) % 96 == 0 network_utils.functions.getHashAndUnlockedAmountPublic(packed).call() network_utils.functions.getHashAndUnlockedAmountPublic(packed[0:-96]).call()
def test_reverse_participants_unlock( web3: Web3, token_network: Contract, get_accounts: Callable, create_settled_channel: Callable, reveal_secrets: Callable, ) -> None: """ unlock() with wrong argument orders """ (A, B, C) = get_accounts(3) settle_timeout = 12 # Mock pending transfers data pending_transfers_tree_A = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) pending_transfers_tree_B = get_pending_transfers_tree(web3, [3, 4, 6], [4], settle_timeout) assert ( pending_transfers_tree_A.hash_of_packed_transfers != pending_transfers_tree_B.hash_of_packed_transfers ) # Reveal secrets reveal_secrets(A, pending_transfers_tree_A.unlockable) reveal_secrets(B, pending_transfers_tree_B.unlockable) # Settle the channel channel_identifier = create_settled_channel( A, pending_transfers_tree_A.locked_amount, pending_transfers_tree_A.hash_of_packed_transfers, B, pending_transfers_tree_B.locked_amount, pending_transfers_tree_B.hash_of_packed_transfers, settle_timeout, ) # A trying to unlock its own locksroot & locked amount MUST fail with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree_A.packed_transfers ).call({"from": A}) # Delegate trying to unlock A's own locksroot & locked amount on behalf of A MUST fail with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree_A.packed_transfers ).call({"from": C}) # B trying to unlock its own locksroot & locked amount MUST fail with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_B.packed_transfers ).call({"from": B}) # Delegate trying to unlock B's own locksroot & locked amount on behalf of B MUST fail with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_B.packed_transfers ).call({"from": B}) with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, A, A, pending_transfers_tree_A.packed_transfers ).call({"from": A}) with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, B, B, pending_transfers_tree_B.packed_transfers ).call({"from": B}) # Someone trying to unlock B's locksroot & locked amount on behalf of A MUST succeed token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree_B.packed_transfers ).call_and_transact({"from": C}) # Someone trying to unlock A's locksroot & locked amount on behalf of B MUST succeed token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_A.packed_transfers ).call_and_transact({"from": C})
def test_channel_unlock_before_settlement_fails( web3: Web3, custom_token: Contract, token_network: Contract, create_channel: Callable, channel_deposit: Callable, get_accounts: Callable, close_and_update_channel: Callable, reveal_secrets: Callable, ) -> None: """ unlock() should not work before settlement """ (A, B) = get_accounts(2) settle_timeout = 8 values_A = ChannelValues(deposit=20, transferred=5) values_B = ChannelValues(deposit=30, transferred=40) # Create channel and deposit channel_identifier = create_channel(A, B, settle_timeout)[0] # Mock pending transfers data pending_transfers_tree = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) values_B.locksroot = pending_transfers_tree.hash_of_packed_transfers values_B.locked_amounts = LockedAmounts( claimable_locked=get_locked_amount(pending_transfers_tree.transfers) ) # Reveal secrets before settlement window ends reveal_secrets(A, pending_transfers_tree.unlockable) # Unlock fails before channel is not settled with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree.packed_transfers ).call() channel_deposit(channel_identifier, A, values_A.deposit, B) channel_deposit(channel_identifier, B, values_B.deposit, A) # Unlock fails before channel is not settled with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree.packed_transfers ).call() close_and_update_channel(channel_identifier, A, values_A, B, values_B) # Unlock fails before settlement window is over and channel is not settled with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree.packed_transfers ).call() # Settlement window must be over before settling the channel web3.testing.mine(settle_timeout) # Unlock fails before settle is called with pytest.raises(TransactionFailed): token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree.packed_transfers ).call() # settle channel call_settle(token_network, channel_identifier, A, values_A, B, values_B) pre_balance_A = custom_token.functions.balanceOf(A).call() pre_balance_B = custom_token.functions.balanceOf(B).call() pre_balance_contract = custom_token.functions.balanceOf(token_network.address).call() # Unlock works after channel is settled token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree.packed_transfers ).call_and_transact() balance_A = custom_token.functions.balanceOf(A).call() balance_B = custom_token.functions.balanceOf(B).call() balance_contract = custom_token.functions.balanceOf(token_network.address).call() assert balance_A == pre_balance_A + 9 assert balance_B == pre_balance_B + 6 assert balance_contract == pre_balance_contract - values_B.locked_amounts.locked
def test_locks_order( web3: Web3, get_accounts: Callable, token_network_test_utils: Contract, reveal_secrets: Callable, token_network: Contract, create_settled_channel: Callable, ) -> None: """ Shuffling the leaves usually changes the root, but sometimes not """ network_utils = token_network_test_utils (A, B) = get_accounts(2) types = ["uint256", "uint256", "bytes32"] pending_transfers_tree = get_pending_transfers_tree(web3, [1, 3, 5], [2, 8, 3]) reveal_secrets(A, pending_transfers_tree.unlockable) channel_identifier = create_settled_channel( A, pending_transfers_tree.locked_amount, pending_transfers_tree.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, ) # Pending locks are in the insertion order. # If we change the order, we change the computed hash. wrong_order = pending_transfers_tree.transfers wrong_order[1], wrong_order[0] = wrong_order[0], wrong_order[1] wrong_order_packed = get_packed_transfers(wrong_order, types) (locksroot, unlocked_amount) = network_utils.functions.getHashAndUnlockedAmountPublic( wrong_order_packed ).call() # If we change the order, we change the computed hash. assert locksroot != pending_transfers_tree.hash_of_packed_transfers assert unlocked_amount == 9 with pytest.raises(TransactionFailed): token_network.functions.unlock(channel_identifier, B, A, wrong_order_packed).call() wrong_order = pending_transfers_tree.transfers wrong_order[2], wrong_order[0] = wrong_order[0], wrong_order[2] wrong_order_packed = get_packed_transfers(wrong_order, types) (locksroot, unlocked_amount) = network_utils.functions.getHashAndUnlockedAmountPublic( wrong_order_packed ).call() assert locksroot != pending_transfers_tree.hash_of_packed_transfers assert unlocked_amount == 9 with pytest.raises(TransactionFailed): token_network.functions.unlock(channel_identifier, B, A, wrong_order_packed).call() wrong_order = pending_transfers_tree.transfers wrong_order[0], wrong_order[-1] = wrong_order[-1], wrong_order[0] wrong_order_packed = get_packed_transfers(wrong_order, types) (locksroot, unlocked_amount) = network_utils.functions.getHashAndUnlockedAmountPublic( wrong_order_packed ).call() assert locksroot != pending_transfers_tree.hash_of_packed_transfers assert unlocked_amount == 9 with pytest.raises(TransactionFailed): token_network.functions.unlock(channel_identifier, B, A, wrong_order_packed).call() (locksroot, unlocked_amount) = network_utils.functions.getHashAndUnlockedAmountPublic( pending_transfers_tree.packed_transfers ).call() assert locksroot == pending_transfers_tree.hash_of_packed_transfers assert unlocked_amount == 9 token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree.packed_transfers ).call_and_transact()
def test_channel_settle_and_unlock( web3: Web3, token_network: Contract, get_accounts: Callable, create_settled_channel: Callable, reveal_secrets: Callable, ) -> None: """ Regular channel life-cycle: open -> settle -> unlock -> open -> settle -> unlock """ (A, B) = get_accounts(2) settle_timeout = 8 # Mock pending transfers data pending_transfers_tree_1 = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) reveal_secrets(A, pending_transfers_tree_1.unlockable) # Settle the channel channel_identifier1 = create_settled_channel( A, pending_transfers_tree_1.locked_amount, pending_transfers_tree_1.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) token_network.functions.unlock( channel_identifier1, B, A, pending_transfers_tree_1.packed_transfers ).call_and_transact({"from": A}) # Mock pending transfers data for a reopened channel pending_transfers_tree_2 = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) reveal_secrets(A, pending_transfers_tree_2.unlockable) # Settle the channel again channel_identifier2 = create_settled_channel( A, pending_transfers_tree_2.locked_amount, pending_transfers_tree_2.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) # 2nd unlocks should go through token_network.functions.unlock( channel_identifier2, B, A, pending_transfers_tree_2.packed_transfers ).call_and_transact({"from": A}) # Edge channel life-cycle: open -> settle -> open -> settle -> unlock1 -> unlock2 # Mock pending transfers data pending_transfers_tree_1 = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) reveal_secrets(A, pending_transfers_tree_1.unlockable) # Settle the channel channel_identifier3 = create_settled_channel( A, pending_transfers_tree_1.locked_amount, pending_transfers_tree_1.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) # Mock pending transfers data for a reopened channel pending_transfers_tree_2 = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) reveal_secrets(A, pending_transfers_tree_2.unlockable) # Settle the channel again channel_identifier4 = create_settled_channel( A, pending_transfers_tree_2.locked_amount, pending_transfers_tree_2.hash_of_packed_transfers, B, 0, LOCKSROOT_OF_NO_LOCKS, settle_timeout, ) # Both old and new unlocks should go through token_network.functions.unlock( channel_identifier4, B, A, pending_transfers_tree_2.packed_transfers ).call_and_transact({"from": A}) token_network.functions.unlock( channel_identifier3, B, A, pending_transfers_tree_1.packed_transfers ).call_and_transact({"from": A})
def test_channel_unlock_registered_expired_lock_refunds( web3: Web3, custom_token: Contract, token_network: Contract, secret_registry_contract: Contract, create_channel: Callable, channel_deposit: Callable, get_accounts: Callable, close_and_update_channel: Callable, ) -> None: """ unlock() should refund tokens locked with secrets revealed after the expiration """ (A, B) = get_accounts(2) max_lock_expiration = 3 settle_timeout = 8 values_A = ChannelValues(deposit=20, transferred=5) values_B = ChannelValues(deposit=30, transferred=40) # Create channel and deposit channel_identifier = create_channel(A, B, settle_timeout)[0] channel_deposit(channel_identifier, A, values_A.deposit, B) channel_deposit(channel_identifier, B, values_B.deposit, A) # Mock pending transfers data pending_transfers_tree = get_pending_transfers_tree( web3, [1, 3, 5], [2, 4], min_expiration_delta=max_lock_expiration - 2, max_expiration_delta=max_lock_expiration, ) values_B.locksroot = pending_transfers_tree.hash_of_packed_transfers values_B.locked_amounts = LockedAmounts( claimable_locked=get_locked_amount(pending_transfers_tree.transfers) ) # Locks expire web3.testing.mine(max_lock_expiration) # Secrets are revealed before settlement window, but after expiration for (_, _, secrethash, secret) in pending_transfers_tree.unlockable: secret_registry_contract.functions.registerSecret(secret).call_and_transact({"from": A}) assert ( secret_registry_contract.functions.getSecretRevealBlockHeight(secrethash).call() == web3.eth.blockNumber ) close_and_update_channel(channel_identifier, A, values_A, B, values_B) web3.testing.mine(settle_timeout) # settle channel call_settle(token_network, channel_identifier, A, values_A, B, values_B) pre_balance_A = custom_token.functions.balanceOf(A).call() pre_balance_B = custom_token.functions.balanceOf(B).call() pre_balance_contract = custom_token.functions.balanceOf(token_network.address).call() # Unlock works after channel is settled token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree.packed_transfers ).call_and_transact() balance_A = custom_token.functions.balanceOf(A).call() balance_B = custom_token.functions.balanceOf(B).call() balance_contract = custom_token.functions.balanceOf(token_network.address).call() # check that all tokens have been refunded, as locks have expired already assert balance_A == pre_balance_A assert balance_B == pre_balance_B + values_B.locked_amounts.locked assert balance_contract == pre_balance_contract - values_B.locked_amounts.locked
def find_max_pending_transfers(gas_limit) -> None: """Measure gas consumption of TokenNetwork.unlock() depending on number of pending transfers and find the maximum number of pending transfers so gas_limit is not exceeded.""" tester = ContractTester(generate_keys=2) tester.deploy_contract("SecretRegistry") tester.deploy_contract( "HumanStandardToken", _initialAmount=100_000, _decimalUnits=3, _tokenName="SomeToken", _tokenSymbol="SMT", ) tester.deploy_contract( "TokenNetwork", _token_address=tester.contract_address("HumanStandardToken"), _secret_registry=tester.contract_address("SecretRegistry"), _chain_id=CHAIN_ID, _settlement_timeout_min=100, _settlement_timeout_max=200, _deprecation_executor=tester.accounts[0], _channel_participant_deposit_limit=10000, _token_network_deposit_limit=10000, ) tester.call_transaction("HumanStandardToken", "transfer", _to=tester.accounts[1], _value=10000) receipt = tester.call_transaction( "TokenNetwork", "openChannel", participant1=tester.accounts[0], participant2=tester.accounts[1], settle_timeout=150, ) channel_identifier = ChannelID( int(encode_hex(receipt["logs"][0]["topics"][1]), 16)) tester.call_transaction( "HumanStandardToken", "approve", sender=tester.accounts[0], _spender=tester.contract_address("TokenNetwork"), _value=10000, ) tester.call_transaction( "HumanStandardToken", "approve", sender=tester.accounts[1], _spender=tester.contract_address("TokenNetwork"), _value=5000, ) tester.call_transaction( "TokenNetwork", "setTotalDeposit", channel_identifier=channel_identifier, participant=tester.accounts[0], total_deposit=5000, partner=tester.accounts[1], ) tester.call_transaction( "TokenNetwork", "setTotalDeposit", channel_identifier=channel_identifier, participant=tester.accounts[1], total_deposit=2000, partner=tester.accounts[0], ) print( "Measuring unlock()'s gas cost for different Merkle tree widths, can take a while..." ) before_closing = tester.tester.take_snapshot() enough = 0 too_much = 1024 nonce = Nonce(10) additional_hash = AdditionalHash(urandom(32)) token_network_address = tester.contract_address("TokenNetwork") while enough + 1 < too_much: tree_size = (enough + too_much) // 2 tester.tester.revert_to_snapshot(before_closing) pending_transfers_tree = get_pending_transfers_tree( tester.web3, unlockable_amounts=[1] * tree_size, expired_amounts=[]) balance_hash = hash_balance_data( transferred_amount=TokenAmount(3000), locked_amount=TokenAmount(2000), locksroot=Locksroot( pending_transfers_tree.hash_of_packed_transfers), ) canonical_identifier = CanonicalIdentifier( chain_identifier=CHAIN_ID, token_network_address=token_network_address, channel_identifier=ChannelID(channel_identifier), ) data_to_sign = pack_balance_proof( nonce=Nonce(nonce), balance_hash=balance_hash, additional_hash=additional_hash, canonical_identifier=canonical_identifier, ) signature = LocalSigner(tester.private_keys[1]).sign(data=data_to_sign) tester.call_transaction( "TokenNetwork", "closeChannel", channel_identifier=channel_identifier, partner=tester.accounts[1], balance_hash=balance_hash, nonce=nonce, additional_hash=additional_hash, signature=signature, ) tester.tester.mine_blocks(160) # close settlement window tester.call_transaction( "TokenNetwork", "settleChannel", channel_identifier=channel_identifier, participant1=tester.accounts[0], participant1_transferred_amount=0, participant1_locked_amount=0, participant1_locksroot=b"\x00" * 32, participant2=tester.accounts[1], participant2_transferred_amount=3000, participant2_locked_amount=2000, participant2_locksroot=pending_transfers_tree. hash_of_packed_transfers, ) receipt = tester.call_transaction( "TokenNetwork", "unlock", channel_identifier=channel_identifier, participant=tester.accounts[0], partner=tester.accounts[1], merkle_tree_leaves=pending_transfers_tree.packed_transfers, ) gas_used = receipt["gasUsed"] if gas_used <= gas_limit: enough = tree_size print( f"{tree_size} pending transfers work ({gas_used} gas needed to unlock)" ) else: too_much = tree_size print( f"{tree_size} pending transfers are too much ({gas_used} gas needed to unlock)" )
def test_channel_unlock( web3: Web3, custom_token: Contract, token_network: Contract, create_channel: Callable, channel_deposit: Callable, get_accounts: Callable, close_and_update_channel: Callable, reveal_secrets: Callable, ) -> None: """ unlock() on pending transfers with unlockable and expired locks should split the locked amount accordingly, to both parties """ (A, B) = get_accounts(2) settle_timeout = 8 values_A = ChannelValues(deposit=20, transferred=5) values_B = ChannelValues(deposit=30, transferred=40) # Create channel and deposit channel_identifier = create_channel(A, B, settle_timeout)[0] channel_deposit(channel_identifier, A, values_A.deposit, B) channel_deposit(channel_identifier, B, values_B.deposit, A) # Mock pending transfers data pending_transfers_tree = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) values_B.locksroot = pending_transfers_tree.hash_of_packed_transfers values_B.locked_amounts = LockedAmounts( claimable_locked=get_locked_amount(pending_transfers_tree.transfers) ) # Reveal secrets before settlement window ends reveal_secrets(A, pending_transfers_tree.unlockable) close_and_update_channel(channel_identifier, A, values_A, B, values_B) # Settlement window must be over before settling the channel web3.testing.mine(settle_timeout) call_settle(token_network, channel_identifier, A, values_A, B, values_B) pre_balance_A = custom_token.functions.balanceOf(A).call() pre_balance_B = custom_token.functions.balanceOf(B).call() pre_balance_contract = custom_token.functions.balanceOf(token_network.address).call() info_B = token_network.functions.getChannelParticipantInfo(channel_identifier, B, A).call() assert info_B[ParticipantInfoIndex.LOCKSROOT] == values_B.locksroot assert info_B[ParticipantInfoIndex.LOCKED_AMOUNT] == values_B.locked_amounts.locked # Unlock the tokens token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree.packed_transfers ).call_and_transact() info_B = token_network.functions.getChannelParticipantInfo(channel_identifier, B, A).call() assert info_B[ParticipantInfoIndex.LOCKSROOT] == NONEXISTENT_LOCKSROOT assert info_B[ParticipantInfoIndex.LOCKED_AMOUNT] == 0 balance_A = custom_token.functions.balanceOf(A).call() balance_B = custom_token.functions.balanceOf(B).call() balance_contract = custom_token.functions.balanceOf(token_network.address).call() assert balance_A == pre_balance_A + 9 assert balance_B == pre_balance_B + 6 assert balance_contract == pre_balance_contract - values_B.locked_amounts.locked
def test_unlock_channel_event( web3: Web3, token_network: Contract, secret_registry_contract: Contract, create_channel: Callable, channel_deposit: Callable, get_accounts: Callable, close_and_update_channel: Callable, reveal_secrets: Callable, event_handler: Callable, ) -> None: """ Successful unlock() should cause an UNLOCKED event """ (A, B) = get_accounts(2) settle_timeout = 8 values_A = ChannelValues(deposit=20, transferred=5) values_B = ChannelValues(deposit=30, transferred=40) # Create channel and deposit channel_identifier = create_channel(A, B, settle_timeout)[0] channel_deposit(channel_identifier, A, values_A.deposit, B) channel_deposit(channel_identifier, B, values_B.deposit, A) # Mock pending transfers data pending_transfers_tree = get_pending_transfers_tree( web3, [1, 3, 5], [2, 4], settle_timeout + 100 ) values_B.locksroot = pending_transfers_tree.hash_of_packed_transfers values_B.locked_amounts = LockedAmounts( claimable_locked=get_locked_amount(pending_transfers_tree.transfers) ) # Reveal secrets before settlement window ends reveal_secrets(A, pending_transfers_tree.unlockable) close_and_update_channel(channel_identifier, A, values_A, B, values_B) # Settlement window must be over before settling the channel web3.testing.mine(settle_timeout) call_settle(token_network, channel_identifier, A, values_A, B, values_B) ev_handler = event_handler(token_network) # Unlock the tokens txn_hash = token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree.packed_transfers ).call_and_transact() unlocked_amount = get_unlocked_amount( secret_registry_contract, pending_transfers_tree.packed_transfers ) # Add event ev_handler.add( txn_hash, ChannelEvent.UNLOCKED, check_channel_unlocked( channel_identifier, A, B, values_B.locksroot, unlocked_amount, values_B.locked_amounts.locked - unlocked_amount, ), ) # Check that event was properly emitted ev_handler.check()
def print_gas_channel_cycle( web3: Web3, token_network: Contract, create_channel: Callable, channel_deposit: Callable, withdraw_channel: Callable, secret_registry_contract: Contract, get_accounts: Callable, print_gas: Callable, create_balance_proof: Callable, create_balance_proof_update_signature: Callable, ) -> None: """ Abusing pytest to print gas costs of TokenNetwork's operations """ (A, B, C, D) = get_accounts(4) settle_timeout = 11 (channel_identifier, txn_hash) = create_channel(A, B, settle_timeout) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".openChannel") (_, txn_hash) = create_channel(C, D, settle_timeout) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".openChannel") txn_hash = channel_deposit(channel_identifier, A, 20, B) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".setTotalDeposit") txn_hash = channel_deposit(channel_identifier, B, 10, A) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".setTotalDeposit") txn_hash = withdraw_channel(channel_identifier, A, 5, B) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".setTotalWithdraw") pending_transfers_tree1 = get_pending_transfers_tree(web3, [1, 1, 2, 3], [2, 1]) locksroot1 = pending_transfers_tree1.hash_of_packed_transfers locked_amount1 = get_locked_amount(pending_transfers_tree1.transfers) pending_transfers_tree2 = get_pending_transfers_tree(web3, [3], [], 7) locksroot2 = pending_transfers_tree2.hash_of_packed_transfers locked_amount2 = get_locked_amount(pending_transfers_tree2.transfers) balance_proof_A = create_balance_proof( channel_identifier, A, 10, locked_amount1, 5, locksroot1 ) balance_proof_B = create_balance_proof(channel_identifier, B, 5, locked_amount2, 3, locksroot2) balance_proof_update_signature_B = create_balance_proof_update_signature( B, channel_identifier, *balance_proof_A ) for lock in pending_transfers_tree1.unlockable: txn_hash = secret_registry_contract.functions.registerSecret(lock[3]).call_and_transact( {"from": A} ) print_gas(txn_hash, CONTRACT_SECRET_REGISTRY + ".registerSecret") for lock in pending_transfers_tree2.unlockable: txn_hash = secret_registry_contract.functions.registerSecret(lock[3]).call_and_transact( {"from": A} ) print_gas(txn_hash, CONTRACT_SECRET_REGISTRY + ".registerSecret") txn_hash = token_network.functions.closeChannel( channel_identifier, B, *balance_proof_B ).call_and_transact({"from": A}) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".closeChannel") txn_hash = token_network.functions.updateNonClosingBalanceProof( channel_identifier, A, B, *balance_proof_A, balance_proof_update_signature_B ).call_and_transact({"from": B}) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".updateNonClosingBalanceProof") web3.testing.mine(settle_timeout) txn_hash = token_network.functions.settleChannel( channel_identifier, B, 5, locked_amount2, locksroot2, A, 10, locked_amount1, locksroot1 ).call_and_transact() print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".settleChannel") txn_hash = token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree2.packed_transfers ).call_and_transact() print_gas( txn_hash, "{0}.unlock {1} locks".format( CONTRACT_TOKEN_NETWORK, len(pending_transfers_tree2.transfers) ), ) txn_hash = token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree1.packed_transfers ).call_and_transact() print_gas( txn_hash, "{0}.unlock {1} locks".format( CONTRACT_TOKEN_NETWORK, len(pending_transfers_tree1.transfers) ), )
def test_lock_data_from_packed_locks( web3: Web3, get_accounts: Callable, token_network_test_utils: Contract, secret_registry_contract: Contract, reveal_secrets: Callable, ) -> None: """ Test getLockDataFromLockPublic() on various offsets """ network_utils = token_network_test_utils A = get_accounts(1)[0] unlockable_amounts = [3, 5] expired_amounts = [2, 8, 7] pending_transfers_tree = get_pending_transfers_tree( web3, unlockable_amounts, expired_amounts, max_expiration_delta=5 ) reveal_secrets(A, pending_transfers_tree.unlockable) def claimable(index: int) -> int: amount = pending_transfers_tree.transfers[index][1] return amount if amount in unlockable_amounts else 0 # Lock data is ordered lexicographically, regardless of expiration status claimable_amount = network_utils.functions.getLockedAmountFromLockPublic( pending_transfers_tree.packed_transfers, 32 ).call() assert claimable_amount == claimable(0) claimable_amount = network_utils.functions.getLockedAmountFromLockPublic( pending_transfers_tree.packed_transfers, 32 + 96 ).call() assert claimable_amount == claimable(1) claimable_amount = network_utils.functions.getLockedAmountFromLockPublic( pending_transfers_tree.packed_transfers, 32 + 2 * 96 ).call() assert claimable_amount == claimable(2) claimable_amount = network_utils.functions.getLockedAmountFromLockPublic( pending_transfers_tree.packed_transfers, 32 + 3 * 96 ).call() assert claimable_amount == claimable(3) claimable_amount = network_utils.functions.getLockedAmountFromLockPublic( pending_transfers_tree.packed_transfers, 32 + 4 * 96 ).call() assert claimable_amount == claimable(4) # Register last secret after expiration web3.testing.mine(5) last_lock = pending_transfers_tree.expired[-1] # expiration assert web3.eth.blockNumber > last_lock[0] # register secret secret_registry_contract.functions.registerSecret(last_lock[3]).call_and_transact() # ensure registration was done assert ( secret_registry_contract.functions.getSecretRevealBlockHeight(last_lock[2]).call() == web3.eth.blockNumber ) # Check that last secret is still regarded as expired claimable_amount = network_utils.functions.getLockedAmountFromLockPublic( pending_transfers_tree.packed_transfers, 32 + 4 * 96 ).call() assert claimable_amount == claimable(4) # If the offset is bigger than the length of the packed locks, return (0, 0) claimable_amount = network_utils.functions.getLockedAmountFromLockPublic( pending_transfers_tree.packed_transfers, 32 + 5 * 96 ).call() assert claimable_amount == 0
def test_deprecation_switch_settle( web3, get_accounts, token_network, custom_token, reveal_secrets, create_channel, channel_deposit, close_and_update_channel, ): """ Channel close and settlement still work after the depracation switch is turned on """ deprecation_executor = token_network.functions.deprecation_executor().call() (A, B) = get_accounts(2) deposit = 100 (vals_A, vals_B) = ( ChannelValues( deposit=deposit, withdrawn=0, transferred=5, claimable_locked=2, unclaimable_locked=4, ), ChannelValues( deposit=deposit, withdrawn=0, transferred=10, claimable_locked=4, unclaimable_locked=6, ), ) pre_balance_A = custom_token.functions.balanceOf(A).call() pre_balance_B = custom_token.functions.balanceOf(B).call() pre_balance_contract = custom_token.functions.balanceOf(token_network.address).call() channel_identifier = create_channel(A, B)[0] channel_deposit(channel_identifier, A, vals_A.deposit, B) channel_deposit(channel_identifier, B, vals_B.deposit, A) # Mock pending transfers data for A -> B pending_transfers_tree_A = get_pending_transfers_tree( web3, unlockable_amount=vals_A.claimable_locked, expired_amount=vals_A.unclaimable_locked, ) vals_A.locksroot = pending_transfers_tree_A.merkle_root # Reveal A's secrets. reveal_secrets(A, pending_transfers_tree_A.unlockable) # Mock pending transfers data for B -> A pending_transfers_tree_B = get_pending_transfers_tree( web3, unlockable_amount=vals_B.claimable_locked, expired_amount=vals_B.unclaimable_locked, ) vals_B.locksroot = pending_transfers_tree_B.merkle_root # Reveal B's secrets reveal_secrets(B, pending_transfers_tree_B.unlockable) # Set the deprecation switch to true token_network.functions.deprecate().transact({ 'from': deprecation_executor, }) assert token_network.functions.safety_deprecation_switch().call() is True # We need to make sure we can still close, settle & unlock the channels close_and_update_channel( channel_identifier, A, vals_A, B, vals_B, ) web3.testing.mine(TEST_SETTLE_TIMEOUT_MIN) call_settle(token_network, channel_identifier, A, vals_A, B, vals_B) # Unlock B's pending transfers that were sent to A token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree_B.packed_transfers, ).transact() # Unlock A's pending transfers that were sent to B token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_A.packed_transfers, ).transact() assert custom_token.functions.balanceOf(A).call() == pre_balance_A + 107 assert custom_token.functions.balanceOf(B).call() == pre_balance_B + 93 assert custom_token.functions.balanceOf( token_network.address, ).call() == pre_balance_contract
def test_channel_unlock_both_participants( web3: Web3, custom_token: Contract, token_network: Contract, secret_registry_contract: Contract, create_channel: Callable, channel_deposit: Callable, get_accounts: Callable, close_and_update_channel: Callable, reveal_secrets: Callable, ) -> None: """ A scenario where both parties get some of the pending transfers """ (A, B) = get_accounts(2) settle_timeout = 8 values_A = ChannelValues(deposit=100, transferred=5) values_B = ChannelValues(deposit=100, transferred=40) # Create channel and deposit channel_identifier = create_channel(A, B, settle_timeout)[0] channel_deposit(channel_identifier, A, values_A.deposit, B) channel_deposit(channel_identifier, B, values_B.deposit, A) # Mock pending transfers data for A pending_transfers_tree_A = get_pending_transfers_tree(web3, [1, 3, 5], [2, 4], settle_timeout) values_A.locksroot = pending_transfers_tree_A.hash_of_packed_transfers values_A.locked_amounts = LockedAmounts( claimable_locked=get_locked_amount(pending_transfers_tree_A.transfers) ) # Reveal A's secrets before settlement window ends reveal_secrets(A, pending_transfers_tree_A.unlockable) # Mock pending transfers data for B pending_transfers_tree_B = get_pending_transfers_tree(web3, [2, 4, 6], [5, 10], settle_timeout) values_B.locksroot = pending_transfers_tree_B.hash_of_packed_transfers values_B.locked_amounts = LockedAmounts( claimable_locked=get_locked_amount(pending_transfers_tree_B.transfers) ) # Reveal B's secrets before settlement window ends reveal_secrets(B, pending_transfers_tree_B.unlockable) close_and_update_channel(channel_identifier, A, values_A, B, values_B) # Settle channel web3.testing.mine(settle_timeout) call_settle(token_network, channel_identifier, A, values_A, B, values_B) pre_balance_A = custom_token.functions.balanceOf(A).call() pre_balance_B = custom_token.functions.balanceOf(B).call() pre_balance_contract = custom_token.functions.balanceOf(token_network.address).call() # A unlock's token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree_B.packed_transfers ).call_and_transact() # B unlock's token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree_A.packed_transfers ).call_and_transact() balance_A = custom_token.functions.balanceOf(A).call() balance_B = custom_token.functions.balanceOf(B).call() balance_contract = custom_token.functions.balanceOf(token_network.address).call() # Unlocked pending transfers A -> B, that belong to B unlockable_A = get_unlocked_amount( secret_registry_contract, pending_transfers_tree_A.packed_transfers ) # Expired pending transfers A -> B, that belong to A expired_A = get_locked_amount(pending_transfers_tree_A.expired) # Unlocked pending transfers B -> A, that belong to A unlockable_B = get_unlocked_amount( secret_registry_contract, pending_transfers_tree_B.packed_transfers ) # Expired pending transfers B -> A, that belong to B expired_B = get_locked_amount(pending_transfers_tree_B.expired) # check that A and B both received the expected amounts assert balance_contract == ( pre_balance_contract - values_B.locked_amounts.locked - values_A.locked_amounts.locked ) assert balance_A == pre_balance_A + unlockable_B + expired_A assert balance_B == pre_balance_B + unlockable_A + expired_B
def print_gas_channel_cycle( web3: Web3, token_network: Contract, create_channel: Callable, channel_deposit: Callable, withdraw_channel: Callable, secret_registry_contract: Contract, get_accounts: Callable, print_gas: Callable, create_balance_proof: Callable, create_balance_proof_countersignature: Callable, ) -> None: """Abusing pytest to print gas costs of TokenNetwork's operations""" (A, B, C, D) = get_accounts(4) settle_timeout = 11 (channel_identifier, txn_hash) = create_channel(A, B, settle_timeout) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".openChannel") (_, txn_hash) = create_channel(C, D, settle_timeout) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".openChannel") txn_hash = channel_deposit(channel_identifier, A, 20, B) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".setTotalDeposit") txn_hash = channel_deposit(channel_identifier, B, 10, A) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".setTotalDeposit") txn_hash = withdraw_channel(channel_identifier, A, 5, UINT256_MAX, B) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".setTotalWithdraw") pending_transfers_tree1 = get_pending_transfers_tree( web3, [1, 1, 2, 3], [2, 1]) locksroot1 = pending_transfers_tree1.hash_of_packed_transfers locked_amount1 = get_locked_amount(pending_transfers_tree1.transfers) pending_transfers_tree2 = get_pending_transfers_tree(web3, [3], [], 7) locksroot2 = pending_transfers_tree2.hash_of_packed_transfers locked_amount2 = get_locked_amount(pending_transfers_tree2.transfers) balance_proof_A = create_balance_proof(channel_identifier, A, 10, locked_amount1, 5, locksroot1) balance_proof_update_signature_B = create_balance_proof_countersignature( participant=B, channel_identifier=channel_identifier, msg_type=MessageTypeId.BALANCE_PROOF_UPDATE, **balance_proof_A._asdict(), ) balance_proof_B = create_balance_proof(channel_identifier, B, 5, locked_amount2, 3, locksroot2) closing_sig_A = create_balance_proof_countersignature( participant=A, channel_identifier=channel_identifier, msg_type=MessageTypeId.BALANCE_PROOF, **balance_proof_B._asdict(), ) for lock in pending_transfers_tree1.unlockable: txn_hash = call_and_transact( secret_registry_contract.functions.registerSecret(lock[3]), {"from": A}) print_gas(txn_hash, CONTRACT_SECRET_REGISTRY + ".registerSecret") for lock in pending_transfers_tree2.unlockable: txn_hash = call_and_transact( secret_registry_contract.functions.registerSecret(lock[3]), {"from": A}) print_gas(txn_hash, CONTRACT_SECRET_REGISTRY + ".registerSecret") txn_hash = call_and_transact( token_network.functions.closeChannel( channel_identifier, B, A, *balance_proof_B._asdict().values(), closing_sig_A), {"from": A}, ) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".closeChannel") txn_hash = call_and_transact( token_network.functions.updateNonClosingBalanceProof( channel_identifier, A, B, *balance_proof_A._asdict().values(), balance_proof_update_signature_B, ), {"from": B}, ) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".updateNonClosingBalanceProof") mine_blocks(web3, settle_timeout) txn_hash = call_and_transact( token_network.functions.settleChannel( channel_identifier, B, 5, locked_amount2, locksroot2, A, 10, locked_amount1, locksroot1, )) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + ".settleChannel") txn_hash = call_and_transact( token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree2.packed_transfers)) print_gas( txn_hash, "{0}.unlock {1} locks".format(CONTRACT_TOKEN_NETWORK, len(pending_transfers_tree2.transfers)), ) txn_hash = call_and_transact( token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree1.packed_transfers)) print_gas( txn_hash, "{0}.unlock {1} locks".format(CONTRACT_TOKEN_NETWORK, len(pending_transfers_tree1.transfers)), )
def print_gas_channel_cycle( web3, token_network, create_channel, channel_deposit, secret_registry_contract, get_accounts, print_gas, create_balance_proof, create_balance_proof_update_signature, ): """ Abusing pytest to print gas costs of TokenNetwork's operations """ (A, B, C, D) = get_accounts(4) settle_timeout = 11 (channel_identifier, txn_hash) = create_channel(A, B, settle_timeout) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + '.openChannel') (_, txn_hash) = create_channel(C, D, settle_timeout) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + '.openChannel') txn_hash = channel_deposit(channel_identifier, A, 20, B) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + '.setTotalDeposit') txn_hash = channel_deposit(channel_identifier, B, 10, A) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + '.setTotalDeposit') pending_transfers_tree1 = get_pending_transfers_tree( web3, [1, 1, 2, 3], [2, 1]) locksroot1 = get_merkle_root(pending_transfers_tree1.merkle_tree) locked_amount1 = get_locked_amount(pending_transfers_tree1.transfers) pending_transfers_tree2 = get_pending_transfers_tree(web3, [3], [], 7) locksroot2 = get_merkle_root(pending_transfers_tree2.merkle_tree) locked_amount2 = get_locked_amount(pending_transfers_tree2.transfers) balance_proof_A = create_balance_proof( channel_identifier, A, 10, locked_amount1, 5, locksroot1, ) balance_proof_B = create_balance_proof( channel_identifier, B, 5, locked_amount2, 3, locksroot2, ) balance_proof_update_signature_B = create_balance_proof_update_signature( B, channel_identifier, *balance_proof_A, ) for lock in pending_transfers_tree1.unlockable: txn_hash = secret_registry_contract.functions.registerSecret( lock[3]).transact({'from': A}) print_gas(txn_hash, CONTRACT_SECRET_REGISTRY + '.registerSecret') for lock in pending_transfers_tree2.unlockable: txn_hash = secret_registry_contract.functions.registerSecret( lock[3]).transact({'from': A}) print_gas(txn_hash, CONTRACT_SECRET_REGISTRY + '.registerSecret') txn_hash = token_network.functions.closeChannel( channel_identifier, B, *balance_proof_B, ).transact({'from': A}) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + '.closeChannel') txn_hash = token_network.functions.updateNonClosingBalanceProof( channel_identifier, A, B, *balance_proof_A, balance_proof_update_signature_B, ).transact({'from': B}) print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + '.updateNonClosingBalanceProof') web3.testing.mine(settle_timeout) txn_hash = token_network.functions.settleChannel( channel_identifier, B, 5, locked_amount2, locksroot2, A, 10, locked_amount1, locksroot1, ).transact() print_gas(txn_hash, CONTRACT_TOKEN_NETWORK + '.settleChannel') txn_hash = token_network.functions.unlock( channel_identifier, A, B, pending_transfers_tree2.packed_transfers, ).transact() print_gas( txn_hash, '{0}.unlock {1} locks'.format( CONTRACT_TOKEN_NETWORK, len(pending_transfers_tree2.transfers), )) txn_hash = token_network.functions.unlock( channel_identifier, B, A, pending_transfers_tree1.packed_transfers, ).transact() print_gas( txn_hash, '{0}.unlock {1} locks'.format( CONTRACT_TOKEN_NETWORK, len(pending_transfers_tree1.transfers), ))