Created
December 10, 2017 08:17
-
-
Save hackfisher/d535440362ee106472586f8858d49f60 to your computer and use it in GitHub Desktop.
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.18; | |
contract Token { | |
function transfer(address _to, uint _value) public returns (bool success); | |
function transferFrom(address _from, address _to, uint _value) public returns (bool success); | |
function approve(address _spender, uint _value) public returns (bool success); | |
} | |
contract LocalEthereumEscrows { | |
// The address of the arbitrator | |
// In the first version, this is always localethereum staff. | |
address public arbitrator; | |
address public owner; | |
address public relayer; | |
uint32 public requestCancellationMinimumTime; | |
uint256 public feesAvailableForWithdraw; | |
uint8 constant ACTION_SELLER_CANNOT_CANCEL = 0x01; // Called when marking as paid or calling a dispute as the buyer | |
uint8 constant ACTION_BUYER_CANCEL = 0x02; | |
uint8 constant ACTION_SELLER_CANCEL = 0x03; | |
uint8 constant ACTION_SELLER_REQUEST_CANCEL = 0x04; | |
uint8 constant ACTION_RELEASE = 0x05; | |
uint8 constant ACTION_DISPUTE = 0x06; | |
event Created(bytes32 _tradeHash); | |
event SellerCancelDisabled(bytes32 _tradeHash); | |
event SellerRequestedCancel(bytes32 _tradeHash); | |
event CancelledBySeller(bytes32 _tradeHash); | |
event CancelledByBuyer(bytes32 _tradeHash); | |
event Released(bytes32 _tradeHash); | |
event DisputeResolved(bytes32 _tradeHash); | |
struct Escrow { | |
// Set so we know the trade has already been created | |
bool exists; | |
// The timestamp in which the seller can cancel the trade if the buyer has not yet marked as paid. Set to 0 on marked paid or dispute | |
// 1 = unlimited cancel time | |
uint32 sellerCanCancelAfter; | |
// The total cost of gas spent by relaying parties. This amount will be | |
// refunded/paid to localethereum.com once the escrow is finished. | |
uint128 totalGasFeesSpentByRelayer; | |
} | |
// Mapping of active trades. Key is a hash of the trade data | |
mapping (bytes32 => Escrow) public escrows; | |
modifier onlyOwner() { | |
require(msg.sender == owner); | |
_; | |
} | |
modifier onlyArbitrator() { | |
require(msg.sender == arbitrator); | |
_; | |
} | |
function getRelayedSender( | |
bytes16 _tradeID, // The unique ID of the trade, generated by localethereum.com | |
uint8 _actionByte, // The desired action of the user, matching an ACTION_* constant | |
uint128 _maximumGasPrice, // The maximum gas price the user is willing to pay | |
uint8 _v, // Signature value | |
bytes32 _r, // Signature value | |
bytes32 _s // Signature value | |
) view private returns (address) { | |
bytes32 _hash = keccak256(_tradeID, _actionByte, _maximumGasPrice); | |
if(tx.gasprice > _maximumGasPrice) return; | |
return ecrecover(_hash, _v, _r, _s); | |
} | |
function LocalEthereumEscrows() public { | |
/** | |
* Initialize the contract. | |
*/ | |
owner = msg.sender; | |
arbitrator = msg.sender; | |
relayer = msg.sender; | |
requestCancellationMinimumTime = 2 hours; // TODO | |
} | |
function getEscrowAndHash( | |
/** | |
* Hashes the values and returns the matching escrow object and trade hash. | |
* Returns an empty escrow struct and 0 _tradeHash if not found | |
*/ | |
bytes16 _tradeID, | |
address _seller, | |
address _buyer, | |
uint256 _value, | |
uint16 _fee | |
) view private returns (Escrow, bytes32) { | |
bytes32 _tradeHash = keccak256(_tradeID, _seller, _buyer, _value, _fee); | |
return (escrows[_tradeHash], _tradeHash); | |
} | |
function createEscrow( | |
/** | |
* Create a new escrow and add it to `escrows`. | |
* _tradeHash is created by hashing _tradeID, _seller, _buyer, _value and _fee variables. These variables must be supplied on future contract calls. | |
* v, r and s is the signature data supplied from the api. The sig is keccak256(_tradeHash, _paymentWindowInSeconds, _expiry). | |
*/ | |
bytes16 _tradeID, // The unique ID of the trade, generated by localethereum.com | |
address _seller, // The selling party of the trade | |
address _buyer, // The buying party of the trade | |
uint256 _value, // The ether amount being held in escrow | |
uint16 _fee, // The localethereum.com fee in 1/10000ths | |
uint32 _paymentWindowInSeconds, // The time in seconds from contract creation that the buyer has to mark as paid | |
uint32 _expiry, // Provided by localethereum.com. This transaction must be created before this time. | |
uint8 _v, // Signature value | |
bytes32 _r, // Signature value | |
bytes32 _s // Signature value | |
) payable external { | |
bytes32 _tradeHash = keccak256(_tradeID, _seller, _buyer, _value, _fee); | |
require(!escrows[_tradeHash].exists); // Require that trade does not already exist | |
require(ecrecover(keccak256(_tradeHash, _paymentWindowInSeconds, _expiry), _v, _r, _s) == relayer); // Signature must have come from the relayer | |
require(block.timestamp < _expiry); | |
require(msg.value == _value && msg.value > 0); // Check sent eth against signed _value and make sure is not 0 | |
uint32 _sellerCanCancelAfter = _paymentWindowInSeconds == 0 ? 1 : uint32(block.timestamp) + _paymentWindowInSeconds; | |
escrows[_tradeHash] = Escrow(true, _sellerCanCancelAfter, 0); | |
Created(_tradeHash); | |
} | |
uint16 constant GAS_doRelease = 36100; | |
function doRelease( | |
/** | |
* Called by the seller to releases the funds for a successful trade. | |
* Deletes the trade from the `escrows` mapping. | |
*/ | |
bytes16 _tradeID, | |
address _seller, | |
address _buyer, | |
uint256 _value, | |
uint16 _fee, | |
uint128 _additionalGas | |
) private returns (bool) { | |
var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee); | |
if (!_escrow.exists) return false; | |
uint128 _gasFees = _escrow.totalGasFeesSpentByRelayer + (msg.sender == relayer ? (GAS_doRelease + _additionalGas) * uint128(tx.gasprice) : 0); | |
delete escrows[_tradeHash]; | |
Released(_tradeHash); | |
transferMinusFees(_buyer, _value, _gasFees, _fee); | |
return true; | |
} | |
uint16 constant GAS_doDisableSellerCancel = 12100; | |
function doDisableSellerCancel( | |
/** | |
* Stops the seller from cancelling the trade. | |
* Can only be called the buyer. | |
* Used to mark the trade as paid, or if the buyer has a dispute. | |
*/ | |
bytes16 _tradeID, | |
address _seller, | |
address _buyer, | |
uint256 _value, | |
uint16 _fee, | |
uint128 _additionalGas | |
) private returns (bool) { | |
var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee); | |
if (!_escrow.exists) return false; | |
if(_escrow.sellerCanCancelAfter == 0) return false; | |
escrows[_tradeHash].sellerCanCancelAfter = 0; | |
SellerCancelDisabled(_tradeHash); | |
if (msg.sender == relayer) { | |
increaseGasSpent(_tradeHash, GAS_doDisableSellerCancel + _additionalGas); | |
} | |
return true; | |
} | |
uint16 constant GAS_doBuyerCancel = 36100; | |
function doBuyerCancel( | |
/** | |
* Cancels the trade and returns the ether to the seller. | |
* Can only be called the buyer. | |
*/ | |
bytes16 _tradeID, | |
address _seller, | |
address _buyer, | |
uint256 _value, | |
uint16 _fee, | |
uint128 _additionalGas | |
) private returns (bool) { | |
var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee); | |
if (!_escrow.exists) return false; | |
uint128 _gasFees = _escrow.totalGasFeesSpentByRelayer + (msg.sender == relayer ? (GAS_doBuyerCancel + _additionalGas) * uint128(tx.gasprice) : 0); | |
delete escrows[_tradeHash]; | |
CancelledByBuyer(_tradeHash); | |
transferMinusFees(_seller, _value, _gasFees, 0); | |
return true; | |
} | |
uint16 constant GAS_doSellerCancel = 36100; | |
function doSellerCancel( | |
/** | |
* Cancels the trade and returns the ether to the seller. | |
* Can only be called the seller. | |
* Can only be called if the payment window was missed by the buyer | |
*/ | |
bytes16 _tradeID, | |
address _seller, | |
address _buyer, | |
uint256 _value, | |
uint16 _fee, | |
uint128 _additionalGas | |
) private returns (bool) { | |
var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee); | |
if (!_escrow.exists) return false; | |
if(_escrow.sellerCanCancelAfter <= 1 || _escrow.sellerCanCancelAfter > block.timestamp) return false; | |
uint128 _gasFees = _escrow.totalGasFeesSpentByRelayer + (msg.sender == relayer ? (GAS_doSellerCancel + _additionalGas) * uint128(tx.gasprice) : 0); | |
delete escrows[_tradeHash]; | |
CancelledBySeller(_tradeHash); | |
transferMinusFees(_seller, _value, _gasFees, 0); | |
return true; | |
} | |
uint16 constant GAS_doSellerRequestCancel = 12100; | |
function doSellerRequestCancel( | |
/** | |
* Called by the seller if the buyer is unresponsive | |
* Can only be called on unlimited payment window trades (sellerCanCancelAfter == 1) | |
* Sets the payment window to `requestCancellationMinimumTime` from now, in which it can be cancelled. | |
*/ | |
bytes16 _tradeID, | |
address _seller, | |
address _buyer, | |
uint256 _value, | |
uint16 _fee, | |
uint128 _additionalGas | |
) private returns (bool) { | |
// Called on unlimited payment window trades wheret the buyer is not responding | |
var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee); | |
if (!_escrow.exists) return false; | |
if(_escrow.sellerCanCancelAfter != 1) return false; | |
escrows[_tradeHash].sellerCanCancelAfter = uint32(block.timestamp) + requestCancellationMinimumTime; | |
SellerRequestedCancel(_tradeHash); | |
if (msg.sender == relayer) { | |
increaseGasSpent(_tradeHash, GAS_doSellerRequestCancel + _additionalGas); | |
} | |
return true; | |
} | |
uint16 constant GAS_doResolveDispute = 36100; | |
function resolveDispute( | |
/** | |
* Called by the arbitrator to resolve a dispute | |
* Requires the signed ACTION_DISPUTE actionByte from either the buyer or the seller | |
*/ | |
bytes16 _tradeID, | |
address _seller, | |
address _buyer, | |
uint256 _value, | |
uint16 _fee, | |
uint8 _v, | |
bytes32 _r, | |
bytes32 _s, | |
uint8 _buyerPercent | |
) external onlyArbitrator { | |
address _signature = ecrecover(keccak256(_tradeID, ACTION_DISPUTE), _v, _r, _s); | |
require(_signature == _buyer || _signature == _seller); | |
var (_escrow, _tradeHash) = getEscrowAndHash(_tradeID, _seller, _buyer, _value, _fee); | |
require(_escrow.exists); | |
require(_buyerPercent <= 100); | |
uint256 _totalFees = _escrow.totalGasFeesSpentByRelayer + GAS_doResolveDispute; | |
require(_value - _totalFees <= _value); // Prevent underflow | |
feesAvailableForWithdraw += _totalFees; // Add the the pot for localethereum to withdraw | |
delete escrows[_tradeHash]; | |
DisputeResolved(_tradeHash); | |
_buyer.transfer((_value - _totalFees) * _buyerPercent / 100); | |
_seller.transfer((_value - _totalFees) * (100 - _buyerPercent) / 100); | |
} | |
function release(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee) external returns (bool){ | |
require(msg.sender == _seller); | |
return doRelease(_tradeID, _seller, _buyer, _value, _fee, 0); | |
} | |
function disableSellerCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee) external returns (bool) { | |
require(msg.sender == _buyer); | |
return doDisableSellerCancel(_tradeID, _seller, _buyer, _value, _fee, 0); | |
} | |
function buyerCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee) external returns (bool) { | |
require(msg.sender == _buyer); | |
return doBuyerCancel(_tradeID, _seller, _buyer, _value, _fee, 0); | |
} | |
function sellerCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee) external returns (bool) { | |
require(msg.sender == _seller); | |
return doSellerCancel(_tradeID, _seller, _buyer, _value, _fee, 0); | |
} | |
function sellerRequestCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee) external returns (bool) { | |
require(msg.sender == _seller); | |
return doSellerRequestCancel(_tradeID, _seller, _buyer, _value, _fee, 0); | |
} | |
function relaySellerCannotCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee, uint128 _maximumGasPrice, uint8 _v, bytes32 _r, bytes32 _s) external returns (bool) { | |
return relay(_tradeID, _seller, _buyer, _value, _fee, _maximumGasPrice, _v, _r, _s, ACTION_SELLER_CANNOT_CANCEL, 0); | |
} | |
function relayBuyerCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee, uint128 _maximumGasPrice, uint8 _v, bytes32 _r, bytes32 _s) external returns (bool) { | |
return relay(_tradeID, _seller, _buyer, _value, _fee, _maximumGasPrice, _v, _r, _s, ACTION_BUYER_CANCEL, 0); | |
} | |
function relayRelease(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee, uint128 _maximumGasPrice, uint8 _v, bytes32 _r, bytes32 _s) external returns (bool) { | |
return relay(_tradeID, _seller, _buyer, _value, _fee, _maximumGasPrice, _v, _r, _s, ACTION_RELEASE, 0); | |
} | |
function relaySellerCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee, uint128 _maximumGasPrice, uint8 _v, bytes32 _r, bytes32 _s) external returns (bool) { | |
return relay(_tradeID, _seller, _buyer, _value, _fee, _maximumGasPrice, _v, _r, _s, ACTION_SELLER_CANCEL, 0); | |
} | |
function relaySellerRequestCancel(bytes16 _tradeID, address _seller, address _buyer, uint256 _value, uint16 _fee, uint128 _maximumGasPrice, uint8 _v, bytes32 _r, bytes32 _s) external returns (bool) { | |
return relay(_tradeID, _seller, _buyer, _value, _fee, _maximumGasPrice, _v, _r, _s, ACTION_SELLER_REQUEST_CANCEL, 0); | |
} | |
function relay( | |
bytes16 _tradeID, | |
address _seller, | |
address _buyer, | |
uint256 _value, | |
uint16 _fee, | |
uint128 _maximumGasPrice, | |
uint8 _v, | |
bytes32 _r, | |
bytes32 _s, | |
uint8 _actionByte, | |
uint128 _additionalGas | |
) private returns (bool) { | |
address _relayedSender = getRelayedSender(_tradeID, _actionByte, _maximumGasPrice, _v, _r, _s); | |
if (_relayedSender == _buyer) { | |
if (_actionByte == ACTION_SELLER_CANNOT_CANCEL) { | |
return doDisableSellerCancel(_tradeID, _seller, _buyer, _value, _fee, _additionalGas); | |
} else if (_actionByte == ACTION_BUYER_CANCEL) { | |
return doBuyerCancel(_tradeID, _seller, _buyer, _value, _fee, _additionalGas); | |
} | |
} else if (_relayedSender == _seller) { | |
if (_actionByte == ACTION_RELEASE) { | |
return doRelease(_tradeID, _seller, _buyer, _value, _fee, _additionalGas); | |
} else if (_actionByte == ACTION_SELLER_CANCEL) { | |
return doSellerCancel(_tradeID, _seller, _buyer, _value, _fee, _additionalGas); | |
} else if (_actionByte == ACTION_SELLER_REQUEST_CANCEL){ | |
return doSellerRequestCancel(_tradeID, _seller, _buyer, _value, _fee, _additionalGas); | |
} | |
} else { | |
return false; | |
} | |
} | |
uint16 constant GAS_batchRelayBaseCost = 28500; | |
function batchRelay( | |
/** | |
* Call multiple relay methods at once to save on gas. | |
*/ | |
bytes16[] _tradeID, | |
address[] _seller, | |
address[] _buyer, | |
uint256[] _value, | |
uint16[] _fee, | |
uint128[] _maximumGasPrice, | |
uint8[] _v, | |
bytes32[] _r, | |
bytes32[] _s, | |
uint8[] _actionByte | |
) public returns (bool[]) { | |
bool[] memory _results = new bool[](_tradeID.length); | |
uint128 _additionalGas = uint128(msg.sender == relayer ? GAS_batchRelayBaseCost / _tradeID.length : 0); | |
for (uint8 i=0; i<_tradeID.length; i++) { | |
_results[i] = relay(_tradeID[i], _seller[i], _buyer[i], _value[i], _fee[i], _maximumGasPrice[i], _v[i], _r[i], _s[i], _actionByte[i], _additionalGas); | |
} | |
return _results; | |
} | |
function increaseGasSpent(bytes32 _tradeHash, uint128 _gas) private { | |
/** Increase `totalGasFeesSpentByRelayer` to be charged later on completion of the trade. | |
*/ | |
escrows[_tradeHash].totalGasFeesSpentByRelayer += _gas * uint128(tx.gasprice); | |
} | |
function transferMinusFees(address _to, uint256 _value, uint128 _totalGasFeesSpentByRelayer, uint16 _fee) private { | |
uint256 _totalFees = (_value * _fee / 10000) + _totalGasFeesSpentByRelayer; | |
if(_value - _totalFees > _value) return; // Prevent underflow | |
feesAvailableForWithdraw += _totalFees; // Add the the pot for localethereum to withdraw | |
_to.transfer(_value - _totalFees); | |
} | |
function withdrawFees(address _to, uint256 _amount) onlyOwner external { | |
/** | |
* Withdraw fees collected by the contract. Only the owner can call this. | |
*/ | |
require(_amount <= feesAvailableForWithdraw); // Also prevents underflow | |
feesAvailableForWithdraw -= _amount; | |
_to.transfer(_amount); | |
} | |
function setArbitrator(address _newArbitrator) onlyOwner external { | |
/** | |
* Set the arbitrator to a new address. Only the owner can call this. | |
* @param address _newArbitrator | |
*/ | |
arbitrator = _newArbitrator; | |
} | |
function setOwner(address _newOwner) onlyOwner external { | |
/** | |
* Change the owner to a new address. Only the owner can call this. | |
* @param address _newOwner | |
*/ | |
owner = _newOwner; | |
} | |
function setRelayer(address _newRelayer) onlyOwner external { | |
/** | |
* Change the relayer to a new address. Only the owner can call this. | |
* @param address _newRelayer | |
*/ | |
relayer = _newRelayer; | |
} | |
function setRequestCancellationMinimumTime(uint32 _newRequestCancellationMinimumTime) onlyOwner external { | |
/** | |
* Change the requestCancellationMinimumTime. Only the owner can call this. | |
* @param uint32 _newRequestCancellationMinimumTime | |
*/ | |
requestCancellationMinimumTime = _newRequestCancellationMinimumTime; | |
} | |
function transferToken(Token _tokenContract, address _transferTo, uint256 _value) onlyOwner external { | |
/** | |
* If ERC20 tokens are sent to this contract, they will be trapped forever. | |
* This function is way for us to withdraw them so we can get them back to their rightful owner | |
*/ | |
_tokenContract.transfer(_transferTo, _value); | |
} | |
function transferTokenFrom(Token _tokenContract, address _transferTo, address _transferFrom, uint256 _value) onlyOwner external { | |
/** | |
* If ERC20 tokens are sent to this contract, they will be trapped forever. | |
* This function is way for us to withdraw them so we can get them back to their rightful owner | |
*/ | |
_tokenContract.transferFrom(_transferTo, _transferFrom, _value); | |
} | |
function approveToken(Token _tokenContract, address _spender, uint256 _value) onlyOwner external { | |
/** | |
* If ERC20 tokens are sent to this contract, they will be trapped forever. | |
* This function is way for us to withdraw them so we can get them back to their rightful owner | |
*/ | |
_tokenContract.approve(_spender, _value); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment