Transaction Hash:
Block:
5157674 at Feb-26-2018 04:33:19 AM +UTC
Transaction Fee:
0.0002640222 ETH
$0.52
Gas Used:
75,220 Gas / 3.51 Gwei
Emitted Events:
| 81 |
LocalEthereumEscrows.Released( _tradeHash=5A58FE8D8387FE94239DA04645C30040956AA9DA3104FB0CB3DA557C3301FE45 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x09678741...07099Ae5d | (LocalEthereum 2) | 107.72400730281593825 Eth | 107.49752404881593825 Eth | 0.226483254 | |
|
0xb2930B35...e543a0347
Miner
| (MiningPoolHub: Old Address) | 16,538.57654870238544516 Eth | 16,538.57681272458544516 Eth | 0.0002640222 | |
| 0xF008E2c7...442016420 |
2.222333165270167614 Eth
Nonce: 26990
|
2.222069143070167614 Eth
Nonce: 26991
| 0.0002640222 | ||
| 0xfe2f0419...f050c13d8 |
0 Eth
Nonce: 0
|
0.226483254 Eth
Nonce: 0
| 0.226483254 |
Execution Trace
LocalEthereumEscrows.batchRelay( ) => ( [true] )
-
Null: 0x000...001.c7811b33( ) - ETH 0.226483254
0xfe2f04194f86e93f3676ed12ea3ca80f050c13d8.CALL( )
batchRelay[LocalEthereumEscrows (ln:342)]
relay[LocalEthereumEscrows (ln:360)]getRelayedSender[LocalEthereumEscrows (ln:321)]ecrecover[LocalEthereumEscrows (ln:64)]
doDisableSellerCancel[LocalEthereumEscrows (ln:324)]getEscrowAndHash[LocalEthereumEscrows (ln:155)]SellerCancelDisabled[LocalEthereumEscrows (ln:159)]increaseGasSpent[LocalEthereumEscrows (ln:161)]
doBuyerCancel[LocalEthereumEscrows (ln:326)]getEscrowAndHash[LocalEthereumEscrows (ln:179)]CancelledByBuyer[LocalEthereumEscrows (ln:183)]transferMinusFees[LocalEthereumEscrows (ln:184)]transfer[LocalEthereumEscrows (ln:375)]
doRelease[LocalEthereumEscrows (ln:330)]getEscrowAndHash[LocalEthereumEscrows (ln:132)]Released[LocalEthereumEscrows (ln:136)]transferMinusFees[LocalEthereumEscrows (ln:137)]transfer[LocalEthereumEscrows (ln:375)]
doSellerCancel[LocalEthereumEscrows (ln:332)]getEscrowAndHash[LocalEthereumEscrows (ln:202)]CancelledBySeller[LocalEthereumEscrows (ln:207)]transferMinusFees[LocalEthereumEscrows (ln:208)]transfer[LocalEthereumEscrows (ln:375)]
doSellerRequestCancel[LocalEthereumEscrows (ln:334)]getEscrowAndHash[LocalEthereumEscrows (ln:227)]SellerRequestedCancel[LocalEthereumEscrows (ln:231)]increaseGasSpent[LocalEthereumEscrows (ln:233)]
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);
}
}