From 3b29f750d0638864e55ab1b3908efc8e4f57542c Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Mon, 17 Dec 2018 16:10:38 -0400 Subject: [PATCH] feat: Created simple escrow contract --- config/contracts.js | 3 ++ contracts/escrow.sol | 95 ++++++++++++++++++++++++++++++++++++++++++ contracts/pausable.sol | 36 ++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 contracts/escrow.sol create mode 100644 contracts/pausable.sol diff --git a/config/contracts.js b/config/contracts.js index 2913c589..e5f9632e 100644 --- a/config/contracts.js +++ b/config/contracts.js @@ -54,6 +54,9 @@ module.exports = { contracts: { License: { args: [ "0x0", 1 ] + }, + Escrow: { + args: ["$License"] } } }, diff --git a/contracts/escrow.sol b/contracts/escrow.sol new file mode 100644 index 00000000..81bfaed3 --- /dev/null +++ b/contracts/escrow.sol @@ -0,0 +1,95 @@ +pragma solidity ^0.5.0; + +import "./ownable.sol"; +import "./pausable.sol"; +import "./license.sol"; + +/** + * @title Escrow + * @dev Escrow contract for buying/selling ETH. Current implementation lacks arbitrage, marking trx as paid, and ERC20 support + */ +contract Escrow is Pausable { + + constructor(address _license) public { + license = License(_license); + } + + struct EscrowTransaction { + address payable seller; + address payable buyer; + uint amount; + uint expirationTime; + bool released; + bool canceled; + } + + EscrowTransaction[] public transactions; + + License public license; + + event Created(address indexed seller, address indexed buyer, uint amount, uint escrowId); + event Paid(uint escrowId); + event Canceled(uint escrowId); + + function create(address payable _buyer, uint _expirationTime) public payable whenNotPaused { + require(_expirationTime > (block.timestamp + 600), "Expiration time must be at least 10min in the future"); + require(msg.value > 0, "ETH amount is required"); // TODO: abstract this to use ERC20. Maybe thru the use of wETH + require(license.isLicenseOwners(msg.sender), "Must be a valid seller to create escrow transactions"); + + uint escrowId = transactions.length++; + + transactions[escrowId].seller = msg.sender; + transactions[escrowId].buyer = _buyer; + transactions[escrowId].amount = msg.value; + transactions[escrowId].expirationTime = _expirationTime; + transactions[escrowId].released = false; + transactions[escrowId].canceled = false; + + emit Created(msg.sender, _buyer, msg.value, escrowId); + } + + function release(uint _escrowId) public whenNotPaused { + require(_escrowId < transactions.length, "Invalid escrow id"); + + EscrowTransaction storage trx = transactions[_escrowId]; + + require(trx.released == false, "Transaction already released"); + require(trx.canceled == false, "Transaction already canceled"); + require(trx.expirationTime > block.timestamp, "Transaction already expired"); + require(trx.seller == msg.sender, "Function can only be invoked by the escrow owner"); + + trx.released = true; + trx.buyer.transfer(trx.amount); // TODO: transfer fee to Status + + emit Paid(_escrowId); + } + + function cancel(uint _escrowId) public whenNotPaused { + require(_escrowId < transactions.length, "Invalid escrow id"); + + EscrowTransaction storage trx = transactions[_escrowId]; + + require(trx.released == false, "Transaction already released"); + require(trx.canceled == false, "Transaction already canceled"); + require(trx.seller == msg.sender, "Function can only be invoked by the escrow owner"); + + trx.canceled = true; + trx.seller.transfer(trx.amount); + + emit Canceled(_escrowId); + } + + function withdraw_emergency(uint _escrowId) public whenPaused { + require(_escrowId < transactions.length, "Invalid escrow id"); + + EscrowTransaction storage trx = transactions[_escrowId]; + + require(trx.released == false, "Transaction already released"); + require(trx.canceled == false, "Transaction already canceled"); + + trx.canceled = true; + trx.seller.transfer(trx.amount); + + emit Canceled(_escrowId); + } +} diff --git a/contracts/pausable.sol b/contracts/pausable.sol new file mode 100644 index 00000000..6e95228d --- /dev/null +++ b/contracts/pausable.sol @@ -0,0 +1,36 @@ +pragma solidity ^0.5.0; + +import "./ownable.sol"; + +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"); + _; + } + + function pause() public onlyOwner whenNotPaused { + paused = true; + emit Paused(); + } + + + function unpause() public onlyOwner whenPaused { + paused = false; + emit Unpaused(); + } +} \ No newline at end of file