Neo核心開發(fā)者:我為N3開發(fā)的第一個智能合約|Neo專欄

上一篇Neo專欄中
作者為我們揭秘了引入隨機(jī)數(shù)的具體困難有哪些
以及Neo在隨機(jī)數(shù)算法領(lǐng)域究竟有何發(fā)展目標(biāo)??
本篇Neo專欄,
Neo核心開發(fā)者廖京輝???♂?
帶您走近他「為N3開發(fā)的第一個智能合約」
在此過程中遇到的困難和解決方案
及對N3區(qū)塊鏈開發(fā)平臺的理解與想法??
快來一起看看吧??
前兩篇文章里介紹了為N3引入安全隨機(jī)數(shù)的事情,雖然最終版本的隨機(jī)數(shù)算法還在不斷的開發(fā)迭代中,但是現(xiàn)有的N3的區(qū)塊內(nèi)已經(jīng)引入了一個由議長生成的隨機(jī)數(shù),可以用來測試相關(guān)合約。因此,我開發(fā)了一個剪刀石頭布的游戲。一是用來學(xué)習(xí)N3的合約開發(fā),二來是測試一下運(yùn)行時隨機(jī)數(shù)。(合約源碼地??https://github.com/Liaojinghui/RPC)
這個剪刀石頭布的游戲很簡單,設(shè)計如下:
用戶每輪的投注需要大于1個GAS,同時需要小于之前合約從用戶那里贏走的錢。也就是說用戶不能期待一把回本,否則合約幾乎永遠(yuǎn)都是輸家。
在別的平臺如果開發(fā)這樣的合約,在沒有可靠的運(yùn)行時隨機(jī)數(shù)的前提下,用戶需要先將手勢的摘要信息發(fā)送到合約里,然后等待下一個區(qū)塊取某種線上數(shù)據(jù)作為隨機(jī)數(shù),再等待一個區(qū)塊才能將用戶手勢的明文發(fā)送到合約里。為了完成一個簡單的小游戲,這樣的操作不可謂不繁瑣復(fù)雜。而在N3里,只需要一筆交易,所有的過程都可以直接在一個區(qū)塊內(nèi)完成。
工具方法
為了方便合約開發(fā),我把常用的權(quán)限控制整理成了OwnerOnly()方法,將條件控制邏輯整理成了Require()方法。
OwnerOnly()方法是為了對合約接口的調(diào)用權(quán)限進(jìn)行約束。我們把只允許管理員進(jìn)行調(diào)用的接口稱為管理員接口,標(biāo)記為***I_owner***。通常,我們需要對合約調(diào)用的交易的簽名進(jìn)行驗證,只有來自合約擁有者,也就是在合約里標(biāo)記的Owner賬戶的簽名才能合法調(diào)用I_owner接口。這樣的驗證邏輯通常是一致的代碼邏輯,因此,為了方便合約開發(fā),對其進(jìn)行代碼進(jìn)行歸納整理為:
private static void OwnerOnly() { if (!Verify()) throw new Exception("No authorization."); }
Require() 方法則是因為各種合約方法內(nèi)部都需要對條件進(jìn)行非常繁瑣的驗證,為了合約安全,我們通常希望合約的驗證越完善越好。但是驗證的過程一般需要引入if語句,而為了邏輯清晰,我們又不能將所有的條件整理成一種。當(dāng)大量的if語句堆疊到一起的時候,代碼就會變得臃腫。
因此,為了讓合約在進(jìn)行邏輯和條件檢查的時候能更加“優(yōu)雅”,我定義了private static void Require(bool isTrue, string msg = "Invalid")(后文稱Require())。Require() 接收兩個參數(shù),第一個參數(shù)為條件或者條件語句,第二個為可選的錯誤信息。當(dāng)?shù)谝粋€參數(shù)的結(jié)果不是true的時候,Require()就會拋出異常,F(xiàn)AULT這筆交易。同時用戶可以通過第二個參數(shù)來自定義異常信息。方法實(shí)現(xiàn)為:
private static void Require(bool isTrue, string msg = "Invalid") { if (!isTrue) throw new Exception($"RPC::{msg}"); }
此外,為了減少由方法調(diào)用而增加的額外開銷,我將這兩個方法都設(shè)置為了內(nèi)聯(lián)函數(shù)。因此兩個方法的最終實(shí)現(xiàn)版本為:
[MethodImpl(MethodImplOptions.AggressiveInlining)]private static void Require(bool isTrue, string msg = "Invalid") { if (!isTrue) throw new Exception($"RPC::{msg}"); }
[MethodImpl(MethodImplOptions.AggressiveInlining)]private static void OwnerOnly() { if (!Verify()) throw new Exception("No authorization."); }
細(xì)粒度安全要求與嚴(yán)格的安全校驗
雖然開發(fā)這個小游戲的代碼量并不大,邏輯更是非常簡單,但是為了給以后的合約開發(fā)者積累更多的經(jīng)驗,我還是按照最高標(biāo)準(zhǔn)來去實(shí)現(xiàn)了這個合約的每一個功能。對于合約來說,最高的標(biāo)準(zhǔn)就是安全標(biāo)準(zhǔn)——這一點(diǎn)是毫無爭議的。我始終認(rèn)為合約的開發(fā)應(yīng)該在最小的單位——方法里,就對安全要求進(jìn)行定義并且驗證。并且每個單位應(yīng)該有自己完全獨(dú)立的安全邏輯,比如參數(shù)的驗證一定要在方法內(nèi)部獨(dú)立完成,不能對上層的calling方法傳入的參數(shù)進(jìn)行任何形式的假設(shè),否則肯定會增加的重復(fù)驗證的開銷。但是,對于合約來說,安全大過天,為了保證安全,合約有時不得不“臃腫”。搭乘過飛機(jī)的小伙伴肯定都知道坐飛機(jī)有非常繁瑣的安檢流程,尤其是跨國轉(zhuǎn)機(jī),幾乎每次中轉(zhuǎn)都要重新安檢。這就是為了排除各種風(fēng)險。而合約的開發(fā)就需要這樣對安全認(rèn)真嚴(yán)謹(jǐn)?shù)膽B(tài)度,在每一個最小節(jié)點(diǎn)里定義安全行為,引入安全檢查。
在該合約里,我是在每個方法的注釋里加入Security Requirements 內(nèi)容來對方法的安全進(jìn)行文字層面的約束,主要就是定義一下參數(shù)的范圍,方法權(quán)限等等。然后在開發(fā)的時候逐一對安全要求進(jìn)行確認(rèn)。再在合約部署在測試網(wǎng)之后對每一個安全要求進(jìn)行測試網(wǎng)上實(shí)際交易的檢驗,當(dāng)檢驗通過時再進(jìn)行最終的確認(rèn)標(biāo)記。也就是在功能測試之外,獨(dú)立進(jìn)行安全測試。舉個例子,合約里的OnNEP17Payment回調(diào)方法:

