前言
這是新年特別企劃的第三篇,我們的目標是在過年前學習寫自己的第一個智能合約並發行自己的加密貨幣,可以作為紅包發送給親友,或是想想自己幣的用途,打造一個貨幣的經濟模型,所以如果你一篇都還沒看過的話,第一篇的連結在
這裡。
但是你會發現,前一篇文章的兩個試作品除了當作花瓶欣賞以外,甚麼事情都做不到。既然是貨幣,轉帳交易功能是基本的吧。
沒問題,我們馬上來寫第三支程式,像前兩個程式一樣,回到檔案總管,在contracts底下開啟新檔,我將其命名為TransferToken.sol,這次程式複雜多了,我們一部分一部分拆開來看,整個完整的程式碼會附在文章的後面。
第三個試作品 | 可交易的智能合約
要完成一個可以正常運作的加密貨幣,除了上次開發的加密貨幣初始參數客製化功能以外,還需要兩個步驟,分為錢包餘額查詢以及轉帳兩部分。
《錢包餘額查詢》
要讓幣可以被交易的話,那就一定要有能夠查詢餘額的功能,為甚麼?
試想若你是銀行,遇到一筆來自客戶的交易請求,請求內容是請你將他的錢匯款到其他人的帳戶之中,那第一個要做的事情,就是先檢查他銀行帳戶的餘額有沒有比要匯出的金額多或是剛好,否則扣款後帳戶變成負的後續會很麻煩,或許銀行還能凍結他的帳戶之類的方式追回錢,但在區塊鏈的世界中,錢匯出就是匯出了根本無從處理,只能祈禱他有一天會記得還錢。
所以要能夠達成交易的功能,查錢包裡的錢夠不夠就會是第一步,這個也是整個程式碼中最簡單的部分,只有一行而已:
mapping(address => uint256) public balanceOf;
意思是將錢包的餘額數據讀取出來,存入名為balanceOf的變數之中。多了這個變數之後,當我們發佈智能合約之後,原先在Deployed Contracts的地方就會從四個變數按紐變為五個。
我們先先複製發佈這個智能合約的錢包地址,錢包地址就在 “DEPLOY & RUN TRANSACTIONS”裡面的ACCOUNT,右邊就有複製標誌了。
點擊balanceOf按鈕之前,只要貼上剛剛複製的錢包地址,再按下balanceOf按紐,錢包餘額數據就直接出現了了,數量應該要跟當初設定的 _totalSupply 一樣,因為這些錢會在發佈智能合約的當下存入發佈者的地址!若是拿其他錢包地址查詢balanceOf的話都必須要是0哦,因為我們還沒有撰寫轉帳交易的功能,除了發佈者以外不可能拿到這個加密貨幣。
《轉帳》
轉帳是這個程式中最重要的部分,一個不能夠被轉出的東西不要說是貨幣了,或許連物品都稱不上。
事件
平常購物時,我們會把錢包裡的錢交給店員,這個動作本身是一個事件,在Solidity叫做event,所以我們必須先定義一個event,這個event是一個以太坊虛擬主機(EVM)提供的便利接口,當真的發生金錢轉移時就可以直接呼叫這個event,它就會協助我們把這筆交易寫在區塊鏈的帳本上哦。
event Transfer(address indexed from, address indexed to, uint256 value);
我們建立一個叫做Transfer的事件,from就是匯款人的錢包地址,to就是接收款人的錢包地址,value是多少錢,value前面的uint256意思是不是一個負數的整數(所以value的值必須是0或正整數),但這是已經結束交易,要請主機幫我們上鏈時呼叫的程式碼,前置作業我們還是得自己來。
轉帳程式碼
事實上在撰寫智能合約時就像在打造一部不需要人力也能自動運作的自動販賣機,既然要協助轉帳,我們就必須將自己想像成一間銀行或一台ATM,以此角度思考會更容易理解。
轉帳會有「匯款人」與「收款人」兩個角色,要做的事情是「給出多少錢」,先看程式碼:
function transfer(address _to, uint256 _value) external returns (bool success) {
require(balanceOf[msg.sender] >= _value);
require(_to != address(0));
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
先大致描述一下這段程式碼的用意:
定義一個轉帳的function叫transfer,要使用這個轉帳功能需要提供 _to 與 _value兩個參數,若這個程式執行成功會返回true表示交易都沒問題了;若返回false表示程式執行時遇到問題,可能是他寫的轉帳地址不對,或是他根本沒有這麼多錢。
角色定義
在區塊鏈中的「匯款人」是誰?就是使用這台自動販賣機的人,也就是使用智能合約的錢包地址,在Solidity中可以直接使用 “msg.sender” (message sender, 訊息的傳遞者),這背後代表的就是發佈或是呼叫智能合約的地址,如果顯示出來的話就會是 0x…….. 。
「收款人」是誰、「給出多少錢」又是什麼呢?想像一下要做一筆銀行轉帳給朋友,我們一定要輸入兩個東西,一個就是朋友的銀行帳戶,一個是多少錢;對應到智能合約調用時也是一樣,_to要填入對方的錢包地址,_value要填入轉帳金額,就是這麼簡單。
檢查機制-檢查匯款人餘額
在Solidity中,require是一種檢查機制,裡面要放判斷式,如果判斷式成立的話程式就會繼續往下運作;如果判斷式不成立的話,程式就會直接回傳false並停止運作。
require(balanceOf[msg.sender] >= _value);
如果匯款人要匯5000塊給對方,但是他帳戶裡只有87塊,當然不能讓他稱心如意的匯款,所以我們需要先用require來檢查餘額,檢查方法就是呼叫我們寫好的餘額查詢balanceOf啦,檢查對象是匯款人(msg.sender),檢查用的判斷式是他的餘額是否大於等於要匯出的金額 _value。
檢查機制-檢查收款人地址正確性
require(_to != address(0));
其實檢查收款人地址是否正確的意思不是保證你輸入的地址是收款人的地址,而是避免不小心沒有填入收款人地址,導致地址是空的 (_to = 0x0),我一開始以為0x0就是一個都是0的錢包地址,錢錢就會匯入這個零地址導致加密貨幣資產丟失,但是研究了一下發現似乎不是這樣,因為在
Solidity官方文檔中寫了這段話:
If the target account is not set (the transaction does not have a recipient or the recipient is set to null), the transaction creates a new contract. As already mentioned, the address of that contract is not the zero address but an address derived from the sender and its number of transactions sent (the “nonce”).
看起來對於以太坊虛擬機而言,如果在做transaction時收到零地址,表示我們打算發佈新智能合約,而不是轉移資產要特別注意!
轉帳流程
轉帳就是匯款人扣錢,收款人加錢這麼簡單,用中文的寫的話就會像這樣:
換款人錢包裡的錢 = 換款人錢包裡的錢 - 匯出的金額
收款人錢包裡的錢 = 收款人錢包裡的錢 + 收到的金額
轉換成程式碼的話就會這樣寫,將對應角色錢包中的錢加上或扣除轉帳的金額。
balanceOf[msg.sender] = balanceOf[msg.sender] - _value;
balanceOf[_to] = balanceOf[_to] + _value;
或是可以利用 += 和 -= 的簡寫方式,讓程式碼看起來更簡潔。
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
發送交易
終於來到程式碼的最後,程式能執行到這裡表示前面都是沒有異常的,所以要正式發送這筆交易紀錄到區塊鏈上了。
emit Transfer(msg.sender, _to, _value);
return true;
利用emit的方式發送交易請求到一開始寫的 Transfer 事件,最後再回傳一切正常的 true 就結束啦。
TransferToken.sol
加上前一篇寫的,整個程式碼現在應該是這樣:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
contract Token {
string public name;
string public symbol;
uint256 public decimals;
uint256 public totalSupply;
constructor(string memory _name, string memory _symbol, uint _decimals, uint _totalSupply){
name = _name;
symbol = _symbol;
decimals = _decimals;
totalSupply = _totalSupply;
balanceOf[msg.sender] = totalSupply;
}
mapping(address => uint256) public balanceOf;
event Transfer(address indexed from, address indexed to, uint256 value);
function transfer(address _to, uint256 _value) external returns(bool success){
require(balanceOf[msg.sender] >= _value);
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
}
在錢包之間走跳的錢
每次程式寫完之後就要記得編譯、發佈,再填入初始參數發幣,這裡就不再贅述了。如果忘記可以回去上一篇看看怎麼操作,接著我們就來實際操作發幣與轉帳,確保程式寫的都能夠正常運作沒有Bug。
轉帳
轉帳時我們會需要兩種角色分別是匯款人與收款人,假設合約發佈的地址是0x5B開頭,被我標籤為1的地址,那麼在合約發佈的當下,這個貨幣的總供應量會全部放入這個地址中,在任何轉帳發生前,全世界只有這個地址會有剛剛發的貨幣,所以它一定是匯款人。
收款人則是被我標籤為2,0xAb開頭的地址,請先將ACCOUNT欄位切換成2,並且按下右邊黃圈圈標示的icon,將它的地址複製起來,這時候你必須要將ACCOUNT再次切回地址1,因為我們要使用地址1去呼叫智能合約中的transfer function,它就會是msg.sender本人。
在”DEPLOY & RUN TRANSACTIONS”往下滑會看到Deployed Contracts,你會發現多了一顆橘色的transfer按紐,這就是拿來讓我們轉帳的,右邊有個向下的小箭頭點下去應該會長這樣:
_to: 是收款人,就是另一個錢包地址,這個地址就直接貼上我們剛剛在ACCOUNT欄位切換到第二個錢包0xAb開頭的地址,或是你想要貼上其他地址也可以。
_value: 就填要轉的金額數,只要不要是負的整數都可以,這裡填100的意思是要匯出0.0000000000000001顆我們發的幣,因為Decimals是小數點後18位數,所以_value欄位中填入100表示要匯出的數量是 100 x 10e-18 = 10e-16 = 0.0000000000000001顆代幣,如果你要匯出100顆的代幣,value就必須填100,000,000,000,000,000,000,就是100後面加18個0。
兩個參數設定完,就能夠按下橘色的transact,轉帳就成功啦!
驗證結果
在這裡我們使用balanceOf功能,直接輸入對應的錢包地址查詢餘額,假設在發佈合約後,我們只做了上面做的那一次轉帳,轉出了_value:100的貨幣(相當於 0.0000000000000001顆)給另一個錢包地址,則兩個錢包的餘額一個應該顯示999999999999999999999999999900,另一個應該顯示100:
餘額若與前面轉帳的操作結果相同,那麼恭喜你已經成功發行了自己的加密貨幣,這個幣就像活的已經可以在各個錢包之間跳來跳去!
結語
經過這一次的更改,我們發行了一個可以交易的幣,實際轉帳應該也都沒問題了表示在本機端的測試都是可以正常運作的。
下一次我們會嘗試將幣發到測試網中,你會需要有一個Metamask錢包,敬請期待!
延伸閱讀
最後,謝謝你在百忙之中願意抽空來花時間來看我的文章,如果還喜歡這些內容的話希望能獲得你的追蹤及支持,也歡迎
點此連結在其他平台找到我。
下次見嚕 o((>ω< ))o~