.. index:: purchase, remote purchase, escrow ******************** Safe Remote Purchase ******************** :: pragma solidity >=0.4.22 <0.7.0; contract Purchase { uint public value; address payable public seller; address payable public buyer; enum State { Created, Locked, Inactive } // The state variable has a default value of the first member, `State.created` State public state; // 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() public payable { seller = msg.sender; value = msg.value / 2; require((2 * value) == msg.value, "Value has to be even."); } modifier condition(bool _condition) { require(_condition); _; } modifier onlyBuyer() { require( msg.sender == buyer, "Only buyer can call this." ); _; } modifier onlySeller() { require( msg.sender == seller, "Only seller can call this." ); _; } modifier inState(State _state) { require( state == _state, "Invalid state." ); _; } event Aborted(); event PurchaseConfirmed(); event ItemReceived(); /// 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; 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 = 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.Inactive; // NOTE: This actually allows both the buyer and the seller to // block the refund - the withdraw pattern should be used. buyer.transfer(value); seller.transfer(address(this).balance); } }