Skip to content

Commit

Permalink
Merge 47af090 into 3c3c65a
Browse files Browse the repository at this point in the history
  • Loading branch information
tinchoabbate committed Aug 31, 2018
2 parents 3c3c65a + 47af090 commit 0ac089d
Show file tree
Hide file tree
Showing 11 changed files with 675 additions and 111 deletions.
20 changes: 20 additions & 0 deletions contracts/mocks/ConditionalTokenEscrowMock.sol
@@ -0,0 +1,20 @@
pragma solidity ^0.4.24;

import "../payment/ConditionalTokenEscrow.sol";
import "../token/ERC20/ERC20.sol";


// mock class using ConditionalTokenEscrow
contract ConditionalTokenEscrowMock is ConditionalTokenEscrow {
mapping(address => bool) public allowed;

constructor (ERC20 _token) public TokenEscrow(_token) { }

function setAllowed(address _payee, bool _allowed) public {
allowed[_payee] = _allowed;
}

function withdrawalAllowed(address _payee) public view returns (bool) {
return allowed[_payee];
}
}
23 changes: 23 additions & 0 deletions contracts/payment/ConditionalTokenEscrow.sol
@@ -0,0 +1,23 @@
pragma solidity ^0.4.24;

import "./TokenEscrow.sol";


/**
* @title ConditionalTokenEscrow
* @dev Base abstract escrow to only allow withdrawal of tokens
* if a condition is met.
*/
contract ConditionalTokenEscrow is TokenEscrow {
/**
* @dev Returns whether an address is allowed to withdraw their tokens.
* To be implemented by derived contracts.
* @param _payee The destination address of the tokens.
*/
function withdrawalAllowed(address _payee) public view returns (bool);

function withdraw(address _payee) public {
require(withdrawalAllowed(_payee));
super.withdraw(_payee);
}
}
78 changes: 78 additions & 0 deletions contracts/payment/RefundTokenEscrow.sol
@@ -0,0 +1,78 @@
pragma solidity ^0.4.24;

import "./ConditionalTokenEscrow.sol";
import "../token/ERC20/ERC20.sol";


/**
* @title RefundTokenEscrow
* @dev Escrow that holds tokens for a beneficiary, deposited from multiple parties.
* The contract owner may close the deposit period, and allow for either withdrawal
* by the beneficiary, or refunds to the depositors.
*/
contract RefundTokenEscrow is ConditionalTokenEscrow {

enum State { Active, Refunding, Closed }

event Closed();
event RefundsEnabled();

State public state;
address public beneficiary;

/**
* @dev Constructor.
* @param _token Address of the ERC20 token that will be put in escrow.
* @param _beneficiary The beneficiary of the deposits.
*/
constructor(ERC20 _token, address _beneficiary) public TokenEscrow(_token) {
require(_beneficiary != address(0));
beneficiary = _beneficiary;
state = State.Active;
}

/**
* @dev Stores tokens that may later be refunded.
* @param _refundee The address tokens will be sent to if a refund occurs.
* @param _amount The amount of tokens to store.
*/
function deposit(address _refundee, uint256 _amount) public {
require(state == State.Active);
super.deposit(_refundee, _amount);
}

/**
* @dev Allows for the beneficiary to withdraw their tokens, rejecting
* further deposits.
*/
function close() public onlyOwner {
require(state == State.Active);
state = State.Closed;
emit Closed();
}

/**
* @dev Allows for refunds to take place, rejecting further deposits.
*/
function enableRefunds() public onlyOwner {
require(state == State.Active);
state = State.Refunding;
emit RefundsEnabled();
}

/**
* @dev Withdraws the beneficiary's tokens.
*/
function beneficiaryWithdraw() public {
require(state == State.Closed);
uint256 amount = token.balanceOf(address(this));
token.safeTransfer(beneficiary, amount);
}

/**
* @dev Returns whether refundees can withdraw their deposits (be refunded).
*/
function withdrawalAllowed(address _payee) public view returns (bool) {
return state == State.Refunding;
}
}
33 changes: 33 additions & 0 deletions contracts/payment/TimelockedEscrow.sol
@@ -0,0 +1,33 @@
pragma solidity ^0.4.24;

import "./ConditionalEscrow.sol";


/**
* @title TimelockedEscrow
* @dev Escrow that holds funds for given amount of time,
* preventing their withdrawal until the time has passed.
*/
contract TimelockedEscrow is ConditionalEscrow {

uint256 public releaseTime;

/**
* @dev Constructor.
* @param _releaseTime Time when the funds will be available for withdrawal.
*/
constructor (uint256 _releaseTime) public {
// solium-disable-next-line security/no-block-members
require(_releaseTime > block.timestamp);
releaseTime = _releaseTime;
}

/**
* @dev Returns whether an address is allowed to withdraw their funds.
*/
function withdrawalAllowed(address _payee) public view returns (bool) {
// solium-disable-next-line security/no-block-members
require(block.timestamp >= releaseTime);
return true;
}
}
34 changes: 34 additions & 0 deletions contracts/payment/TimelockedTokenEscrow.sol
@@ -0,0 +1,34 @@
pragma solidity ^0.4.24;

import "./ConditionalTokenEscrow.sol";


/**
* @title TimelockedTokenEscrow
* @dev Escrow that holds tokens for given amount of time,
* preventing their whithdrawal until the time has passed.
*/
contract TimelockedTokenEscrow is ConditionalTokenEscrow {

uint256 public releaseTime;

/**
* @dev Constructor.
* @param _token Address of the ERC20 token that will be put in escrow.
* @param _releaseTime Time when the tokens will be available for withdrawal.
*/
constructor (ERC20 _token, uint256 _releaseTime) public TokenEscrow(_token) {
// solium-disable-next-line security/no-block-members
require(_releaseTime > block.timestamp);
releaseTime = _releaseTime;
}

/**
* @dev Returns whether an address is allowed to withdraw their funds.
*/
function withdrawalAllowed(address _payee) public view returns (bool) {
// solium-disable-next-line security/no-block-members
require(block.timestamp >= releaseTime);
return true;
}
}
55 changes: 55 additions & 0 deletions test/payment/ConditionalTokenEscrow.test.js
@@ -0,0 +1,55 @@
const { shouldBehaveLikeTokenEscrow } = require('./TokenEscrow.behavior');
const { expectThrow } = require('../helpers/expectThrow');
const { EVMRevert } = require('../helpers/EVMRevert');

const BigNumber = web3.BigNumber;

require('chai')
.use(require('chai-bignumber')(BigNumber))
.should();

const ConditionalTokenEscrow = artifacts.require('ConditionalTokenEscrowMock');
const StandardToken = artifacts.require('StandardTokenMock');

contract('ConditionalTokenEscrow', function ([_, owner, payee, ...otherAccounts]) {
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);

it('reverts when deployed with a null token address', async function () {
await expectThrow(
ConditionalTokenEscrow.new(ZERO_ADDRESS, { from: owner }), EVMRevert
);
});

context('with token', function () {
beforeEach(async function () {
this.token = await StandardToken.new(owner, MAX_UINT256);
this.escrow = await ConditionalTokenEscrow.new(this.token.address, { from: owner });
});

context('when withdrawal is allowed', function () {
beforeEach(async function () {
await Promise.all(otherAccounts.map(
payee => this.escrow.setAllowed(payee, true))
);
});

shouldBehaveLikeTokenEscrow(owner, otherAccounts);
});

context('when withdrawal is disallowed', function () {
const amount = web3.toWei(23.0, 'ether');

beforeEach(async function () {
await this.token.approve(this.escrow.address, MAX_UINT256, { from: owner });
await this.escrow.setAllowed(payee, false);
});

it('reverts on withdrawals', async function () {
await this.escrow.deposit(payee, amount, { from: owner });

await expectThrow(this.escrow.withdraw(payee, { from: owner }), EVMRevert);
});
});
});
});

0 comments on commit 0ac089d

Please sign in to comment.