Solidity 智能合約入門
存儲合約示例
將一個數(shù)據(jù)放置在鏈上
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
我們對代碼進行逐行分析,首先第一行
第一行表明此段代碼是被GPL-3.0所授權。默認情況下,在發(fā)布源代碼時加入機器可讀許可證說明是很重要的。
GPL(GNU General Public License Versions)
GPL協(xié)議一般還可以分為GPL2.0和GPL3.0兩種,而GPL3.0是更新一代的開源標準,在對用戶專利的保護和DRM的限制方面有所更改。GPL協(xié)議同其它的自由軟件許可證一樣,許可社會公眾享有:運行、復制軟件的自由,發(fā)行傳播軟件的自由,獲得軟件源碼的自由,改進軟件并將自己作出的改進版本向社會發(fā)行傳播的自由。 而GPL協(xié)議就像一種開源“病毒”,任何一款沾染上他的軟件都不得不保持開源和免費。
下一行是告訴編譯器源代碼所適用的Solidity版本為>=0.4.16 及 <0.9.0 。這樣的說明是為了確保合約不會在新的編譯器版本中發(fā)生異常的行為。關鍵字 pragma
是告知編譯器如何處理源代碼的通用指令
Solidity中智能合約的含義就是一組代碼(它的 功能 )和數(shù)據(jù)(它的 狀態(tài) )的集合,并且它們是位于以太坊區(qū)塊鏈的一個特定地址上的
uint storedData; 這一行代碼聲明了一個名為storedData
的狀態(tài)變量,其類型為 uint (256位無符號整數(shù))。 你也可以認為它是數(shù)據(jù)庫里的一個插槽,并且可以通過調用管理數(shù)據(jù)庫代碼的函數(shù)進行查詢和更改。在這個例子中,上述的合約定義了set
和get
函數(shù),可以用來修改或檢索變量的值。
要訪問當前合約的成員(如:狀態(tài)變量),通常不需要像添加 this.
這樣的前綴,你只需要通過名字就可以直接訪問它。 與其他一些語言不同的是,省略它不僅僅是一個風格問題,因為它是一種完全不同的訪問成員的方式
該合約能完成的事情并不多(由于以太坊構建的基礎架構的原因):它能允許任何人在合約中存儲一個單獨的數(shù)字,并且這個數(shù)字可以被世界上任何人訪問,且沒有可行的辦法阻止你發(fā)布這個數(shù)字。當然,任何人都可以再次調用 set
,傳入不同的值,覆蓋你的數(shù)字,但是這個數(shù)字仍會被存儲在區(qū)塊鏈的歷史記錄中。隨后,我們會看到怎樣施加訪問限制,以確保只有你才能改變這個數(shù)字。
所有的標識符(合約名稱,函數(shù)名稱和變量名稱)都只能使用ASCII字符集。UTF-8編碼的數(shù)據(jù)可以用字符串變量的形式存儲。
小心使用Unicode文本,因為有些字符雖然長得相像(甚至一樣),但其字符碼是不同的,其編碼后的字符數(shù)組也會不一樣。
貨幣合約(Subcurrency)示例
下面的合約實現(xiàn)了一個最簡單的加密貨幣。這里,幣確實可以無中生有地產(chǎn)生,但是只有創(chuàng)建合約的人才能做到(實現(xiàn)一個不同的發(fā)行計劃也不難)。而且,任何人都可以給其他人轉幣,不需要注冊用戶名和密碼 —— 所需要的只是以太坊密鑰對
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Coin {
// 關鍵字“public”讓這些變量可以從外部讀取
address public minter;
mapping (address => uint) public balances;
// 輕客戶端可以通過事件針對變化作出高效的反應
event Sent(address from, address to, uint amount);
// 這是構造函數(shù),只有當合約創(chuàng)建時運行
constructor() {
minter = msg.sender;
}
function mint(address receiver, uint amount) public {
require(msg.sender == minter);
balances[receiver] += amount;
}
// Errors allow you to provide information about
// why an operation failed. They are returned
// to the caller of the function.
error InsufficientBalance(uint requested, uint available);
function send(address receiver, uint amount) public {
if (amount > balances[msg.sender])
revert InsufficientBalance({
requested: amount,
available: balances[msg.sender]
});
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
}
這個合約引入了一些新的概念,讓我們逐一解讀。
address public minter
; 這一行聲明了一個可以被公開訪問的 address
類型的狀態(tài)變量。 address
類型是一個160位的值,且不允許任何算數(shù)操作。這種類型適合存儲合約地址或外部人員的密鑰對。關鍵字 public
自動生成一個函數(shù),允許你在這個合約之外訪問這個狀態(tài)變量的當前值。如果沒有這個關鍵字,其他的合約沒有辦法訪問這個變量。由編譯器生成的函數(shù)的代碼大致如下所示(暫時忽略 external 和 view):
function minter() external view returns (address) { return minter; }
當然,加一個和上面完全一樣的函數(shù)是行不通的,因為我們會有同名的一個函數(shù)和一個變量,這里,主要是希望你能明白——編譯器已經(jīng)幫你實現(xiàn)了。
下一行, mapping (address => uint) public balances;
也創(chuàng)建一個公共狀態(tài)變量,但它是一個更復雜的數(shù)據(jù)類型。 該類型將address映射為無符號整數(shù)。 Mappings 可以看作是一個 哈希表
它會執(zhí)行虛擬初始化,以使所有可能存在的鍵都映射到一個字節(jié)表示為全零的值。 但是,這種類比并不太恰當,因為它既不能獲得映射的所有鍵的列表,也不能獲得所有值的列表。 因此,要么記住你添加到mapping中的數(shù)據(jù)(使用列表或更高級的數(shù)據(jù)類型會更好),要么在不需要鍵列表或值列表的上下文中使用它,就如本例。 而由 public
關鍵字創(chuàng)建的getter函數(shù) getter function
則是更復雜一些的情況, 它大致如下所示:
function balances(address account) external view returns (uint) {
return balances[account];
}
正如你所看到的,你可以通過該函數(shù)輕松地查詢到賬戶的余額。
event Sent(address from, address to, uint amount);
這行聲明了一個所謂的“事件(event)”,它會在 send
函數(shù)的最后一行被發(fā)出。用戶界面(當然也包括服務器應用程序)可以監(jiān)聽區(qū)塊鏈上正在發(fā)送的事件,而不會花費太多成本。一旦它被發(fā)出,監(jiān)聽該事件的listener都將收到通知。而所有的事件都包含了 from
, to
和 amount
三個參數(shù),可方便追蹤交易。 為了監(jiān)聽這個事件,你可以使用如下JavaScript代碼(假設 Coin 是已經(jīng)通過 web3.js 創(chuàng)建好的合約對象 ):
Coin.Sent().watch({}, '', function(error, result) {
if (!error) {
console.log("Coin transfer: " + result.args.amount +
" coins were sent from " + result.args.from +
" to " + result.args.to + ".");
console.log("Balances now:\n" +
"Sender: " + Coin.balances.call(result.args.from) +
"Receiver: " + Coin.balances.call(result.args.to));
}
})
這里請注意自動生成的 balances
函數(shù)是如何從用戶界面調用的。
特殊函數(shù) constructor
是僅在創(chuàng)建合約期間運行的構造函數(shù),不能在創(chuàng)建之后調用。 在 Coin 合約中,構造函數(shù)永久存儲創(chuàng)建合約的人的地址: msg (類似的還有 tx
和 block
) 是一個特殊的全局變量, 參考 特殊變量和函數(shù)
,這些變量允許我們訪問區(qū)塊鏈的屬性。 msg.sender
始終記錄當前(外部)函數(shù)調用是來自于哪一個地址。
最后,真正被用戶或其他合約所調用的,以完成本合約功能的方法是 mint
和 send
。
mint
函數(shù)用來新發(fā)行一定數(shù)量的幣到一個地址。 require
用來檢查某些條件,如果不滿足這些條件就會回推所有的狀態(tài)變化。 在這個例子中, require(msg.sender == minter)
; 確保只有合約的創(chuàng)建者可以調用 mint
。 一般來說,創(chuàng)建者可以隨心所欲地鑄造代幣,但在某些時候,這將導致一種叫做 “溢出” 的現(xiàn)象。
請注意,由于默認的 算術檢查模式
,如果表達式 balances[receiver] += amount
; 溢出交易將被還原。 即當任意精度算術中的 balances[receiver]+ amount 大于 uint (2**256 - 1)
。同樣在在函數(shù) send
中的 balances[receiver] += amount;
這對語句來說也是如此。
Errors
用來向調用者描述錯誤信息。Error與 revert 語句
一起使用。 revert 語句無條件地中止執(zhí)行并回退所有的變化,類似于 require
函數(shù),它也同樣允許你提供一個錯誤的名稱和額外的數(shù)據(jù),這些額外數(shù)據(jù)將提供給調用者(并最終提供給前端應用程序或區(qū)塊資源管理器),這樣就可以更容易地調試或應對失敗。
任何人(已經(jīng)擁有一些代幣)都可以使用 send
函數(shù)來向其他人發(fā)送代幣。如果發(fā)送者沒有足夠的代幣可以發(fā)送, if
條件為真 revert
將觸發(fā)失敗,并通過 InsufficientBalance
向發(fā)送者提供錯誤細節(jié)
如果你用這個合約向一個地址發(fā)送代幣,當你在區(qū)塊鏈瀏覽器上看這個地址時,你不會看到任何東西,因為你發(fā)送代幣的記錄和余額的變化只存儲在這個特定的Coin合約的數(shù)據(jù)存儲中。 通過使用事件,你可以讓 “區(qū)塊鏈瀏覽器”,跟蹤代幣的交易和余額變化,但你必須查看代幣合約地址(下的交易記錄),而不是持有人的地址文章來源:http://www.zghlxwxcb.cn/news/detail-783885.html
如果 mint
被合約創(chuàng)建者外的其他人調用則什么也不會發(fā)生。 另一方面, send
函數(shù)可被任何人用于向他人發(fā)送幣 (當然,前提是發(fā)送者擁有這些幣)。記住,如果你使用合約發(fā)送幣給一個地址,當你在區(qū)塊鏈瀏覽器上查看該地址時是看不到任何相關信息的。因為,實際上你發(fā)送幣和更改余額的信息僅僅存儲在特定合約的數(shù)據(jù)存儲器中。通過使用事件,你可以非常簡單地為你的新幣創(chuàng)建一個“區(qū)塊鏈瀏覽器”來追蹤交易和余額。文章來源地址http://www.zghlxwxcb.cn/news/detail-783885.html
到了這里,關于Solidity 智能合約入門的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!