The Bancor crowdsale contract. Opened 2017-06-12 and collected 396,619.89 ETH, about $153M at the time.
Historical Significance
One of the largest token sales of 2017, raising 396,619.89 ETH (about $153M), confirmed by the contract's totalEtherContributed value.
Key Facts
Description
Ran the Bancor token sale, issuing 100 BNT per ETH contributed. It also accepted contributions routed through a separate BTC contribution path, limited transaction gas price to 50 gwei, and used a hidden cap committed as a hash and revealed during the sale. The sale opened on 2017-06-12 at 14:00 UTC.
Source Verified
Historian Categories
Spurious Dragon Era
Continued DoS protection. State trie clearing.
Bytecode Overview
Verified Source Available
Source verified on Etherscan.
Show source code (Solidity)
pragma solidity ^0.4.11;
/*
Overflow protected math functions
*/
contract SafeMath {
/**
constructor
*/
function SafeMath() {
}
/**
@dev returns the sum of _x and _y, asserts if the calculation overflows
@param _x value 1
@param _y value 2
@return sum
*/
function safeAdd(uint256 _x, uint256 _y) internal returns (uint256) {
uint256 z = _x + _y;
assert(z >= _x);
return z;
}
/**
@dev returns the difference of _x minus _y, asserts if the subtraction results in a negative number
@param _x minuend
@param _y subtrahend
@return difference
*/
function safeSub(uint256 _x, uint256 _y) internal returns (uint256) {
assert(_x >= _y);
return _x - _y;
}
/**
@dev returns the product of multiplying _x by _y, asserts if the calculation overflows
@param _x factor 1
@param _y factor 2
@return product
*/
function safeMul(uint256 _x, uint256 _y) internal returns (uint256) {
uint256 z = _x * _y;
assert(_x == 0 || z / _x == _y);
return z;
}
}
/*
Owned contract interface
*/
contract IOwned {
// this function isn't abstract since the compiler emits automatically generated getter functions as external
function owner() public constant returns (address owner) { owner; }
function transferOwnership(address _newOwner) public;
function acceptOwnership() public;
}
/*
Provides support and utilities for contract ownership
*/
contract Owned is IOwned {
address public owner;
address public newOwner;
event OwnerUpdate(address _prevOwner, address _newOwner);
/**
@dev constructor
*/
function Owned() {
owner = msg.sender;
}
// allows execution by the owner only
modifier ownerOnly {
assert(msg.sender == owner);
_;
}
/**
@dev allows transferring the contract ownership
the new owner still need to accept the transfer
can only be called by the contract owner
@param _newOwner new contract owner
*/
function transferOwnership(address _newOwner) public ownerOnly {
require(_newOwner != owner);
newOwner = _newOwner;
}
/**
@dev used by a new owner to accept an ownership transfer
*/
function acceptOwnership() public {
require(msg.sender == newOwner);
OwnerUpdate(owner, newOwner);
owner = newOwner;
newOwner = 0x0;
}
}
/*
ERC20 Standard Token interface
*/
contract IERC20Token {
// these functions aren't abstract since the compiler emits automatically generated getter functions as external
function name() public constant returns (string name) { name; }
function symbol() public constant returns (string symbol) { symbol; }
function decimals() public constant returns (uint8 decimals) { decimals; }
function totalSupply() public constant returns (uint256 totalSupply) { totalSupply; }
function balanceOf(address _owner) public constant returns (uint256 balance) { _owner; balance; }
function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { _owner; _spender; remaining; }
function transfer(address _to, uint256 _value) public returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
function approve(address _spender, uint256 _value) public returns (bool success);
}
/*
Token Holder interface
*/
contract ITokenHolder is IOwned {
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
}
/*
We consider every contract to be a 'token holder' since it's currently not possible
for a contract to deny receiving tokens.
The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
the owner to send tokens that were sent to the contract by mistake back to their sender.
*/
contract TokenHolder is ITokenHolder, Owned {
/**
@dev constructor
*/
function TokenHolder() {
}
// validates an address - currently only checks that it isn't null
modifier validAddress(address _address) {
require(_address != 0x0);
_;
}
// verifies that the address is different than this contract address
modifier notThis(address _address) {
require(_address != address(this));
_;
}
/**
@dev withdraws tokens held by the contract and sends them to an account
can only be called by the owner
@param _token ERC20 token contract address
@param _to account to receive the new amount
@param _amount amount to withdraw
*/
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
public
ownerOnly
validAddress(_token)
validAddress(_to)
notThis(_to)
{
assert(_token.transfer(_to, _amount));
}
}
/*
Smart Token interface
*/
contract ISmartToken is ITokenHolder, IERC20Token {
function disableTransfers(bool _disable) public;
function issue(address _to, uint256 _amount) public;
function destroy(address _from, uint256 _amount) public;
}
/*
The smart token controller is an upgradable part of the smart token that allows
more functionality as well as fixes for bugs/exploits.
Once it accepts ownership of the token, it becomes the token's sole controller
that can execute any of its functions.
To upgrade the controller, ownership must be transferred to a new controller, along with
any relevant data.
The smart token must be set on construction and cannot be changed afterwards.
Wrappers are provided (as opposed to a single 'execute' function) for each of the token's functions, for easier access.
Note that the controller can transfer token ownership to a new controller that
doesn't allow executing any function on the token, for a trustless solution.
Doing that will also remove the owner's ability to upgrade the controller.
*/
contract SmartTokenController is TokenHolder {
ISmartToken public token; // smart token
/**
@dev constructor
*/
function SmartTokenController(ISmartToken _token)
validAddress(_token)
{
token = _token;
}
// ensures that the controller is the token's owner
modifier active() {
assert(token.owner() == address(this));
_;
}
// ensures that the controller is not the token's owner
modifier inactive() {
assert(token.owner() != address(this));
_;
}
/**
@dev allows transferring the token ownership
the new owner still need to accept the transfer
can only be called by the contract owner
@param _newOwner new token owner
*/
function transferTokenOwnership(address _newOwner) public ownerOnly {
token.transferOwnership(_newOwner);
}
/**
@dev used by a new owner to accept a token ownership transfer
can only be called by the contract owner
*/
function acceptTokenOwnership() public ownerOnly {
token.acceptOwnership();
}
/**
@dev disables/enables token transfers
can only be called by the contract owner
@param _disable true to disable transfers, false to enable them
*/
function disableTokenTransfers(bool _disable) public ownerOnly {
token.disableTransfers(_disable);
}
/**
@dev allows the owner to execute the token's issue function
@param _to account to receive the new amount
@param _amount amount to increase the supply by
*/
function issueTokens(address _to, uint256 _amount) public ownerOnly {
token.issue(_to, _amount);
}
/**
@dev allows the owner to execute the token's destroy function
@param _from account to remove the amount from
@param _amount amount to decrease the supply by
*/
function destroyTokens(address _from, uint256 _amount) public ownerOnly {
token.destroy(_from, _amount);
}
/**
@dev withdraws tokens held by the token and sends them to an account
can only be called by the owner
@param _token ERC20 token contract address
@param _to account to receive the new amount
@param _amount amount to withdraw
*/
function withdrawFromToken(IERC20Token _token, address _to, uint256 _amount) public ownerOnly {
token.withdrawTokens(_token, _to, _amount);
}
}
/*
Crowdsale v0.1
The crowdsale version of the smart token controller, allows contributing ether in exchange for Bancor tokens
The price remains fixed for the entire duration of the crowdsale
Note that 20% of the contributions are the Bancor token's reserve
*/
contract CrowdsaleController is SmartTokenController, SafeMath {
uint256 public constant DURATION = 14 days; // crowdsale duration
uint256 public constant TOKEN_PRICE_N = 1; // initial price in wei (numerator)
uint256 public constant TOKEN_PRICE_D = 100; // initial price in wei (denominator)
uint256 public constant BTCS_ETHER_CAP = 50000 ether; // maximum bitcoin suisse ether contribution
uint256 public constant MAX_GAS_PRICE = 50000000000 wei; // maximum gas price for contribution transactions
string public version = '0.1';
uint256 public startTime = 0; // crowdsale start time (in seconds)
uint256 public endTime = 0; // crowdsale end time (in seconds)
uint256 public totalEtherCap = 1000000 ether; // current ether contribution cap, initialized with a temp value as a safety mechanism until the real cap is revealed
uint256 public totalEtherContributed = 0; // ether contributed so far
bytes32 public realEtherCapHash; // ensures that the real cap is predefined on deployment and cannot be changed later
address public beneficiary = 0x0; // address to receive all ether contributions
address public btcs = 0x0; // bitcoin suisse address
// triggered on each contribution
event Contribution(address indexed _contributor, uint256 _amount, uint256 _return);
/**
@dev constructor
@param _token smart token the crowdsale is for
@param _startTime crowdsale start time
@param _beneficiary address to receive all ether contributions
@param _btcs bitcoin suisse address
*/
function CrowdsaleController(ISmartToken _token, uint256 _startTime, address _beneficiary, address _btcs, bytes32 _realEtherCapHash)
SmartTokenController(_token)
validAddress(_beneficiary)
validAddress(_btcs)
earlierThan(_startTime)
validAmount(uint256(_realEtherCapHash))
{
startTime = _startTime;
endTime = startTime + DURATION;
beneficiary = _beneficiary;
btcs = _btcs;
realEtherCapHash = _realEtherCapHash;
}
// verifies that an amount is greater than zero
modifier validAmount(uint256 _amount) {
require(_amount > 0);
_;
}
// verifies that the gas price is lower than 50 gwei
modifier validGasPrice() {
assert(tx.gasprice <= MAX_GAS_PRICE);
_;
}
// verifies that the ether cap is valid based on the key provided
modifier validEtherCap(uint256 _cap, uint256 _key) {
require(computeRealCap(_cap, _key) == realEtherCapHash);
_;
}
// ensures that it's earlier than the given time
modifier earlierThan(uint256 _time) {
assert(now < _time);
_;
}
// ensures that the current time is between _startTime (inclusive) and _endTime (exclusive)
modifier between(uint256 _startTime, uint256 _endTime) {
assert(now >= _startTime && now < _endTime);
_;
}
// ensures that the sender is bitcoin suisse
modifier btcsOnly() {
assert(msg.sender == btcs);
_;
}
// ensures that we didn't reach the ether cap
modifier etherCapNotReached(uint256 _contribution) {
assert(safeAdd(totalEtherContributed, _contribution) <= totalEtherCap);
_;
}
// ensures that we didn't reach the bitcoin suisse ether cap
modifier btcsEtherCapNotReached(uint256 _ethContribution) {
assert(safeAdd(totalEtherContributed, _ethContribution) <= BTCS_ETHER_CAP);
_;
}
/**
@dev computes the real cap based on the given cap & key
@param _cap cap
@param _key key used to compute the cap hash
@return computed real cap hash
*/
function computeRealCap(uint256 _cap, uint256 _key) public constant returns (bytes32) {
return keccak256(_cap, _key);
}
/**
@dev enables the real cap defined on deployment
@param _cap predefined cap
@param _key key used to compute the cap hash
*/
function enableRealCap(uint256 _cap, uint256 _key)
public
ownerOnly
active
between(startTime, endTime)
validEtherCap(_cap, _key)
{
require(_cap < totalEtherCap); // validate input
totalEtherCap = _cap;
}
/**
@dev computes the number of tokens that should be issued for a given contribution
@param _contribution contribution amount
@return computed number of tokens
*/
function computeReturn(uint256 _contribution) public constant returns (uint256) {
return safeMul(_contribution, TOKEN_PRICE_D) / TOKEN_PRICE_N;
}
/**
@dev ETH contribution
can only be called during the crowdsale
@return tokens issued in return
*/
function contributeETH()
public
payable
between(startTime, endTime)
returns (uint256 amount)
{
return processContribution();
}
/**
@dev Contribution through BTCs (Bitcoin Suisse only)
can only be called before the crowdsale started
@return tokens issued in return
*/
function contributeBTCs()
public
payable
btcsOnly
btcsEtherCapNotReached(msg.value)
earlierThan(startTime)
returns (uint256 amount)
{
return processContribution();
}
/**
@dev handles contribution logic
note that the Contribution event is triggered using the sender as the contributor, regardless of the actual contributor
@return tokens issued in return
*/
function processContribution() private
active
etherCapNotReached(msg.value)
validGasPrice
returns (uint256 amount)
{
uint256 tokenAmount = computeReturn(msg.value);
assert(beneficiary.send(msg.value)); // transfer the ether to the beneficiary account
totalEtherContributed = safeAdd(totalEtherContributed, msg.value); // update the total contribution amount
token.issue(msg.sender, tokenAmount); // issue new funds to the contributor in the smart token
token.issue(beneficiary, tokenAmount); // issue tokens to the beneficiary
Contribution(msg.sender, msg.value, tokenAmount);
return tokenAmount;
}
// fallback
function() payable {
contributeETH();
}
}External Links
Related contracts
Bancor
Same deployerBancor Network Token (BNT), the first Bancor Smart Token, deployed 2017-06-10 ahead of the Bancor crowdsale.
0x1f573d...a7ff1cJune 10, 2017BancorFormula
Same deployerThe original Bancor pricing formula, computing purchase and sale returns for Smart Tokens from supply, reserve and reserve ratio.
0x8d10c0...395b98June 22, 2017Ether Token
Same deployerBancor's ETH wrapper token, used as the ETH reserve of the original BNT converter.
0xd76b5c...b746aeJune 22, 2017BancorChanger
Same deployerThe original Bancor converter for BNT, deployed 2017-06-22 with an ETH reserve held as EtherToken.
0xca83bd...629ff4June 22, 2017BancorPriceFloor
Same deployerA contract that bought back BNT for ETH at the 0.01 ETH crowdsale price, deployed 2017-06-25.
0x7ec6a7...268549June 25, 2017BancorGasPriceLimit
Same deployerHolds the maximum gas price allowed for Bancor conversions, set to 20 gwei at deployment on 2017-10-19.
0xafb06c...b99590October 19, 2017