mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Enhancing all the .rst doc files by adding highlighting for the code snippets, including the following langs: 1. Solidity 2. bash 3. javascript 4. assembly
		
			
				
	
	
		
			150 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			150 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
| .. index:: purchase, remote purchase, escrow
 | |
| 
 | |
| ********************
 | |
| Safe Remote Purchase
 | |
| ********************
 | |
| 
 | |
| Purchasing goods remotely currently requires multiple parties that need to trust each other.
 | |
| The simplest configuration involves a seller and a buyer. The buyer would like to receive
 | |
| an item from the seller and the seller would like to get money (or an equivalent)
 | |
| in return. The problematic part is the shipment here: There is no way to determine for
 | |
| sure that the item arrived at the buyer.
 | |
| 
 | |
| There are multiple ways to solve this problem, but all fall short in one or the other way.
 | |
| In the following example, both parties have to put twice the value of the item into the
 | |
| contract as escrow. As soon as this happened, the money will stay locked inside
 | |
| the contract until the buyer confirms that they received the item. After that,
 | |
| the buyer is returned the value (half of their deposit) and the seller gets three
 | |
| times the value (their deposit plus the value). The idea behind
 | |
| this is that both parties have an incentive to resolve the situation or otherwise
 | |
| their money is locked forever.
 | |
| 
 | |
| This contract of course does not solve the problem, but gives an overview of how
 | |
| you can use state machine-like constructs inside a contract.
 | |
| 
 | |
| 
 | |
| .. code-block:: solidity
 | |
| 
 | |
|     // SPDX-License-Identifier: GPL-3.0
 | |
|     pragma solidity ^0.8.4;
 | |
|     contract Purchase {
 | |
|         uint public value;
 | |
|         address payable public seller;
 | |
|         address payable public buyer;
 | |
| 
 | |
|         enum State { Created, Locked, Release, Inactive }
 | |
|         // The state variable has a default value of the first member, `State.created`
 | |
|         State public state;
 | |
| 
 | |
|         modifier condition(bool _condition) {
 | |
|             require(_condition);
 | |
|             _;
 | |
|         }
 | |
| 
 | |
|         /// Only the buyer can call this function.
 | |
|         error OnlyBuyer();
 | |
|         /// Only the seller can call this function.
 | |
|         error OnlySeller();
 | |
|         /// The function cannot be called at the current state.
 | |
|         error InvalidState();
 | |
|         /// The provided value has to be even.
 | |
|         error ValueNotEven();
 | |
| 
 | |
|         modifier onlyBuyer() {
 | |
|             if (msg.sender != buyer)
 | |
|                 revert OnlyBuyer();
 | |
|             _;
 | |
|         }
 | |
| 
 | |
|         modifier onlySeller() {
 | |
|             if (msg.sender != seller)
 | |
|                 revert OnlySeller();
 | |
|             _;
 | |
|         }
 | |
| 
 | |
|         modifier inState(State _state) {
 | |
|             if (state != _state)
 | |
|                 revert InvalidState();
 | |
|             _;
 | |
|         }
 | |
| 
 | |
|         event Aborted();
 | |
|         event PurchaseConfirmed();
 | |
|         event ItemReceived();
 | |
|         event SellerRefunded();
 | |
| 
 | |
|         // Ensure that `msg.value` is an even number.
 | |
|         // Division will truncate if it is an odd number.
 | |
|         // Check via multiplication that it wasn't an odd number.
 | |
|         constructor() payable {
 | |
|             seller = payable(msg.sender);
 | |
|             value = msg.value / 2;
 | |
|             if ((2 * value) != msg.value)
 | |
|                 revert ValueNotEven();
 | |
|         }
 | |
| 
 | |
|         /// Abort the purchase and reclaim the ether.
 | |
|         /// Can only be called by the seller before
 | |
|         /// the contract is locked.
 | |
|         function abort()
 | |
|             public
 | |
|             onlySeller
 | |
|             inState(State.Created)
 | |
|         {
 | |
|             emit Aborted();
 | |
|             state = State.Inactive;
 | |
|             // We use transfer here directly. It is
 | |
|             // reentrancy-safe, because it is the
 | |
|             // last call in this function and we
 | |
|             // already changed the state.
 | |
|             seller.transfer(address(this).balance);
 | |
|         }
 | |
| 
 | |
|         /// Confirm the purchase as buyer.
 | |
|         /// Transaction has to include `2 * value` ether.
 | |
|         /// The ether will be locked until confirmReceived
 | |
|         /// is called.
 | |
|         function confirmPurchase()
 | |
|             public
 | |
|             inState(State.Created)
 | |
|             condition(msg.value == (2 * value))
 | |
|             payable
 | |
|         {
 | |
|             emit PurchaseConfirmed();
 | |
|             buyer = payable(msg.sender);
 | |
|             state = State.Locked;
 | |
|         }
 | |
| 
 | |
|         /// Confirm that you (the buyer) received the item.
 | |
|         /// This will release the locked ether.
 | |
|         function confirmReceived()
 | |
|             public
 | |
|             onlyBuyer
 | |
|             inState(State.Locked)
 | |
|         {
 | |
|             emit ItemReceived();
 | |
|             // It is important to change the state first because
 | |
|             // otherwise, the contracts called using `send` below
 | |
|             // can call in again here.
 | |
|             state = State.Release;
 | |
| 
 | |
|             buyer.transfer(value);
 | |
|         }
 | |
| 
 | |
|         /// This function refunds the seller, i.e.
 | |
|         /// pays back the locked funds of the seller.
 | |
|         function refundSeller()
 | |
|             public
 | |
|             onlySeller
 | |
|             inState(State.Release)
 | |
|         {
 | |
|             emit SellerRefunded();
 | |
|             // It is important to change the state first because
 | |
|             // otherwise, the contracts called using `send` below
 | |
|             // can call in again here.
 | |
|             state = State.Inactive;
 | |
| 
 | |
|             seller.transfer(3 * value);
 | |
|         }
 | |
|     }
 |