
原文:Go-Ethereum 1.7.2 結(jié)合 Mist 0.9.2 實現(xiàn)代幣智能合約的實例
作者:迦壹
(注:本文是在原文的基礎(chǔ)上,根據(jù)個人的理解,修改部分內(nèi)容并添加了一些注釋)
基礎(chǔ)版的代幣合約
下面是一個最簡單的代幣合約代碼,主要介紹可以看注釋:
pragma solidity 0.4.20;
/**
* @title 基礎(chǔ)版的代幣合約
*/
contract token {
/* 公共變量 */
string public standard = "https://mshk.top";
/*記錄所有余額的映射*/
mapping (address => uint256) public balanceOf;
/* 初始化合約,并且把初始的所有代幣都給這合約的創(chuàng)建者
* @param initialSupply 代幣的總數(shù)
*/
function token (uint256 initialSupply) public {
balanceOf[msg.sender] = initialSupply;
}
/**
* 私有方法從一個帳戶發(fā)送給另一個帳戶代幣
* @param from address 發(fā)送代幣的地址
* @param to address 接受代幣的地址
* @param value uint256 接受代幣的數(shù)量
*/
function _transfer (address from, address to, uint256 value) internal {
//避免轉(zhuǎn)帳的地址是0x0
require(to != 0x0);
//檢查發(fā)送者是否擁有足夠余額
require(balanceOf[from] >= value);
//檢查是否溢出
require(balanceOf[to] + value > balanceOf[to]);
//保存數(shù)據(jù)用于后面的判斷
uint previousBalances = balanceOf[from] + balanceOf[to];
//從發(fā)送者減掉發(fā)送額
balanceOf[from] -= value;
//給接收者加上相同的量
balanceOf[to] += value;
//判斷買、賣雙方的數(shù)據(jù)是否和轉(zhuǎn)換前一致
assert(balanceOf[from] + balanceOf[to] == previousBalances);
}
/**
* 從主帳戶合約調(diào)用者發(fā)送給別人代幣
* @param to address 接受代幣的地址
* @param value uint256 接受代幣的數(shù)量
*/
function transfer (address to, uint256 value) public {
_transfer(msg.sender, to, value);
}
}
接下來我們將上面的合約代碼,通過Mist部署到我們的私有鏈
首先如下圖中,點擊合約->部署新合約:

然后如下面的兩張圖,選擇創(chuàng)建合約的帳戶,將上面的代碼貼到SOLIDITY合約原始代碼處,在右側(cè)選擇要部署的合約token,在token的下面Initial Supply處,輸入我們要初始化的金額5000用于獎勵合約創(chuàng)建者,然后在頁面的最下面,點擊部署,的彈出的層中,輸入創(chuàng)建合約這個帳號的密碼,輸入正確以后,合約創(chuàng)建成功。


創(chuàng)建一個合約以后,再點擊 Mist 右側(cè)的合約,然后在 定制化合約 的下面,可以看到我們剛剛創(chuàng)建的合約 TOKEN,如下圖:

注意,在合約下面也有一串0x開頭的地址,這個就相當(dāng)于一個錢包的地址。在以太坊中,合約也相當(dāng)于一個帳戶。
點擊 TOKEN ,進(jìn)入到合約里。在下面的 Balance Of 處,輸入剛才創(chuàng)建合約帳戶的地址0xa18e688326ab13b6147ce3ca2213db143a4ec2ee,可以看到是有5000個代幣在里面,如下圖:

在代幣創(chuàng)建的時候,初始值我們設(shè)置的是5000,所以只有創(chuàng)建帳戶的地址有代幣,而輸入其他帳戶的地址,如0xc81896af13449a82f22699311df4ec4b48c07718,是沒有值的。
接下來,我們向0xc81896af13449a82f22699311df4ec4b48c07718這個帳戶地址,轉(zhuǎn)入一些代幣。點擊右側(cè)選擇函數(shù)->選擇Transfer,在_to中輸入0xc81896af13449a82f22699311df4ec4b48c07718,在_value中輸入500,然后點擊執(zhí)行,在彈出的層中輸入調(diào)用合約帳戶的密碼,確認(rèn)操作。


我們能夠看到實際調(diào)用合約的過程中,會花費一定的
gas,gas和以太幣會根據(jù)區(qū)塊的算力有一個計算公式,gas一般用于獎勵給挖礦者。
在 transfer 方法中,我們設(shè)定了,只有合約的調(diào)用者msg.sender才能向指定地址轉(zhuǎn)移代幣。
/**
* 從主帳戶合約調(diào)用者發(fā)送給別人代幣
* @param _to address 接受代幣的地址
* @param _value uint256 接受代幣的數(shù)量
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}
這時,再次進(jìn)入合約,在Balance Of處,輸入兩個帳戶的地址,可以看到,余額都發(fā)生的變化,如下圖:

改善代幣
通過上面的操作,我們已經(jīng)可以將合約代碼,通過 Mist 部署到我們創(chuàng)建的私有鏈中,同樣如果部署到生產(chǎn)環(huán)境,只需要連上以太坊的網(wǎng)絡(luò),同樣的方法也可以將你的合約,部署到生產(chǎn)環(huán)境中,不過要根據(jù)代碼的大小,花費一些以太幣。
實際使用過程中,交易的過程,需要通知到客戶端,并且記錄到區(qū)塊中,我們可以使用event事件來指定,如下代碼進(jìn)行聲明:
//在區(qū)塊鏈上創(chuàng)建一個事件,用以通知客戶端
event Transfer(address indexed from, address indexed to, uint256 value);
設(shè)置一些代幣的基本信息
/* 公共變量 */
string public standard = 'https://mshk.top';
string public name; //代幣名稱
string public symbol; //代幣符號比如'$'
uint8 public decimals = 18; //代幣單位,展示的小數(shù)點后面多少個0,和以太幣一樣后面是是18個0
uint256 public totalSupply; //代幣總量
某些特定的場景中,不允許某個帳戶花費超過指定的上限,避免大額支出,我們可以添加一個 approve 方法,來設(shè)置一個允許支出最大金額的列表。
注:根據(jù)個人的理解,approve是其他合約調(diào)用此合約,或者是代理,例如交易所,使用你的賬戶時,你可以設(shè)置一個代幣數(shù)量。他們只能對你設(shè)置的數(shù)量的代幣進(jìn)行操作,不知道這樣理解是否準(zhǔn)確。下面是一個對approve、allowance,以及后面的transferFrom的解釋:
注:approve、transferFrom及allowance解釋:
賬戶A有1000個ETH,想允許B賬戶隨意調(diào)用100個ETH。A賬戶按照以下形式調(diào)用approve函數(shù)approve(B,100)。當(dāng)B賬戶想用這100個ETH中的10個ETH給C賬戶時,則調(diào)用transferFrom(A, C, 10)。這時調(diào)用allowance[A][B]可以查看B賬戶還能夠調(diào)用A賬戶多少個token。
mapping (address => mapping (address => uint256)) public allowance;
/**
* 設(shè)置帳戶允許支付的最大金額
*
* 一般在智能合約的時候,避免支付過多,造成風(fēng)險
*
* @param _spender 帳戶地址
* @param _value 金額
*/
function approve(address _spender, uint256 _value) public returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
同樣在 solidity 中,合約之間也可以相互調(diào)用,我們可以增加一個 approveAndCall 方法,用于在設(shè)置帳戶最大支出金額后,可以做一些其他操作。
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }
/**
* 設(shè)置帳戶允許支付的最大金額
*
* 一般在智能合約的時候,避免支付過多,造成風(fēng)險
*
* @param _spender 帳戶地址
* @param _value 金額
*/
function approve(address _spender, uint256 _value) public returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
/**
* 設(shè)置帳戶允許支付的最大金額
*
* 一般在智能合約的時候,避免支付過多,造成風(fēng)險,加入時間參數(shù),可以在 tokenRecipient 中做其他操作
*
* @param _spender 帳戶地址
* @param _value 金額
* @param _extraData 操作的時間
*/
function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {
tokenRecipient spender = tokenRecipient(_spender);
if (approve(_spender, _value)) {
spender.receiveApproval(msg.sender, _value, this, _extraData);
return true;
}
}
我們可以增加一個 burn 方法,用于管理員減去指定帳戶的指定金額。進(jìn)行該方法操作時,通知客戶端記錄到區(qū)塊鏈中。
注:代碼并沒有判斷調(diào)用者是否為管理員。
注:代理B在調(diào)用burnFrom刪除A賬戶余額時,也需要刪除A授權(quán)給自己的可調(diào)用余額數(shù)。
//減去用戶余額事件
event Burn(address indexed from, uint256 value);
/**
* 減少代幣調(diào)用者的余額
*
* 操作以后是不可逆的
*
* @param _value 要刪除的數(shù)量
*/
function burn(uint256 _value) public returns (bool success) {
//檢查帳戶余額是否大于要減去的值
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
//給指定帳戶減去余額
balanceOf[msg.sender] -= _value;
//代幣問題做相應(yīng)扣除
totalSupply -= _value;
Burn(msg.sender, _value);
return true;
}
/**
* 刪除帳戶的余額(含其他帳戶)
*
* 刪除以后是不可逆的
*
* @param _from 要操作的帳戶地址
* @param _value 要減去的數(shù)量
*/
function burnFrom(address _from, uint256 _value) public returns (bool success) {
//檢查帳戶余額是否大于要減去的值
require(balanceOf[_from] >= _value);
//檢查 其他帳戶 的余額是否夠使用
require(_value <= allowance[_from][msg.sender]);
//減掉代幣
balanceOf[_from] -= _value;
allowance[_from][msg.sender] -= _value;
//更新總量
totalSupply -= _value;
Burn(_from, _value);
return true;
}
完整的代碼如下:
pragma solidity 0.4.20;
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; }
/**
* @title 基礎(chǔ)版的代幣合約
*/
contract token {
/* 公共變量 */
string public standard = 'https://mshk.top';
string public name; //代幣名稱
string public symbol; //代幣符號比如'$'
uint8 public decimals = 18; //代幣單位,展示的小數(shù)點后面多少個0,和以太幣一樣后面是是18個0
uint256 public totalSupply; //代幣總量
/*記錄所有余額的映射*/
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;
/* 在區(qū)塊鏈上創(chuàng)建一個事件,用以通知客戶端*/
event Transfer(address indexed from, address indexed to, uint256 value); //轉(zhuǎn)帳通知事件
event Burn(address indexed from, uint256 value); //減去用戶余額事件
/* 初始化合約,并且把初始的所有代幣都給這合約的創(chuàng)建者
* @param initialSupply 代幣的總數(shù)
* @param tokenName 代幣名稱
* @param tokenSymbol 代幣符號
*/
function token(uint256 initialSupply, string tokenName, string tokenSymbol) public {
//初始化總量
totalSupply = initialSupply * 10 ** uint256(decimals); //以太幣是10^18,后面18個0,所以默認(rèn)decimals是18
//給指定帳戶初始化代幣總量,初始化用于獎勵合約創(chuàng)建者
balanceOf[msg.sender] = totalSupply;
name = tokenName;
symbol = tokenSymbol;
}
/**
* 私有方法從一個帳戶發(fā)送給另一個帳戶代幣
* @param _from address 發(fā)送代幣的地址
* @param _to address 接受代幣的地址
* @param _value uint256 接受代幣的數(shù)量
*/
function _transfer(address _from, address _to, uint256 _value) internal {
//避免轉(zhuǎn)帳的地址是0x0
require(_to != 0x0);
//檢查發(fā)送者是否擁有足夠余額
require(balanceOf[_from] >= _value);
//檢查是否溢出
require(balanceOf[_to] + _value > balanceOf[_to]);
//保存數(shù)據(jù)用于后面的判斷
uint previousBalances = balanceOf[_from] + balanceOf[_to];
//從發(fā)送者減掉發(fā)送額
balanceOf[_from] -= _value;
//給接收者加上相同的量
balanceOf[_to] += _value;
//通知任何監(jiān)聽該交易的客戶端
Transfer(_from, _to, _value);
//判斷買、賣雙方的數(shù)據(jù)是否和轉(zhuǎn)換前一致
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
/**
* 從主帳戶合約調(diào)用者發(fā)送給別人代幣
* @param _to address 接受代幣的地址
* @param _value uint256 接受代幣的數(shù)量
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}
/**
* 從某個指定的帳戶中,向另一個帳戶發(fā)送代幣
*
* 調(diào)用過程,會檢查設(shè)置的允許最大交易額
*
* @param _from address 發(fā)送者地址
* @param _to address 接受者地址
* @param _value uint256 要轉(zhuǎn)移的代幣數(shù)量
* @return success 是否交易成功
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success){
//檢查發(fā)送者是否擁有足夠余額
require(_value <= allowance[_from][msg.sender]); // Check allowance
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
/**
* 設(shè)置帳戶允許支付的最大金額
*
* 一般在智能合約的時候,避免支付過多,造成風(fēng)險
*
* @param _spender 帳戶地址
* @param _value 金額
*/
function approve(address _spender, uint256 _value) public returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
/**
* 設(shè)置帳戶允許支付的最大金額
*
* 一般在智能合約的時候,避免支付過多,造成風(fēng)險,加入時間參數(shù),可以在 tokenRecipient 中做其他操作
*
* @param _spender 帳戶地址
* @param _value 金額
* @param _extraData 操作的時間
*/
function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {
tokenRecipient spender = tokenRecipient(_spender);
if (approve(_spender, _value)) {
spender.receiveApproval(msg.sender, _value, this, _extraData);
return true;
}
}
/**
* 減少代幣調(diào)用者的余額
*
* 操作以后是不可逆的
*
* @param _value 要刪除的數(shù)量
*/
function burn(uint256 _value) public returns (bool success) {
//檢查帳戶余額是否大于要減去的值
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
//給指定帳戶減去余額
balanceOf[msg.sender] -= _value;
//代幣問題做相應(yīng)扣除
totalSupply -= _value;
Burn(msg.sender, _value);
return true;
}
/**
* 刪除帳戶的余額(含其他帳戶)
*
* 刪除以后是不可逆的
*
* @param _from 要操作的帳戶地址
* @param _value 要減去的數(shù)量
*/
function burnFrom(address _from, uint256 _value) public returns (bool success) {
//檢查帳戶余額是否大于要減去的值
require(balanceOf[_from] >= _value);
//檢查 其他帳戶 的余額是否夠使用
require(_value <= allowance[_from][msg.sender]);
//減掉代幣
balanceOf[_from] -= _value;
allowance[_from][msg.sender] -= _value;
//更新總量
totalSupply -= _value;
Burn(_from, _value);
return true;
}
}
如上面的部署中,我們將完整的代碼,貼到 Mist 的 solidity合約原始代碼處,在右側(cè)選擇 token , Initial Supply 輸入初始金額5000,Token name 輸入我們的代幣名稱 陌上花開 , Token symbol代幣符號我們輸入 $$ ,然后點擊 部署 ,輸入部署帳戶的密碼。

部署合約以后,我們能夠在合約頁面看到剛才創(chuàng)建的合約。

點擊合約名稱,可以看到合約的一些基本信息,以及合約和操作函數(shù)

我們能夠在 Mist 上方的 錢包 中的主帳號這里看到有個小圖標(biāo),說明主帳戶已經(jīng)有了代幣,其他帳戶是沒有這個圖標(biāo)的

點擊進(jìn)入主帳號以后,我們就可以看到主帳戶已經(jīng)擁有的代幣和以太幣的數(shù)量,因為我們是參考以太幣進(jìn)行設(shè)置,最小單位是wei,所以小數(shù)點后面有18個0。

接下來,我們向另一個帳戶發(fā)送一些 陌上花開 幣,點擊 Mist 上方的發(fā)送,輸入發(fā)送的帳戶地址,輸入數(shù)量 500 ,選擇發(fā)送的是 陌上花開 幣,點擊發(fā)送,如下圖

再次回到錢包中,我們可以看到,另一個帳戶也有了一個代幣的圖標(biāo),說明代幣已經(jīng)轉(zhuǎn)入成功。

現(xiàn)在你擁有了自己的代幣,也可以做轉(zhuǎn)入轉(zhuǎn)出操作??梢员挥糜?a target="_blank" rel="nofollow">價值交換,或者工作時間追蹤或者其他項目。
高級版的代幣功能
雖然區(qū)塊鏈?zhǔn)侨ブ行幕模菍崿F(xiàn)對代幣(合約)的管理,也在許多應(yīng)用中有需求,為了對代幣進(jìn)行管理,首先需要給合約添加一個管理者。
**
* owned 是一個管理者
*/
contract owned {
address public owner;
/**
* 初臺化構(gòu)造函數(shù)
*/
function owned() {
owner = msg.sender;
}
/**
* 判斷當(dāng)前合約調(diào)用者是否是管理員
*/
modifier onlyOwner {
require (msg.sender == owner);
_;
}
/**
* 指派一個新的管理員
* @param newOwner address 新的管理員帳戶地址
*/
function transferOwnership(address newOwner) onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}
上面的代碼是一個非常簡單的合約,我們可以在后面的代碼中,使用 繼承 來實現(xiàn)后續(xù)的功能。
/**
* @title 高級版代幣
* 增加凍結(jié)用戶、挖礦、根據(jù)指定匯率購買(售出)代幣價格的功能
*/
contract MyAdvancedToken is owned{}
在 MyAdvancedToken 的所有方法中,可以使用 owned 的變量 owner 和 modifier onlyOwner。
去中心化的管理者
我們也可以在構(gòu)造函數(shù)中設(shè)置是否需要一個去中心化的管理者。
/*初始化合約,并且把初始的所有的令牌都給這合約的創(chuàng)建者
* @param initialSupply 所有幣的總數(shù)
* @param tokenName 代幣名稱
* @param tokenSymbol 代幣符號
* @param centralMinter 是否指定其他帳戶為合約所有者,為0是去中心化
*/
function MyAdvancedToken(
uint256 initialSupply,
string tokenName,
string tokenSymbol,
address centralMinter
) {
//設(shè)置合約的管理者
if(centralMinter != 0 ) owner = centralMinter;
}
代幣增發(fā)
實現(xiàn)代幣增發(fā),代幣增發(fā)就如同央行印鈔票一樣,想必很多人都需要這樣的功能。
/**
* 合約擁有者,可以為指定帳戶創(chuàng)造一些代幣
* @param target address 帳戶地址
* @param mintedAmount uint256 增加的金額(單位是wei)
*/
function mintToken(address target, uint256 mintedAmount) onlyOwner {
//給指定地址增加代幣,同時總量也相加
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;
}
在方法的最后有一個 onlyOwner,說明 mintToken 是繼承了 onlyOwner方法,會先調(diào)用 modifier onlyOwner 方法,然后將 mintToken 方法的內(nèi)容,插入到下劃線 _ 處調(diào)用。
凍結(jié)資產(chǎn)
有的場景中,某些用戶違反了規(guī)定,需要凍結(jié)/解凍帳戶,不想讓他使用已經(jīng)擁有的代幣.可以增加以下代碼來控制:
//是否凍結(jié)帳戶的列表
mapping (address => bool) public frozenAccount;
//定義一個事件,當(dāng)有資產(chǎn)被凍結(jié)的時候,通知正在監(jiān)聽事件的客戶端
event FrozenFunds(address target, bool frozen);
/**
* 增加凍結(jié)帳戶名稱
*
* 你可能需要監(jiān)管功能以便你能控制誰可以/誰不可以使用你創(chuàng)建的代幣合約
*
* @param target address 帳戶地址
* @param freeze bool 是否凍結(jié)
*/
function freezeAccount(address target, bool freeze) onlyOwner {
frozenAccount[target] = freeze;
FrozenFunds(target, freeze);
}
代幣買賣(兌換)
可以自己的貨幣中實現(xiàn)代幣與其他數(shù)字貨幣(ether 或其他tokens)的兌換機制。有了這個功能,我們的合約就可以在一買一賣中賺利潤了。
//賣出的匯率,一個代幣,可以賣出多少個以太幣,單位是wei
uint256 public sellPrice;
//買入的匯率,1個以太幣,可以買幾個代幣
uint256 public buyPrice;
/**
* 設(shè)置買賣價格
*
* 如果你想讓ether(或其他代幣)為你的代幣進(jìn)行背書,以便可以市場價自動化買賣代幣,我們可以這么做。如果要使用浮動的價格,也可以在這里設(shè)置
*
* @param newSellPrice 新的賣出價格
* @param newBuyPrice 新的買入價格
*/
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}
然后增加買、賣的方法,每一次的交易,都會消耗掉一定的 ether。在 Solidity 0.4.0 之后,要接收 ether 的函數(shù)都要加一個 payable 屬性,如果你開放的合約,需要別人轉(zhuǎn)錢給你,就需要加 payable。
下面的方法,不會增加代幣,只是改變調(diào)用合約者的代幣數(shù)量,買、賣的價格單位不是 ether,而是 wei,這是以太幣中最小的單位(就像美元里的美分,比特幣里的聰)。1 ether = 1000000000000000000 wei。因此使用 ether 設(shè)置價格的時候,在最后加18個0。
當(dāng)創(chuàng)建合約的時候,發(fā)送足夠多的 ether 作為代幣的背書,否則你的合約就是破產(chǎn)的,你的用戶就不能夠賣掉他們的代幣。
/**
* 使用以太幣購買代幣
*/
function buy() payable public {
uint amount = msg.value / buyPrice;
_transfer(this, msg.sender, amount);
}
/**
* @dev 賣出代幣
* @return 要賣出的數(shù)量(單位是wei)
*/
function sell(uint256 amount) public {
//檢查合約的余額是否充足
require(this.balance >= amount * sellPrice);
_transfer(msg.sender, this, amount);
msg.sender.transfer(amount * sellPrice);
}
實現(xiàn)Gas的自動補充
以太坊中的交易時需要gas(支付給礦工的費用,費用以ether來支付)。而如果用戶沒有以太幣,只有代幣的情況(或者我們想向用戶隱藏以太坊的細(xì)節(jié)),就需要自動補充gas的功能。這個功能將使我們代幣更加好用。
自動補充的邏輯是這樣了,在執(zhí)行交易之前,我們判斷用戶的余額(用來支付礦工的費用),如果用戶的余額非常少(低于某個閾值時)可能影響到交易進(jìn)行,合約自動售出一部分代幣來補充余額,以幫助用戶順利完成交易。
先來設(shè)定余額閾值:
uint minBalanceForAccounts;
function setMinBalance(uint minimumBalanceInFinney) onlyOwner {
minBalanceForAccounts = minimumBalanceInFinney * 1 finney;
}
finney 是貨幣單位 1 finney = 0.001eth
然后交易中加入對用戶的余額的判斷。
function transfer(address _to, uint256 _value) {
...
if(msg.sender.balance < minBalanceForAccounts)
sell((minBalanceForAccounts - msg.sender.balance) / sellPrice);
if(_to.balance<minBalanceForAccounts) // 可選,讓接受者也補充余額,以便接受者使用代幣。
_to.send(sell((minBalanceForAccounts - _to.balance) / sellPrice));
}
全部代碼
pragma solidity 0.4.20;
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; }
/**
* owned 是一個管理者
*/
contract owned {
address public owner;
/**
* 初臺化構(gòu)造函數(shù)
*/
function owned () public {
owner = msg.sender;
}
/**
* 判斷當(dāng)前合約調(diào)用者是否是管理員
*/
modifier onlyOwner {
require (msg.sender == owner);
_;
}
/**
* 指派一個新的管理員
* @param newOwner address 新的管理員帳戶地址
*/
function transferOwnership(address newOwner) onlyOwner public {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}
/**
* @title 基礎(chǔ)版的代幣合約
*/
contract token {
/* 公共變量 */
string public standard = 'https://mshk.top';
string public name; //代幣名稱
string public symbol; //代幣符號比如'$'
uint8 public decimals = 18; //代幣單位,展示的小數(shù)點后面多少個0,和以太幣一樣后面是是18個0
uint256 public totalSupply; //代幣總量
/*記錄所有余額的映射*/
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;
/* 在區(qū)塊鏈上創(chuàng)建一個事件,用以通知客戶端*/
event Transfer(address indexed from, address indexed to, uint256 value); //轉(zhuǎn)帳通知事件
event Burn(address indexed from, uint256 value); //減去用戶余額事件
/* 初始化合約,并且把初始的所有代幣都給這合約的創(chuàng)建者
* @param initialSupply 代幣的總數(shù)
* @param tokenName 代幣名稱
* @param tokenSymbol 代幣符號
*/
function token(uint256 initialSupply, string tokenName, string tokenSymbol) public {
//初始化總量
totalSupply = initialSupply * 10 ** uint256(decimals); //以太幣是10^18,后面18個0,所以默認(rèn)decimals是18
//給指定帳戶初始化代幣總量,初始化用于獎勵合約創(chuàng)建者
//balanceOf[msg.sender] = totalSupply;
balanceOf[this] = totalSupply;
name = tokenName;
symbol = tokenSymbol;
}
/**
* 私有方法從一個帳戶發(fā)送給另一個帳戶代幣
* @param _from address 發(fā)送代幣的地址
* @param _to address 接受代幣的地址
* @param _value uint256 接受代幣的數(shù)量
*/
function _transfer(address _from, address _to, uint256 _value) internal {
//避免轉(zhuǎn)帳的地址是0x0
require(_to != 0x0);
//檢查發(fā)送者是否擁有足夠余額
require(balanceOf[_from] >= _value);
//檢查是否溢出
require(balanceOf[_to] + _value > balanceOf[_to]);
//保存數(shù)據(jù)用于后面的判斷
uint previousBalances = balanceOf[_from] + balanceOf[_to];
//從發(fā)送者減掉發(fā)送額
balanceOf[_from] -= _value;
//給接收者加上相同的量
balanceOf[_to] += _value;
//通知任何監(jiān)聽該交易的客戶端
Transfer(_from, _to, _value);
//判斷買、賣雙方的數(shù)據(jù)是否和轉(zhuǎn)換前一致
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
/**
* 從主帳戶合約調(diào)用者發(fā)送給別人代幣
* @param _to address 接受代幣的地址
* @param _value uint256 接受代幣的數(shù)量
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}
/**
* 從某個指定的帳戶中,向另一個帳戶發(fā)送代幣
*
* 調(diào)用過程,會檢查設(shè)置的允許最大交易額
*
* @param _from address 發(fā)送者地址
* @param _to address 接受者地址
* @param _value uint256 要轉(zhuǎn)移的代幣數(shù)量
* @return success 是否交易成功
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
//檢查發(fā)送者是否擁有足夠余額
require(_value <= allowance[_from][msg.sender]); // Check allowance
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
/**
* 設(shè)置帳戶允許支付的最大金額
*
* 一般在智能合約的時候,避免支付過多,造成風(fēng)險
*
* @param _spender 帳戶地址
* @param _value 金額
*/
function approve(address _spender, uint256 _value) public returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
/**
* 設(shè)置帳戶允許支付的最大金額
*
* 一般在智能合約的時候,避免支付過多,造成風(fēng)險,加入時間參數(shù),可以在 tokenRecipient 中做其他操作
*
* @param _spender 帳戶地址
* @param _value 金額
* @param _extraData 操作的時間
*/
function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {
tokenRecipient spender = tokenRecipient(_spender);
if (approve(_spender, _value)) {
spender.receiveApproval(msg.sender, _value, this, _extraData);
return true;
}
}
/**
* 減少代幣調(diào)用者的余額
*
* 操作以后是不可逆的
*
* @param _value 要刪除的數(shù)量
*/
function burn(uint256 _value) public returns (bool success) {
//檢查帳戶余額是否大于要減去的值
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
//給指定帳戶減去余額
balanceOf[msg.sender] -= _value;
//代幣問題做相應(yīng)扣除
totalSupply -= _value;
Burn(msg.sender, _value);
return true;
}
/**
* 刪除帳戶的余額(含其他帳戶)
*
* 刪除以后是不可逆的
*
* @param _from 要操作的帳戶地址
* @param _value 要減去的數(shù)量
*/
function burnFrom(address _from, uint256 _value) public returns (bool success) {
//檢查帳戶余額是否大于要減去的值
require(balanceOf[_from] >= _value);
//檢查 其他帳戶 的余額是否夠使用
require(_value <= allowance[_from][msg.sender]);
//減掉代幣
balanceOf[_from] -= _value;
allowance[_from][msg.sender] -= _value;
//更新總量
totalSupply -= _value;
Burn(_from, _value);
return true;
}
/**
* 匿名方法,預(yù)防有人向這合約發(fā)送以太幣
*/
/*function() {
//return; // Prevents accidental sending of ether
}*/
}
/**
* @title 高級版代幣
* 增加凍結(jié)用戶、挖礦、根據(jù)指定匯率購買(售出)代幣價格的功能
*/
contract MyAdvancedToken is owned, token {
//賣出的匯率,一個代幣,可以賣出多少個以太幣,單位是wei
uint256 public sellPrice;
//買入的匯率,1個以太幣,可以買幾個代幣
uint256 public buyPrice;
//是否凍結(jié)帳戶的列表
mapping (address => bool) public frozenAccount;
//定義一個事件,當(dāng)有資產(chǎn)被凍結(jié)的時候,通知正在監(jiān)聽事件的客戶端
event FrozenFunds(address target, bool frozen);
/*初始化合約,并且把初始的所有的令牌都給這合約的創(chuàng)建者
* @param initialSupply 所有幣的總數(shù)
* @param tokenName 代幣名稱
* @param tokenSymbol 代幣符號
* @param centralMinter 是否指定其他帳戶為合約所有者,為0是去中心化
*/
function MyAdvancedToken (
uint256 initialSupply,
string tokenName,
string tokenSymbol,
address centralMinter
) token (initialSupply, tokenName, tokenSymbol) public {
//設(shè)置合約的管理者
if(centralMinter != 0 ) owner = centralMinter;
sellPrice = 2; //設(shè)置1個單位的代幣(單位是wei),能夠賣出2個以太幣
buyPrice = 4; //設(shè)置1個以太幣,可以買0.25個代幣
}
/**
* 私有方法,從指定帳戶轉(zhuǎn)出余額
* @param _from address 發(fā)送代幣的地址
* @param _to address 接受代幣的地址
* @param _value uint256 接受代幣的數(shù)量
*/
function _transfer(address _from, address _to, uint _value) internal {
//避免轉(zhuǎn)帳的地址是0x0
require (_to != 0x0);
//檢查發(fā)送者是否擁有足夠余額
require (balanceOf[_from] > _value);
//檢查是否溢出
require (balanceOf[_to] + _value > balanceOf[_to]);
//檢查 凍結(jié)帳戶
require(!frozenAccount[_from]);
require(!frozenAccount[_to]);
//從發(fā)送者減掉發(fā)送額
balanceOf[_from] -= _value;
//給接收者加上相同的量
balanceOf[_to] += _value;
//通知任何監(jiān)聽該交易的客戶端
Transfer(_from, _to, _value);
}
/**
* 合約擁有者,可以為指定帳戶創(chuàng)造一些代幣
* @param target address 帳戶地址
* @param mintedAmount uint256 增加的金額(單位是wei)
*/
function mintToken(address target, uint256 mintedAmount) onlyOwner public {
//給指定地址增加代幣,同時總量也相加
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;
Transfer(0, this, mintedAmount);
Transfer(this, target, mintedAmount);
}
/**
* 增加凍結(jié)帳戶名稱
*
* 你可能需要監(jiān)管功能以便你能控制誰可以/誰不可以使用你創(chuàng)建的代幣合約
*
* @param target address 帳戶地址
* @param freeze bool 是否凍結(jié)
*/
function freezeAccount(address target, bool freeze) onlyOwner public {
frozenAccount[target] = freeze;
FrozenFunds(target, freeze);
}
/**
* 設(shè)置買賣價格
*
* 如果你想讓ether(或其他代幣)為你的代幣進(jìn)行背書,以便可以市場價自動化買賣代幣,我們可以這么做。如果要使用浮動的價格,也可以在這里設(shè)置
*
* @param newSellPrice 新的賣出價格
* @param newBuyPrice 新的買入價格
*/
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner public {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}
/**
* 使用以太幣購買代幣
*/
function buy() payable public {
uint amount = msg.value / buyPrice;
_transfer(this, msg.sender, amount);
}
/**
* @dev 賣出代幣
* @return 要賣出的數(shù)量(單位是wei)
*/
function sell(uint256 amount) public {
//檢查合約的余額是否充足
require(this.balance >= amount * sellPrice);
_transfer(msg.sender, this, amount);
msg.sender.transfer(amount * sellPrice);
}
}
參考之前的方法,在 Mist 中重新部署合約,貼完代碼后,在右側(cè)選擇 My Advanced Token,Initial Supply 輸入初始金額5000,Token name 輸入我們的代幣名稱 陌上花開A,Token symbol 代幣符號我們輸入 #,然后點擊部署,輸入部署帳戶的密碼。

創(chuàng)建成功以后,我們在合約列表頁,可以看到剛才創(chuàng)建的新合約陌上花開A。

點擊 Mist 上面的發(fā)送,我們先給帳戶0xd29adaadf3a40fd0b68c83c222c10d3ea637dce0轉(zhuǎn)入100個以太幣。

操作成功以后,我們能夠在錢包頁面看到Account 4已經(jīng)有了100以太幣。

使用以太幣購買代幣
接下來,我們進(jìn)入合約頁面,使用以太幣購買 陌上花開A 代幣,進(jìn)入合約界面后,我們能夠看到代幣上的以太幣是 0 ether,在右側(cè)選擇 Buy 方法,Execut from 選擇 Account 4,在 Send ether 輸入 10 個以太幣,點擊 執(zhí)行。

執(zhí)行成功以后,能夠看到當(dāng)前頁面自動刷新,合約中已經(jīng)有了10 ether,代幣的總量不變

再次回到 錢包 頁面,可以看到 Account 4 已經(jīng)從 100 ether 變成了 90 ether,并且多了一個代幣圖標(biāo)。

點擊 Account 4 帳號進(jìn)去,可以看到一些詳細(xì)信息,ether的總量是 89,999081514 而不是 90,是因為執(zhí)行合約的時候,我們會消費一定的 gas。我們設(shè)置的費率是1:4,所以 10 ether,只可以購買 2.5個 陌上花開A 代幣,最小單位也是wei,所以是 2,500000000000000000。

賣出代幣
進(jìn)入合約界面后,我們能夠看到代幣上的以太幣是 10 ether,在右側(cè)選擇 Sell 方法,在 Amount 處輸入 2000000000000000000(因為我們剛才購買了2.5個代幣,現(xiàn)在賣出2個,賣出的最小單位是wei),Execut from 選擇 Account 4,點擊 執(zhí)行。

執(zhí)行以后,在代幣的詳情頁面,能夠看到從 10 ether 變成了 6 ether ,因為剛才 Account 4 賣出了 2 個 陌上花開A 代幣,而我們設(shè)置的賣價是 1個代幣 能賣出 2個以太幣。

再次回到 Account 4 的詳情頁面,能夠看到以太幣變成了 93,998273026,而 陌上花開A 代幣的數(shù)量,變成了 0,500000000000000000。

注:Account4使用10個以太幣購買了2.5個代幣,如果交易機制沒有手續(xù)費,Account4賣出2.5個代幣仍然會獲得10個以太幣。而這里通過設(shè)置買價4和賣價2,Account4賣出2.5個代幣只會獲得5個以太幣。代幣合約利用手續(xù)費賺取了5個以太幣。
擴(kuò)展閱讀
參考:實現(xiàn)一個可管理、增發(fā)、兌換、凍結(jié)等高級功能的代幣
作者:Tiny熊