Commit e748c3ea by Manuel Aráoz Committed by GitHub

Merge pull request #283 from OpenZeppelin/crowdsale

Add Crowdsale contracts
parents 96b550b7 b961eea8
...@@ -7,7 +7,6 @@ node_js: ...@@ -7,7 +7,6 @@ node_js:
cache: cache:
yarn: true yarn: true
script: script:
- testrpc > /dev/null & - yarn test
- truffle test
after_script: after_script:
- yarn run coveralls - yarn run coveralls
pragma solidity ^0.4.11;
import '../SafeMath.sol';
import './Crowdsale.sol';
/**
* @title CappedCrowdsale
* @dev Extension of Crowsdale with a max amount of funds raised
*/
contract CappedCrowdsale is Crowdsale {
using SafeMath for uint256;
uint256 public cap;
function CappedCrowdsale(uint256 _cap) {
cap = _cap;
}
// overriding Crowdsale#validPurchase to add extra cap logic
// @return true if investors can buy at the moment
function validPurchase() internal constant returns (bool) {
bool withinCap = weiRaised.add(msg.value) <= cap;
return super.validPurchase() && withinCap;
}
// overriding Crowdsale#hasEnded to add cap logic
// @return true if crowdsale event has ended
function hasEnded() public constant returns (bool) {
bool capReached = weiRaised >= cap;
return super.hasEnded() || capReached;
}
}
pragma solidity ^0.4.11;
import '../token/MintableToken.sol';
import '../SafeMath.sol';
/**
* @title Crowdsale
* @dev Crowdsale is a base contract for managing a token crowdsale.
* Crowdsales have a start and end block, where investors can make
* token purchases and the crowdsale will assign them tokens based
* on a token per ETH rate. Funds collected are forwarded to a wallet
* as they arrive.
*/
contract Crowdsale {
using SafeMath for uint256;
// The token being sold
MintableToken public token;
// start and end block where investments are allowed (both inclusive)
uint256 public startBlock;
uint256 public endBlock;
// address where funds are collected
address public wallet;
// how many token units a buyer gets per wei
uint256 public rate;
// amount of raised money in wei
uint256 public weiRaised;
/**
* event for token purchase logging
* @param purchaser who paid for the tokens
* @param beneficiary who got the tokens
* @param value weis paid for purchase
* @param amount amount of tokens purchased
*/
event TokenPurchase(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount);
function Crowdsale(uint256 _startBlock, uint256 _endBlock, uint256 _rate, address _wallet) {
require(_startBlock >= block.number);
require(_endBlock >= _startBlock);
require(_rate > 0);
require(_wallet != 0x0);
token = createTokenContract();
startBlock = _startBlock;
endBlock = _endBlock;
rate = _rate;
wallet = _wallet;
}
// creates the token to be sold.
// override this method to have crowdsale of a specific mintable token.
function createTokenContract() internal returns (MintableToken) {
return new MintableToken();
}
// fallback function can be used to buy tokens
function () payable {
buyTokens(msg.sender);
}
// low level token purchase function
function buyTokens(address beneficiary) payable {
require(beneficiary != 0x0);
require(validPurchase());
uint256 weiAmount = msg.value;
uint256 updatedWeiRaised = weiRaised.add(weiAmount);
// calculate token amount to be created
uint256 tokens = weiAmount.mul(rate);
// update state
weiRaised = updatedWeiRaised;
token.mint(beneficiary, tokens);
TokenPurchase(msg.sender, beneficiary, weiAmount, tokens);
forwardFunds();
}
// send ether to the fund collection wallet
// override to create custom fund forwarding mechanisms
function forwardFunds() internal {
wallet.transfer(msg.value);
}
// @return true if the transaction can buy tokens
function validPurchase() internal constant returns (bool) {
uint256 current = block.number;
bool withinPeriod = current >= startBlock && current <= endBlock;
bool nonZeroPurchase = msg.value != 0;
return withinPeriod && nonZeroPurchase;
}
// @return true if crowdsale event has ended
function hasEnded() public constant returns (bool) {
return block.number > endBlock;
}
}
pragma solidity ^0.4.11;
import '../SafeMath.sol';
import '../ownership/Ownable.sol';
import './Crowdsale.sol';
/**
* @title FinalizableCrowdsale
* @dev Extension of Crowsdale where an owner can do extra work
* after finishing. By default, it will end token minting.
*/
contract FinalizableCrowdsale is Crowdsale, Ownable {
using SafeMath for uint256;
bool public isFinalized = false;
event Finalized();
// should be called after crowdsale ends, to do
// some extra finalization work
function finalize() onlyOwner {
require(!isFinalized);
require(hasEnded());
finalization();
Finalized();
isFinalized = true;
}
// end token minting on finalization
// override this with custom logic if needed
function finalization() internal {
token.finishMinting();
}
}
pragma solidity ^0.4.11;
import '../SafeMath.sol';
import '../ownership/Ownable.sol';
/**
* @title RefundVault
* @dev This contract is used for storing funds while a crowdsale
* is in progress. Supports refunding the money if crowdsale fails,
* and forwarding it if crowdsale is successful.
*/
contract RefundVault is Ownable {
using SafeMath for uint256;
enum State { Active, Refunding, Closed }
mapping (address => uint256) public deposited;
address public wallet;
State public state;
event Closed();
event RefundsEnabled();
event Refunded(address indexed beneficiary, uint256 weiAmount);
function RefundVault(address _wallet) {
require(_wallet != 0x0);
wallet = _wallet;
state = State.Active;
}
function deposit(address investor) onlyOwner payable {
require(state == State.Active);
deposited[investor] = deposited[investor].add(msg.value);
}
function close() onlyOwner {
require(state == State.Active);
state = State.Closed;
Closed();
wallet.transfer(this.balance);
}
function enableRefunds() onlyOwner {
require(state == State.Active);
state = State.Refunding;
RefundsEnabled();
}
function refund(address investor) {
require(state == State.Refunding);
uint256 depositedValue = deposited[investor];
deposited[investor] = 0;
investor.transfer(depositedValue);
Refunded(investor, depositedValue);
}
}
pragma solidity ^0.4.11;
import '../SafeMath.sol';
import './FinalizableCrowdsale.sol';
import './RefundVault.sol';
/**
* @title RefundableCrowdsale
* @dev Extension of Crowdsale contract that adds a funding goal, and
* the possibility of users getting a refund if goal is not met.
* Uses a RefundVault as the crowdsale's vault.
*/
contract RefundableCrowdsale is FinalizableCrowdsale {
using SafeMath for uint256;
// minimum amount of funds to be raised in weis
uint256 public goal;
// refund vault used to hold funds while crowdsale is running
RefundVault public vault;
function RefundableCrowdsale(uint256 _goal) {
vault = new RefundVault(wallet);
goal = _goal;
}
// We're overriding the fund forwarding from Crowdsale.
// In addition to sending the funds, we want to call
// the RefundVault deposit function
function forwardFunds() internal {
vault.deposit.value(msg.value)(msg.sender);
}
// if crowdsale is unsuccessful, investors can claim refunds here
function claimRefund() {
require(isFinalized);
require(!goalReached());
vault.refund(msg.sender);
}
// vault finalization task, called when owner calls finalize()
function finalization() internal {
if (goalReached()) {
vault.close();
} else {
vault.enableRefunds();
}
super.finalization();
}
function goalReached() public constant returns (bool) {
return weiRaised >= goal;
}
}
pragma solidity ^0.4.11;
import "./StandardToken.sol";
/**
* @title CrowdsaleToken
*
* @dev Simple ERC20 Token example, with crowdsale token creation
* @dev IMPORTANT NOTE: do not use or deploy this contract as-is. It needs some changes to be
* production ready.
*/
contract CrowdsaleToken is StandardToken {
string public constant name = "CrowdsaleToken";
string public constant symbol = "CRW";
uint256 public constant decimals = 18;
// replace with your fund collection multisig address
address public constant multisig = 0x0;
// 1 ether = 500 example tokens
uint256 public constant PRICE = 500;
/**
* @dev Fallback function which receives ether and sends the appropriate number of tokens to the
* msg.sender.
*/
function () payable {
createTokens(msg.sender);
}
/**
* @dev Creates tokens and send to the specified address.
* @param recipient The address which will recieve the new tokens.
*/
function createTokens(address recipient) payable {
if (msg.value == 0) {
throw;
}
uint256 tokens = msg.value.mul(getPrice());
totalSupply = totalSupply.add(tokens);
balances[recipient] = balances[recipient].add(tokens);
if (!multisig.send(msg.value)) {
throw;
}
}
/**
* @dev replace this with any other price function
* @return The price per unit of token.
*/
function getPrice() constant returns (uint256 result) {
return PRICE;
}
}
...@@ -7,19 +7,15 @@ import '../lifecycle/Pausable.sol'; ...@@ -7,19 +7,15 @@ import '../lifecycle/Pausable.sol';
* Pausable token * Pausable token
* *
* Simple ERC20 Token example, with pausable token creation * Simple ERC20 Token example, with pausable token creation
* Issue:
* https://github.com/OpenZeppelin/zeppelin-solidity/issues/194
* Based on code by BCAPtoken:
* https://github.com/BCAPtoken/BCAPToken/blob/5cb5e76338cc47343ba9268663a915337c8b268e/sol/BCAPToken.sol#L27
**/ **/
contract PausableToken is StandardToken, Pausable { contract PausableToken is StandardToken, Pausable {
function transfer(address _to, uint256 _value) whenNotPaused { function transfer(address _to, uint _value) whenNotPaused {
super.transfer(_to, _value); super.transfer(_to, _value);
} }
function transferFrom(address _from, address _to, uint256 _value) whenNotPaused { function transferFrom(address _from, address _to, uint _value) whenNotPaused {
super.transferFrom(_from, _to, _value); super.transferFrom(_from, _to, _value);
} }
} }
CrowdsaleToken
=============================================
Simple ERC20 Token example, with crowdsale token creation.
Inherits from contract StandardToken.
createTokens(address recipient) payable
"""""""""""""""""""""""""""""""""""""""""
Creates tokens based on message value and credits to the recipient.
getPrice() constant returns (uint result)
"""""""""""""""""""""""""""""""""""""""""
Returns the amount of tokens per 1 ether.
\ No newline at end of file
...@@ -4,10 +4,22 @@ output=$(nc -z localhost 8545; echo $?) ...@@ -4,10 +4,22 @@ output=$(nc -z localhost 8545; echo $?)
[ $output -eq "0" ] && trpc_running=true [ $output -eq "0" ] && trpc_running=true
if [ ! $trpc_running ]; then if [ ! $trpc_running ]; then
echo "Starting our own testrpc node instance" echo "Starting our own testrpc node instance"
testrpc > /dev/null & # we give each account 1M ether, needed for high-value tests
testrpc \
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200,1000000000000000000000000" \
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501201,1000000000000000000000000" \
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501202,1000000000000000000000000" \
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501203,1000000000000000000000000" \
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501204,1000000000000000000000000" \
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501205,1000000000000000000000000" \
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501206,1000000000000000000000000" \
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501207,1000000000000000000000000" \
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501208,1000000000000000000000000" \
--account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501209,1000000000000000000000000" \
> /dev/null &
trpc_pid=$! trpc_pid=$!
fi fi
./node_modules/truffle/cli.js test ./node_modules/truffle/cli.js test "$@"
if [ ! $trpc_running ]; then if [ ! $trpc_running ]; then
kill -9 $trpc_pid kill -9 $trpc_pid
fi fi
import ether from './helpers/ether'
import advanceToBlock from './helpers/advanceToBlock'
import EVMThrow from './helpers/EVMThrow'
const BigNumber = web3.BigNumber
require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should()
const CappedCrowdsale = artifacts.require('./helpers/CappedCrowdsaleImpl.sol')
const MintableToken = artifacts.require('MintableToken')
contract('CappedCrowdsale', function ([_, wallet]) {
const rate = new BigNumber(1000)
const cap = ether(300)
const lessThanCap = ether(60)
beforeEach(async function () {
this.startBlock = web3.eth.blockNumber + 10
this.endBlock = web3.eth.blockNumber + 20
this.crowdsale = await CappedCrowdsale.new(this.startBlock, this.endBlock, rate, wallet, cap)
this.token = MintableToken.at(await this.crowdsale.token())
})
describe('accepting payments', function () {
beforeEach(async function () {
await advanceToBlock(this.startBlock - 1)
})
it('should accept payments within cap', async function () {
await this.crowdsale.send(cap.minus(lessThanCap)).should.be.fulfilled
await this.crowdsale.send(lessThanCap).should.be.fulfilled
})
it('should reject payments outside cap', async function () {
await this.crowdsale.send(cap)
await this.crowdsale.send(1).should.be.rejectedWith(EVMThrow)
})
it('should reject payments that exceed cap', async function () {
await this.crowdsale.send(cap.plus(1)).should.be.rejectedWith(EVMThrow)
})
})
describe('ending', function () {
beforeEach(async function () {
await advanceToBlock(this.startBlock - 1)
})
it('should not be ended if under cap', async function () {
let hasEnded = await this.crowdsale.hasEnded()
hasEnded.should.equal(false)
await this.crowdsale.send(lessThanCap)
hasEnded = await this.crowdsale.hasEnded()
hasEnded.should.equal(false)
})
it('should not be ended if just under cap', async function () {
await this.crowdsale.send(cap.minus(1))
let hasEnded = await this.crowdsale.hasEnded()
hasEnded.should.equal(false)
})
it('should be ended if cap reached', async function () {
await this.crowdsale.send(cap)
let hasEnded = await this.crowdsale.hasEnded()
hasEnded.should.equal(true)
})
})
})
import ether from './helpers/ether'
import advanceToBlock from './helpers/advanceToBlock'
import EVMThrow from './helpers/EVMThrow'
const BigNumber = web3.BigNumber
const should = require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should()
const Crowdsale = artifacts.require('Crowdsale')
const MintableToken = artifacts.require('MintableToken')
contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
const rate = new BigNumber(1000)
const value = ether(42)
const expectedTokenAmount = rate.mul(value)
beforeEach(async function () {
this.startBlock = web3.eth.blockNumber + 10
this.endBlock = web3.eth.blockNumber + 20
this.crowdsale = await Crowdsale.new(this.startBlock, this.endBlock, rate, wallet)
this.token = MintableToken.at(await this.crowdsale.token())
})
it('should be token owner', async function () {
const owner = await this.token.owner()
owner.should.equal(this.crowdsale.address)
})
it('should be ended only after end', async function () {
let ended = await this.crowdsale.hasEnded()
ended.should.equal(false)
await advanceToBlock(this.endBlock + 1)
ended = await this.crowdsale.hasEnded()
ended.should.equal(true)
})
describe('accepting payments', function () {
it('should reject payments before start', async function () {
await this.crowdsale.send(value).should.be.rejectedWith(EVMThrow)
await this.crowdsale.buyTokens(investor, value, {from: purchaser}).should.be.rejectedWith(EVMThrow)
})
it('should accept payments after start', async function () {
await advanceToBlock(this.startBlock - 1)
await this.crowdsale.send(value).should.be.fulfilled
await this.crowdsale.buyTokens(investor, {value: value, from: purchaser}).should.be.fulfilled
})
it('should reject payments after end', async function () {
await advanceToBlock(this.endBlock)
await this.crowdsale.send(value).should.be.rejectedWith(EVMThrow)
await this.crowdsale.buyTokens(investor, {value: value, from: purchaser}).should.be.rejectedWith(EVMThrow)
})
})
describe('high-level purchase', function () {
beforeEach(async function() {
await advanceToBlock(this.startBlock)
})
it('should log purchase', async function () {
const {logs} = await this.crowdsale.sendTransaction({value: value, from: investor})
const event = logs.find(e => e.event === 'TokenPurchase')
should.exist(event)
event.args.purchaser.should.equal(investor)
event.args.beneficiary.should.equal(investor)
event.args.value.should.be.bignumber.equal(value)
event.args.amount.should.be.bignumber.equal(expectedTokenAmount)
})
it('should increase totalSupply', async function () {
await this.crowdsale.send(value)
const totalSupply = await this.token.totalSupply()
totalSupply.should.be.bignumber.equal(expectedTokenAmount)
})
it('should assign tokens to sender', async function () {
await this.crowdsale.sendTransaction({value: value, from: investor})
let balance = await this.token.balanceOf(investor);
balance.should.be.bignumber.equal(expectedTokenAmount)
})
it('should forward funds to wallet', async function () {
const pre = web3.eth.getBalance(wallet)
await this.crowdsale.sendTransaction({value, from: investor})
const post = web3.eth.getBalance(wallet)
post.minus(pre).should.be.bignumber.equal(value)
})
})
describe('low-level purchase', function () {
beforeEach(async function() {
await advanceToBlock(this.startBlock)
})
it('should log purchase', async function () {
const {logs} = await this.crowdsale.buyTokens(investor, {value: value, from: purchaser})
const event = logs.find(e => e.event === 'TokenPurchase')
should.exist(event)
event.args.purchaser.should.equal(purchaser)
event.args.beneficiary.should.equal(investor)
event.args.value.should.be.bignumber.equal(value)
event.args.amount.should.be.bignumber.equal(expectedTokenAmount)
})
it('should increase totalSupply', async function () {
await this.crowdsale.buyTokens(investor, {value, from: purchaser})
const totalSupply = await this.token.totalSupply()
totalSupply.should.be.bignumber.equal(expectedTokenAmount)
})
it('should assign tokens to beneficiary', async function () {
await this.crowdsale.buyTokens(investor, {value, from: purchaser})
const balance = await this.token.balanceOf(investor)
balance.should.be.bignumber.equal(expectedTokenAmount)
})
it('should forward funds to wallet', async function () {
const pre = web3.eth.getBalance(wallet)
await this.crowdsale.buyTokens(investor, {value, from: purchaser})
const post = web3.eth.getBalance(wallet)
post.minus(pre).should.be.bignumber.equal(value)
})
})
})
import advanceToBlock from './helpers/advanceToBlock'
import EVMThrow from './helpers/EVMThrow'
const BigNumber = web3.BigNumber
const should = require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should()
const FinalizableCrowdsale = artifacts.require('./helpers/FinalizableCrowdsaleImpl.sol')
const MintableToken = artifacts.require('MintableToken')
contract('FinalizableCrowdsale', function ([_, owner, wallet, thirdparty]) {
const rate = new BigNumber(1000)
beforeEach(async function () {
this.startBlock = web3.eth.blockNumber + 10
this.endBlock = web3.eth.blockNumber + 20
this.crowdsale = await FinalizableCrowdsale.new(this.startBlock, this.endBlock, rate, wallet, {from: owner})
this.token = MintableToken.at(await this.crowdsale.token())
})
it('cannot be finalized before ending', async function () {
await this.crowdsale.finalize({from: owner}).should.be.rejectedWith(EVMThrow)
})
it('cannot be finalized by third party after ending', async function () {
await advanceToBlock(this.endBlock)
await this.crowdsale.finalize({from: thirdparty}).should.be.rejectedWith(EVMThrow)
})
it('can be finalized by owner after ending', async function () {
await advanceToBlock(this.endBlock)
await this.crowdsale.finalize({from: owner}).should.be.fulfilled
})
it('cannot be finalized twice', async function () {
await advanceToBlock(this.endBlock + 1)
await this.crowdsale.finalize({from: owner})
await this.crowdsale.finalize({from: owner}).should.be.rejectedWith(EVMThrow)
})
it('logs finalized', async function () {
await advanceToBlock(this.endBlock)
const {logs} = await this.crowdsale.finalize({from: owner})
const event = logs.find(e => e.event === 'Finalized')
should.exist(event)
})
it('finishes minting of token', async function () {
await advanceToBlock(this.endBlock)
await this.crowdsale.finalize({from: owner})
const finished = await this.token.mintingFinished()
finished.should.equal(true)
})
})
...@@ -52,7 +52,7 @@ contract('MultisigWallet', function(accounts) { ...@@ -52,7 +52,7 @@ contract('MultisigWallet', function(accounts) {
//Balance of account2 should have increased //Balance of account2 should have increased
let newAccountBalance = web3.eth.getBalance(accounts[2]); let newAccountBalance = web3.eth.getBalance(accounts[2]);
assert.isTrue(newAccountBalance > accountBalance); assert.isTrue(newAccountBalance.greaterThan(accountBalance));
}); });
it('should prevent execution of transaction if above daily limit', async function() { it('should prevent execution of transaction if above daily limit', async function() {
......
const BigNumber = web3.BigNumber
require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should()
import ether from './helpers/ether'
import EVMThrow from './helpers/EVMThrow'
const RefundVault = artifacts.require('RefundVault')
contract('RefundVault', function ([_, owner, wallet, investor]) {
const value = ether(42)
beforeEach(async function () {
this.vault = await RefundVault.new(wallet, {from: owner})
})
it('should accept contributions', async function () {
await this.vault.deposit(investor, {value, from: owner}).should.be.fulfilled
})
it('should not refund contribution during active state', async function () {
await this.vault.deposit(investor, {value, from: owner})
await this.vault.refund(investor).should.be.rejectedWith(EVMThrow)
})
it('only owner can enter refund mode', async function () {
await this.vault.enableRefunds({from: _}).should.be.rejectedWith(EVMThrow)
await this.vault.enableRefunds({from: owner}).should.be.fulfilled
})
it('should refund contribution after entering refund mode', async function () {
await this.vault.deposit(investor, {value, from: owner})
await this.vault.enableRefunds({from: owner})
const pre = web3.eth.getBalance(investor)
await this.vault.refund(investor)
const post = web3.eth.getBalance(investor)
post.minus(pre).should.be.bignumber.equal(value)
})
it('only owner can close', async function () {
await this.vault.close({from: _}).should.be.rejectedWith(EVMThrow)
await this.vault.close({from: owner}).should.be.fulfilled
})
it('should forward funds to wallet after closing', async function () {
await this.vault.deposit(investor, {value, from: owner})
const pre = web3.eth.getBalance(wallet)
await this.vault.close({from: owner})
const post = web3.eth.getBalance(wallet)
post.minus(pre).should.be.bignumber.equal(value)
})
})
import ether from './helpers/ether'
import advanceToBlock from './helpers/advanceToBlock'
import EVMThrow from './helpers/EVMThrow'
const BigNumber = web3.BigNumber
require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should()
const RefundableCrowdsale = artifacts.require('./helpers/RefundableCrowdsaleImpl.sol')
contract('RefundableCrowdsale', function ([_, owner, wallet, investor]) {
const rate = new BigNumber(1000)
const goal = ether(800)
const lessThanGoal = ether(750)
beforeEach(async function () {
this.startBlock = web3.eth.blockNumber + 10
this.endBlock = web3.eth.blockNumber + 20
this.crowdsale = await RefundableCrowdsale.new(this.startBlock, this.endBlock, rate, wallet, goal, {from: owner})
})
it('should deny refunds before end', async function () {
await this.crowdsale.claimRefund({from: investor}).should.be.rejectedWith(EVMThrow)
await advanceToBlock(this.endBlock - 1)
await this.crowdsale.claimRefund({from: investor}).should.be.rejectedWith(EVMThrow)
})
it('should deny refunds after end if goal was reached', async function () {
await advanceToBlock(this.startBlock - 1)
await this.crowdsale.sendTransaction({value: goal, from: investor})
await advanceToBlock(this.endBlock)
await this.crowdsale.claimRefund({from: investor}).should.be.rejectedWith(EVMThrow)
})
it('should allow refunds after end if goal was not reached', async function () {
await advanceToBlock(this.startBlock - 1)
await this.crowdsale.sendTransaction({value: lessThanGoal, from: investor})
await advanceToBlock(this.endBlock)
await this.crowdsale.finalize({from: owner})
const pre = web3.eth.getBalance(investor)
await this.crowdsale.claimRefund({from: investor, gasPrice: 0})
.should.be.fulfilled
const post = web3.eth.getBalance(investor)
post.minus(pre).should.be.bignumber.equal(lessThanGoal)
})
it('should forward funds to wallet after end if goal was reached', async function () {
await advanceToBlock(this.startBlock - 1)
await this.crowdsale.sendTransaction({value: goal, from: investor})
await advanceToBlock(this.endBlock)
const pre = web3.eth.getBalance(wallet)
await this.crowdsale.finalize({from: owner})
const post = web3.eth.getBalance(wallet)
post.minus(pre).should.be.bignumber.equal(goal)
})
})
pragma solidity ^0.4.11;
import '../../contracts/crowdsale/CappedCrowdsale.sol';
contract CappedCrowdsaleImpl is CappedCrowdsale {
function CappedCrowdsaleImpl (
uint256 _startBlock,
uint256 _endBlock,
uint256 _rate,
address _wallet,
uint256 _cap
)
Crowdsale(_startBlock, _endBlock, _rate, _wallet)
CappedCrowdsale(_cap)
{
}
}
export default 'invalid opcode'
pragma solidity ^0.4.11;
import '../../contracts/crowdsale/FinalizableCrowdsale.sol';
contract FinalizableCrowdsaleImpl is FinalizableCrowdsale {
function FinalizableCrowdsaleImpl (
uint256 _startBlock,
uint256 _endBlock,
uint256 _rate,
address _wallet
)
Crowdsale(_startBlock, _endBlock, _rate, _wallet)
FinalizableCrowdsale()
{
}
}
pragma solidity ^0.4.11;
import '../../contracts/crowdsale/RefundableCrowdsale.sol';
contract RefundableCrowdsaleImpl is RefundableCrowdsale {
function RefundableCrowdsaleImpl (
uint256 _startBlock,
uint256 _endBlock,
uint256 _rate,
address _wallet,
uint256 _goal
)
Crowdsale(_startBlock, _endBlock, _rate, _wallet)
RefundableCrowdsale(_goal)
{
}
}
export function advanceBlock() {
return new Promise((resolve, reject) => {
web3.currentProvider.sendAsync({
jsonrpc: '2.0',
method: 'evm_mine',
id: Date.now(),
}, (err, res) => {
return err ? reject(err) : resolve(res)
})
})
}
// Advances the block number so that the last mined block is `number`.
export default async function advanceToBlock(number) {
if (web3.eth.blockNumber > number) {
throw Error(`block number ${number} is in the past (current is ${web3.eth.blockNumber})`)
}
while (web3.eth.blockNumber < number) {
await advanceBlock()
}
}
export default function ether(n) {
return new web3.BigNumber(web3.toWei(n, 'ether'))
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment