Created
May 11, 2018 17:21
-
-
Save antoniovassell/c2da58d1296d56d1519947abdea31933 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.4.23+commit.124ca40d.js&optimize=false&gist=
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
pragma solidity ^0.4.23; | |
/// MVP Contract to handle trading of eth - fiat currency. | |
contract LocalEthExchange { | |
// Trade statuses | |
bytes32 constant TRADE_STATUS_STARTED = 'TRADE_STATUS_STARTED'; | |
bytes32 constant TRADE_STATUS_CANCELLED = 'TRADE_STATUS_CANCELLED'; | |
bytes32 constant TRADE_STATUS_PAYMENT_DONE = 'TRADE_STATUS_PAYMENT_DONE'; | |
bytes32 constant TRADE_STATUS_COMPLETED = 'TRADE_STATUS_COMPLETED'; | |
bytes32 constant TRADE_STATUS_DISPUTE = 'TRADE_STATUS_DISPUTE'; | |
// Amount of items we can retrieve at once | |
uint constant PAGING_LIMIT = 10; | |
// Objects that we are storing | |
struct User { | |
address userAddress; | |
bytes32 name; | |
} | |
struct Listing { | |
bytes32 listingId; | |
address user; | |
bytes32 description; | |
uint256 created; | |
} | |
struct Trade { | |
bytes32 tradeId; | |
address seller; | |
address buyer; | |
uint256 value; | |
bytes32 offerId; | |
bytes32 status; | |
uint256 created; | |
} | |
struct Offer { | |
bytes32 offerId; | |
address offerer; | |
bytes32 listingId; | |
uint256 value; | |
bytes32 description; | |
bool accepted; | |
uint256 created; | |
} | |
// Mappings of the list of objects that we are storing | |
// using their ids as keys | |
mapping(bytes32 => Listing) public listings; | |
mapping(address => User) users; | |
mapping(bytes32 => Trade) trades; | |
mapping(bytes32 => Offer) offers; | |
// Llist of ids of objects to allow us to iterate (we can't iterate with maps) | |
bytes32[] public listingIds; | |
bytes32[] public offerIds; | |
bytes32[] public tradeIds; | |
// Listing ID will be used as the key | |
mapping(bytes32 => bytes32[]) associatedOffers; | |
/// Events that clients can listen for | |
// When a user adds a new listing | |
event NewListingEvent( | |
bytes32 listingId, | |
address user, | |
bytes32 description, | |
uint256 created | |
); | |
// When a buyer adds a new offer to a listing | |
event NewOfferEvent( | |
bytes32 offerId, | |
address offerer, | |
bytes32 listingId, | |
uint256 value, | |
bytes32 description, | |
uint256 created | |
); | |
// When a seller accepts an offer | |
event AcceptOfferEvent( | |
bytes32 offerId, | |
bytes32 tradeId, | |
address buyer, | |
address seller, | |
uint256 value, | |
uint256 created | |
); | |
// When a buyer confirms that they made a payment | |
event BuyerConfirmEvent( | |
bytes32 tradeId, | |
address buyer, | |
address seller, | |
uint256 created | |
); | |
// When a seller confirm that the buyer paid and contract should release | |
// escrow funds to buyer | |
event SellerConfirmEvent( | |
bytes32 tradeId, | |
address buyer, | |
address seller, | |
uint256 created | |
); | |
// When a user cancels a trade | |
event TradeCancelledEvent( | |
bytes32 tradeId, | |
address buyer, | |
address seller, | |
uint256 created | |
); | |
/// Listing management | |
// Allows a user to add a new listing | |
function addListing(bytes32 listingId, address lister, bytes32 description) public { | |
require(msg.sender == lister, 'User address should be equal to the sender address'); | |
uint256 listingTime = getTime(); | |
listings[listingId] = Listing(listingId, lister, description, listingTime); | |
listingIds.push(listingId); | |
emit NewListingEvent(listingId, lister, description, listingTime); | |
} | |
// Gets a listing given a listing ID | |
function getListing(bytes32 _listingId) public constant returns (bytes32 listingId, address user, bytes32 description, uint256 created) { | |
// todo: get listing | |
Listing storage listing = listings[_listingId]; | |
return (listing.listingId, listing.user, listing.description, listing.created); | |
} | |
/// Returns the total count of listings | |
function getListingCount() private view returns(uint listingsCount) { | |
return listingIds.length; | |
} | |
/// Get a list of indexed listings that are added to the contract | |
/// this stands as a pager function to allow a set number of items | |
/// since we cant return a dynamic array | |
function getListings(uint startingIndex) public view returns ( | |
uint totalCount, | |
bytes32[PAGING_LIMIT] _listingIds, | |
address[PAGING_LIMIT] _users, | |
bytes32[PAGING_LIMIT] _descriptions, | |
uint256[PAGING_LIMIT] _created | |
) { | |
// Checking for a valid starting index | |
require(startingIndex >= 0, 'Starting Index should be greater than or equal to 0'); | |
uint listingsCount = getListingCount(); | |
require(startingIndex <= listingsCount, 'Starting index is out of range'); | |
// Determine the max amount of items we should return (maybe only 3 items are currently added) | |
uint maxCount = listingsCount - startingIndex; | |
// Ensure max count is less than our overall limit of items | |
if (maxCount > PAGING_LIMIT) { | |
maxCount = PAGING_LIMIT; | |
} | |
// Index to be used to add items that we are going to return, it should start from zero | |
// despite users starting index being different | |
uint writeIndex = 0; | |
// Loop through starting from the index that the user stated | |
for (uint index = startingIndex; index < maxCount; index++) { | |
// Get the associated offer based on the indexed offer ID | |
bytes32 listingId = listingIds[index]; | |
Listing storage listing = listings[listingId]; | |
// todo: Can check if offer is actual exist here | |
// Set properties | |
_listingIds[writeIndex] = listing.listingId; | |
_users[writeIndex] = listing.user; | |
_descriptions[writeIndex] = listing.description; | |
_created[writeIndex] = listing.created; | |
writeIndex++; | |
} | |
// Return values in which each property is a static array | |
// since we can't return structs currently | |
return (listingsCount, _listingIds, _users, _descriptions, _created); | |
} | |
/// Remove a listing, only the user who created the listing can delete | |
function removeListing(bytes32 listingId) public { | |
Listing storage listing = listings[listingId]; | |
require(listing.user != 0, 'Listing not found'); | |
require(msg.sender == listing.user, 'Listing owner address should be equal to the sender address'); | |
delete listings[listingId]; | |
} | |
/// Returns how much offers the contrat has indexed (how much offers has been added) | |
function getOffersCount() private view returns(uint offersCount) { | |
return offerIds.length; | |
} | |
/// Allows user to add an offer | |
function addOffer(bytes32 offerId, address offerer, bytes32 listingId, uint256 value, bytes32 description) public { | |
require(offerer == msg.sender, 'Offerer address should be equal to sender address'); | |
// todo: check that the offerer is not the seller, minor | |
uint256 created = getTime(); | |
offers[offerId] = Offer(offerId, offerer, listingId, value, description, false, created); | |
offerIds.push(offerId); | |
emit NewOfferEvent(offerId, | |
offerer, | |
listingId, | |
value, | |
description, | |
created | |
); | |
} | |
/// Get a list of indexed offers that are added to the contract | |
/// this stands as a pager function to allow a set number of items | |
/// since we cant return a dynamic array | |
function getOffers(uint startingIndex) public view returns ( | |
uint totalCount, | |
bytes32[PAGING_LIMIT] _offerIds, | |
bytes32[PAGING_LIMIT] _listingIds, | |
address[PAGING_LIMIT] _offerers, | |
uint256[PAGING_LIMIT] _values, | |
bytes32[PAGING_LIMIT] _descriptions, | |
uint256[PAGING_LIMIT] _created | |
) { | |
// Checking for a valid starting index | |
require(startingIndex >= 0, 'Starting Index should be greater than or equal to 0'); | |
uint offersCount = getOffersCount(); | |
require(startingIndex <= offersCount, 'Starting index is out of range'); | |
// Determine the max amount of items we should return (maybe only 3 items are currently added) | |
uint maxCount = offersCount - startingIndex; | |
// Ensure max count is less than our overall limit of items | |
if (maxCount > PAGING_LIMIT) { | |
maxCount = PAGING_LIMIT; | |
} | |
// Index to be used to add items that we are going to return, it should start from zero | |
// despite users starting index being different | |
uint writeIndex = 0; | |
// Loop through starting from the index that the user stated | |
for (uint index = startingIndex; index < maxCount; index++) { | |
// Get the associated offer based on the indexed offer ID | |
bytes32 offerId = offerIds[index]; | |
Offer storage offer = offers[offerId]; | |
// todo: Can check if offer is actual exist here | |
// Set properties | |
_offerIds[writeIndex] = offer.offerId; | |
_listingIds[writeIndex] = offer.listingId; | |
_offerers[writeIndex] = offer.offerer; | |
_values[writeIndex] = offer.value; | |
_descriptions[writeIndex] = offer.description; | |
_created[writeIndex] = offer.created; | |
writeIndex++; | |
} | |
// Return values in which each property is a static array | |
// since we can't return structs currently | |
return (offersCount, _offerIds, _listingIds, _offerers, _values, _descriptions, _created); | |
} | |
function removeOffer(bytes32 offerId) public { | |
Offer storage offer = offers[offerId]; | |
require(offer.offerId != 0, 'Offer not found'); | |
delete offers[offerId]; | |
} | |
/// When the user accepts an offer, the value of the offer will be deducted from their address/account | |
/// and placed in escrow | |
/// Money is automatically placed in the contracts account once this function executes successfully | |
function acceptOffer(bytes32 offerId, uint256 value, bytes32 tradeId) public payable { | |
// Lets start the trade/escrow | |
// Get the existing offer based on the id | |
Offer storage offer = offers[offerId]; | |
require(offer.offerId != 0, 'Offer not found'); | |
// Get associated listing | |
Listing storage listing = listings[offer.listingId]; | |
require(listing.listingId != 0, 'Listing not found'); | |
require(listing.user == msg.sender, 'Can only accept offer if user address is equal to sender address'); | |
require(msg.value == value, 'Value should be equal to what was transfered'); | |
offer.accepted = true; | |
offers[offerId] = offer; | |
// todo: assuming the seller is the one who created the listing for now | |
// todo: assuming trade doesn't already exist | |
// create new trade here | |
addTrade(tradeId, msg.sender, offer.offerer, offer.value, offerId); | |
} | |
/// Trade management | |
/// Adds a new trade after a user accepts an offer | |
function addTrade(bytes32 tradeId, address seller, address buyer, uint256 value, bytes32 offerId) private { | |
// Get current timestamp | |
uint256 created = getTime(); | |
// Creates a new trade | |
trades[tradeId] = Trade( | |
tradeId, | |
seller, | |
buyer, | |
value, | |
offerId, | |
TRADE_STATUS_STARTED, | |
created | |
); | |
// ... And add it to the tradeIds index to enable us to loop | |
// over later | |
tradeIds.push(tradeId); | |
// Emits an event so that clients listening to the contract will be alerted | |
emit AcceptOfferEvent( | |
offerId, | |
tradeId, | |
buyer, | |
seller, | |
value, | |
created | |
); | |
} | |
// Gets the count of trades that had been added to the contract | |
function getTradesCount() private view returns(uint tradesCount) { | |
return tradeIds.length; | |
} | |
/// Get a list of indexed offers that are added to the contract | |
/// this stands as a pager function to allow a set number of items | |
/// since we cant return a dynamic array | |
function getTrades(uint startingIndex) public view returns ( | |
uint totalCount, | |
bytes32[PAGING_LIMIT] _tradeIds, | |
bytes32[PAGING_LIMIT] _offerIds, | |
address[PAGING_LIMIT] _buyers, | |
address[PAGING_LIMIT] _sellers, | |
uint256[PAGING_LIMIT] _values, | |
bytes32[PAGING_LIMIT] _statues, | |
uint256[PAGING_LIMIT] _created | |
) { | |
// Checking for a valid starting index | |
require(startingIndex >= 0, 'Starting Index should be greater than or equal to 0'); | |
uint tradesCount = getTradesCount(); | |
require(startingIndex <= tradesCount, 'Starting index is out of range'); | |
// Determine the max amount of items we should return (maybe only 3 items are currently added) | |
uint maxCount = tradesCount - startingIndex; | |
// Ensure max count is less than our overall limit of items | |
if (maxCount > PAGING_LIMIT) { | |
maxCount = PAGING_LIMIT; | |
} | |
// Loop through starting from the index that the user stated | |
uint writeIndex = 0; | |
for (uint index = startingIndex; index < maxCount; index++) { | |
// Get the associated trade based on the indexed trade ID | |
bytes32 tradeId = tradeIds[index]; | |
Trade storage trade = trades[tradeId]; | |
// todo: Can check if offer is actual exist here | |
// Set properties | |
_tradeIds[writeIndex] = trade.tradeId; | |
_offerIds[writeIndex] = trade.offerId; | |
_buyers[writeIndex] = trade.buyer; | |
_sellers[writeIndex] = trade.seller; | |
_values[writeIndex] = trade.value; | |
_statues[writeIndex] = trade.status; | |
_created[writeIndex] = trade.created; | |
writeIndex++; | |
} | |
// Return values in which each property is a static array | |
// since we can't return structs currently | |
return (tradesCount, _tradeIds, _offerIds, _buyers, _sellers, _values, _statues, _created); | |
} | |
/// Allows user to cancel a trade given a trade ID | |
function cancelTrade(bytes32 tradeId) public { | |
// Get trade by ID | |
Trade storage trade = trades[tradeId]; | |
// Check that trade was found/exist | |
require(trade.tradeId != 0, 'Could not find trade'); | |
// Only the buyer or seller can cancel a trade | |
require(msg.sender == trade.seller || msg.sender == trade.buyer, 'You have to be the seller or buyer to cancel trade'); | |
// User cannot cancel trade if its in a certain state | |
// A better design would be with a statemachine | |
require(trade.status != TRADE_STATUS_CANCELLED, 'Trade is already cancelled'); | |
require(trade.status != TRADE_STATUS_DISPUTE, 'Cannot cancel a trade in dispute'); | |
require(trade.status != TRADE_STATUS_COMPLETED, 'Cannot cancel a trade that is already completed'); | |
// Seller can't cancel trade if Buyer already confirm payment | |
if (trade.status == TRADE_STATUS_PAYMENT_DONE) { | |
require(msg.sender != trade.seller, 'Cannot cancel a trade when payment is already confirmed by buyer'); | |
} | |
// Set trade status to cancel if we reach this far | |
trade.status = TRADE_STATUS_CANCELLED; | |
trades[tradeId] = trade; | |
// todo: validate contract has enough value to actually do a transfer, | |
// and alert that this could be something wrong | |
// Transfer back eth/token to seller | |
trade.seller.transfer(trade.value); | |
// Emit event that trade has been cancelled | |
emit TradeCancelledEvent( | |
tradeId, | |
trade.buyer, | |
trade.seller, | |
getTime() | |
); | |
} | |
/// Buyers confirm that they made payment for a given tradeId | |
function buyerConfirmPayment(bytes32 tradeId) public { | |
// Get trade | |
Trade storage trade = trades[tradeId]; | |
// Verify if it exist | |
require(trade.tradeId != 0, 'Trade not found'); | |
// Only the buyer should be able to confirm payment | |
require(msg.sender == trade.buyer, 'Buyer address should be equal to sender address'); | |
// Verify trade is in the correct status before buyer confirms payment | |
require(trade.status != TRADE_STATUS_CANCELLED, 'Cannot confirm payment when trade is cancelled'); | |
require(trade.status != TRADE_STATUS_DISPUTE, 'Cannot confirm payment when trade is in dispute'); | |
require(trade.status != TRADE_STATUS_COMPLETED, 'Cannot confirm payment when seller already confirmed payment'); | |
// Update trade status if we reach this far | |
trade.status = TRADE_STATUS_PAYMENT_DONE; | |
trades[tradeId] = trade; | |
// emit Event that buyer confirmed payment | |
emit BuyerConfirmEvent( | |
tradeId, | |
trade.buyer, | |
trade.seller, | |
getTime() | |
); | |
} | |
/// Seller can confirm that a payment was made and release funds to buyer | |
/// for a trade given a trade ID | |
function sellerReleasePayment(bytes32 tradeId) public { | |
// Get trade | |
Trade storage trade = trades[tradeId]; | |
// Verify that trade exist | |
require(trade.tradeId != 0, 'Trade not found'); | |
// Only the seller can release payment | |
require(msg.sender == trade.seller, 'Seller address must equal to sender address'); | |
// Validate that trade is in the correct status to be able to | |
require(trade.status != TRADE_STATUS_CANCELLED, 'Cannot confirm payment when trade is cancelled'); | |
require(trade.status != TRADE_STATUS_COMPLETED, 'Cannot confirm payment when trade already completed'); | |
// Update trade status | |
trade.status = TRADE_STATUS_COMPLETED; | |
trades[tradeId] = trade; | |
// todo: validate that contract has enough balance to actually do this | |
// if not, something is wrong | |
// Transfer what ever that was suppose to be traded to the buyer, trade is completed | |
trade.buyer.transfer(trade.value); | |
emit SellerConfirmEvent( | |
tradeId, | |
trade.buyer, | |
trade.seller, | |
getTime() | |
); | |
} | |
/// Get a trade given a trade ID | |
function getTrade(bytes32 _tradeId) constant public returns (bytes32 tradeId, address seller, address buyer, uint256 value, bytes32 offerId, bytes32 status) { | |
Trade storage trade = trades[_tradeId]; | |
require(trade.tradeId != 0, 'Trade not found'); | |
return (trade.tradeId, trade.seller, trade.buyer, trade.value, trade.offerId, trade.status); | |
} | |
/// Utility functions | |
/// Get the current timestamp | |
function getTime() private constant returns(uint256 currentTime) { | |
return now; | |
} | |
/// Get the contract's balance | |
function getContractBalance() public view returns(uint256 balance) { | |
return address(this).balance; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment