智能合約教學(5) 把代幣存到合約和外部合約呼叫
這次要來介紹怎麼把代幣存到合約中,然後讓合約來處理自己的資產,順便也會提到怎麼在自己合約中呼叫別的鏈上合約的函數
代幣分成二種:原生代幣和ERC20代幣,這次二種都會用到,原生代幣就像以太坊中的以太幣,本身並沒有ERC20合約在運作,其轉移的方式是直接透過區塊鏈系統本身,而非透過合約
接下來我們直接看這次的合約程式碼:
https://github.com/vcckvv/SmartContract/blob/gh-pages/DepositCoin/DepositCoin.sol
--------------------------------------
pragma solidity ^0.4.0;
interface Token{
function balanceOf(address owner) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from,address to,uint256 value)external returns (bool);
}
contract DepositCoin {
address tokenAddr;
function getContractBalance() public view returns (uint256) {
return address(this).balance;
}
function pay() public payable{
}
function withdraw() public{
msg.sender.transfer(1 wei);
}
function withdraw(uint256 v) public{
msg.sender.transfer(v);
}
function setTokenAddr(address addr) public{
tokenAddr=addr;
}
function getTokenAddr() public view returns (address) {
return tokenAddr;
}
function getTokenBalance() public view returns (uint256) {
Token t = Token(tokenAddr);
return t.balanceOf(msg.sender);
}
function getContractTokenBalance() public view returns (uint256) {
Token t = Token(tokenAddr);
return t.balanceOf(this);
}
function depositToken(uint256 v) public{
Token t = Token(tokenAddr);
t.transferFrom(msg.sender, this, v);
}
function withdrawToken(uint256 v) public{
Token t = Token(tokenAddr);
t.transfer(msg.sender, v);
}
}
--------------------------------------
解說:
interface Token{
function balanceOf(address owner) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from,address to,uint256 value)external returns (bool);
}
由於這次我們會呼叫到鏈上其他合約的函數,所以要先建立一個interface資料,裡面包含需要呼叫的函數,之後我們會再提到要怎麼去呼叫
注意這裡不用把ERC20的所有函數都列出來,只需要列出有呼叫到的部份就好
--------------------------------------
function getContractBalance() public view returns (uint256) {
return address(this).balance;
}
這個函數可以讀取這個合約自身擁有多少原生代幣,this
是指這個合約本身,address(this)
就是這個合約發布的位址,.balance
則是讀取位址的餘額
--------------------------------------
function pay() public payable{
}
這個payable函數在執行時可以另外給出指定數目的原生代幣給合約,除了接收代幣之外沒有其他功能所以無內容,不過某些合約會額外進行emit event的動作,這個之後有想做的時候再來詳細講解
(補充:呼叫者傳了多少原生代幣,可以用msg.value
來讀取)
當我們在Remix上用內建虛擬機進行操作時,介面中有一個VALUE輸入框,旁邊可以指定不同單位:Wei、Gwei、Finney、Ether,在VALUE輸入框輸入想要傳送的原生代幣數值後,此時按下紅色的pay函數按鈕就可以傳送原生代幣給合約
另外一提,在JavaScript上執行payable函數是這樣:
const options = {value: ethers.utils.parseEther("0.00000001")};//指定傳送0.00000001 Ether給合約
let tx = await contractWithSigner.pay(options);
pay函數本身沒有參數,所以是加一個options的參數,如果pay函數本身有多個參數,則options的參數會加到所有參數的最後面去,比如:
let tx = await contractWithSigner.pay(param1, param2, options);
完整的測試運作程式碼在https://github.com/vcckvv/SmartContract/blob/gh-pages/DepositCoin/index.html
可以到https://vcckvv.github.io/SmartContract/DepositCoin/ 上面進行測試運作,按下那個pay按鈕就會執行上面說的pay()函數
要查詢結果或進行其他的功能可以到https://goerli.etherscan.io/address/0x416d91a77948ca36f79a9609c239a41bd5e0e566#readContract 的合約互動介面上去做,因為沒其他新東西所以這次不另外講解網頁程式碼
另外一樣要注意一下如果有多個人同時進行測試,有可能會產生意外的結果,這個時候就要注意這個合約的交易狀況:
https://goerli.etherscan.io/address/0x416d91a77948ca36f79a9609c239a41bd5e0e566
--------------------------------------
function withdraw() public{
msg.sender.transfer(1 wei);
}
function withdraw(uint256 v) public{
msg.sender.transfer(v);
}
這裡寫了二種出金函數,一種是執行後會固定傳送原生代幣1 wei給使用者,另一種則是可以指定傳送的數量有多少wei,因為輸入參數不同,所以用同樣的函數名也可以
msg.sender.transfer(v)
這個程式碼的實際作用是把合約的原生代幣傳給msg.sender
另外除了wei這個單位外,還有gwei
(10^9 wei)、ether
(10^18 wei)的單位可以用
--------------------------------------
補充一下,solidity目前沒內建支援浮點數,所以原生代幣和ERC20代幣的數值都是採用「uint256儲存和處理數值」+「固定位數的小數點顯示」的方式來呈現,原生代幣1 wei就是最低單位
--------------------------------------
function setTokenAddr(address addr) public{
tokenAddr=addr;
}
這個函數是用來設定ERC20代幣合約的位址,設定完之後我們才可以使用其他的函數來轉移這種ERC20代幣,而我們測試的當然是之前創造的TestToken,合約位址為:0x63E1ddeB828eD1634B3165f56e2F31950b4d0B93
沒拿過的人可以到https://vcckvv.github.io/SmartContract/TestToken/ 的水龍頭網站領取
--------------------------------------
function getTokenBalance() public view returns (uint256) {
Token t = Token(tokenAddr);
return t.balanceOf(msg.sender);
}
function getContractTokenBalance() public view returns (uint256) {
Token t = Token(tokenAddr);
return t.balanceOf(this);
}
這二個函數是用來讀取使用者帳戶或這個合約中擁有的ERC20代幣數量,使用我們之前設定好的interface Token以及代幣合約的位址,就能呼叫代幣合約的balanceOf函數
--------------------------------------
function depositToken(uint256 v) public{
Token t = Token(tokenAddr);
t.transferFrom(msg.sender, this, v);
}
這個函數是把使用者帳戶的ERC20代幣存到這個合約中,但要注意的是,由於動用到的是transferFrom函數(function transferFrom(address from, address to, uint256 value)
,把value的代幣值從from帳號轉移到to帳號),因為ERC20代幣的安全規範限制,需要使用者先到代幣合約那邊執行approve函數
approve函數的輸入參數是「一個位址和一個數值」(function approve(address spender, uint256 value)
),代表帳戶允許那個位址的人最多動用到多少自己手上的代幣,假設已經設成100,則這裡的transferFrom函數最多就只能移動100個代幣
所以要測試的話,就先要執行以下網頁的互動介面的approve函數,spender位址參數就填入這個合約的位址:0x416d91a77948ca36f79a9609c239a41bd5e0e566
https://goerli.etherscan.io/address/0x63E1ddeB828eD1634B3165f56e2F31950b4d0B93#writeContract
因為ERC20代幣的安全規範,同時又因為solidity的呼叫其他合約函數的限制,我們無法在自己的合約中呼叫approve而讓使用者帳戶直接允許合約動用它的代幣,所以目前只能要求使用者依序呼叫二種函數才能完成存入代幣的動作,一般的做法是讓approve一次把允許的數量拉到最大,這樣之後就不用每次都再呼叫approve
--------------------------------------
function withdrawToken(uint256 v) public{
Token t = Token(tokenAddr);
t.transfer(msg.sender, v);
}
這個函數是把這個合約擁有的ERC20代幣存到使用者帳戶中,做法和depositToken差不多,只是換成呼叫transfer函數(function transfer(address to, uint256 value)
,把value的代幣值從呼叫者帳號轉移到to帳號),transfer函數是由這個合約呼叫,所以是把合約擁有的幣轉移給現在的msg.sender
--------------------------------------
最後來提一下智能合約的位址產生方式,智能合約的位址是用「創造者的帳號位址」+「nonce」(每次交易都會增加的數字)丟給keccak256的hash函數來產生,所以本身沒有密鑰,如果把代幣直接傳到合約位址的話,就只能仰賴合約自身的出金函數來想辦法取出
所以為了避免有人亂傳,有些智能合約會設置一個出金函數,用來處理代幣亂傳的情況,不過當然會設定成只有管理員帳戶才能存取,怎麼設定管理員帳戶之後會提到
把代幣存到合約中的功能就介紹到這裡,下一篇我們要利用區塊鏈功能來打造一個Web3線上聊天室,裡面全部的文章都會上鏈,敬請期待~