Repository: decentraland/mana
Branch: master
Commit: d92bba7f43d8
Files: 32
Total size: 36.6 KB
Directory structure:
gitextract_8qja0b87/
├── .babelrc
├── .circleci/
│ └── config.yml
├── .editorconfig
├── .eslintrc
├── .flowconfig
├── .gitattributes
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── contracts/
│ ├── BurnableToken.sol
│ ├── ContinuousSale.sol
│ ├── MANAContinuousSale.sol
│ ├── MANACrowdsale.sol
│ ├── MANAToken.sol
│ ├── Migrations.sol
│ └── WhitelistedCrowdsale.sol
├── flow-typed/
│ └── defs.js
├── migrations/
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── package.json
├── scripts/
│ └── test.sh
├── test/
│ ├── BurnableToken.js
│ ├── MANAContinuousSale.js
│ ├── MANACrowdsale.js
│ ├── MANAToken.js
│ ├── WhitelistedCrowdsale.js
│ ├── helpers/
│ │ ├── BurnableTokenMock.sol
│ │ └── WhitelistedCrowdsaleImpl.sol
│ └── utils.js
└── truffle.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": ["flow", "env"]
}
================================================
FILE: .circleci/config.yml
================================================
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: circleci/node:10.2-stretch
working_directory: ~/repo
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package-lock.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run:
name: Install packages
command: npm install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package-lock.json" }}
# run tests!
- run:
name: Run tests
command: npm run test
release:
docker:
# specify the version you desire here
- image: circleci/node:10.2-stretch
working_directory: ~/repo
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package-lock.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run:
name: Install packages
command: npm install
# Compile contracts!
- run:
name: Compile contracts
command: npm run compile
- run:
name: Semantic release
command: npm run semantic-release
workflows:
version: 2
test-build-release:
jobs:
- build
- release:
requires:
- build
filters:
branches:
only:
- master
================================================
FILE: .editorconfig
================================================
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
[*.sol]
indent_style = space
indent_size = 4
================================================
FILE: .eslintrc
================================================
{
"extends": ["standard"],
"env": {
"node": true,
"jasmine": true
},
"globals": {
"web3": false,
"artifacts": false,
"contract": false
}
}
================================================
FILE: .flowconfig
================================================
[ignore]
[include]
[libs]
[options]
[lints]
================================================
FILE: .gitattributes
================================================
*.sol linguist-language=Solidity
*.js linguist-vendored
================================================
FILE: .gitignore
================================================
node_modules/
build/
================================================
FILE: .npmignore
================================================
migrations
test
zos.*
scripts
flow-typed
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "7"
cache:
directories:
- "node_modules"
================================================
FILE: LICENSE
================================================
Copyright 2017 The Decentraland Team
Licensed under the Apache License, Version 2.0 (the "License");
you may not use files included in this repository except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================

