Skip to content

Instantly share code, notes, and snippets.

@richard-ramos
Last active February 6, 2020 12:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save richard-ramos/90fc42a83a76e02e7f33241fa3ba4084 to your computer and use it in GitHub Desktop.
Save richard-ramos/90fc42a83a76e02e7f33241fa3ba4084 to your computer and use it in GitHub Desktop.
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