Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import "./../../state/StateMachine.sol";


/**
* @title Issuance
* @title IssuanceAdvanced
* @notice Implements the investment round procedure for issuances
*/
contract Issuance is Ownable, StateMachine, ReentrancyGuard {
contract IssuanceAdvanced is Ownable, StateMachine, ReentrancyGuard {

using SafeMath for uint256;

Expand Down
154 changes: 154 additions & 0 deletions contracts/drafts/issuance/IssuanceSimple.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
pragma solidity ^0.5.10;

import "@openzeppelin/contracts/ownership/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Mintable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./IERC20Mintable.sol";
import "./../../state/StateMachine.sol";


/**
* @title IssuanceSimple
* @notice Implements a very simple issuance process for tokens
*/
contract IssuanceSimple is Ownable, StateMachine, ReentrancyGuard {

using SafeMath for uint256;

event IssuanceCreated();

event IssuePriceSet();

event InvestmentAdded(address investor, uint256 amount);
event InvestmentCancelled(address investor, uint256 amount);

IERC20 public currencyToken;
IERC20Mintable public issuanceToken;

address[] public investors;
mapping(address => uint256) public investments;

uint256 public amountRaised;
uint256 public issuePrice;

uint256 nextInvestor;

constructor(
address _issuanceToken,
address _currencyToken
) public Ownable() StateMachine() {
issuanceToken = IERC20Mintable(_issuanceToken);
currencyToken = IERC20(_currencyToken);
_createState("OPEN");
_createState("LIVE");
_createState("FAILED");
_createTransition("SETUP", "OPEN");
_createTransition("OPEN", "LIVE");
_createTransition("OPEN", "FAILED");
emit IssuanceCreated();
}

/**
* @dev Use this function to invest. Must have approved this contract (from the frontend) to spend _amount of currencyToken tokens.
* @param _amount The amount of currencyToken tokens that will be invested.
*/
function invest(uint256 _amount) external {
require(
currentState == "OPEN",
"Not open for investments."
);
require(
_amount.mod(issuePrice) == 0,
"Fractional investments not allowed."
);

currencyToken.transferFrom(msg.sender, address(this), _amount);

if (investments[msg.sender] == 0){
investors.push(msg.sender);
}
investments[msg.sender] = investments[msg.sender].add(_amount);

amountRaised = amountRaised.add(_amount);

emit InvestmentAdded(msg.sender, _amount);
}

function withdraw() external nonReentrant {
require(
currentState == "LIVE",
"Cannot withdraw now."
);
require(
investments[msg.sender] > 0,
"No investments found."
);
uint256 amount = investments[msg.sender];
investments[msg.sender] = 0;
issuanceToken.mint(msg.sender, amount.div(issuePrice));
}

/**
* @dev Function for an investor to cancel his investment
*/
function cancelInvestment() external nonReentrant {
require (
currentState == "OPEN" || currentState == "FAILED",
"Cannot cancel now."
);
require(
investments[msg.sender] > 0,
"No investments found."
);
uint256 amount = investments[msg.sender];
investments[msg.sender] = 0;
currencyToken.transfer(msg.sender, amount);
emit InvestmentCancelled(msg.sender, amount);
}

/**
* @dev Function to open the issuance to investors
*/
function openIssuance() public onlyOwner {
require(
issuePrice > 0,
"Issue price not set."
);
_transition("OPEN");
}

/**
* @dev Function to move to the distributing phase
*/
function startDistribution() public onlyOwner {
_transition("LIVE");
}

/**
* @dev Function to cancel all investments
*/
function cancelAllInvestments() public onlyOwner{
_transition("FAILED");
}

/**
* @dev Function to transfer all collected tokens to the wallet of the owner
*/
function transferFunds(address _wallet) public onlyOwner {
require(
currentState == "LIVE",
"Cannot transfer funds now."
);
currencyToken.transfer(_wallet, amountRaised);
}

function setIssuePrice(uint256 _issuePrice) public onlyOwner {
require(
currentState == "SETUP",
"Cannot setup now."
);
issuePrice = _issuePrice;
emit IssuePriceSet();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { BigNumber } from 'bignumber.js';
import { should } from 'chai';
// tslint:disable-next-line:no-var-requires
const { advanceTimeAndBlock, takeSnapshot, revertToSnapshot } = require('ganache-time-traveler');
import { ERC20MintableMockInstance, IssuanceInstance } from '../../../types/truffle-contracts';
import { ERC20MintableMockInstance, IssuanceAdvancedInstance } from '../../../types/truffle-contracts';

const Issuance = artifacts.require('./drafts/issuance/Issuance.sol') as Truffle.Contract<IssuanceInstance>;
const IssuanceAdvanced = artifacts.require(
'./drafts/issuance/IssuanceAdvanced.sol',
) as Truffle.Contract<IssuanceAdvancedInstance>;
const ERC20MintableMock = artifacts.require(
'./test/issuance/ERC20MintableMock.sol',
) as Truffle.Contract<ERC20MintableMockInstance>;
Expand All @@ -14,14 +16,14 @@ should();
// tslint:disable-next-line no-var-requires
const { itShouldThrow } = require('./../../utils');

contract('Issuance', (accounts) => {
contract('IssuanceAdvanced', (accounts) => {
let snapshotId: any;

const investor1 = accounts[1];
const investor2 = accounts[2];
const wallet = accounts[3];

let issuance: IssuanceInstance;
let issuance: IssuanceAdvancedInstance;
let currencyToken: ERC20MintableMockInstance;
let issuanceToken: ERC20MintableMockInstance;

Expand All @@ -30,7 +32,7 @@ contract('Issuance', (accounts) => {
snapshotId = snapShot.result;
currencyToken = await ERC20MintableMock.new();
issuanceToken = await ERC20MintableMock.new();
issuance = await Issuance.new(
issuance = await IssuanceAdvanced.new(
issuanceToken.address,
currencyToken.address,
);
Expand All @@ -47,23 +49,23 @@ contract('Issuance', (accounts) => {
});

/**
* @test {Issuance#openIssuance}
* @test {IssuanceAdvanced#openIssuance}
*/
it('openIssuance can succefully open the Issuance', async () => {
await issuance.openIssuance();
bytes32ToString(await issuance.currentState()).should.be.equal('OPEN');
});

/**
* @test {Issuance#openIssuance}
* @test {IssuanceAdvanced#openIssuance}
*/
itShouldThrow('cannot open issuance outside allotted timeframe', async () => {
await advanceTimeAndBlock(4000);
await issuance.openIssuance();
}, 'Not the right time.');

/**
* @test {Issuance#invest}
* @test {IssuanceAdvanced#invest}
*/
it('invest should succesfully invest', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -76,7 +78,7 @@ contract('Issuance', (accounts) => {
});

/**
* @test {Issuance#invest}
* @test {IssuanceAdvanced#invest}
*/
itShouldThrow('cannot invest if state is not "OPEN"', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -85,7 +87,7 @@ contract('Issuance', (accounts) => {
}, 'Not open for investments.');

/**
* @test {Issuance#invest}
* @test {IssuanceAdvanced#invest}
*/
itShouldThrow('cannot invest outisde allotted timespan', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -96,7 +98,7 @@ contract('Issuance', (accounts) => {
}, 'Not the right time.');

/**
* @test {Issuance#invest}
* @test {IssuanceAdvanced#invest}
*/
itShouldThrow('cannot invest with fractional investments', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -106,7 +108,7 @@ contract('Issuance', (accounts) => {
}, 'Fractional investments not allowed.');

/**
* @test {Issuance#invest}
* @test {IssuanceAdvanced#invest}
*/
itShouldThrow('cannot invest with investment below minimum threshold', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -116,7 +118,7 @@ contract('Issuance', (accounts) => {
}, 'Investment below minimum threshold.');

/**
* @test {Issuance#startDistribution}
* @test {IssuanceAdvanced#startDistribution}
*/
it('startDistribution can succesfully close the Issuance', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -132,7 +134,7 @@ contract('Issuance', (accounts) => {
});

/**
* @test {Issuance#startDistribution}
* @test {IssuanceAdvanced#startDistribution}
*/
itShouldThrow('cannot start distribution before closing time', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -146,7 +148,7 @@ contract('Issuance', (accounts) => {
}, 'Not the right time yet.');

/**
* @test {Issuance#startDistribution}
* @test {IssuanceAdvanced#startDistribution}
*/
itShouldThrow('cannot start distribution when soft cap not reached', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -161,7 +163,7 @@ contract('Issuance', (accounts) => {
}, 'Not enough funds collected.');

/**
* @test {Issuance#withdraw}
* @test {IssuanceAdvanced#withdraw}
*/
it('withdraw sends tokens to investors', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -181,7 +183,7 @@ contract('Issuance', (accounts) => {
});

/**
* @test {Issuance#withdraw}
* @test {IssuanceAdvanced#withdraw}
*/
itShouldThrow('cannot withdraw when state is not "LIVE"', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -196,7 +198,7 @@ contract('Issuance', (accounts) => {
}, 'Cannot withdraw now.');

/**
* @test {Issuance#withdraw}
* @test {IssuanceAdvanced#withdraw}
*/
itShouldThrow('cannot withdraw when not invested', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -211,7 +213,7 @@ contract('Issuance', (accounts) => {
}, 'No investments found.');

/**
* @test {Issuance#cancelInvestment}
* @test {IssuanceAdvanced#cancelInvestment}
*/
it('cancelInvestment should cancel an investor investments', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -226,7 +228,7 @@ contract('Issuance', (accounts) => {
});

/**
* @test {Issuance#cancelInvestment}
* @test {IssuanceAdvanced#cancelInvestment}
*/
itShouldThrow('cannot cancel investment when state is not "OPEN" or "FAILED"', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -240,7 +242,7 @@ contract('Issuance', (accounts) => {
}, 'Cannot cancel now.');

/**
* @test {Issuance#cancelInvestment}
* @test {IssuanceAdvanced#cancelInvestment}
*/
itShouldThrow('cannot cancel investment when not invested', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -250,7 +252,7 @@ contract('Issuance', (accounts) => {
}, 'No investments found.');

/**
* @test {Issuance#cancelAllInvestments}
* @test {IssuanceAdvanced#cancelAllInvestments}
*/
it('cancelAllInvestments should begin the process to cancel all investor investments', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -269,7 +271,7 @@ contract('Issuance', (accounts) => {
});

/**
* @test {Issuance#transferFunds}
* @test {IssuanceAdvanced#transferFunds}
*/
it('transferFunds should transfer all collected tokens to the wallet of the owner', async () => {
await currencyToken.mint(investor1, new BigNumber(100e18));
Expand All @@ -288,7 +290,7 @@ contract('Issuance', (accounts) => {
});

/**
* @test {Issuance#transferFunds}
* @test {IssuanceAdvanced#transferFunds}
*/
itShouldThrow('cannot transfer funds when issuance state is not "LIVE"', async () => {
await issuance.openIssuance();
Expand Down
Loading