Decentraland MANA Token
=======================
[](https://travis-ci.org/decentraland/mana)
Solidity smart contracts for the Decentraland Crowdsale and MANA Token
================================================
FILE: contracts/BurnableToken.sol
================================================
pragma solidity ^0.4.11;
import 'zeppelin-solidity/contracts/token/StandardToken.sol';
/**
* @title Burnable Token
* @dev A token that can be irreversibly burned.
*/
contract BurnableToken is StandardToken {
event Burn(address indexed burner, uint256 value);
/**
* @dev Burns a specified amount of tokens.
* @param _value The amount of tokens to burn.
*/
function burn(uint256 _value) public {
require(_value > 0);
address burner = msg.sender;
balances[burner] = balances[burner].sub(_value);
totalSupply = totalSupply.sub(_value);
Burn(msg.sender, _value);
}
}
================================================
FILE: contracts/ContinuousSale.sol
================================================
pragma solidity ^0.4.11;
import "zeppelin-solidity/contracts/token/MintableToken.sol";
/**
* @title ContinuousSale
* @dev ContinuousSale implements a contract for managing a continuous token sale
*/
contract ContinuousSale {
using SafeMath for uint256;
// time bucket size
uint256 public constant BUCKET_SIZE = 12 hours;
// the token being sold
MintableToken public token;
// address where funds are collected
address public wallet;
// amount of tokens emitted per wei
uint256 public rate;
// amount of raised money in wei
uint256 public weiRaised;
// max amount of tokens to mint per time bucket
uint256 public issuance;
// last time bucket from which tokens have been purchased
uint256 public lastBucket = 0;
// amount issued in the last bucket
uint256 public bucketAmount = 0;
event TokenPurchase(address indexed investor, address indexed beneficiary, uint256 weiAmount, uint256 tokens);
function ContinuousSale(
uint256 _rate,
address _wallet,
MintableToken _token
) {
require(_rate != 0);
require(_wallet != 0);
// require(address(token) != 0x0);
rate = _rate;
wallet = _wallet;
token = _token;
}
function() payable {
buyTokens(msg.sender);
}
function buyTokens(address beneficiary) public payable {
require(beneficiary != 0x0);
require(msg.value != 0);
prepareContinuousPurchase();
uint256 tokens = processPurchase(beneficiary);
checkContinuousPurchase(tokens);
}
function prepareContinuousPurchase() internal {
uint256 timestamp = block.timestamp;
uint256 bucket = timestamp - (timestamp % BUCKET_SIZE);
if (bucket > lastBucket) {
lastBucket = bucket;
bucketAmount = 0;
}
}
function checkContinuousPurchase(uint256 tokens) internal {
uint256 updatedBucketAmount = bucketAmount.add(tokens);
require(updatedBucketAmount <= issuance);
bucketAmount = updatedBucketAmount;
}
function processPurchase(address beneficiary) internal returns(uint256) {
uint256 weiAmount = msg.value;
// calculate token amount to be created
uint256 tokens = weiAmount.mul(rate);
// update state
weiRaised = weiRaised.add(weiAmount);
token.mint(beneficiary, tokens);
TokenPurchase(msg.sender, beneficiary, weiAmount, tokens);
forwardFunds();
return tokens;
}
function forwardFunds() internal {
wallet.transfer(msg.value);
}
}
================================================
FILE: contracts/MANAContinuousSale.sol
================================================
pragma solidity ^0.4.11;
import "zeppelin-solidity/contracts/ownership/Ownable.sol";
import "./ContinuousSale.sol";
import "./MANAToken.sol";
contract MANAContinuousSale is ContinuousSale, Ownable {
uint256 public constant INFLATION = 8;
bool public started = false;
event RateChange(uint256 amount);
event WalletChange(address wallet);
function MANAContinuousSale(
uint256 _rate,
address _wallet,
MintableToken _token
) ContinuousSale(_rate, _wallet, _token) {
}
modifier whenStarted() {
require(started);
_;
}
function start() onlyOwner {
require(!started);
// initialize issuance
uint256 finalSupply = token.totalSupply();
uint256 annualIssuance = finalSupply.mul(INFLATION).div(100);
issuance = annualIssuance.mul(BUCKET_SIZE).div(1 years);
started = true;
}
function buyTokens(address beneficiary) whenStarted public payable {
super.buyTokens(beneficiary);
}
function setWallet(address _wallet) onlyOwner {
require(_wallet != 0x0);
wallet = _wallet;
WalletChange(_wallet);
}
function setRate(uint256 _rate) onlyOwner {
rate = _rate;
RateChange(_rate);
}
function unpauseToken() onlyOwner {
MANAToken(token).unpause();
}
function pauseToken() onlyOwner {
MANAToken(token).pause();
}
}
================================================
FILE: contracts/MANACrowdsale.sol
================================================
pragma solidity ^0.4.11;
import "zeppelin-solidity/contracts/crowdsale/CappedCrowdsale.sol";
import "zeppelin-solidity/contracts/crowdsale/Crowdsale.sol";
import "zeppelin-solidity/contracts/crowdsale/FinalizableCrowdsale.sol";
import "./WhitelistedCrowdsale.sol";
import "./MANAContinuousSale.sol";
import "./MANAToken.sol";
contract MANACrowdsale is WhitelistedCrowdsale, CappedCrowdsale, FinalizableCrowdsale {
uint256 public constant TOTAL_SHARE = 100;
uint256 public constant CROWDSALE_SHARE = 40;
uint256 public constant FOUNDATION_SHARE = 60;
// price at which whitelisted buyers will be able to buy tokens
uint256 public preferentialRate;
// customize the rate for each whitelisted buyer
mapping (address => uint256) public buyerRate;
// initial rate at which tokens are offered
uint256 public initialRate;
// end rate at which tokens are offered
uint256 public endRate;
// continuous crowdsale contract
MANAContinuousSale public continuousSale;
event WalletChange(address wallet);
event PreferentialRateChange(address indexed buyer, uint256 rate);
event InitialRateChange(uint256 rate);
event EndRateChange(uint256 rate);
function MANACrowdsale(
uint256 _startBlock,
uint256 _endBlock,
uint256 _initialRate,
uint256 _endRate,
uint256 _preferentialRate,
address _wallet
)
CappedCrowdsale(86206 ether)
WhitelistedCrowdsale()
FinalizableCrowdsale()
Crowdsale(_startBlock, _endBlock, _initialRate, _wallet)
{
require(_initialRate > 0);
require(_endRate > 0);
require(_preferentialRate > 0);
initialRate = _initialRate;
endRate = _endRate;
preferentialRate = _preferentialRate;
continuousSale = createContinuousSaleContract();
MANAToken(token).pause();
}
function createTokenContract() internal returns(MintableToken) {
return new MANAToken();
}
function createContinuousSaleContract() internal returns(MANAContinuousSale) {
return new MANAContinuousSale(rate, wallet, token);
}
function setBuyerRate(address buyer, uint256 rate) onlyOwner public {
require(rate != 0);
require(isWhitelisted(buyer));
require(block.number < startBlock);
buyerRate[buyer] = rate;
PreferentialRateChange(buyer, rate);
}
function setInitialRate(uint256 rate) onlyOwner public {
require(rate != 0);
require(block.number < startBlock);
initialRate = rate;
InitialRateChange(rate);
}
function setEndRate(uint256 rate) onlyOwner public {
require(rate != 0);
require(block.number < startBlock);
endRate = rate;
EndRateChange(rate);
}
function getRate() internal returns(uint256) {
// some early buyers are offered a discount on the crowdsale price
if (buyerRate[msg.sender] != 0) {
return buyerRate[msg.sender];
}
// whitelisted buyers can purchase at preferential price before crowdsale ends
if (isWhitelisted(msg.sender)) {
return preferentialRate;
}
// otherwise compute the price for the auction
uint256 elapsed = block.number - startBlock;
uint256 rateRange = initialRate - endRate;
uint256 blockRange = endBlock - startBlock;
return initialRate.sub(rateRange.mul(elapsed).div(blockRange));
}
// low level token purchase function
function buyTokens(address beneficiary) payable {
require(beneficiary != 0x0);
require(validPurchase());
uint256 weiAmount = msg.value;
uint256 updatedWeiRaised = weiRaised.add(weiAmount);
uint256 rate = getRate();
// 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();
}
function setWallet(address _wallet) onlyOwner public {
require(_wallet != 0x0);
wallet = _wallet;
continuousSale.setWallet(_wallet);
WalletChange(_wallet);
}
function unpauseToken() onlyOwner {
require(isFinalized);
MANAToken(token).unpause();
}
function pauseToken() onlyOwner {
require(isFinalized);
MANAToken(token).pause();
}
function beginContinuousSale() onlyOwner public {
require(isFinalized);
token.transferOwnership(continuousSale);
continuousSale.start();
continuousSale.transferOwnership(owner);
}
function finalization() internal {
uint256 totalSupply = token.totalSupply();
uint256 finalSupply = TOTAL_SHARE.mul(totalSupply).div(CROWDSALE_SHARE);
// emit tokens for the foundation
token.mint(wallet, FOUNDATION_SHARE.mul(finalSupply).div(TOTAL_SHARE));
// NOTE: cannot call super here because it would finish minting and
// the continuous sale would not be able to proceed
}
}
================================================
FILE: contracts/MANAToken.sol
================================================
pragma solidity ^0.4.11;
import "zeppelin-solidity/contracts/token/PausableToken.sol";
import "zeppelin-solidity/contracts/token/MintableToken.sol";
import "./BurnableToken.sol";
contract MANAToken is BurnableToken, PausableToken, MintableToken {
string public constant symbol = "MANA";
string public constant name = "Decentraland MANA";
uint8 public constant decimals = 18;
function burn(uint256 _value) whenNotPaused public {
super.burn(_value);
}
}
================================================
FILE: contracts/Migrations.sol
================================================
pragma solidity ^0.4.4;
contract Migrations {
address public owner;
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
function Migrations() {
owner = msg.sender;
}
function setCompleted(uint completed) restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
================================================
FILE: contracts/WhitelistedCrowdsale.sol
================================================
pragma solidity ^0.4.11;
import 'zeppelin-solidity/contracts/math/SafeMath.sol';
import 'zeppelin-solidity/contracts/crowdsale/Crowdsale.sol';
/**
* @title WhitelistedCrowdsale
* @dev Extension of Crowsdale where an owner can whitelist addresses
* which can buy in crowdsale before it opens to the public
*/
contract WhitelistedCrowdsale is Crowdsale, Ownable {
using SafeMath for uint256;
// list of addresses that can purchase before crowdsale opens
mapping (address => bool) public whitelist;
function addToWhitelist(address buyer) public onlyOwner {
require(buyer != 0x0);
whitelist[buyer] = true;
}
// @return true if buyer is whitelisted
function isWhitelisted(address buyer) public constant returns (bool) {
return whitelist[buyer];
}
// overriding Crowdsale#validPurchase to add whitelist logic
// @return true if buyers can buy at the moment
function validPurchase() internal constant returns (bool) {
// [TODO] issue with overriding and associativity of logical operators
return super.validPurchase() || (!hasEnded() && isWhitelisted(msg.sender));
}
}
================================================
FILE: flow-typed/defs.js
================================================
declare var contract: (string, Function) => void
declare var it: (string, Function) => void
declare var describe: (string, Function) => void
declare var beforeEach: Function => void
declare var artifacts: {
require: string => any
}
declare var web3: any
================================================
FILE: migrations/1_initial_migration.js
================================================
var Migrations = artifacts.require("./Migrations.sol");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};
================================================
FILE: migrations/2_deploy_contracts.js
================================================
const MANACrowdsale = artifacts.require("./MANACrowdsale.sol");
module.exports = function(deployer) {
// deployer.deploy(MANACrowdsale);
};
================================================
FILE: package.json
================================================
{
"name": "decentraland-mana",
"version": "0.0.0-semantic-release",
"description": "Solidity Contracts for the Decentraland MANA Token",
"main": "y",
"directories": {
"test": "test"
},
"dependencies": {
"sha3": "^1.2.1",
"truffle-hdwallet-provider": "0.0.3",
"zeppelin-solidity": "1.2.0"
},
"devDependencies": {
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-flow": "^6.23.0",
"babel-register": "^6.26.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chai-bignumber": "^2.0.2",
"eslint": "^4.19.1",
"eslint-config-standard": "^10.2.1",
"eslint-plugin-import": "^2.11.0",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-promise": "^3.7.0",
"eslint-plugin-standard": "^3.1.0",
"ganache-cli": "^6.1.0",
"husky": "^1.3.1",
"semantic-release": "^15.13.3",
"truffle": "^3.4.11",
"validate-commit-msg": "^2.14.0"
},
"scripts": {
"test": "./scripts/test.sh",
"eslint": "eslint test/",
"semantic-release": "semantic-release",
"commit-msg": "validate-commit-msg",
"compile": "truffle compile"
},
"repository": {
"type": "git",
"url": "git+https://github.com/decentraland/mana.git"
},
"keywords": [
"decentraland",
"mana",
"solidity",
"truffle",
"crowdsale"
],
"author": "Federico Bond",
"license": "MIT",
"bugs": {
"url": "https://github.com/decentraland/mana/issues"
},
"homepage": "https://github.com/decentraland/mana#readme"
}
================================================
FILE: scripts/test.sh
================================================
#!/usr/bin/env bash
# Executes cleanup function at script exit.
trap cleanup EXIT
cleanup() {
# Kill the testrpc instance that we started (if we started one and if it's still running).
if [ -n "$testrpc_pid" ] && ps -p $testrpc_pid > /dev/null; then
kill -9 $testrpc_pid
fi
}
testrpc_running() {
nc -z localhost 8545
}
if testrpc_running; then
echo "Using existing testrpc instance"
else
echo "Starting our own testrpc instance"
# We define 10 accounts with balance 1M ether, needed for high-value tests.
ganache-cli -l 10000000 \
--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 &
testrpc_pid=$!
fi
node_modules/.bin/truffle test "$@"
================================================
FILE: test/BurnableToken.js
================================================
// @flow
'use strict'
const { should, EVMThrow } = require('./utils.js')
const BurnableTokenMock = artifacts.require('./helpers/BurnableTokenMock.sol')
const BigNumber = web3.BigNumber
contract('BurnableToken', function (accounts) {
let token
let expectedTokenSupply = new BigNumber(900)
beforeEach(async function () {
token = await BurnableTokenMock.new(accounts[1], 1000)
})
it('owner should be able to burn tokens', async function () {
const { logs } = await token.burn(100, { from: accounts[1] })
const balance = await token.balanceOf(accounts[1])
balance.should.be.bignumber.equal(expectedTokenSupply)
const totalSupply = await token.totalSupply()
totalSupply.should.be.bignumber.equal(expectedTokenSupply)
const event = logs.find(e => e.event === 'Burn')
should.exist(event)
})
it('cannot burn more tokens that you have', async function () {
await token.burn(2000, { from: accounts[1] })
.should.be.rejectedWith(EVMThrow)
})
})
================================================
FILE: test/MANAContinuousSale.js
================================================
// @flow
'use strict'
const expect = require('chai').expect
const { advanceTime, EVMRevert } = require('./utils')
const MANAContinuousSale = artifacts.require('./MANAContinuousSale.sol')
const MANAToken = artifacts.require('./MANAToken.sol')
const BigNumber = web3.BigNumber
contract('MANAContinuousSale', function([owner, wallet, buyer, wallet2]) {
const rate = new BigNumber(1)
const newRate = new BigNumber(2)
const value = new BigNumber(100)
let token, sale
beforeEach(async function() {
token = await MANAToken.new()
await token.pause()
await token.mint(owner, new BigNumber(1000000))
sale = await MANAContinuousSale.new(rate, wallet, token.address)
await token.transferOwnership(sale.address)
})
it('should start with continuous sale disabled', async function() {
let started = await sale.started()
started.should.equal(false)
await sale.start()
started = await sale.started()
started.should.equal(true)
})
it('owner should be able to pause/unpause token', async function() {
await sale.unpauseToken().should.be.fulfilled
let paused = await token.paused()
paused.should.equal(false)
await sale.pauseToken().should.be.fulfilled
paused = await token.paused()
paused.should.equal(true)
})
it('non-owners should not be able to pause/unpause token', async function() {
await sale.unpauseToken({ from: buyer }).should.be.rejectedWith(EVMRevert)
await sale.unpauseToken({ from: owner })
await sale.pauseToken({ from: buyer }).should.be.rejectedWith(EVMRevert)
})
it('should accept payments only if sale has started', async function() {
await sale.send(value, { from: buyer }).should.be.rejectedWith(EVMRevert)
await sale.start().should.be.fulfilled
await sale.buyTokens(buyer, {
value: new BigNumber(100),
from: buyer
}).should.be.fulfilled
})
it('only owner can change rate', async function() {
await sale
.setRate(newRate, { from: buyer })
.should.be.rejectedWith(EVMRevert)
const { logs } = await sale.setRate(newRate)
const event = logs.find(e => e.event === 'RateChange')
expect(event).to.exist
const updatedRate = await sale.rate()
updatedRate.should.be.bignumber.equal(newRate)
})
it('only owner can change wallet', async function() {
await sale
.setRate(newRate, { from: buyer })
.should.be.rejectedWith(EVMRevert)
const { logs } = await sale.setRate(newRate)
const event = logs.find(e => e.event === 'RateChange')
expect(event).to.exist
const updatedRate = await sale.rate()
updatedRate.should.be.bignumber.equal(newRate)
})
it('should reject payments if amount of tokens is bigger than block bucket', async function() {
await sale.start()
await sale
.send(new BigNumber(1000), { from: buyer })
.should.be.rejectedWith(EVMRevert)
})
it('should start with a fixed issuance rate', async function() {
// total tokens emitted are 1000000 so issuance must be:
//
// seconds in 12 hours: 43200
// seconds in a year: 31536000
// 1000000 * 0.08 * 43200 / 31536000 = 109
let issuance = await sale.issuance()
issuance.should.be.bignumber.equal(new BigNumber(0))
await sale.start()
issuance = await sale.issuance()
issuance.should.be.bignumber.equal(new BigNumber(109))
})
it('should handle time buckets for token issuance', async function() {
await sale.start()
await sale.buyTokens(buyer, { value, from: buyer }).should.be.fulfilled
await sale
.buyTokens(buyer, { value, from: buyer })
.should.be.rejectedWith(EVMRevert)
const bucketSize = await sale.BUCKET_SIZE()
await advanceTime(bucketSize)
await sale.buyTokens(buyer, { value, from: buyer }).should.be.fulfilled
})
})
================================================
FILE: test/MANACrowdsale.js
================================================
// @flow
'use strict'
const expect = require('chai').expect
const { advanceToBlock, ether, should, EVMRevert } = require('./utils')
const MANACrowdsale = artifacts.require('./MANACrowdsale.sol')
const MANAContinuousSale = artifacts.require('./MANAContinuousSale.sol')
const MANAToken = artifacts.require('./MANAToken.sol')
const BigNumber = web3.BigNumber
contract('MANACrowdsale', function([
_,
wallet,
wallet2,
buyer,
purchaser,
buyer2,
purchaser2
]) {
const initialRate = new BigNumber(1000)
const endRate = new BigNumber(900)
const newRate = new BigNumber(500)
const preferentialRate = new BigNumber(2000)
const value = ether(1)
const expectedFoundationTokens = new BigNumber(6000)
const expectedTokenSupply = new BigNumber(10000)
let startBlock, endBlock
let crowdsale, token
beforeEach(async function() {
startBlock = web3.eth.blockNumber + 10
endBlock = web3.eth.blockNumber + 20
crowdsale = await MANACrowdsale.new(
startBlock,
endBlock,
initialRate,
endRate,
preferentialRate,
wallet
)
token = MANAToken.at(await crowdsale.token())
})
it('starts with token paused', async function() {
const paused = await token.paused()
paused.should.equal(true)
})
it('owner should be able to change wallet', async function() {
await crowdsale.setWallet(wallet2)
let wallet = await crowdsale.wallet()
wallet.should.equal(wallet2)
const continuousSale = MANAContinuousSale.at(
await crowdsale.continuousSale()
)
wallet = await continuousSale.wallet()
wallet.should.equal(wallet2)
})
it('non-owner should not be able to change wallet', async function() {
await crowdsale
.setWallet(wallet2, { from: purchaser })
.should.be.rejectedWith(EVMRevert)
})
it('owner should be able to start continuous sale', async function() {
await crowdsale.beginContinuousSale().should.be.rejectedWith(EVMRevert)
await advanceToBlock(endBlock)
await crowdsale.finalize()
const sale = MANAContinuousSale.at(await crowdsale.continuousSale())
let started = await sale.started()
started.should.equal(false)
await crowdsale.beginContinuousSale().should.be.fulfilled
started = await sale.started()
started.should.equal(true)
})
it('owner should be able to unpause token after crowdsale ends', async function() {
await advanceToBlock(endBlock)
await crowdsale.unpauseToken().should.be.rejectedWith(EVMRevert)
await crowdsale.finalize()
let paused = await token.paused()
paused.should.equal(true)
await crowdsale.unpauseToken()
paused = await token.paused()
paused.should.equal(false)
})
it('non-owners should not be able to start continuous sale', async function() {
await crowdsale
.beginContinuousSale({ from: purchaser })
.should.be.rejectedWith(EVMRevert)
})
describe('rate during auction should decrease at a fixed step every block', async function() {
let balance, startBlock, endBlock
let initialRate = 9166
let endRate = 5500
let preferentialRate = initialRate
const rateAtBlock10 = new BigNumber(9165)
const rateAtBlock20 = new BigNumber(9164)
const rateAtBlock100 = new BigNumber(9155)
const rateAtBlock2 = new BigNumber(9166)
const rateAtBlock10000 = new BigNumber(7973)
const rateAtBlock30000 = new BigNumber(5586)
beforeEach(async function() {
startBlock = web3.eth.blockNumber + 10
endBlock = web3.eth.blockNumber + 10 + 30720
crowdsale = await MANACrowdsale.new(
startBlock,
endBlock,
initialRate,
endRate,
preferentialRate,
wallet
)
token = MANAToken.at(await crowdsale.token())
})
it('at start', async function() {
await advanceToBlock(startBlock - 1)
await crowdsale.buyTokens(buyer, { value, from: purchaser })
balance = await token.balanceOf(buyer)
balance.should.be.bignumber.equal(value.mul(initialRate))
})
it('at block 10', async function() {
await advanceToBlock(startBlock + 9)
await crowdsale.buyTokens(buyer2, { value, from: purchaser2 })
balance = await token.balanceOf(buyer2)
balance.should.be.bignumber.equal(value.mul(rateAtBlock10))
})
it('at block 20', async function() {
await advanceToBlock(startBlock + 19)
await crowdsale.buyTokens(buyer2, { value, from: purchaser2 })
balance = await token.balanceOf(buyer2)
balance.should.be.bignumber.equal(value.mul(rateAtBlock20))
})
it('at block 100', async function() {
await advanceToBlock(startBlock + 99)
await crowdsale.buyTokens(buyer2, { value, from: purchaser2 })
balance = await token.balanceOf(buyer2)
balance.should.be.bignumber.equal(value.mul(rateAtBlock100))
})
it('at block 2', async function() {
await advanceToBlock(startBlock + 1)
await crowdsale.buyTokens(buyer2, { value, from: purchaser2 })
balance = await token.balanceOf(buyer2)
balance.should.be.bignumber.equal(value.mul(rateAtBlock2))
})
it.skip('at block 10000', async function() {
await advanceToBlock(startBlock + 9999)
await crowdsale.buyTokens(buyer2, { value, from: purchaser2 })
balance = await token.balanceOf(buyer2)
balance.should.be.bignumber.equal(value.mul(rateAtBlock10000))
})
it.skip('at block 30000', async function() {
await advanceToBlock(startBlock + 29999)
await crowdsale.buyTokens(buyer2, { value, from: purchaser2 })
balance = await token.balanceOf(buyer2)
balance.should.be.bignumber.equal(value.mul(rateAtBlock30000))
})
})
it('whitelisted buyers should access tokens at reduced price until end of auction', async function() {
await crowdsale.addToWhitelist(buyer)
await crowdsale.buyTokens(buyer, { value, from: buyer })
const balance = await token.balanceOf(buyer)
balance.should.be.bignumber.equal(value.mul(preferentialRate))
})
it('whitelisted big whale investor should not exceed the cap', async function() {
const cap = await crowdsale.cap()
const overCap = cap.mul(2)
await crowdsale.addToWhitelist(buyer)
await crowdsale
.buyTokens(buyer, { value: overCap, from: buyer })
.should.be.rejectedWith(EVMRevert)
const balance = await token.balanceOf(buyer)
const raised = await crowdsale.weiRaised()
balance.should.be.bignumber.equal(0)
raised.should.be.bignumber.most(cap)
})
it('owner can set the price for a particular buyer', async function() {
await crowdsale.addToWhitelist(buyer)
const preferentialRateForBuyer = new BigNumber(200)
const { logs } = await crowdsale.setBuyerRate(
buyer,
preferentialRateForBuyer
)
const event = logs.find(e => e.event === 'PreferentialRateChange')
expect(event).to.exist
await crowdsale.buyTokens(buyer, { value, from: buyer })
const balance = await token.balanceOf(buyer)
balance.should.be.bignumber.equal(value.mul(preferentialRateForBuyer))
balance.should.not.be.bignumber.equal(value.mul(preferentialRate))
// cannot change rate after crowdsale starts
await advanceToBlock(startBlock - 1)
await crowdsale
.setBuyerRate(buyer, preferentialRateForBuyer)
.should.be.rejectedWith(EVMRevert)
})
it('owner cannot set a custom rate before whitelisting a buyer', async function() {
await crowdsale
.setBuyerRate(buyer, new BigNumber(200))
.should.be.rejectedWith(EVMRevert)
})
it('beneficiary is not the same as buyer', async function() {
const beneficiary = buyer2
await crowdsale.addToWhitelist(buyer)
await crowdsale.addToWhitelist(beneficiary)
const preferentialRateForBuyer = new BigNumber(200)
const invalidRate = new BigNumber(100)
await crowdsale.setBuyerRate(buyer, preferentialRateForBuyer)
await crowdsale.setBuyerRate(beneficiary, invalidRate)
await crowdsale.buyTokens(beneficiary, { value, from: buyer })
const balance = await token.balanceOf(beneficiary)
balance.should.be.bignumber.equal(value.mul(preferentialRateForBuyer))
})
it('tokens should be assigned correctly to foundation when finalized', async function() {
await advanceToBlock(startBlock - 1)
// since price at first block is 1000, total tokens emitted will be 4000
await crowdsale.buyTokens(buyer, { value: 4, from: purchaser })
await advanceToBlock(endBlock)
await crowdsale.finalize()
const balance = await token.balanceOf(wallet)
balance.should.be.bignumber.equal(expectedFoundationTokens)
const totalSupply = await token.totalSupply()
totalSupply.should.be.bignumber.equal(expectedTokenSupply)
})
})
================================================
FILE: test/MANAToken.js
================================================
// @flow
'use strict'
const { EVMRevert } = require('./utils')
const MANAToken = artifacts.require('./MANAToken.sol')
const BigNumber = web3.BigNumber
contract('MANACrowdsale', function([owner, holder]) {
let token
beforeEach(async function() {
token = await MANAToken.new()
})
it('cannot burn tokens while paused', async function() {
await token.mint(holder, 1000)
await token.pause()
await token.burn(500, { from: holder }).should.be.rejectedWith(EVMRevert)
await token.unpause()
await token.burn(500, { from: holder }).should.be.fulfilled
})
})
================================================
FILE: test/WhitelistedCrowdsale.js
================================================
// @flow
import { ether, EVMRevert } from './utils'
const BigNumber = web3.BigNumber
const WhitelistedCrowdsale = artifacts.require(
'./helpers/WhitelistedCrowdsaleImpl.sol'
)
const MintableToken = artifacts.require(
'zeppelin-solidity/contracts/tokens/MintableToken'
)
contract('WhitelistCrowdsale', function([
_,
owner,
wallet,
beneficiary,
sender
]) {
const rate = new BigNumber(1000)
beforeEach(async function() {
this.startBlock = web3.eth.blockNumber + 10
this.endBlock = this.startBlock + 10
this.crowdsale = await WhitelistedCrowdsale.new(
this.startBlock,
this.endBlock,
rate,
wallet,
{ from: owner }
)
this.token = MintableToken.at(await this.crowdsale.token())
})
describe('whitelisting', function() {
const amount = ether(1)
it('should add address to whitelist', async function() {
let whitelisted = await this.crowdsale.isWhitelisted(sender)
whitelisted.should.equal(false)
await this.crowdsale.addToWhitelist(sender, { from: owner })
whitelisted = await this.crowdsale.isWhitelisted(sender)
whitelisted.should.equal(true)
})
it('should reject non-whitelisted sender', async function() {
await this.crowdsale
.buyTokens(beneficiary, { value: amount, from: sender })
.should.be.rejectedWith(EVMRevert)
})
it('should sell to whitelisted address', async function() {
await this.crowdsale.addToWhitelist(sender, { from: owner })
await this.crowdsale.buyTokens(beneficiary, {
value: amount,
from: sender
}).should.be.fulfilled
})
})
})
================================================
FILE: test/helpers/BurnableTokenMock.sol
================================================
pragma solidity ^0.4.11;
import '../../contracts/BurnableToken.sol';
contract BurnableTokenMock is BurnableToken {
function BurnableTokenMock(address initialAccount, uint initialBalance) {
balances[initialAccount] = initialBalance;
totalSupply = initialBalance;
}
}
================================================
FILE: test/helpers/WhitelistedCrowdsaleImpl.sol
================================================
pragma solidity ^0.4.11;
import '../../contracts/WhitelistedCrowdsale.sol';
contract WhitelistedCrowdsaleImpl is WhitelistedCrowdsale {
function WhitelistedCrowdsaleImpl (
uint256 _startBlock,
uint256 _endBlock,
uint256 _rate,
address _wallet
)
Crowdsale(_startBlock, _endBlock, _rate, _wallet)
WhitelistedCrowdsale()
{
}
}
================================================
FILE: test/utils.js
================================================
// @flow
const BigNumber = web3.BigNumber
export const should = require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should()
type RPCParams = {
method: string,
params?: Array<mixed>
}
function rpcCall(call: RPCParams) {
const payload: any = Object.assign(
{},
{
jsonrpc: '2.0',
id: Date.now()
},
call
)
return new Promise((resolve, reject) => {
web3.currentProvider.sendAsync(payload, (err, res) => {
return err ? reject(err) : resolve(res)
})
})
}
export function advanceBlock() {
return rpcCall({ method: 'evm_mine' })
}
export function increaseTime(seconds: number) {
return rpcCall({ method: 'evm_increaseTime', params: [+seconds] })
}
// Advances the block number so that the last mined block is `number`.
export async function advanceToBlock(number: 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 async function advanceToTime(timestamp: number) {
const block = await web3.eth.getBlock('latest')
if (block.timestamp > timestamp) {
throw Error(
`time ${timestamp} is in the past (current is ${block.timestamp})`
)
}
await advanceTime(timestamp - block.timestamp)
}
export async function advanceTime(time: number) {
await increaseTime(time)
await advanceBlock()
}
export function ether(n: number) {
return new web3.BigNumber(web3.toWei(n, 'ether'))
}
export const EVMThrow = 'invalid opcode'
export const EVMRevert = 'revert'
================================================
FILE: truffle.js
================================================
require('babel-register');
require('babel-polyfill');
module.exports = {
networks: {
livenet: {
host: "localhost",
port: 8546,
network_id: "1" // Match any network id
},
development: {
host: "localhost",
port: 8545,
gas: 10000000,
network_id: "*" // Match any network id
}
}
};
gitextract_8qja0b87/ ├── .babelrc ├── .circleci/ │ └── config.yml ├── .editorconfig ├── .eslintrc ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── contracts/ │ ├── BurnableToken.sol │ ├── ContinuousSale.sol │ ├── MANAContinuousSale.sol │ ├── MANACrowdsale.sol │ ├── MANAToken.sol │ ├── Migrations.sol │ └── WhitelistedCrowdsale.sol ├── flow-typed/ │ └── defs.js ├── migrations/ │ ├── 1_initial_migration.js │ └── 2_deploy_contracts.js ├── package.json ├── scripts/ │ └── test.sh ├── test/ │ ├── BurnableToken.js │ ├── MANAContinuousSale.js │ ├── MANACrowdsale.js │ ├── MANAToken.js │ ├── WhitelistedCrowdsale.js │ ├── helpers/ │ │ ├── BurnableTokenMock.sol │ │ └── WhitelistedCrowdsaleImpl.sol │ └── utils.js └── truffle.js
SYMBOL INDEX (7 symbols across 1 files)
FILE: test/utils.js
function rpcCall (line 15) | function rpcCall(call: RPCParams) {
function advanceBlock (line 32) | function advanceBlock() {
function increaseTime (line 36) | function increaseTime(seconds: number) {
function advanceToBlock (line 41) | async function advanceToBlock(number: number) {
function advanceToTime (line 55) | async function advanceToTime(timestamp: number) {
function advanceTime (line 66) | async function advanceTime(time: number) {
function ether (line 71) | function ether(n: number) {
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (41K chars).
[
{
"path": ".babelrc",
"chars": 33,
"preview": "{\n \"presets\": [\"flow\", \"env\"]\n}\n"
},
{
"path": ".circleci/config.yml",
"chars": 1609,
"preview": "version: 2\njobs:\n build:\n docker:\n # specify the version you desire here\n - image: circleci/node:10.2-stre"
},
{
"path": ".editorconfig",
"chars": 124,
"preview": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\n\n[*.sol]\nindent_style = space\nindent_size "
},
{
"path": ".eslintrc",
"chars": 169,
"preview": "{\n \"extends\": [\"standard\"],\n \"env\": {\n \"node\": true,\n \"jasmine\": true\n },\n \"globals\": {\n \"web3\": false,\n "
},
{
"path": ".flowconfig",
"chars": 48,
"preview": "[ignore]\n\n[include]\n\n[libs]\n\n[options]\n\n[lints]\n"
},
{
"path": ".gitattributes",
"chars": 56,
"preview": "*.sol linguist-language=Solidity\n*.js linguist-vendored\n"
},
{
"path": ".gitignore",
"chars": 21,
"preview": "node_modules/\nbuild/\n"
},
{
"path": ".npmignore",
"chars": 42,
"preview": "migrations\ntest\nzos.*\nscripts \nflow-typed\n"
},
{
"path": ".travis.yml",
"chars": 78,
"preview": "language: node_js\nnode_js:\n - \"7\"\ncache:\n directories:\n - \"node_modules\"\n"
},
{
"path": "LICENSE",
"chars": 586,
"preview": "Copyright 2017 The Decentraland Team\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use fi"
},
{
"path": "README.md",
"chars": 282,
"preview": "\n\nDecentraland MANA Token\n=======================\n\n[ => void\ndeclare var it: (string, Function) => void\ndeclare var describe: (strin"
},
{
"path": "migrations/1_initial_migration.js",
"chars": 129,
"preview": "var Migrations = artifacts.require(\"./Migrations.sol\");\n\nmodule.exports = function(deployer) {\n deployer.deploy(Migrati"
},
{
"path": "migrations/2_deploy_contracts.js",
"chars": 143,
"preview": "const MANACrowdsale = artifacts.require(\"./MANACrowdsale.sol\");\n\nmodule.exports = function(deployer) {\n // deployer.dep"
},
{
"path": "package.json",
"chars": 1532,
"preview": "{\n \"name\": \"decentraland-mana\",\n \"version\": \"0.0.0-semantic-release\",\n \"description\": \"Solidity Contracts for the Dec"
},
{
"path": "scripts/test.sh",
"chars": 1748,
"preview": "#!/usr/bin/env bash\n\n# Executes cleanup function at script exit.\ntrap cleanup EXIT\n\ncleanup() {\n # Kill the testrpc ins"
},
{
"path": "test/BurnableToken.js",
"chars": 1001,
"preview": "// @flow\n'use strict'\n\nconst { should, EVMThrow } = require('./utils.js')\nconst BurnableTokenMock = artifacts.require('."
},
{
"path": "test/MANAContinuousSale.js",
"chars": 3820,
"preview": "// @flow\n'use strict'\n\nconst expect = require('chai').expect\nconst { advanceTime, EVMRevert } = require('./utils')\nconst"
},
{
"path": "test/MANACrowdsale.js",
"chars": 8798,
"preview": "// @flow\n'use strict'\n\nconst expect = require('chai').expect\n\nconst { advanceToBlock, ether, should, EVMRevert } = requi"
},
{
"path": "test/MANAToken.js",
"chars": 588,
"preview": "// @flow\n'use strict'\n\nconst { EVMRevert } = require('./utils')\nconst MANAToken = artifacts.require('./MANAToken.sol')\n\n"
},
{
"path": "test/WhitelistedCrowdsale.js",
"chars": 1642,
"preview": "// @flow\nimport { ether, EVMRevert } from './utils'\n\nconst BigNumber = web3.BigNumber\n\nconst WhitelistedCrowdsale = arti"
},
{
"path": "test/helpers/BurnableTokenMock.sol",
"chars": 282,
"preview": "pragma solidity ^0.4.11;\n\nimport '../../contracts/BurnableToken.sol';\n\ncontract BurnableTokenMock is BurnableToken {\n\n "
},
{
"path": "test/helpers/WhitelistedCrowdsaleImpl.sol",
"chars": 363,
"preview": "pragma solidity ^0.4.11;\n\n\nimport '../../contracts/WhitelistedCrowdsale.sol';\n\n\ncontract WhitelistedCrowdsaleImpl is Whi"
},
{
"path": "test/utils.js",
"chars": 1685,
"preview": "// @flow\n\nconst BigNumber = web3.BigNumber\n\nexport const should = require('chai')\n .use(require('chai-as-promised'))\n "
},
{
"path": "truffle.js",
"chars": 341,
"preview": "require('babel-register');\nrequire('babel-polyfill');\n\nmodule.exports = {\n networks: {\n livenet: {\n host: \"loca"
}
]
About this extraction
This page contains the full source code of the decentraland/mana GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (36.6 KB), approximately 10.1k tokens, and a symbol index with 7 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.