怎樣可以讓智能合約安全升級
本節(jié)介紹的內(nèi)容將有助于為你日后解決智能合約的Bug做好充足準(zhǔn)備。
注意:在系統(tǒng)中添加新組件時(shí)總是有風(fēng)險(xiǎn)的。設(shè)計(jì)不當(dāng)?shù)墓收媳Wo(hù)措施本身可能會成為一個(gè)漏洞,許多精心設(shè)計(jì)的故障保護(hù)措施之間的交互也會造成漏洞的存在。仔細(xì)研究智能合約中使用的每一項(xiàng)技術(shù),并仔細(xì)研究它們?nèi)绾螀f(xié)同工作以創(chuàng)建一個(gè)健壯的系統(tǒng)。
智能合約高效升級
智能合約在運(yùn)行過程中發(fā)現(xiàn)Bug或者代碼需要改進(jìn),這會影響整個(gè)系統(tǒng)的健壯性,如果發(fā)現(xiàn)無法解決的Bug,那就可能會造成嚴(yán)重的經(jīng)濟(jì)損失。
在本文中,我們無法涉及到任何復(fù)雜的問題。然而有兩種最常用的基本方法,最簡單的是擁有一個(gè)注冊表智能合約,包含該智能合約最新版本的地址。對于智能合約用戶來說,用戶可以無縫的將該智能合約調(diào)用和數(shù)據(jù)轉(zhuǎn)發(fā)到最新版本的智能合約中。
無論采用哪種技術(shù),模塊化和組件之間的隔離都是非常重要,這樣代碼更改就不會破壞智能合約功能、孤立數(shù)據(jù)或需要大量的移植代碼成本。這樣代碼更改就不會破壞功能、孤立數(shù)據(jù)或需要大量的移植成本。
同樣重要的是要有一個(gè)安全的方式,讓社區(qū)決定升級智能合約代碼。根據(jù)您的智能合約,代碼更改可能需要由單個(gè)受信任方、一組成員或全部涉眾投票批準(zhǔn)。如果這個(gè)過程可能需要一些時(shí)間,那么您需要考慮是否有其他方法可以在發(fā)生攻擊時(shí)更快地做出反應(yīng),例如緊急停止或斷路器。
示例1:使用注冊表智能合約來存儲智能合約的最新版本
在本例中,調(diào)用不會被轉(zhuǎn)發(fā),因此用戶應(yīng)該在每次與當(dāng)前地址交互之前獲取該地址。
contract SomeRegister {
address backendContract;
address[] previousBackends;
address owner;
function SomeRegister() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner)
_;
}
funcTIon changeBackend(address newBackend) public
onlyOwner()
returns (bool)
{
if(newBackend != backendContract) {
previousBackends.push(backendContract);
backendContract = newBackend;
return true;
}
return false;
}
}
這種方法有兩個(gè)主要缺點(diǎn):
1. 用戶必須始終查找當(dāng)前地址,否則任何人都會使用舊版本的智能合約來冒充
2. 在更換智能合約時(shí),您需要仔細(xì)考慮如何處理智能合約數(shù)據(jù)
另一種方法是讓智能合約將調(diào)用和數(shù)據(jù)轉(zhuǎn)發(fā)到最新版本的智能合約:
示例2:使用DELEGATECALL轉(zhuǎn)發(fā)數(shù)據(jù)和調(diào)用
contract Relay {
address public currentVersion;
address public owner;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
funcTIon Relay(address initAddr) {
currentVersion = initAddr;
owner = msg.sender; // this owner may be another contract with mulTIsig, not a single contract owner
}
funcTIon changeContract(address newVersion) public
onlyOwner()
{
currentVersion = newVersion;
}
function() {
require(currentVersion.delegatecall(msg.data));
}
}
這種方法避免了先前的問題,但有其自身的問題。您在此智能合約中如何存儲數(shù)據(jù)時(shí)必須格外小心。如果新智能合約的存儲架構(gòu)與第一個(gè)不同,則數(shù)據(jù)可能最終損壞。此外,該模式的簡單版本無法從函數(shù)返回值,而只能轉(zhuǎn)發(fā)它們,這限制了其適用性。(更復(fù)雜的實(shí)現(xiàn)嘗試通過內(nèi)聯(lián)匯編代碼和返回大小注冊表來解決此問題。)
無論采用哪種方法,找到合適的智能合約升級的方法很重要,否則當(dāng)在智能合約中發(fā)現(xiàn)不可避免的bug時(shí),后果將變得不可預(yù)計(jì)。
斷路器Circuit Breakers
如果滿足某些條件,斷路器將停止智能合約的執(zhí)行,并且在發(fā)現(xiàn)新bug時(shí)非常有用。例如如果發(fā)現(xiàn)一個(gè)bug,智能合約中的大多數(shù)操作可能會被掛起,而現(xiàn)在唯一有效的操作是撤回。您可以給某些受信任方觸發(fā)斷路器的能力,也可以使用編程規(guī)則在滿足某些條件時(shí)自動觸發(fā)特定斷路器。
示例:
bool private stopped = false;
address private owner;
modifier isAdmin() {
require(msg.sender == owner);
_;
}
function toggleContractActive() isAdmin public {
// You can add an additional modifier that restricts stopping a contract to be based on another action, such as a vote of users
stopped = !stopped;
}
modifier stopInEmergency { if (!stopped) _; }
modifier onlyInEmergency { if (stopped) _; }
function deposit() stopInEmergency public {
// some code
}
function withdraw() onlyInEmergency public {
// some code
}
減速帶Speed Bumps (減緩智能合約操作)
減速帶降低了操作的速度,因此,如果發(fā)生惡意操作,則有時(shí)間進(jìn)行恢復(fù)。例如DAO在成功請求拆分DAO和這樣做的能力之間需要27天。這樣可以確保將資金保留在智能合約內(nèi),從而增加了收回的可能性。對于DAO,在減速帶給定的時(shí)間內(nèi)如沒有采取任何有效的措施時(shí),結(jié)合我們的其他技術(shù),它們可以非常有效。
示例:
struct RequestedWithdrawal {
uint amount;
uint time;
}
mapping (address =》 uint) private balances;
mapping (address =》 RequestedWithdrawal) private requestedWithdrawals;
uint constant withdrawalWaitPeriod = 28 days; // 4 weeks
function requestWithdrawal() public {
if (balances[msg.sender] 》 0) {
uint amountToWithdraw = balances[msg.sender];
balances[msg.sender] = 0; // for simplicity, we withdraw everything;
// presumably, the deposit function prevents new deposits when withdrawals are in progress
requestedWithdrawals[msg.sender] = RequestedWithdrawal({
amount: amountToWithdraw,
time: now
});
}
}
function withdraw() public {
if(requestedWithdrawals[msg.sender].amount 》 0 && now 》 requestedWithdrawals[msg.sender].time + withdrawalWaitPeriod) {
uint amountToWithdraw = requestedWithdrawals[msg.sender].amount;
requestedWithdrawals[msg.sender].amount = 0;
require(msg.sender.send(amountToWithdraw));
}
}
速率限制Rate Limiting
速率限制暫?;蛐枰鷾?zhǔn)才能進(jìn)行重大更改。例如在某個(gè)時(shí)間段內(nèi)(例如在1天之內(nèi)最多只能提取100個(gè)以太幣),可能只允許存款人提取一定數(shù)量或一定比例的總存款-該時(shí)間段內(nèi)的其他提取可能會失敗或需要某種特殊的批準(zhǔn) ?;蛘咚俾氏拗瓶梢蕴幱谥悄芎霞s級別,在一段時(shí)間內(nèi)僅由智能合約發(fā)行一定數(shù)量的代幣。
智能合約版本推出
智能合約新版本在正式推出使用前,已經(jīng)經(jīng)過大量的安全測試和代碼測試。
至少您應(yīng)該:
1. 擁有100%測試覆蓋率的完整測試軟件。
2. 在自己的testnet上部署。
3. 在公共測試網(wǎng)上進(jìn)行大量測試和漏洞賞金。
4. 測試應(yīng)允許各種參與者大量參與智能合約進(jìn)行交互。
5. 在主網(wǎng)上部署beta版本,并限制風(fēng)險(xiǎn)數(shù)量。
自動棄用AUTOMATIC DEPRECATION
在測試過程中,您可以在一定的時(shí)間段后通過阻止任何操作來強(qiáng)制自動棄用。例如alpha版本智能合約可能會工作幾個(gè)星期后,然后自動關(guān)閉所有操作,但最終退出除外。
modifier isActive() {
require(block.number 《= SOME_BLOCK_NUMBER);
_;
}
function deposit() public isActive {
// some code
}
function withdraw() public {
// some code
}
在早期階段,您可以限制用戶(或整個(gè)智能合約)使用以太坊的數(shù)量,從而降低風(fēng)險(xiǎn)。
Bug賞金計(jì)劃
進(jìn)行賞金計(jì)劃的一些技巧:
· 決定使用(BTC和/或ETH)哪種代幣進(jìn)行賞金分配。
· 決定賞金的預(yù)算總金額。
· 根據(jù)預(yù)算,確定三層獎勵:
1. 最低獎勵
2.最高獎勵
3.如果發(fā)現(xiàn)非常嚴(yán)重的漏洞,將授予額外的獎勵
· 確定賞金管理員(3個(gè)可能是典型的理想人選)。
· 首席開發(fā)人員可能應(yīng)該是賞金評委之一。
· 當(dāng)收到一個(gè)bug報(bào)告時(shí),開發(fā)負(fù)責(zé)人,在評審的建議下,應(yīng)該評估bug的嚴(yán)重性。
· 這個(gè)階段的工作應(yīng)該是在一個(gè)私有的存儲庫中進(jìn)行的,問題應(yīng)在Github上歸檔。
· 如果這是一個(gè)應(yīng)該被修復(fù)的bug,那么在私有repo中,開發(fā)人員應(yīng)該編寫一個(gè)測試用例,測試用例應(yīng)該失敗,從而確認(rèn)這個(gè)bug。
· 開發(fā)人員應(yīng)實(shí)施此修復(fù)程序并確保測試可以通過;根據(jù)需要編寫其他測試,向賞金獵人顯示修復(fù)方法,將補(bǔ)丁程序合并回公共倉庫。
· 確定賞金獵人是否有其他關(guān)于修復(fù)的反饋。
· 賞金評委根據(jù)對漏洞的可能性和影響的評估來確定獎勵的大小。
· 在整個(gè)過程中讓賞金參與者了解情況,然后盡量避免延遲向他們發(fā)送獎勵。
關(guān)于三層獎勵的示例,請參見以太坊的賞金計(jì)劃:https://bounty.ethereum.org/
獎勵的價(jià)值將根據(jù)影響的嚴(yán)重程度而有所不同。對輕微“bug”的獎勵從0.05個(gè)BTC開始。重要的bug,例如導(dǎo)致共識問題的bug,將會得到最多5個(gè)BTC的獎勵。如果存在非常嚴(yán)重的漏洞,則可能獲得更高的回報(bào)(最多25個(gè)BTC)。