Last active
February 20, 2020 00:39
-
-
Save alexytiger/f1f7fb149d08a2d0f14cae74e64f5a43 to your computer and use it in GitHub Desktop.
e-book
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
based on | |
https://solidity.readthedocs.io/en/v0.5.14/solidity-by-example.html | |
*/ | |
pragma solidity >=0.4.22 <0.7.0; | |
import "@openzeppelin/contracts/math/SafeMath.sol"; | |
import "./Ownable.sol"; | |
contract SafeRemotePurchase is Ownable { | |
using SafeMath for uint256; | |
uint256 private _commissionRate; // for example, 350 == >(350/100)=3.5% | |
address payable public seller; | |
address payable public buyer; | |
uint256 public price; | |
bytes32 public key; // unique string identifier | |
string public description; | |
string public ipfsImageHash; | |
enum State { | |
Created, | |
Locked, | |
Canceled, | |
ItemReceived, | |
SellerPaid, | |
OwnerPaid, | |
Completed | |
} | |
State public state; | |
// Contract created by the seller | |
// Ensure that `msg.value` is an even number. | |
constructor( | |
uint256 _rate, | |
address payable _seller, | |
bytes32 _key, | |
string memory _description, | |
string memory _ipfxImageHash | |
) public payable { | |
require(_key != 0x0, "Key cannot be 0x0"); | |
require(bytes(_description).length > 0, "Description can't be empty"); | |
require(_rate > 0, "Must specify the commission rate"); | |
require(_seller != address(0), "The seller is the zero address"); | |
_commissionRate = _rate; | |
seller = _seller; | |
key = _key; | |
ipfsImageHash = _ipfxImageHash; | |
description = _description; | |
price = msg.value.div(2); | |
require(price > 0, "Must specify price"); | |
require((price * 2) == msg.value, "Price has to be even"); | |
} | |
modifier condition(bool _condition) { | |
require(_condition, "Condition is not valid."); | |
_; | |
} | |
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 LogCanceledBySeller( | |
address indexed sender, | |
uint256 amount, | |
bytes32 key | |
); | |
event LogPurchaseConfirmed( | |
address indexed sender, | |
uint256 amount, | |
bytes32 key | |
); | |
event LogReceivedByBuyer( | |
address indexed sender, | |
uint256 amount, | |
bytes32 key | |
); | |
event LogWithdrawBySeller( | |
address indexed sender, | |
uint256 amount, | |
bytes32 key | |
); | |
event LogWithdrawByOwner( | |
address indexed sender, | |
uint256 amount, | |
bytes32 key | |
); | |
// Confirm the purchase as buyer. | |
// Transaction has to include `2 * value` ether. | |
// The ether will be locked until buyerConfirmReceived is called | |
function buyerPurchase() | |
external | |
payable | |
inState(State.Created) | |
condition(msg.value == price.mul(2)) | |
returns (bool result) | |
{ | |
emit LogPurchaseConfirmed(msg.sender, msg.value, key); | |
buyer = msg.sender; | |
state = State.Locked; | |
return true; | |
} | |
// Confirm that the buyer received the item from the seller. | |
// The buyer will receive the locked ether | |
// in the amount of the price. | |
function buyerConfirmReceived() | |
external | |
onlyBuyer | |
inState(State.Locked) | |
returns (bool result) | |
{ | |
emit LogReceivedByBuyer(msg.sender, price, key); | |
state = State.ItemReceived; | |
buyer.transfer(price); | |
return true; | |
} | |
// Abort the purchase and reclaim the ether. | |
// Can only be called by the seller before | |
// the contract is locked. | |
function abortBySeller() | |
external | |
onlySeller | |
inState(State.Created) | |
returns (bool result) | |
{ | |
uint256 amount = balanceOf(); | |
state = State.Canceled; | |
emit LogCanceledBySeller(msg.sender, amount, key); | |
// 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(amount); | |
return true; | |
} | |
function withdrawBySeller() | |
external | |
onlySeller | |
condition(state == State.ItemReceived || state == State.OwnerPaid) | |
returns (bool result) | |
{ | |
if (state == State.OwnerPaid) { | |
uint256 amount = balanceOf(); | |
emit LogWithdrawBySeller(msg.sender, amount, key); | |
state = State.Completed; | |
seller.transfer(amount); | |
return true; | |
} else if (state == State.ItemReceived) { | |
// calculate commission part | |
uint256 commission = (price.mul(_commissionRate)).div(10000); | |
// subtracts commission part: 3.5% ==> 350 /100 | |
uint256 amount = (price.mul(3)).sub(commission); | |
emit LogWithdrawBySeller(msg.sender, amount, key); | |
state = State.SellerPaid; | |
seller.transfer(amount); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function withdrawByOwner() | |
external | |
onlyOwner | |
condition(state == State.ItemReceived || state == State.SellerPaid) | |
returns (bool result) | |
{ | |
if (state == State.SellerPaid) { | |
uint256 amount = balanceOf(); | |
emit LogWithdrawByOwner(msg.sender, amount, key); | |
state = State.Completed; | |
owner().transfer(amount); | |
return true; | |
} else if (state == State.ItemReceived) { | |
// calculate commission part: 3.5% ==> 350 /100 | |
uint256 commission = (price.mul(_commissionRate)).div(10000); | |
emit LogWithdrawByOwner(msg.sender, commission, key); | |
state = State.OwnerPaid; | |
owner().transfer(commission); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
// only owner (==deployer) and seller can see it | |
function commissionRate() | |
external | |
view | |
condition(isOwner() || msg.sender == seller) | |
returns (uint256 commission) | |
{ | |
return _commissionRate; | |
} | |
// Get balance of the contract | |
function balanceOf() public view returns (uint256) { | |
return address(this).balance; | |
} | |
// Prevent someone sending ether to the contract | |
// It will cause an exception, | |
// because the fallback function does not have the 'payable' | |
// modifier. | |
function() external { | |
revert("No Ether excepted"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment