text: # Test for Safe Remote Purchase
  # (https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst)
  # ported to vyper and optimized
  
  # Rundown of the transaction:
  # 1. Seller posts item for sale and posts safety deposit of double the item
  #    value. Balance is 2*value.
  # (1.1. Seller can reclaim deposit and close the sale as long as nothing was purchased.)
  # 2. Buyer purchases item (value) plus posts an additional safety deposit (Item
  #    value). Balance is 4*value
  # 3. Seller ships item
  # 4. Buyer confirms receiving the item. Buyer's deposit (value) is returned.
  #    Seller's deposit (2*value) + items value is returned. Balance is 0.
  import pytest
  
  
  @pytest.fixture
  def contract_code(get_contract):
      with open("examples/safe_remote_purchase/safe_remote_purchase.vy") as f:
          contract_code = f.read()
      return contract_code
  
  
  @pytest.fixture
  def get_balance(w3, tester):
      def get_balance():
          a0, a1 = w3.eth.accounts[:2]
          # balance of a1 = seller, a2 = buyer
          return w3.eth.getBalance(a0), w3.eth.getBalance(a1)
      return get_balance
  
  
  def test_initial_state(w3, assert_tx_failed, get_contract, get_balance, contract_code):
      # Inital deposit has to be divisible by two
      assert_tx_failed(lambda: get_contract(contract_code, value=13))
      # Seller puts item up for sale
      a0_pre_bal, a1_pre_bal = get_balance()
      c = get_contract(contract_code, value_in_eth=2)
      # Check that the seller is set correctly
      assert c.seller() == w3.eth.accounts[0]
      # Check if item value is set correctly (Half of deposit)
      assert c.value() == w3.toWei(1, 'ether')
      # Check if unlocked() works correctly after initialization
      assert c.unlocked() is True
      # Check that sellers (and buyers) balance is correct
      assert get_balance() == ((a1_pre_bal - w3.toWei(2, 'ether')), a1_pre_bal)
  
  
  def test_abort(w3, assert_tx_failed, get_balance, get_contract, contract_code):
      a0, a1, a2 = w3.eth.accounts[:3]
  
      a0_pre_bal, a1_pre_bal = get_balance()
      c = get_contract(contract_code, value=w3.toWei(2, 'ether'))
      assert c.value() == w3.toWei(1, 'ether')
      # Only sender can trigger refund
      assert_tx_failed(lambda: c.abort(transact={'from': a2}))
      # Refund works correctly
      c.abort(transact={'from': a0, 'gasPrice': 0})
      assert get_balance() == (a0_pre_bal, a1_pre_bal)
      # Purchase in process, no refund possible
      c = get_contract(contract_code, value=2)
      c.purchase(transact={'value': 2, 'from': a1, 'gasPrice': 0})
      assert_tx_failed(lambda: c.abort(transact={'from': a0}))
  
  
  def test_purchase(w3, get_contract, assert_tx_failed, get_balance, contract_code):
      a0, a1, a2, a3 = w3.eth.accounts[:4]
      init_bal_a0, init_bal_a1 = get_balance()
      c = get_contract(contract_code, value=2)
      # Purchase for too low/high price
      assert_tx_failed(lambda: c.purchase(transact={'value': 1, 'from': a1}))
      assert_tx_failed(lambda: c.purchase(transact={'value': 3, 'from': a1}))
      # Purchase for the correct price
      c.purchase(transact={'value': 2, 'from': a1, 'gasPrice': 0})
      # Check if buyer is set correctly
      assert c.buyer() == a1
      # Check if contract is locked correctly
      assert c.unlocked() is False
      # Check balances, both deposits should have been deducted
      assert get_balance() == (init_bal_a0 - 2, init_bal_a1 - 2)
      # Allow nobody else to purchase
      assert_tx_failed(lambda: c.purchase(transact={'value': 2, 'from': a3}))
  
  
  def test_received(w3, get_contract, assert_tx_failed, get_balance, contract_code):
      a0, a1 = w3.eth.accounts[:2]
      init_bal_a0, init_bal_a1 = get_balance()
      c = get_contract(contract_code, value=2)
      # Can only be called after purchase
      assert_tx_failed(lambda: c.received(transact={'from': a1, 'gasPrice': 0}))
      # Purchase completed
      c.purchase(transact={'value': 2, 'from': a1, 'gasPrice': 0})
      # Check that e.g. sender cannot trigger received
      assert_tx_failed(lambda: c.received(transact={'from': a0, 'gasPrice': 0}))
      # Check if buyer can call receive
      c.received(transact={'from': a1, 'gasPrice': 0})
      # Final check if everything worked. 1 value has been transferred
      assert get_balance() == (init_bal_a0 + 1, init_bal_a1 - 1)
  
  
  def test_received_reentrancy(w3, get_contract, assert_tx_failed, get_balance, contract_code):
  
      buyer_contract_code = """
  contract PurchaseContract:
  
      def received(): modifying
      def purchase(): modifying
      def unlocked() -> bool: constant
  
  purchase_contract: PurchaseContract
  
  
  @public
  def __init__(_purchase_contract: address):
      self.purchase_contract = PurchaseContract(_purchase_contract)
  
  
  @payable
  @public
  def start_purchase():
      self.purchase_contract.purchase(value=2)
  
  
  @payable
  @public
  def start_received():
      self.purchase_contract.received()
  
  
  @public
  @payable
  def __default__():
      self.purchase_contract.received()
  
      """
  
      a0 = w3.eth.accounts[0]
      c = get_contract(contract_code, value=2)
      buyer_contract = get_contract(buyer_contract_code, *[c.address])
      buyer_contract_address = buyer_contract.address
      init_bal_a0, init_bal_buyer_contract = (
          w3.eth.getBalance(a0),
          w3.eth.getBalance(buyer_contract_address),
      )
      # Start purchase
      buyer_contract.start_purchase(transact={
          'gasPrice': 0,
          'value': 4,
          'from': w3.eth.accounts[1],
          'gas': 100000,
      })
      assert c.unlocked() is False
      assert c.buyer() == buyer_contract_address
  
      # Trigger "re-entry"
      buyer_contract.start_received(transact={
          'from': w3.eth.accounts[1],
          'gasPrice': 0,
          'gas': 100000,
      })
  
      # Final check if everything worked. 1 value has been transferred
      assert w3.eth.getBalance(a0), w3.eth.getBalance(buyer_contract_address) == (
          init_bal_a0 + 1,
          init_bal_buyer_contract - 1,
      )
  