OnNEP17Payment回調(diào)方法
方法的注釋里有四條安全約束,這四條安全約束都在代碼中進(jìn)行了體現(xiàn),具體到代碼所在的行:
// <2> -- confirmed by jinghuiRequire(!Paused());
// <3> -- yet to confirmRequire(Runtime.CallingScriptHash == GAS.Hash, "Script format error.");//Require(Runtime.EntryScriptHash == GAS.Hash, "Runtime.EntryScriptHash == ((Transaction)Runtime.ScriptContainer).Hash");if (((Transaction)Runtime.ScriptContainer).Script.Length > 96) throw new Exception("RPC::Transaction script length error. No wrapper contract or extra script allowed.");// should not be called from a contract// --confirmedRequire(ContractManagement.GetContract(from) is null, "ContractManagement.GetContract(from) is null");
// <1> -- confirmed by jinghuiRequire(move == 0 || move == 1 || move == 2, "Invalid move.");
// <0> -- confirmed by jinghuiRequire(amount >= 1_0000_0000, "Please at least bet 1 GAS.");
雖然這樣的方式不能排除合約存在漏洞的可能,但是卻可以強(qiáng)制合約開發(fā)者對合約安全進(jìn)行思考并予以約束,進(jìn)而為合約提供最低限度的安全保障。
最后,特別感謝NGD的陳志同同學(xué)和鴨脖小朋友提供的大量幫助和指點(diǎn)。這個合約部署在N3的測試網(wǎng)上??https://neo3.testnet.neotube.io/contract/0x9c01a8640dff7c086dca99758d71645f57164d7c。感興趣的朋友可以去試試。不過調(diào)用合約最好不要用Neo-cli,這個里面的send命令有點(diǎn)問題,必須傳from字段才能傳data,而且data還只能是string類型。建議大家用別的工具來進(jìn)行調(diào)用。
合約代碼開源且完全授權(quán),任何同學(xué)都可以對其進(jìn)行任何形式的更改,進(jìn)行任何方式的使用。希望能夠幫助到想要進(jìn)行N3合約開發(fā)的同學(xué)。
All in One · All in Neo
Neo是一個由社區(qū)驅(qū)動的開源平臺。利用區(qū)塊鏈技術(shù)與數(shù)字身份,開發(fā)者可以通過智能合約實(shí)現(xiàn)資產(chǎn)管理數(shù)字化與自動化。Neo致力于通過分布式網(wǎng)絡(luò)建設(shè)下一代互聯(lián)網(wǎng)基礎(chǔ)設(shè)施,為區(qū)塊鏈技術(shù)大規(guī)模落地奠定基礎(chǔ),以實(shí)現(xiàn)智能經(jīng)濟(jì)的宏大愿景。
自2016年上線至今,Neo主網(wǎng)已穩(wěn)定運(yùn)行超過四年。全新版本Neo N3已于2021年發(fā)布,能夠提供更高吞吐量、更強(qiáng)穩(wěn)定性與安全性,帶來優(yōu)化的智能合約系統(tǒng)及功能豐富的基礎(chǔ)設(shè)施集合,賦能開發(fā)者并加速企業(yè)級區(qū)塊鏈創(chuàng)新。


