Skip to content
Closed
Next Next commit
Pull Request PseudoMinter
  • Loading branch information
lead4good committed Oct 27, 2017
commit aba694f0fda9e47d2b19c85819b384f4d932ff4c
6 changes: 3 additions & 3 deletions contracts/crowdsale/Crowdsale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import '../math/SafeMath.sol';
contract Crowdsale {
using SafeMath for uint256;

// The token being sold
MintableToken public token;
// minter interface providing the tokens being sold
Mintable public token;

// start and end timestamps where investments are allowed (both inclusive)
uint256 public startTime;
Expand Down Expand Up @@ -55,7 +55,7 @@ contract Crowdsale {

// creates the token to be sold.
// override this method to have crowdsale of a specific mintable token.
function createTokenContract() internal returns (MintableToken) {
function createTokenContract() internal returns (Mintable) {
return new MintableToken();
}

Expand Down
52 changes: 52 additions & 0 deletions contracts/examples/PreMintedCrowdsale.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
pragma solidity ^0.4.11;

import "../token/PseudoMinter.sol";
import "../crowdsale/Crowdsale.sol";
import "./SimpleToken.sol";

/**
* @title PreMintedCrowdsaleVault
* @dev Simple contract which acts as a vault for the pre minted tokens to be
* sold during crowdsale. The tokens approve function is limited to one call
* which makes the PreMintedCrowdsale to a capped crowdsale.
*/
contract PreMintedCrowdsaleVault {

SimpleToken public token;
PreMintedCrowdsale public crowdsale;

function PreMintedCrowdsaleVault(uint256 _startTime, uint256 _endTime, uint256 _rate, address _wallet) {
token = new SimpleToken();
crowdsale = new PreMintedCrowdsale(_startTime, _endTime, _rate, _wallet, token);

PseudoMinter _pseudoMinter = PseudoMinter(crowdsale.token());
token.approve(_pseudoMinter, token.balanceOf(this));
}
}

/**
* @title PreMintedCrowdsale
* @dev This is an example of a crowdsale which has had its tokens already minted
* in advance. By storing the spendable tokens in the PreMintedCrowdsaleVault
* and limiting the call to the tokens approve function, the crowdsale supports a
* definite hard cap.
*/
contract PreMintedCrowdsale is Crowdsale {

function PreMintedCrowdsale(uint256 _startTime, uint256 _endTime, uint256 _rate, address _wallet, ERC20 _token)
Crowdsale(_startTime, _endTime, _rate, _wallet)
{
token = new PseudoMinter(_token, msg.sender);
}

// return address zero since Mintable is created in the constructor above
function createMintableContract() internal returns (Mintable) {
return Mintable(0x0);
}

// @return true if crowdsale event has ended
function hasEnded() public constant returns (bool) {
bool capReached = PseudoMinter(token).availableSupply() < rate;
return super.hasEnded() || capReached;
}
}
2 changes: 1 addition & 1 deletion contracts/examples/SampleCrowdsale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ contract SampleCrowdsale is CappedCrowdsale, RefundableCrowdsale {
require(_goal <= _cap);
}

function createTokenContract() internal returns (MintableToken) {
function createTokenContract() internal returns (Mintable) {
return new SampleCrowdsaleToken();
}

Expand Down
10 changes: 10 additions & 0 deletions contracts/token/Mintable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pragma solidity ^0.4.11;


/**
* @title Mintable
* @dev Interface for mintable token contracts
*/
contract Mintable {
function mint(address _to, uint256 _amount) public returns (bool);
}
3 changes: 2 additions & 1 deletion contracts/token/MintableToken.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pragma solidity ^0.4.11;


import './Mintable.sol';
import './StandardToken.sol';
import '../ownership/Ownable.sol';

Expand All @@ -13,7 +14,7 @@ import '../ownership/Ownable.sol';
* Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol
*/

contract MintableToken is StandardToken, Ownable {
contract MintableToken is Mintable, StandardToken, Ownable {
event Mint(address indexed to, uint256 amount);
event MintFinished();

Expand Down
56 changes: 56 additions & 0 deletions contracts/token/PseudoMinter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
pragma solidity ^0.4.11;

import "./ERC20.sol";
import "./Mintable.sol";
import "../ownership/Ownable.sol";

/**
* @title PseudoMinter
* @dev Proxy contract providing the necessary minting abbilities needed
* within crowdsale contracts to ERC20 token contracts with a pre minted
* fixed amount of tokens.
* PseudoMinter is initialized with the token contract and the vault contract
* which provides the spendable tokens. Cap is automatically set by approving
* to the PseudoMinter instance the chosen amount of tokens. Be aware that
* this does not necessarily represent the hard cap of spendable tokens. If
* vault can arbitrarily call the tokens approve function, this might even
* be a security risk since the cap can be manipulated at will. Best practice
* is to design vault as a contract with limited ablity to call the tokens
* approve function.
* For an example implementation see contracts/example/PreMintedCrowdsale.sol
*/
contract PseudoMinter is Mintable, Ownable {

// The token being sold
ERC20 public token;
// address which provides tokens via token.approve(...) function
address public vault;

function PseudoMinter(ERC20 _token, address _vault) {
require(address(_token) != 0x0);
require(_vault != 0x0);

token = _token;
vault = _vault;
}

/**
* @dev Function to pseudo mint tokens, once the approved amount is used,
* cap is automatically reached.
* @param _to The address that will receive the minted tokens.
* @param _amount The amount of tokens to mint.
* @return A boolean that indicates if the operation was successful.
*/
function mint(address _to, uint256 _amount) onlyOwner public returns (bool) {
token.transferFrom(vault, _to, _amount);
return true;
}

/**
* @dev returns amount of tokens that can be pseudo minted. Be aware that
* this does not necessarily represent the hard cap of spendable tokens!
*/
function availableSupply() public returns (uint256) {
return token.allowance(vault, this);
}
}
88 changes: 88 additions & 0 deletions test/PreMintedCrowdsale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import ether from './helpers/ether'
import {advanceBlock} from './helpers/advanceToBlock'
import {increaseTimeTo, duration} from './helpers/increaseTime'
import latestTime from './helpers/latestTime'
import EVMThrow from './helpers/EVMThrow'

const BigNumber = web3.BigNumber

require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should()

const PreMintedCrowdsale = artifacts.require('PreMintedCrowdsale')
const PreMintedCrowdsaleVault = artifacts.require('PreMintedCrowdsaleVault')
const PseudoMinter = artifacts.require("./token/PseudoMinter.sol");

contract('PreMintedCrowdsale', function ([_, wallet]) {

const rate = new BigNumber(1000)
before(async function() {
//Advance to the next block to correctly read time in the solidity "now" function interpreted by testrpc
await advanceBlock()
})

beforeEach(async function () {
this.startTime = latestTime() + duration.weeks(1);
this.endTime = this.startTime + duration.weeks(1);

this.vault = await PreMintedCrowdsaleVault.new(this.startTime, this.endTime, rate, wallet)
this.crowdsale = PreMintedCrowdsale.at(await this.vault.crowdsale())
this.pseudoMinter = PseudoMinter.at(await this.crowdsale.token())
this.tokenCap = new BigNumber(await this.pseudoMinter.availableSupply.call())
this.cap = new BigNumber(this.tokenCap/rate)
this.lessThanCap = rate
})

describe('accepting payments', function () {

beforeEach(async function () {
await increaseTimeTo(this.startTime+ duration.minutes(1))
})

it('should accept payments within cap', async function () {
await this.crowdsale.send(this.cap.minus(this.lessThanCap)).should.be.fulfilled
await this.crowdsale.send(this.lessThanCap).should.be.fulfilled
})

it('should reject payments outside cap', async function () {
await this.crowdsale.send(this.cap)
await this.crowdsale.send(1).should.be.rejectedWith(EVMThrow)
})

it('should reject payments that exceed cap', async function () {
await this.crowdsale.send(this.cap.plus(1)).should.be.rejectedWith(EVMThrow)
})

})

describe('ending', function () {

beforeEach(async function () {
await increaseTimeTo(this.startTime)
})

it('should not be ended if under cap', async function () {
let hasEnded = await this.crowdsale.hasEnded()
hasEnded.should.equal(false)
await this.crowdsale.send(this.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(this.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(this.cap)
let hasEnded = await this.crowdsale.hasEnded()
hasEnded.should.equal(true)
})

})

})
60 changes: 60 additions & 0 deletions test/PseudoMinter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import ether from './helpers/ether'
import EVMThrow from './helpers/EVMThrow'

const BigNumber = web3.BigNumber

const should = require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should()

var PseudoMinter = artifacts.require("./token/PseudoMinter.sol");
var SimpleToken = artifacts.require("./example/SimpleToken.sol");


contract('PseudoMinter', function ([owner, tokenAddress]) {

beforeEach(async function () {

this.simpleToken = await SimpleToken.new()
this.pseudoMinter = await PseudoMinter.new(this.simpleToken.address,owner)

this.cap = await this.simpleToken.totalSupply();
this.lessThanCap = 1;

await this.simpleToken.approve(this.pseudoMinter.address, this.cap)
})

it('should accept minting within approved cap', async function () {
await this.pseudoMinter.mint(tokenAddress, this.cap.minus(this.lessThanCap)).should.be.fulfilled
await this.pseudoMinter.mint(tokenAddress, this.lessThanCap).should.be.fulfilled

let amount = await this.simpleToken.balanceOf(tokenAddress)
amount.should.be.bignumber.equal(this.cap)
})

it('should reject minting outside approved cap', async function () {
await this.pseudoMinter.mint(tokenAddress, this.cap).should.be.fulfilled
await this.pseudoMinter.mint(tokenAddress, 1).should.be.rejectedWith(EVMThrow)

let amount = await this.simpleToken.balanceOf(tokenAddress)
amount.should.be.bignumber.equal(this.cap)
})

it('should reject minting that exceed approved cap', async function () {
await this.pseudoMinter.mint(tokenAddress, this.cap.plus(1)).should.be.rejectedWith(EVMThrow)

let amount = await this.simpleToken.balanceOf(tokenAddress)
amount.should.be.bignumber.equal(0)
})

it('should be able to change cap by calling tokens approve function', async function () {
await this.simpleToken.approve(this.pseudoMinter.address, 0)

let amount = await this.pseudoMinter.availableSupply.call()
amount.should.be.bignumber.equal(0)

await this.pseudoMinter.mint(tokenAddress, 1).should.be.rejectedWith(EVMThrow)
})

})