Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
675 additions
and
111 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.