date:20170803
從合約中取回錢幣
發(fā)送錢幣的推薦方法是使用取回模式。盡管發(fā)送錢幣最簡(jiǎn)單的簡(jiǎn)單方法是直接調(diào)用send。但是它會(huì)引入潛在的風(fēng)險(xiǎn),所以也不推薦使用。你可以閱讀安全考慮章節(jié),了解更多信息。
這是一個(gè)在合約中使用取回模式的例子,功能是發(fā)送最多的錢幣到合約中,來(lái)成為首富,靈感來(lái)自King of the ether。
在下面的合約中,如果你要篡奪成為首富,你要接收去世的人的財(cái)產(chǎn)來(lái)成為新的首富。
pragma solidity ^0.4.11;
contract WithdrawalContract {
address public richest;
uint public mostSent;
mapping (address => uint) pendingWithdrawals;
function WithdrawalContract() payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() payable returns (bool) {
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;
}
}
function withdraw() {
uint amount = pendingWithdrawals[msg.sender];
// 記得在發(fā)送之前要把余額設(shè)置為0,以防止重入攻擊
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
這和直接發(fā)送模式相反:
pragma solidity ^0.4.11;
contract SendContract {
address public richest;
uint public mostSent;
function SendContract() payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() payable returns (bool) {
if (msg.value > mostSent) {
// 這一行會(huì)出現(xiàn)問(wèn)題(詳情在下文解釋)
richest.transfer(msg.value);
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;
}
}
}
注意,在這個(gè)例子中,攻擊者可以困住合約,讓合約處于不可使用的狀態(tài)。通過(guò)讓richest成為一個(gè)合約的地址,該合約有一個(gè)回調(diào)函數(shù),但是這個(gè)回調(diào)會(huì)執(zhí)行失敗(例如使用revert()或者只是消耗2300 以上的gas),這樣,通過(guò)transfer調(diào)用分發(fā)錢幣到”有毒“的合約中,它就會(huì)失敗,因此,becomeRichest就會(huì)失敗,讓合約永遠(yuǎn)不能正常執(zhí)行。
相反,如果你使用第一個(gè)例子取回模式,那么攻擊者只能讓他或她的取回函數(shù)失敗,但是合約的其余代碼都沒(méi)有問(wèn)題。
限制訪問(wèn)
限制訪問(wèn)時(shí)合約的通用模式。注意你不能限制任何人或者計(jì)算機(jī)讀取你的交易內(nèi)容或者你合約狀態(tài)。你可以用加密提升難度。但是如果你的合約是可以讀取數(shù)據(jù)的,那么所有人都可以讀取數(shù)據(jù)。
你可以通過(guò)其他合約,來(lái)限制合約的訪問(wèn)。默認(rèn)情況下是這樣的,除非你把狀態(tài)變量聲明為public。
另外,你可以限制誰(shuí)可以修改你的合約代碼,或者調(diào)用你的合約函數(shù),這是這個(gè)章節(jié)要說(shuō)的:
使用函數(shù)修改器使得限制高度可讀。
pragma solidity ^0.4.11;
contract AccessRestriction {
// 這些變量在構(gòu)造期間執(zhí)行,
// `msg.sender`是創(chuàng)建該合約的賬號(hào)
address public owner = msg.sender;
uint public creationTime = now;
// 修改器可以用來(lái)修改函數(shù)體。
// 如果使用了這個(gè)修改器,它會(huì)先檢測(cè),并且只有函數(shù)從特定地址調(diào)用才會(huì)放行
modifier onlyBy(address _account)
{
require(msg.sender == _account);
// 不要忘了"_;"!
// 它代表了使用該修改器的函數(shù)的函數(shù)體
_;
}
/// 讓 `_newOwner` 成為該合約的擁有者
function changeOwner(address _newOwner)
onlyBy(owner)
{
owner = _newOwner;
}
modifier onlyAfter(uint _time) {
require(now >= _time);
_;
}
/// 擦除擁有者信息。
/// 只能在合約創(chuàng)建6個(gè)星期之后被調(diào)用。
function disown()
onlyBy(owner)
onlyAfter(creationTime + 6 weeks)
{
delete owner;
}
// 這個(gè)修改器要求函數(shù)調(diào)用要有一定數(shù)量的錢幣
// 如果調(diào)用者發(fā)送了太多錢幣,會(huì)被退還,但是會(huì)在函數(shù)體執(zhí)行之后,再退還
// Solidity v0.4.0之前這么做是很危險(xiǎn)的,因?yàn)榭赡軙?huì)忽略`_;`之后的版本。
modifier costs(uint _amount) {
require(msg.value >= _amount);
_;
if (msg.value > _amount)
msg.sender.send(msg.value - _amount);
}
function forceOwnerChange(address _newOwner)
costs(200 ether)
{
owner = _newOwner;
// 一些示例條件
if (uint(owner) & 0 == 1)
// Solidity v0.4.0之前不會(huì)退還.
return;
// 返回多余的錢幣
}
}
訪問(wèn)控制更加特殊的方法會(huì)在下個(gè)例子中討論。
狀態(tài)機(jī)
合約通常像一個(gè)狀態(tài)機(jī),這意味著他們有特定的階段,不同的階段表現(xiàn)不同或者會(huì)調(diào)用不同的函數(shù)。一個(gè)函數(shù)調(diào)用可以結(jié)束一個(gè)階段,讓合約進(jìn)入到另一個(gè)階段(尤其是合約模型是可交互的)。有些階段在某個(gè)時(shí)間點(diǎn)會(huì)自動(dòng)達(dá)到,也是很普遍的。
一個(gè)例子是秘密競(jìng)價(jià)合約,在”接收秘密競(jìng)價(jià)“的時(shí)候開(kāi)始,然后轉(zhuǎn)換到”披露競(jìng)價(jià)“階段,然后在確定競(jìng)價(jià)結(jié)果的階段結(jié)束。
函數(shù)修改器可以用在這種場(chǎng)合中,來(lái)改變狀態(tài),以及保護(hù)合約的不正當(dāng)使用。
例子
在下面的例子中,修改器atStage保證了函數(shù)只能在特定的時(shí)候被調(diào)用。
自動(dòng)計(jì)時(shí)交易通過(guò)timeTransitions修改器來(lái)處理,可以用在任意函數(shù)中。
注意:修改器順序很重要。如果atStage和timedTransitions結(jié)合起來(lái),你要保證atStage在最后面,然后合約會(huì)進(jìn)入新的階段。
最后,當(dāng)函數(shù)結(jié)束的時(shí)候,transitionNext修改器會(huì)自動(dòng)的用來(lái)進(jìn)入下個(gè)階段。
注意:修改器可能會(huì)被忽略。在Solidity v0.4.0之前可能會(huì)發(fā)生:因?yàn)樾薷钠髦皇呛?jiǎn)單的替換代碼,而不是使用函數(shù)調(diào)用,所以如果函數(shù)return掉的時(shí)候,transitionNext修改器中的代碼可能會(huì)被忽略。 如果你要這么做,那么你應(yīng)該在這些函數(shù)中手動(dòng)調(diào)用nextStage。從版本0.4.0開(kāi)始,修改器代碼即使函數(shù)return了,也能夠執(zhí)行。
pragma solidity ^0.4.11;
contract StateMachine {
enum Stages {
AcceptingBlindedBids,
RevealBids,
AnotherStage,
AreWeDoneYet,
Finished
}
// 這是當(dāng)前狀態(tài)。
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;
modifier atStage(Stages _stage) {
require(stage == _stage);
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);
}
// 實(shí)現(xiàn)階段過(guò)渡。要確保這個(gè)修改器放在最前面
// 否則不會(huì)進(jìn)入下個(gè)階段
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)
nextStage();
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)
nextStage();
// 通過(guò)交易進(jìn)入下一個(gè)階段
_;
}
// 修改器的順序在這里是非常重要的
function bid()
payable
timedTransitions
atStage(Stages.AcceptingBlindedBids)
{
// 這里我們不會(huì)先實(shí)現(xiàn)。
}
function reveal()
timedTransitions
atStage(Stages.RevealBids)
{
}
// 這個(gè)修改器會(huì)使得函數(shù)結(jié)束的時(shí)候進(jìn)入下一個(gè)階段
modifier transitionNext()
{
_;
nextStage();
}
function g()
timedTransitions
atStage(Stages.AnotherStage)
transitionNext
{
}
function h()
timedTransitions
atStage(Stages.AreWeDoneYet)
transitionNext
{
}
function i()
timedTransitions
atStage(Stages.Finished)
{
}
}