原文: https://www.ethereum.org/crowdsale
翻译: terryc007
版本: v1.0
Crowdfund your idea
为你的idea众筹
Sometimes a good idea takes a lot of funds and collective effort. You could ask for donations, but donors prefer to give to projects they are more certain will get traction and proper funding. This is an example where a crowdfunding would be ideal: you set up a goal and a deadline for reaching it. If you miss your goal, the donations are returned, therefore reducing the risk for donors. Since the code is open and auditable, there is no need for a centralized, trusted platform and therefore the only fees everyone will pay are just the gas fees.
有时要实现一个好的想法需要很多资金,集体的努力。你可以寻求捐款,但是捐助者更喜欢给那些,他们认为更具吸引力,资金需求恰当的项目捐赠。这有个例子,在众筹上是完美的: 你设置众筹的目标,到期时间。如果目标没有完成,捐款会退回给捐赠者,这减低捐赠者的风险。因为代码是公开的,可审计的,不需要中心的,可信的平台,因此每个捐赠者只需要支付gas费用。
TOKENS AND DAOS
Tokens 跟 DAOS
In this example we will make a better crowdfunding by solving two important problems: how rewards are managed and kept, and how the money is spent after the funds are raised.
在这个例子,我们会进行一个更好的众筹,它可以解决两个重要的问题: 如何管理奖励,以及在完成资金募集后,如何使用这些钱?
Rewards in crowdfundings are usually handled by a central unchangeable database that keeps track of all donors: anyone who missed the deadline for the campaign cannot get in anymore and any donor who changed their mind can't get out. Instead we are going to do this the decentralized way and just create a token to keep track of rewards, anyone who contributes gets a token that they can trade, sell or keep for later. When the time comes to give the physical reward the producer only needs to exchange the tokens for real products. Donors get to keep their tokens, even if the project doesn't achieve its goals, as a souvenir.
在众筹中,通常用一个中心不可修改的数据库来处理所有捐赠者的奖励。任何人只要错过截止时间,他们就无法再参与,任何捐赠者一旦完成捐赠就没法退出。取而代之,我们将用一个去中心化的方式来处理这个事情。 通过创建一个token来跟踪奖励,任何捐赠者会获得一个token,随后,他们可以交易,卖掉或保留它。 当到了发送实物奖励产品时,只能通过token来换取实物奖品。即使项目没有完成目标,他们也可以保留token,把它当做纪念品。
Also, generally those who are funding can't have any say on how the money is spent after the funds are raised and mismanagement often causes projects never to deliver anything at all. In this project we will use a Democratic Organization that will have to approve any money coming out of the system. This is often called a crowdsale or crowd equity and is so fundamental that in some cases the token can be the reward itself, especially in projects where a group of people gather together to build a common public good.
同样,通常捐赠者对于募集到的资金,什么也不能说,缺乏管理经常使得这些项目永远无法交付产品。在这个项目中,我们将会使用一个民主的组织。要从这个组织里面拿钱,就必须要通过该组织允许。这个通常被称为crowdsale 或 crowd equity。因为这是非常根本的东西,所以在一些情况下,token可以实现自我奖励,特别是在那些聚集很多人一起构建一个公共产品的的项目中。
- If you are just testing, switch the wallet to the testnet and start mining.
- First of all, create a fixed supply token. For this example, we are going to create a supply of 100, use the name gadgets, the box emoji (📦) as a symbol and 0decimal places. Deploy it and save the address.
- Now create a shareholder association. In this example we are going to use the address of the token we just created as the Shares Address, a minimum quorum of 10, and 1500 minutes (25 hours) as the voting time. Deploy this contract and save the address.
- 如果要测试,把钱包切换到测试网络,在启动挖矿。
- 首先,创建一个总量固定的token。在这个例子中,我们创建100个,名字叫gadgets, emoji 箱子表情📦作为token的符号, 小数位设置为0, 部署它,然后保存token的合约地址。
- 现在创建一个股东协会。在这个例子中,我们使用token合约的地址作为股份地址,最少参股人10个,投票时间为1500分钟(25小时)。 部署这个合约,并保存这个合约地址。
THE CODE
Now copy this code and let's create the crowdsale:
代码
现在复制这些代码,然后创建crowdsale:
pragma solidity ^0.4.18;
interface token {
function transfer(address receiver, uint amount) external;
}
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;
bool crowdsaleClosed = false;
event GoalReached(address recipient, uint totalAmountRaised);
event FundTransfer(address backer, uint amount, bool isContribution);
/**
* Constructor function
*
* Setup the owner
*/
function Crowdsale(
address ifSuccessfulSendTo,
uint fundingGoalInEthers,
uint durationInMinutes,
uint etherCostOfEachToken,
address addressOfTokenUsedAsReward
) public {
beneficiary = ifSuccessfulSendTo;
fundingGoal = fundingGoalInEthers * 1 ether;
deadline = now + durationInMinutes * 1 minutes;
price = etherCostOfEachToken * 1 ether;
tokenReward = token(addressOfTokenUsedAsReward);
}
/**
* Fallback function
*
* The function without name is the default function that is called whenever anyone sends funds to a contract
*/
function () payable public {
require(!crowdsaleClosed);
uint amount = msg.value;
balanceOf[msg.sender] += amount;
amountRaised += amount;
tokenReward.transfer(msg.sender, amount / price);
emit FundTransfer(msg.sender, amount, true);
}
modifier afterDeadline() { if (now >= deadline) _; }
/**
* Check if goal was reached
*
* Checks if the goal or time limit has been reached and ends the campaign
*/
function checkGoalReached() public afterDeadline {
if (amountRaised >= fundingGoal){
fundingGoalReached = true;
emit GoalReached(beneficiary, amountRaised);
}
crowdsaleClosed = true;
}
/**
* Withdraw the funds
*
* Checks to see if goal or time limit has been reached, and if so, and the funding goal was reached,
* sends the entire amount to the beneficiary. If goal was not reached, each contributor can withdraw
* the amount they contributed.
*/
function safeWithdrawal() public afterDeadline {
if (!fundingGoalReached) {
uint amount = balanceOf[msg.sender];
balanceOf[msg.sender] = 0;
if (amount > 0) {
if (msg.sender.send(amount)) {
emit FundTransfer(msg.sender, amount, false);
} else {
balanceOf[msg.sender] = amount;
}
}
}
if (fundingGoalReached && beneficiary == msg.sender) {
if (beneficiary.send(amountRaised)) {
emit FundTransfer(beneficiary, amountRaised, false);
} else {
//If we fail to send the funds to beneficiary, unlock funders balance
fundingGoalReached = false;
}
}
}
}
CODE HIGHLIGHTS
Notice that in the Crowdsale function (the one that is called upon contract creation), how the variables deadline and fundingGoal are set:
代码强调
注意Crowdslae函数中变量deadline跟fundingGoal是如何设置的。
fundingGoal = fundingGoalInEthers * 1 ether;
deadline = now + durationInMinutes * 1 minutes;
price = etherCostOfEachToken * 1 ether;
Those are some of the special keywords in solidity that help you code, allowing you to evaluate some things like 1 ether == 1000 finney or 2 days == 48 hours. Inside the system all ether amounts are kept track in wei, the smallest divisible unit of ether. The code above converts the funding goal into wei by multiplying it by 1,000,000,000,000,000,000 (which is what the special keyword ether converts into). The next line creates a timestamp that is exactly X minutes away from today by also using a combination of the special keywords now and minutes. For more global keywords, check the solidity documentation on Globally available variables.
在solidity语言中,这是一些特殊关键字,来帮助你计算一些东西,像1 ETH == 1000 finney 或 2 天 == 48 小时。在以太坊系统,所有的ETH数量都采用wei来计量, 它是ETH最小的单位。 上面的代码把众筹的目的额度,通过乘以1,000,000,000,000,000,000( 由关键字ether 会转化而来),转化成以wei来计量。下一行通用使用特别关键字now跟minutes来创建一个基于当前的时间戳。 要查看全局关键字,请查看solidity文档中的全局变量。
The following line will instantiate a contract at a given address:
下面这行代码会用给定的地址实例化一个合约对象。
tokenReward = token(addressOfTokenUsedAsReward);
Notice that the contract understands what a token is because we defined it earlier by starting the code with:
注意, 合约知道token是什么,因为在开头,已经定义了它。
interface token { function transfer(address receiver, uint amount){ } }
This doesn't fully describe how the contract works or all the functions it has, but describes only the ones this contract needs: a token is a contract with a transferfunction, and we have one at this address.
这并没有完整描叙这个合约是如何工作的,或它是里面所有的函数,但有这个合约仅需要描述的一件事情: 一个token是一个合约,它有一个transfer 函数, 我们在这个地址上有这么一个token合约。
HOW TO USE
如何使用
Go to contracts and then deploy contract:
去“合约”页面,然后部署这合约:
- Put the address of the organization you just created in the field if successful, send to.
- Put 250 ethers as the funding goal
- If you are just doing it for a test or demonstration, put the crowdsale duration as 3-10 minutes, but if you are really raising funds you can put a larger amount, like 45,000 (31 days).
- The ether cost of each token should be calculated based on how many tokens you are putting up for sale (a maximum of how many you added as "initial supply" of your token on the previous step). In this example, put 5 ethers.
- The address of the token you created should be added to the token reward address
- 输入你刚创建的组织地址,如果众筹成功,会发ETH发送到这个地址。
- 设置众筹目标为250个ETH
- 如果你只是测试或演示,把众筹时间设置为3 ~ 10分钟,但如果你是真正的筹集资金,你可以把值设的更大一点,比如45000(31天)
- 每个token兑换多少个ETH,应基于你发售的token数量来计算(最大值是token的总发行量)。在这个例子中,设置为5个ETH.
- 你创建的token的地址,它会被加入到token奖励地址。
Put a gas price, click deploy and wait for your crowdsale to be created. Once the crowdsale page is created, you now need to deposit enough rewards so it can pay the rewards back. Click the address of the crowdsale, then deposit and send 50 gadgets to the crowdsale.
设置gas价格,点击“部署”, 然后等待你的crowdsale合约成功创建。 一旦crowdsale页面创建成功,你需要在合约里面充值足够的奖励(你的token),这样它才有给打ETH的人会送奖励。 点击crowdsale的地址,然后往里面冲50个gadgets用来众筹。
I have 100 gadgets. Why not sell them all?
This is a very important point. The crowdsale we are building will be completely controlled by the token holders. This creates the danger that someone controlling 50%+1 of all the tokens will be able to send all the funds to themselves. You can try to create special code on the association contract to prevent these hostile takeovers, or you can instead have all the funds sent to a simple address. To simplify we are simply selling off half of all the gadgets: if you want to further decentralize this, split the remaining half between trusted organizations.
我有100个gadgets,为什么不卖完?
这是一个非常重要的点。如果这样做,我们构建的众筹会会完全被token的持有者控制。一些人控制了51%的token后,会有他们把所有的资金发送给他们自己的风险。你可以在协会合约中,创建一些特定的代码来防止恶意收购,或者你可以把所有的资金发送到一个简单的地址。为简化这个事情,我们卖掉其中的一半。如果你想进一步的去中心化,把剩下的另一半token分发到一些可信的组织。
RAISE FUNDS
募集资金
Once the crowdsale has all the necessary tokens, contributing to it is easy and you can do it from any ethereum wallet: just send funds to it. You can see the relevant code bit here:
一旦crowdsale合约有所有必要的token,捐助它是非常容易的,你可以从任何以太坊钱包给它发送资金。下面是相关的代码
function () {
require(!crowdsaleClosed);
uint amount = msg.value;
// ...
The unnamed function is the default function executed whenever a contract receives ether. This function will automatically check if the crowdsale is active, calculate how many tokens the caller bought and send the equivalent. If the crowdsale has ended or if the contract is out of tokens the contract will throwmeaning the execution will be stopped and the ether sent will be returned (but all the gas will be spent).
当合约收到ETH时,合约会执行默认的没有命名的函数。这个函数会自动检查crowdsale是否还在进行,计算调用者购买了多少token,并发送给他们。如果crowdsale已经结束或如果合约token不够,合约会抛出异常。这就意味着停止合约执行,并退回ETH(除了被花掉的gas)。
This has the advantage that the contract prevents falling into a situation that someone will be left without their ether or tokens. In a previous version of this contract we would also self destruct the contract after the crowdsale ended: this would mean that any transaction sent after that moment would lose their funds. By creating a fallback function that throws when the sale is over, we prevent anyone losing money.
这有做有一个好处,合约可以防止一些人的丢ETH或Token。 在该合约之前的一个版本,当众筹结束后,我们会自我销毁这个合约。这就意味着,当众筹结束后,任何发送到合约的资金都会丢失。通过创建一个回调函数,当众筹结束后,它会抛出异常,来防止任何人丢失资金。
The contract has a safeWithdrawl() function, without any parameters, that can be executed by the beneficiary to access the amount raised or by the funders to get back their funds in the case of a failed fundraise.
这个合约有一个没有参数的safeWithdrawl()函数,募集资金的受益者可以执行它来获取这些募集到的资金,或者但众筹失败后,投资者执行它拿回自己的资金。
Extending the crowdsale
扩展Crowdsale合约
WHAT IF THE CROWDSALE OVERSHOOTS ITS TARGET?
如果超募怎么办?
In our code, only two things can happen: either the crowdsale reaches its target or it doesn't. Since the token amount is limited, it means that once the goal has been reached no one else can contribute. But the history of crowdfunding is full of projects that overshoot their goals in much less time than predicted or that raised many times over the required amount.
在我们的代码里,只发生了两件事: 募集的资金达到设定目标,或者没有。因为token的数量是有限的,这意味着一旦达到目标,没人能再捐款了。但是在众筹的历史中,太多超募的项目,在比预期短很多时间里达成目标,或募集到的资金是所需要的好几倍。
UNLIMITED CROWDSALE
无限制众筹
So we are going to modify our project slightly so that instead of sending a limited set of tokens, the project actually creates a new token out of thin air whenever someone sends them ether. First of all, we need to create a Mintable token.
所以我们要对项目代码稍做修改下,这样在任何时候,只要有人给合约发送ETH, 这个项目实际上会凭空创建一个新的token。 首先,我们需要创建一个 Mintable token.
Then modify the crowdsale to rename all mentions of transfer to mintToken:
然后修改这个众筹合约,把所有用到transfer的函数重命名为mintToken:
contract token { function mintToken(address receiver, uint amount){ } }
// ...
function () {
// ...
tokenReward.mintToken(msg.sender, amount / price);
// ...
}
Once you published the crowdsale contract, get its address and go into your Token Contract to execute a Change Ownership function. This will allow your crowdsale to call the Mint Token function as much as it wants.
一旦发布这个众筹合约,去你的Token合约页面,执行Change Ownership函数。这会允许你的众筹合约可无限制的调用Mint Token函数。
Warning: This opens you to the danger of hostile takeover. At any point during the crowdsale anyone who donates more than the amount already raised will be able to control the whole pie and steal it. There are many strategies to prevent that, but implementing will be left as an exercise to the reader:
警告: 这会有恶意收购的风险。在众筹期间,任何人只要捐赠的资金超过目前已募集到的资金,它就能够控制整个合约,并窃取它。有很多策略来防止这种事情,这留给读者当做练习来做。
- Modify the crowdsale such that when a token is bought, also send the same quantity of tokens to the founder's account so that they always control 50% of the project
- Modify the Organization to create a veto power to some trusted third party that could stop any hostile proposal
- Modify the token to allow a central trusted party to freeze token accounts, so as to require a verification that there isn't any single entity controlling a majority of them
- 修改众筹合约,当token被购买时,也发送同样数量的token给创始人账号,这样创始人总是保证控制项目的50%token。
- 修改这个组织,创建一个否决权利,给一些可信的第三方,他们可停止任何恶意收购。
- 修改Token,允许一个中心的可信方能够冻结token账号,以便来核实:没有任何一个实体控制大部分token。