Skip to content

Instantly share code, notes, and snippets.

@chriseth
Last active May 2, 2021 13:27
Show Gist options
  • Save chriseth/b16e8e76a423b7671e99 to your computer and use it in GitHub Desktop.
Save chriseth/b16e8e76a423b7671e99 to your computer and use it in GitHub Desktop.
Purchase
// This contract can be used to hold money while an item is in transit
// during purchase.
// This protocol is a variation of a protocol by Oleg Andreev which is
// described at
// https://gatecoin.com/blog/2015/10/blockchain2-disrupting-disrutors/
//
// Assume Bob wants to buy an item worth x Ether from Alice.
// Alice creates this contract and and sends 2x Ether to the contract
// together with its creation transaction.
// If Bob does not react, Alice can get her money back.
// Next, Bob sends 2x Ether to the contract.
// From now on, both Alice's and Bob's money is locked in the contract.
// Now, Alice sends the item to Bob.
// Bob receives the item, checks that it is what he expected and confirms
// this with a transaction to the contract. This causes the contract to be
// disabled and both getting their deposits plus/minus the item price back.
//
// If Bob does not receive the item or the item is not what he expected,
// he can negotiate with Alice (off-chain). The contract will keep
// their money until come to an agreement: Either Bob finally confirms
// or Alice fully refunds Bob.
// A more complex version of this contract could include an arbitrator who
// can be called if Alice and Bob cannot resolve their dispute.
contract Purchase
{
uint public value;
address public seller;
address public buyer;
enum State { Created, Locked, Inactive }
State public state;
/// Create a new locked purchase about
/// `msg.value / 2` Wei.
function Purchase()
require(msg.value % 2 == 0)
{
seller = msg.sender;
value = msg.value / 2;
}
modifier require(bool _condition)
{
if (!_condition) throw;
_
}
modifier onlyBuyer()
{
if (msg.sender != buyer) throw;
_
}
modifier onlySeller()
{
if (msg.sender != seller) throw;
_
}
modifier inState(State _state)
{
if (state != _state) throw;
_
}
event Aborted();
event PurchaseConfirmed();
event ItemReceived();
event Refunded();
/// Abort the purchase and reclaim the ether.
/// Can only be called by the seller before
/// the contract is locked.
function abort()
onlySeller
inState(State.Created)
{
seller.send(this.balance);
state = State.Inactive;
Aborted();
}
/// Confirm the purchase as buyer.
/// Transaction has to include `2 * value` Wei.
/// The ether will be locked until either
/// confirmReceived is called by the buyer
/// or refund is called by the seller.
function confirmPurchase()
inState(State.Created)
require(msg.value == 2 * value)
{
buyer = msg.sender;
state = State.Locked;
PurchaseConfirmed();
}
/// Confirm that you (the buyer) received the item.
/// This will send `value` to the buyer and
/// `3 * value` to the seller.
function confirmReceived()
onlyBuyer
inState(State.Locked)
{
buyer.send(value); // We ignore the return value on purpose
seller.send(this.balance);
state = State.Inactive;
ItemReceived();
}
/// Fully refund the buyer. This can only be called
/// by the seller and will send `2 * value` both to
/// the buyer and the sender.
function refund()
onlySeller
inState(State.Locked)
{
buyer.send(2 * value); // We ignore the return value on purpose
seller.send(this.balance);
state = State.Inactive;
Refunded();
}
function() { throw; }
}
@frozeman
Copy link

frozeman commented Nov 2, 2015

A few comments:

  • Events should be uppercase
  • i would call the events after all is done, so that any exception would not make a false alarm.
  • Maybe we should remove the amount of modifiers if they are only used once (let the contract look more simple)
  • what happens if i don't send enough money to the confirmPurchase func? it seems like it eats my money, but never executes, as there is no throw right?
  • rename confirmPurchase() to purchase(), so its more clear of what you're doing.

@frozeman
Copy link

frozeman commented Nov 2, 2015

We could add a cancel buy function, which both have to agree to the cancel the buy.
This would add a better conflict resolution.

Like the buyer can call a requestCancel function. And the the seller can call a acceptCancel function.

@alexvandesande
Copy link

I don't think it needs a requestCancel and AcceptCancel button, all the communication can be off-chain. I would just do a function equivalent to confirmReceived() but for seller only:

       event Refund();

     /// Seller confirms the item is lost and refunds.
    /// This will release the locked ether.
    function confirmRefund()
        onlySeller
        inState(State.Locked)
    {
        Refund();
        buyer.send(2*value); // We ignore the return value on purpose
        seller.send(this.balance);
        state = State.Inactive;
    }

In this case, once the contract is in the state.locked there are only two solutions: either the buyer confirms the purchase or the seller refunds it.

@frozeman
Copy link

frozeman commented Nov 2, 2015

Cool, but then we basically only need to improve the abort function, and put it all there...

@chriseth
Copy link
Author

chriseth commented Nov 2, 2015

If we add it as part of abort, it will create a race condition. We might add a parameter, but I think it is better to add a new function.

@eOMS
Copy link

eOMS commented Jul 23, 2017

What about a deadline?. Don't you think it's absolutelly necessary?

@GeoffreySari
Copy link

GeoffreySari commented Nov 23, 2017

Make the contract time bound, once time exceeds without any transaction, then ether's get returned to respective buyer and/or seller.

@palexs
Copy link

palexs commented Dec 11, 2017

@GeoffreySari Per my knowledge a contract can't be time bound - there's no notion of timer in Ethereum. The only way to initiate an event in Ethereum blockchain is trigger it from externally owned account (aka human action).

However, some deadline could be set in a contract, so that when a seller or buyer initiate interaction with the contract it could use some oraclization service to get current date and act appropriately, e.g. return ether.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment