The ethereum.org crowdsale tutorial: contributions run to a deadline against a goal, pay out a separate reward token, and are refunded if the goal is missed.
Historical Significance
The ethereum.org crowdsale tutorial was the worked example most token sales of the period started from, and its shape, a deadline, a goal, a fixed price and a refund if the goal is missed, is the one the sales of the following two years repeated. The flaws are instructive too: unchecked send() calls and an unbounded refund loop are exactly the failures auditors spent the next years writing up.
Context
By 2016 the ethereum.org tutorial had been rewritten around the transfer interface that ERC-20 was standardising, and its crowdsale took its funding goal and token price in whole ether rather than wei. Copies of it went on chain steadily through the year, most of them as trials with short deadlines and small goals rather than as real sales.
Key Facts
Description
The crowdfunding contract from the ethereum.org tutorial. Its constructor fixes the beneficiary, the funding goal in whole ether, the length of the campaign in minutes, the price of one reward token in whole ether, and the token contract that pays contributors. None of them can be changed afterwards.
Contributions arrive through the fallback function, which records the sender and amount in a public funders array and calls transfer on the reward token for the amount divided by the price. After the deadline anyone may call checkGoalReached(): if the goal was met the balance is sent to the beneficiary, otherwise the contract loops over the funders and refunds them, and in both cases whatever remains is swept to the beneficiary and the sale is marked closed.
The reward tokens must already be held by this contract when the sale opens, and nothing checks that they are. The refund loop is unbounded and no send() return value is checked, so a campaign with many contributors can leave funds it cannot pay out.
This one pays 0x56baf00c2121b604a29ca0f299a26b2022d094a4 on success. It was opened with a goal of 4 ether, a window of 5 minutes, and a price of 1 ether for one token, handing out the token at 0x56baf00c2121b604a29ca0f299a26b2022d094a4.
Heuristic Analysis
The following characteristics were detected through bytecode analysis and may not be accurate.
Spurious Dragon Era
Continued DoS protection. State trie clearing.
Bytecode Overview
Verified Source Available
This contract has verified source code on Etherscan.
Show source code (Solidity)
// Submitted by EthereumHistory (ethereumhistory.com)
pragma solidity ^0.4.2;
contract token { function transfer(address receiver, uint amount){ } }
contract Crowdsale {
address public beneficiary;
uint public fundingGoal; uint public amountRaised; uint public deadline; uint public price;
token public tokenReward;
mapping(address => uint256) public balanceOf;
bool fundingGoalReached = false;
event GoalReached(address beneficiary, uint amountRaised);
event FundTransfer(address backer, uint amount, bool isContribution);
bool crowdsaleClosed = false;
/* data structure to hold information about campaign contributors */
/* at initialization, setup the owner */
function Crowdsale(
address ifSuccessfulSendTo,
uint fundingGoalInEthers,
uint durationInMinutes,
uint etherCostOfEachToken,
token addressOfTokenUsedAsReward
) {
beneficiary = ifSuccessfulSendTo;
fundingGoal = fundingGoalInEthers * 1 ether;
deadline = now + durationInMinutes * 1 minutes;
price = etherCostOfEachToken * 1 ether;
tokenReward = token(addressOfTokenUsedAsReward);
}
/* The function without name is the default function that is called whenever anyone sends funds to a contract */
function () payable {
if (crowdsaleClosed) throw;
uint amount = msg.value;
balanceOf[msg.sender] = amount;
amountRaised += amount;
tokenReward.transfer(msg.sender, amount / price);
FundTransfer(msg.sender, amount, true);
}
modifier afterDeadline() { if (now >= deadline) _; }
/* checks if the goal or time limit has been reached and ends the campaign */
function checkGoalReached() afterDeadline {
if (amountRaised >= fundingGoal){
fundingGoalReached = true;
GoalReached(beneficiary, amountRaised);
}
crowdsaleClosed = true;
}
function safeWithdrawal() afterDeadline {
if (!fundingGoalReached) {
uint amount = balanceOf[msg.sender];
balanceOf[msg.sender] = 0;
if (amount > 0) {
if (msg.sender.send(amount)) {
FundTransfer(msg.sender, amount, false);
} else {
balanceOf[msg.sender] = amount;
}
}
}
if (fundingGoalReached && beneficiary == msg.sender) {
if (beneficiary.send(amountRaised)) {
FundTransfer(beneficiary, amountRaised, false);
} else {
//If we fail to send the funds to beneficiary, unlock funders balance
fundingGoalReached = false;
}
}
}
}
External Links
Related contracts
Contract 0x24fe45...1837fc
Same eraThe public counter that sells permissioned storage deployments for a fee
0x24fe45...1837fcNovember 23, 2016Contract 0xf8b094...41732a
Same eraThe public counter that sells mintable token deployments for a fee
0xf8b094...41732aNovember 24, 2016Contract 0xad8d30...369f49
Same eraThe public counter that sells DAO container deployments for a fee
0xad8d30...369f49November 24, 2016TokenTraderFactory
Same eraThe factory behind the TokenTrader contracts: it deploys a trader per asset and price, hands it to the caller, and vouches for what it built.
0x780a5c...600d50November 26, 2016Contract 0xb4ba41...2509fe
Same eraThe public counter that sells congress governance deployments for a fee
0xb4ba41...2509feNovember 28, 2016Contract 0x46a6c1...f12cca
Same eraA factory library that deploys permissioned key value storage
0x46a6c1...f12ccaNovember 30, 2016