Last active
February 6, 2020 12:42
-
-
Save richard-ramos/90fc42a83a76e02e7f33241fa3ba4084 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.5.7; | |
// Abstract contract for the full ERC 20 Token standard | |
// https://github.com/ethereum/EIPs/issues/20 | |
interface ERC20Token { | |
/** | |
* @notice send `_value` token to `_to` from `msg.sender` | |
* @param _to The address of the recipient | |
* @param _value The amount of token to be transferred | |
* @return Whether the transfer was successful or not | |
*/ | |
function transfer(address _to, uint256 _value) external returns (bool success); | |
/** | |
* @notice `msg.sender` approves `_spender` to spend `_value` tokens | |
* @param _spender The address of the account able to transfer the tokens | |
* @param _value The amount of tokens to be approved for transfer | |
* @return Whether the approval was successful or not | |
*/ | |
function approve(address _spender, uint256 _value) external returns (bool success); | |
/** | |
* @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` | |
* @param _from The address of the sender | |
* @param _to The address of the recipient | |
* @param _value The amount of token to be transferred | |
* @return Whether the transfer was successful or not | |
*/ | |
function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); | |
/** | |
* @param _owner The address from which the balance will be retrieved | |
* @return The balance | |
*/ | |
function balanceOf(address _owner) external view returns (uint256 balance); | |
/** | |
* @param _owner The address of the account owning tokens | |
* @param _spender The address of the account able to transfer the tokens | |
* @return Amount of remaining tokens allowed to spent | |
*/ | |
function allowance(address _owner, address _spender) external view returns (uint256 remaining); | |
/** | |
* @notice return total supply of tokens | |
*/ | |
function totalSupply() external view returns (uint256 supply); | |
event Transfer(address indexed _from, address indexed _to, uint256 _value); | |
event Approval(address indexed _owner, address indexed _spender, uint256 _value); | |
} | |
/** | |
* @notice Standard ERC20 token for tests | |
*/ | |
contract StandardToken is ERC20Token { | |
uint256 private supply; | |
mapping (address => uint256) balances; | |
mapping (address => mapping (address => uint256)) allowed; | |
constructor() public { } | |
/** | |
* @notice send `_value` token to `_to` from `msg.sender` | |
* @param _to The address of the recipient | |
* @param _value The amount of token to be transferred | |
* @return Whether the transfer was successful or not | |
*/ | |
function transfer( | |
address _to, | |
uint256 _value | |
) | |
external | |
returns (bool success) | |
{ | |
return transfer(msg.sender, _to, _value); | |
} | |
/** | |
* @notice `msg.sender` approves `_spender` to spend `_value` tokens | |
* @param _spender The address of the account able to transfer the tokens | |
* @param _value The amount of tokens to be approved for transfer | |
* @return Whether the approval was successful or not | |
*/ | |
function approve(address _spender, uint256 _value) | |
external | |
returns (bool success) | |
{ | |
allowed[msg.sender][_spender] = _value; | |
emit Approval(msg.sender, _spender, _value); | |
return true; | |
} | |
/** | |
* @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` | |
* @param _from The address of the sender | |
* @param _to The address of the recipient | |
* @param _value The amount of token to be transferred | |
* @return Whether the transfer was successful or not | |
*/ | |
function transferFrom( | |
address _from, | |
address _to, | |
uint256 _value | |
) | |
external | |
returns (bool success) | |
{ | |
if (balances[_from] >= _value && | |
allowed[_from][msg.sender] >= _value && | |
_value > 0) { | |
allowed[_from][msg.sender] -= _value; | |
return transfer(_from, _to, _value); | |
} else { | |
return false; | |
} | |
} | |
/** | |
* @param _owner The address from which the balance will be retrieved | |
* @param _spender The address of the account able to spend the tokens | |
* @return The balance | |
*/ | |
function allowance(address _owner, address _spender) | |
external | |
view | |
returns (uint256 remaining) | |
{ | |
return allowed[_owner][_spender]; | |
} | |
/** | |
* @param _owner The address of the account owning tokens | |
* @return Amount of remaining tokens allowed to spent | |
*/ | |
function balanceOf(address _owner) | |
external | |
view | |
returns (uint256 balance) | |
{ | |
return balances[_owner]; | |
} | |
/** | |
* @notice return total supply of tokens | |
*/ | |
function totalSupply() | |
external | |
view | |
returns(uint256 currentTotalSupply) | |
{ | |
return supply; | |
} | |
/** | |
* @notice Mints tokens for testing | |
*/ | |
function mint( | |
address _to, | |
uint256 _amount | |
) | |
public | |
{ | |
balances[_to] += _amount; | |
supply += _amount; | |
emit Transfer(address(0), _to, _amount); | |
} | |
/** | |
* @notice send `_value` token to `_to` from `_from` | |
* @param _from The address of the sender | |
* @param _to The address of the recipient | |
* @param _value The amount of token to be transferred | |
* @return Whether the transfer was successful or not | |
*/ | |
function transfer( | |
address _from, | |
address _to, | |
uint256 _value | |
) | |
internal | |
returns (bool success) | |
{ | |
if (balances[_from] >= _value && _value > 0) { | |
balances[_from] -= _value; | |
balances[_to] += _value; | |
emit Transfer(_from, _to, _value); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
} | |
contract ReentrancyGuard { | |
bool public locked = false; | |
/** | |
* @dev Use this modifier on functions susceptible to reentrancy attacks | |
*/ | |
modifier reentrancyGuard() { | |
require(!locked, "Reentrant call detected!"); | |
locked = true; | |
_; | |
locked = false; | |
} | |
} | |
contract SafeTransfer { | |
function _safeTransfer(ERC20Token _token, address _to, uint256 _value) internal returns (bool result) { | |
_token.transfer(_to, _value); | |
assembly { | |
switch returndatasize() | |
case 0 { | |
result := not(0) | |
} | |
case 32 { | |
returndatacopy(0, 0, 32) | |
result := mload(0) | |
} | |
default { | |
revert(0, 0) | |
} | |
} | |
require(result, "Unsuccessful token transfer"); | |
} | |
function _safeTransferFrom( | |
ERC20Token _token, | |
address _from, | |
address _to, | |
uint256 _value | |
) internal returns (bool result) | |
{ | |
_token.transferFrom(_from, _to, _value); | |
assembly { | |
switch returndatasize() | |
case 0 { | |
result := not(0) | |
} | |
case 32 { | |
returndatacopy(0, 0, 32) | |
result := mload(0) | |
} | |
default { | |
revert(0, 0) | |
} | |
} | |
require(result, "Unsuccessful token transfer"); | |
} | |
} | |
/** | |
* @title Ownable | |
* @dev The Ownable contract has an owner address, and provides basic authorization control | |
* functions, this simplifies the implementation of "user permissions". | |
*/ | |
contract Ownable { | |
address private _owner; | |
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | |
/** | |
* @dev The Ownable constructor sets the original `owner` of the contract to the sender | |
* account. | |
*/ | |
constructor () internal { | |
_owner = msg.sender; | |
emit OwnershipTransferred(address(0), _owner); | |
} | |
/** | |
* @dev is sender the owner of the contract? | |
* @return true if `msg.sender` is the owner of the contract. | |
*/ | |
function isOwner() public view returns (bool) { | |
return msg.sender == _owner; | |
} | |
/** | |
* @dev Throws if called by any account other than the owner. | |
*/ | |
modifier onlyOwner() { | |
require(isOwner(), "Only the contract's owner can invoke this function"); | |
_; | |
} | |
/** | |
* @dev Allows the current owner to relinquish control of the contract. | |
* Renouncing to ownership will leave the contract without an owner. | |
* It will not be possible to call the functions with the `onlyOwner` | |
* modifier anymore. | |
*/ | |
function renounceOwnership() external onlyOwner { | |
emit OwnershipTransferred(_owner, address(0)); | |
_owner = address(0); | |
} | |
/** | |
* @dev Allows the current owner to transfer control of the contract to a newOwner. | |
* @param _newOwner The address to transfer ownership to. | |
*/ | |
function transferOwnership(address _newOwner) external onlyOwner { | |
_transferOwnership(_newOwner); | |
} | |
/** | |
* @dev Get the contract's owner | |
* @return the address of the owner. | |
*/ | |
function owner() public view returns (address) { | |
return _owner; | |
} | |
/** | |
* @dev Transfers control of the contract to a newOwner. | |
* @param _newOwner The address to transfer ownership to. | |
*/ | |
function _transferOwnership(address _newOwner) internal { | |
require(_newOwner != address(0), "New owner cannot be address(0)"); | |
emit OwnershipTransferred(_owner, _newOwner); | |
_owner = _newOwner; | |
} | |
/** | |
* @dev Sets an owner address | |
* @param _newOwner new owner address | |
*/ | |
function _setOwner(address _newOwner) internal { | |
_owner = _newOwner; | |
} | |
} | |
/** | |
* @title Pausable | |
* @dev Makes contract functions pausable by the owner | |
*/ | |
contract Pausable is Ownable { | |
event Paused(); | |
event Unpaused(); | |
bool public paused; | |
constructor () internal { | |
paused = false; | |
} | |
modifier whenNotPaused() { | |
require(!paused, "Contract must be unpaused"); | |
_; | |
} | |
modifier whenPaused() { | |
require(paused, "Contract must be paused"); | |
_; | |
} | |
/** | |
* @dev Disables contract functions marked with "whenNotPaused" and enables the use of functions marked with "whenPaused" | |
* Only the owner of the contract can invoke this function | |
*/ | |
function pause() external onlyOwner whenNotPaused { | |
paused = true; | |
emit Paused(); | |
} | |
/** | |
* @dev Enables contract functions marked with "whenNotPaused" and disables the use of functions marked with "whenPaused" | |
* Only the owner of the contract can invoke this function | |
*/ | |
function unpause() external onlyOwner whenPaused { | |
paused = false; | |
emit Unpaused(); | |
} | |
} | |
contract Escrow is Ownable, Pausable, ReentrancyGuard, SafeTransfer { | |
Transaction[] public transactions; | |
enum EscrowStatus {NONE, ACTIVE, RELEASED, CANCELED} | |
struct Transaction { | |
EscrowStatus status; | |
address[] tokenAddress; | |
uint256[] tokenAmount; | |
uint256 expirationTime; | |
address payable owner; | |
bytes32 hash; | |
} | |
mapping(bytes32 => bool) hashDictionary; | |
function tokenCnt(uint escrowId) public view returns (uint) { | |
Transaction storage trx = transactions[escrowId]; | |
return trx.tokenAddress.length; | |
} | |
function tokenInfo(uint escrowId, uint i) public view returns (address tokenAddress, uint tokenAmount) { | |
Transaction storage trx = transactions[escrowId]; | |
tokenAddress = trx.tokenAddress[i]; | |
tokenAmount = trx.tokenAmount[i]; | |
} | |
event Created(uint indexed escrowId, address indexed owner); | |
event Released(uint indexed escrowId, address indexed owner); | |
event Canceled(uint indexed escrowId, address indexed owner); | |
uint internal deadline = 300; // 5 minutes, TODO: replace for 72 hours | |
function createEscrow( | |
bytes32 _hash, | |
address[] memory _tokenAddress, | |
uint[] memory _tokenAmount | |
) public | |
payable | |
whenNotPaused | |
returns(uint escrowId) { | |
require(!hashDictionary[_hash], "Hash already used"); | |
escrowId = transactions.length++; | |
Transaction storage trx = transactions[escrowId]; | |
trx.status = EscrowStatus.ACTIVE; | |
trx.expirationTime = block.timestamp + deadline; | |
trx.owner = msg.sender; | |
trx.hash = _hash; | |
hashDictionary[_hash] = true; | |
uint length = _tokenAddress.length; | |
uint currValue = msg.value; | |
require(length == _tokenAmount.length, "Invalid input"); | |
for(uint i = 0; i < length; i++){ | |
trx.tokenAddress.push(_tokenAddress[i]); | |
trx.tokenAmount.push(_tokenAmount[i]); | |
require(_tokenAmount[i] != 0, "Amounts cannot be 0"); | |
if (_tokenAddress[i] != address(0)) { | |
ERC20Token tokenToPay = ERC20Token(_tokenAddress[i]); | |
require(_safeTransferFrom(tokenToPay, msg.sender, address(this), _tokenAmount[i]), "Unsuccessful token transfer"); | |
} else { | |
require(_tokenAmount[i] <= currValue, "ETH amount is required"); | |
currValue -= _tokenAmount[i]; | |
} | |
} | |
emit Created(escrowId, msg.sender); | |
} | |
function release(uint _escrowId, string memory code) public whenNotPaused reentrancyGuard { | |
Transaction storage trx = transactions[_escrowId]; | |
require(block.timestamp < trx.expirationTime, "Already expired"); | |
require(trx.status == EscrowStatus.ACTIVE, "Escrow not active"); | |
require(keccak256(abi.encodePacked(code)) == trx.hash, "Invalid code"); | |
trx.status = EscrowStatus.RELEASED; | |
uint length = trx.tokenAddress.length; | |
for(uint i = 0; i < length; i++){ | |
if(trx.tokenAddress[i] == address(0)){ | |
(bool success, ) = msg.sender.call.value(trx.tokenAmount[i])(""); | |
require(success, "Transfer failed."); | |
} else { | |
require(_safeTransfer(ERC20Token(trx.tokenAddress[i]), msg.sender, trx.tokenAmount[i]), "Couldn't transfer funds"); | |
} | |
} | |
emit Released(_escrowId, trx.owner); | |
} | |
function release(uint _escrowId, address payable _destination) public whenNotPaused reentrancyGuard { | |
Transaction storage trx = transactions[_escrowId]; | |
require(block.timestamp < trx.expirationTime, "Already expired"); | |
require(trx.status == EscrowStatus.ACTIVE, "Escrow not active"); | |
trx.status = EscrowStatus.RELEASED; | |
uint length = trx.tokenAddress.length; | |
for(uint i = 0; i < length; i++){ | |
if(trx.tokenAddress[i] == address(0)){ | |
(bool success, ) = _destination.call.value(trx.tokenAmount[i])(""); | |
require(success, "Transfer failed."); | |
} else { | |
require(_safeTransfer(ERC20Token(trx.tokenAddress[i]), _destination, trx.tokenAmount[i]), "Couldn't transfer funds"); | |
} | |
} | |
emit Released(_escrowId, trx.owner); | |
} | |
function cancel(uint _escrowId) external reentrancyGuard { | |
Transaction storage trx = transactions[_escrowId]; | |
require(trx.status == EscrowStatus.ACTIVE, "Only active transactions can be canceled"); | |
require(trx.owner == msg.sender, "Only the owner can invoke this function"); | |
trx.status = EscrowStatus.CANCELED; | |
uint length = trx.tokenAddress.length; | |
for(uint i = 0; i < length; i++){ | |
if(trx.tokenAddress[i] == address(0)){ | |
(bool success, ) = trx.owner.call.value(trx.tokenAmount[i])(""); | |
require(success, "Transfer failed."); | |
} else { | |
require(_safeTransfer(ERC20Token(trx.tokenAddress[i]), trx.owner, trx.tokenAmount[i]), "Couldn't transfer funds"); | |
} | |
} | |
} | |
function expired(uint _escrowId) external reentrancyGuard { | |
Transaction storage trx = transactions[_escrowId]; | |
require(trx.status == EscrowStatus.ACTIVE, "Only transactions active can be canceled"); | |
trx.status = EscrowStatus.CANCELED; | |
uint length = trx.tokenAddress.length; | |
for(uint i = 0; i < length; i++){ | |
if(trx.tokenAddress[i] == address(0)){ | |
(bool success, ) = trx.owner.call.value(trx.tokenAmount[i])(""); | |
require(success, "Transfer failed."); | |
} else { | |
require(_safeTransfer(ERC20Token(trx.tokenAddress[i]), trx.owner, trx.tokenAmount[i]), "Couldn't transfer funds"); | |
} | |
} | |
} | |
} | |
/* | |
To create an escrow | |
web3.utils.soliditySha3("ABCDE") | |
"0x5122f081847f611bf434067d91ae40ea84ac717c60b0cd66d9a7b6bf041107af", ["0x0000000000000000000000000000000000000000"], ["1000000000000000000"] | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment