发行个人数字货币
在以太坊生态系统中,Token 可以代表任何可交易的商品:货币、忠诚度(loyalty points)、金币、白条、游戏道具等。由于所有 Token 都以标准方式实现了一些基本功能,这也意味着您创建的 Token 将立即与以太坊钱包以及使用相同标准的任何其他客户或合同相兼容。
创建最简单的货币
标准的合约是非常复杂的。一个最的合约可以归结为以下代码:
pragma solidity ^0.4.20;
contract MyToken {
/* 创建一个包含所有余额的数组 */
mapping (address => uint256) public balanceOf;
/* 将初始供应 token 初始化为合约的创建者 */
function MyToken(
uint256 initialSupply
) public {
balanceOf[msg.sender] = initialSupply; // 为创建者提供所有的初始 token
}
/* 发放货币 */
function transfer(address _to, uint256 _value) public {
require(balanceOf[msg.sender] >= _value); // 检查发件人是否正确
require(balanceOf[_to] + _value >= balanceOf[_to]); // 检查溢出
balanceOf[msg.sender] -= _value; // 从发件人中减去金额
balanceOf[_to] += _value; // 将金额添加到收件人
}
}
下面是完整代码
pragma solidity ^0.4.16;
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; }
contract TokenERC20 {
// token 的公共变量
string public name;
string public symbol;
uint8 public decimals = 18;
// 强烈建议将 18 位小数作为默认值
uint256 public totalSupply;
// 创建一个包含所有余额的数组
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;
// 在区块链上创建一个公共事件来进行转账
event Transfer(address indexed from, address indexed to, uint256 value);
// 通知用户已经销毁的代币
event Burn(address indexed from, uint256 value);
/**
* 构造函数
*
* 将初始 token 初始合约的创建者
*/
function TokenERC20(
uint256 initialSupply,
string tokenName,
string tokenSymbol
) public {
totalSupply = initialSupply * 10 ** uint256(decimals); // 用小数更新总供应量
balanceOf[msg.sender] = totalSupply; // 为创建者提供所有初始 token
name = tokenName; // 设置用途名称
symbol = tokenSymbol; // 设置用途符号
}
/**
* 内部转移,只能由本合同调用
*/
function _transfer(address _from, address _to, uint _value) internal {
// 防止转移到 0x0 地址。 使用 burn()代替
require(_to != 0x0);
// 检查发件人是否合法
require(balanceOf[_from] >= _value);
// 检查溢出
require(balanceOf[_to] + _value >= balanceOf[_to]);
// Save this for an assertion in the future
uint previousBalances = balanceOf[_from] + balanceOf[_to];
// 从发件人中减去金额
balanceOf[_from] -= _value;
// 为收件人增加金额
balanceOf[_to] += _value;
emit Transfer(_from, _to, _value);
// Asserts 判断转移后的金额是否与原始金额相等
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
/**
* 发送货币
*
* 从您的帐户发送`_value`令牌给`_to`
*
* @param _to 收件人的地址
* @param _value 要发送的金额
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}
/**
* 从其他地址转移货币
*
* 代表 `_from` 发送 `_value` 给 `_to`
*
* @param _from 发件人的地址
* @param _to 收件人的地址
* @param _value 要发送的金额
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= allowance[_from][msg.sender]); // 检测转移金额的合法性
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
/**
* 授权其他地址使用你的货币
*
* 允许 `_spender` 使用你的货币
*
* @param _spender 授权地址
* @param _value 该地址可使用的最大额度
*/
function approve(address _spender, uint256 _value) public
returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
/**
* 授权其他地址使用你的货币,并且可进行通知操作
*
* 允许 `_spender` 使用的你货币, 并且可以对合同进行 ping 操作
*
* @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;
}
}
/**
* 销毁货币
*
* 不可逆地从系统中删除货币
*
* @param _value 可销毁的最大额度
*/
function burn(uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value); // 检查是否有足够的余额
balanceOf[msg.sender] -= _value; // 从发件人减去余额
totalSupply -= _value; // 更新整个合同中的额度
emit Burn(msg.sender, _value);
return true;
}
/**
* 销毁来自其他帐户的货币
*
* 代表`_from`不可逆地从系统中删除货币
*
* @param _from 发件人的地址
* @param _value 可销毁的最大额度
*/
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; // Subtract from the sender's allowance
totalSupply -= _value; // 更新整个合同中的额度
emit Burn(_from, _value);
return true;
}
}
代码解析
我们从基础开始,打开电子钱包应用程序,转到 'CONTRACTS' 选项卡,然后部署新合同。在 'SOLIDITY CONTRACT SOURCE CODE' 中,输入以下代码:
contract MyToken {
/* 创建一个包含所有余额的数组 */
mapping (address => uint256) public balanceOf;
}
映射(mapping) 意味着一个关联数组,在这个数组中将地址与余额相关联。 地址是基本的十六进制 Ethereum 格式,余额是整数,范围从 0 到 115 quattuorvigintillion(quutuorvigintillion 的数量比你计划中要用代币购买的任何东西都要多)。 'public' 意味着这个变量将被区块链上的任何人访问,这意味着所有的余额都是公开的。
目前我们是没有任何货币的。因此我们将在启动时创建几个货币。在 'mapping ..' 一行代码下面添加如下代码:
function MyToken() {
balanceOf[msg.sender] = 21000000;
}
请注意,函数 MyToken 与合同 MyToken 具有相同的名称。 这非常重要,如果您重命名一个,则必须重命名另一个:这是一个特殊的启动功能,仅在合同首次被上传到网络时才运行一次,也称为构造函数。 此功能将设置 msg.sender 的余额,即部署合同的用户本人余额为 2,100 万。
2,100 万是笔者随手写的,你可以在代码中将它改变为任何你想要的额度。更好的方法是:将它作为函数的参数提供,参考如下代码:
function MyToken(uint256 initialSupply) public {
balanceOf[msg.sender] = initialSupply;
}
在合同旁边,有一个下拉列表,写着“Pick a contract”,选择“MyToken”合同,你会看到现在它显示了一个名为 'Constructor Parameters' 的部分。 这些是货币的可变参数,因此您可以重复使用相同的代码,并且将来只能更改这些变量。
现在已经具有创建余额的功能,但还没有转账功能,它所做的只是保留在同一个帐户上。 现在我们在最后一行添加如下代码:
/* 发送货币 */
function transfer(address _to, uint256 _value) {
/* 对发送者减余额,对接收者加余额 */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
这是一个非常简单的函数:它有一个接收者和一个值作为参数,每当有人调用它时,它将从发送者的余额中减去转账金额并将其添加到接收者余额中。 这里存在一个新的问题:如果这个人想发送的金额远超过自己现有的余额,会发生什么? 我们只需进行快速检查,如果发件人没有足够的资金,合同执行就会停止。 这也是检查溢出的一种手段。
要在合同执行时中断执行,您可以返回或抛出异常,这只会消耗很少的 gas,但可能会更加令人头疼,因为迄今为止对合同所做的任何更改都将保留。 另一方面,抛出异常将取消所有合约的执行,恢复可能已经发生的任何变化,并且发件人将失去他所发送的所有以太币。 但是由于电子钱包可以检测到合同抛出的异常,并且显示警报,有效阻止货币的损失。
function transfer(address _to, uint256 _value) {
/* 检查发件人是否有足够的余额,同时检查溢出 */
require(balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]);
/* 对不同用户进行余额操作 */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
现在缺少的是有关合同的一些基本信息。 在后期,我们也可以通过 Token 注册表来处理。现在我们将它们直接添加到合同中:
string public name;
string public symbol;
uint8 public decimals;
现在我们修改构造函数来初始化这些变量:
/* 将初始提供的 Token 初始化为合同的创建者 */
function MyToken(uint256 initialSupply, string tokenName, string tokenSymbol, uint8 decimalUnits) {
balanceOf[msg.sender] = initialSupply; // 为创建者提供所有初始 token
name = tokenName; // 设置用途名称
symbol = tokenSymbol; // 设置用途符号
decimals = decimalUnits; // 设置显示的小数位数
}
最后,我们再实现一些名为 Events 的代码。 Event 应以大写字母开头。 在合同开始处添加此行以声明事件:
event Transfer(address indexed from, address indexed to, uint256 value);
然后在“Transfer”函数中添加这两行:
/* 通知所有监听此次转帐的人 */
Transfer(msg.sender, _to, _value);
现在你的货币就准备好了。
关于注释
你可能会问,注释中那些 @notice 和 @param 是什么? 这就是 Natspec 自然语言规范的一个新兴标准,它允许钱包向用户显示合同所做事情的自然语言描述。 虽然目前很多钱包尚未支持,但这种情况会被逐渐普及,所以很有必要做好准备。
如何部署
打开以太坊钱包,转到“CONTRACTS(合同)”选项卡,然后点击“部署新合同”。
现在将上方的代码整理下来并将其粘贴到“SOLIDITY CONTRACT SOURCE CODE”中。 如果代码编译没有任何错误,这时在右侧会看到一个“SELECT CONTRACT TO DEPLOY (选择合同)”下拉列表。 获取并选择“MyToken”合同。 在右栏中,我们可以看到许多自定义参数, 这里可以根据需要来调整它们。在本教程中,我们建议您选择以下参数:_supply (供货商) 设置为 10,000,_name 可以自行设置,_symbol 设置为“%”,_decimals (小数位) 设置为 2 位。 如下图:
滚动到页面底部,我们可以看到该合同计算成本的估计值,这里可以选择一个费用来确定我们愿意为此支付多少以太币。 这里请读者放心,任何多余的以太币最终是可以返还给您的。 按下“deploy (部署)”,输入您的账户密码,然后等待几秒钟,以完成交易。
稍后会跳转至首页,我们可以看到交易正在等待确认。 点击名为“Etherbase”(主要帐户)的帐户,稍后,我们可以看到主帐户持有 100%的货币。 我们可以试着将这些货币发送给几个朋友:选择“SEND”,然后选择要发送的货币(以太币或我们自己创建的货币),将朋友的地址粘贴到“to”字段并点击“send”。
虽然已经发送给朋友,但是他们无法在他们的钱包中看到任何东西。 这是因为钱包只追踪它知道的货币,因此我们必须手动添加这些货币。 现在转到“CONTRACTS”选项卡,可以看到一个新创建合同的链接。 点击链接进入其页面。 这只是一个简单的界面,因为等会需要用到它,我们点击“复制地址”并将合同地址粘贴到一个地方保存。
我们回到“CONTRACTS”界面,然后点击“Watch Token”。在弹出窗中,我们只需要粘贴合同地址, 货币名称、符号和十进制数字应该会自动填充。如果没有自动填充,您可以参考下图进行填写(这只会影响它在钱包上的显示方式)。 完成之后,会自动显示出你拥有该货币的任何权利,并且可以将它发送给其他人。
现在你已经拥有了属于自己的加密货币!
完善货币
你可以部署你的整个加密货币,而不需要修改任何代码。 以下部分将提供有关可以添加到货币功能的建议,以使其更适合我们的需求。
更多的功能
我们会注意到基本货币合约中还有一些功能,比如 approve,sendFrom 等等。 这些功能用于货币与其他合同交互:如果我们想要将货币销售给交易所,只需将它们发送到地址就足够了,因为交易所不会意识到新代币或谁发送给他们,因此合同不能订阅事件,只是函数调用。 所以,对于合同,我们应该首先批准他们可以从我们账户转移的货币数量,然后通过 ping 让他们知道他们应该做的事情 - 或者通过 approveAndCall 完成两项操作。
由于这些功能都必须重新实现令牌的转移,所以我们可以将它们改为内部函数,只能由合约本身调用它们:
/* 内部转移,只能由本合同调用 */
function _transfer(address _from, address _to, uint _value) internal {
require (_to != 0x0); // 防止转移到 0x0 地址。 使用 burn()代替
require (balanceOf[_from] >= _value); // 检查发送人是否合法
require (balanceOf[_to] + _value >= balanceOf[_to]); // 检查溢出
require(!frozenAccount[_from]); // 检查发件人是否被冻结
require(!frozenAccount[_to]); // 检查收件人是否被冻结
balanceOf[_from] -= _value; // 扣除发件人余额
balanceOf[_to] += _value; // 为收件人增加余额
Transfer(_from, _to, _value);
}
现在,所有导致货币转移的功能都可以执行相应的安全检测,然后使用正确的参数进行调用。 注意这个函数会将货币从任何账户转移到任何其他账户,而不需要当事人的许可:这就是为什么它是一个内部函数,只能由合约调用。
中央管理员
所有的 Dapps 默认都是完全去中心化的,但这并不意味着他们不能拥有某种中央管理者。比如你想要创建更多的货币,或者禁止一些人使用我们的货币,这时你就需要一个管理者。我们可以进行这样的操作,但是只能在开始时添加这些设定。因此所有货币持有者在游戏开始前就要制定好游戏规则。
从这来看,我们需要一个货币中央控制器,可以是一个简单的账户,也可以是一个合同。为了做到这一点,我们将学习一个非常有用的契约属性:继承。继承允许合同获得父合同的属性,而不必重新定义所有这些属性。这使得代码更清晰,更易于重用。在合约“MyToken”之前,将此代码添加到代码的第一行。
contract owned {
address public owner;
function owned() {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) onlyOwner {
owner = newOwner;
}
}
这里创建了一个非常基本的合同。 现在下一步就是将文本添加到您的合同中:
contract MyToken is owned {
/* 合同的其他内容 */
这意味着现在 MyToken 中的所有函数都可以访问父合同的变量“owner”和函数"modifier onlyOwner"。 合同也获得了转让所有权的功能。 我们也可以把它添加到构造函数中:
function MyToken(
uint256 initialSupply,
string tokenName,
uint8 decimalUnits,
string tokenSymbol,
address centralMinter
) {
if(centralMinter != 0 ) owner = centralMinter;
}
中央造币厂
假设你想要改变流通中的货币数量,比如我们希望对货币的价格进行一定的控制,并希望发行或销毁货币。
首先,我们需要添加一个变量来存储 totalSupply 并将其分配给我们的构造函数。
contract MyToken {
uint256 public totalSupply;
function MyToken(...) {
totalSupply = initialSupply;
...
}
...
}
现在让我们添加一个新的函数,允许所有者创建新的 Token:
function mintToken(address target, uint256 mintedAmount) onlyOwner {
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;
Transfer(0, owner, mintedAmount);
Transfer(owner, target, mintedAmount);
}
请注意函数名称末尾的修饰符 onlyOwner。 这意味着该函数将在编译时被重写,以继承我们之前定义的修饰符 onlyOwner 中的代码。 此函数的代码将插入到父合同的 modifier onlyOwner 的下划线处。 这样,我们就可以创建更多的货币。
资产冻结
实际使用中,我们可能需要对用户使用货币的权限进行监管。 为此,我们可以添加一个参数,使合同所有者能够冻结或解冻资产。
将此变量和函数添加到合同中的任何位置。
mapping (address => bool) public frozenAccount;
event FrozenFunds(address target, bool frozen);
function freezeAccount(address target, bool freeze) onlyOwner {
frozenAccount[target] = freeze;
FrozenFunds(target, freeze);
}
使用此代码,默认情况下所有帐户都处于解冻状态,但所有者可以通过调用该方法将其中的任何帐户设置为冻结状态。 但是,冻结没有实际效果,因为我们没有在 transfer 函数中添加任何代码。 现在做以下修改:
function transfer(address _to, uint256 _value) {
require(!frozenAccount[msg.sender]);
现在被冻结的账户都会保持其资金不变,并且无法移动。 默认所有帐户都处于非冻结状态,我们也可以设置一个手动批准的白名单。 只需将 frozenAccount 重命名为 approvedAccount 并将最后一行更改为:
require(approvedAccount[msg.sender]);
自动出售和购买
到目前为止,我们依靠效用和信任来评估我们的货币。 但是我们可以通过创建一个能够以市场价值自动销售和购买的基金来让货币的价值得到以太币(或其他代币)的支持。
首先,我们来设置买入和卖出的价格:
uint256 public sellPrice;
uint256 public buyPrice;
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}
对于不经常更改的价格,这是可以接受的,因为每次新的价格变化都需要您执行交易并花费一点以太币。
下一步是制定买入和卖出功能:
function buy() payable returns (uint amount){
amount = msg.value / buyPrice; // 计算金额
require(balanceOf[this] >= amount); // 检查是否有余量来出售
balanceOf[msg.sender] += amount; // 将该金额添加到买方的余额中
balanceOf[this] -= amount; // 从卖家的余额中减去金额
Transfer(this, msg.sender, amount); // 反映变更
return amount; // 结束功能并返回
}
function sell(uint amount) returns (uint revenue){
require(balanceOf[msg.sender] >= amount); // 检查发件人是否有足够余量来销售
balanceOf[this] += amount; // 将金额添加到所有者的余额中
balanceOf[msg.sender] -= amount; // 减去卖家余额中的金额
revenue = amount * sellPrice;
msg.sender.transfer(revenue); // 向卖家发送以太币:最后这样做是很重要的,以防止递归攻击
Transfer(msg.sender, this, amount); // 反映变更
return revenue; // 结束功能并返回
}
请注意,这不会创建新的货币,但会更改合同所拥有的余额。合同既可以拥有自己的货币,也可以拥有合约的所有者,同时可以设定价格,或者在某些情况下创建新的货币。出售和购买是这个合同调动资金的唯一方式。
注意买入和卖出“价格”不是以以太币设定,而是以系统的最小货币(相当于欧元和美元的分值,或者比特币中的 Satoshi)为单位设置的。一个以太是 1000000000000000000 wei。因此,当在以太坊中设置货币价格时,最后添加 18 个零。
在创建合同时,向其发放足够的以太币,以便它可以回购市场上的所有货币,否则您的合同将破产,您的用户将无法销售货币。
前面的例子当然描述了与单个中央买方和卖方签订的合同,更有趣的合同将允许任何人都可以设定不同的价格,或者直接从外部加价购买。
自动补充
在 Ethereum 上进行交易时,我们需要向该块矿工支付费用,以计算智能合约的结果。 虽然这可能会在未来发生变化,但目前费用只能在以太网中支付。 余额不足的账户会被锁定,直到业主可以支付必要的费用。 但在某些使用案例中,您可能不希望用户考虑以太坊,区块链或如何获得以太币,因此只要检测到平衡危险性低,您的硬币就会自动重新填充用户余额。
为了做到这一点,首先你需要创建一个变量来保存阈值量和一个函数来改变这个阈值。 如果你不知道任何价格,将它设置为 5 芬尼(0.005 Ether)。
uint public minBalanceForAccounts;
function setMinBalance(uint minimumBalanceInFinney) onlyOwner {
minBalanceForAccounts = minimumBalanceInFinney * 1 finney;
}
然后,将此行添加到 'transfer' 函数中,以便发件人退款:
/* Send coins */
function transfer(address _to, uint256 _value) {
...
if(msg.sender.balance < minBalanceForAccounts)
sell((minBalanceForAccounts - msg.sender.balance) / sellPrice);
}
您也可以改变它,以便发件人向收件人支付费用:
/* Send coins */
function transfer(address _to, uint256 _value) {
...
if(_to.balance<minBalanceForAccounts)
_to.send(sell((minBalanceForAccounts - _to.balance) / sellPrice));
}
工作量证明
有一些方法可以将我们的货币供应与数学公式结合。
最简单的方法之一是将它与 Ether 合并为一个“合并矿场”,这意味着任何在以太坊上发现区块的人都会从你的货币中获得奖励,因为任何人都会在该块上调用奖励功能。
您可以使用特殊关键字 coinbase 来引用找到该区块的矿工。
function giveBlockReward() {
balanceOf[block.coinbase] += 1;
}
也可以添加一个数学公式,这样任何有数学能力的人都可以获得奖励。 在下一个例子中,您必须计算当前挑战的立方根,并有权设置下一个挑战:
uint public currentChallenge = 1; // 你能计算出这个数字的立方根吗?
function rewardMathGeniuses(uint answerToCurrentReward, uint nextChallenge) {
require(answerToCurrentReward**3 == currentChallenge); // 如果答案不对,请不再继续
balanceOf[msg.sender] += 1; // 奖励玩家
currentChallenge = nextChallenge; // 设置下一个挑战
}
当然,虽然计算立方根对于某人来说很难做到,但用计算器很容易,所以这个游戏很容易被计算机破坏。另外,由于最后的赢家可以选择下一个挑战,他们可以选择他们知道的东西,因此对其他玩家来说不会是一个非常公平的比赛。有些任务对于人类来说很容易,但是对于计算机来说很难,但是通常很难在这些简单的脚本中编写代码。相反,更公平的系统应该是计算机非常难以做到的系统,但是对于计算机来说不是很难验证。一个很好的候选人将创建一个哈希挑战,挑战者必须从多个数字生成哈希,直到他们发现一个低于给定难度的哈希。
这个过程最早由 Adam Back 于 1997 年提出为 Hashcash,之后在 2008 年由 Satoshi Nakamoto 在比特币中实现,出现了工作量证明机制。以太坊在其安全模型中使用此类机制,但计划从工作量证明机制转化为一种权益证明和投注系统混合的机制,称为 Casper。
但是如果你喜欢哈希作为随机发行货币的形式,你仍然可以创建自己的以太坊版本的货币,并且使用工作量证明机制:
bytes32 public currentChallenge; // 挑战开始
uint public timeOfLastProof; // 变量以跟踪何时给予奖励
uint public difficulty = 10**32; // 难度开始时较低
function proofOfWork(uint nonce){
bytes8 n = bytes8(sha3(nonce, currentChallenge)); // 根据输入生成随机哈希值
require(n >= bytes8(difficulty)); // 检测与给定难度的关系
uint timeSinceLastProof = (now - timeOfLastProof); // 计算自上次奖励以来的时间
require(timeSinceLastProof >= 5 seconds); // 奖励不能太快
balanceOf[msg.sender] += timeSinceLastProof / 60 seconds; // 对获胜者的奖励会随着分钟而增加
difficulty = difficulty * 10 minutes / timeSinceLastProof + 1; // 调整难度
timeOfLastProof = now; // 重置计数器
currentChallenge = sha3(nonce, currentChallenge, block.blockhash(block.number - 1)); // 保存将用作下一个证明的散列
}
还要更改构造函数,添加此行,以便难度调整不会太疯狂:
timeOfLastProof = now;
当合同已经上线后,选择功能“proofOfWork”,将您最喜欢的数字添加到 nonce 字段并尝试执行它。 如果确认窗口发出红色警告,提示“无法执行数据”,那我们选择另一个数字,直至找到允许交易前进的数字:此过程是随机的。 如果您发现一个奖励,您将获得自上次奖励后每分钟增长 1 个货币的奖励,然后将挑战难度向上或向下调整,以平均每个奖励 10 分钟为目标。
这个试图找到奖励数字的过程就是所谓的挖掘:如果难度增加,找到一个幸运数字可能非常困难。
改进的货币
完整的货币代码
如果您添加了所有高级选项,则最终代码应如下所示:
pragma solidity ^0.4.16;
contract owned {
address public owner;
function owned() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) onlyOwner public {
owner = newOwner;
}
}
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }
contract TokenERC20 {
// 公共变量
string public name;
string public symbol;
uint8 public decimals = 18;
// 8 位小数是强烈建议的默认值
uint256 public totalSupply;
// 创建一个包含所有余额的数组
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;
// 在区块链上产生一个公共事件来通知用户
event Transfer(address indexed from, address indexed to, uint256 value);
// 通知客户有关已销毁的金额
event Burn(address indexed from, uint256 value);
/**
* 构造函数
*
*/
function TokenERC20(
uint256 initialSupply,
string tokenName,
string tokenSymbol
) public {
totalSupply = initialSupply * 10 ** uint256(decimals);
balanceOf[msg.sender] = totalSupply;
name = tokenName;
symbol = tokenSymbol;
}
/**
* 只能由本合同调用
*/
function _transfer(address _from, address _to, uint _value) internal {
require(_to != 0x0);
require(balanceOf[_from] >= _value);
require(balanceOf[_to] + _value > balanceOf[_to]);
uint previousBalances = balanceOf[_from] + balanceOf[_to];
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
Transfer(_from, _to, _value);
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
/**
* 发送货币
*
* 从您的帐户发送`_value`令牌给`_to`
*
* @param _to 收件人的地址
* @param _value 要发送的金额
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}
/**
* 从其他地址转移货币
*
* 代表 `_from` 发送 `_value` 给 `_to`
*
* @param _from 发件人的地址
* @param _to 收件人的地址
* @param _value 要发送的金额
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= allowance[_from][msg.sender]); // 检测转移金额的合法性
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
/**
* 授权其他地址使用你的货币
*
* 允许 `_spender` 使用你的货币
*
* @param _spender 授权地址
* @param _value 该地址可使用的最大额度
*/
function approve(address _spender, uint256 _value) public
returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
/**
* 授权其他地址使用你的货币,并且可进行通知操作
*
* 允许 `_spender` 使用的你货币, 并且可以对合同进行 ping 操作
*
* @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;
}
}
/**
* 销毁货币
*
* 不可逆地从系统中删除货币
*
* @param _value 可销毁的最大额度
*/
function burn(uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value); // 检查是否有足够的余额
balanceOf[msg.sender] -= _value; // 从发件人减去余额
totalSupply -= _value; // 更新整个合同中的额度
emit Burn(msg.sender, _value);
return true;
}
/**
* 销毁来自其他帐户的货币
*
* 代表`_from`不可逆地从系统中删除货币
*
* @param _from 发件人的地址
* @param _value 可销毁的最大额度
*/
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; // Subtract from the sender's allowance
totalSupply -= _value; // 更新整个合同中的额度
emit Burn(_from, _value);
return true;
}
}
/******************************************/
/* 更先进的货币 */
/******************************************/
contract MyAdvancedToken is owned, TokenERC20 {
uint256 public sellPrice;
uint256 public buyPrice;
mapping (address => bool) public frozenAccount;
/* 在区块链上产生一个公共事件来通知客户 */
event FrozenFunds(address target, bool frozen);
function MyAdvancedToken(
uint256 initialSupply,
string tokenName,
string tokenSymbol
) TokenERC20(initialSupply, tokenName, tokenSymbol) public {}
/* 内部资金转移,只能由本合同调用 */
function _transfer(address _from, address _to, uint _value) internal {
require (_to != 0x0);
require (balanceOf[_from] >= _value);
require (balanceOf[_to] + _value >= balanceOf[_to]);
require(!frozenAccount[_from]);
require(!frozenAccount[_to]);
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
Transfer(_from, _to, _value);
}
function mintToken(address target, uint256 mintedAmount) onlyOwner public {
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;
Transfer(0, this, mintedAmount);
Transfer(this, target, mintedAmount);
}
function freezeAccount(address target, bool freeze) onlyOwner public {
frozenAccount[target] = freeze;
FrozenFunds(target, freeze);
}
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);
}
function sell(uint256 amount) public {
require(this.balance >= amount * sellPrice);
_transfer(msg.sender, this, amount);
msg.sender.transfer(amount * sellPrice);
}
}
部署
在电子钱包应用中向下滚动,我们会看到部署的估计成本。 我们可以更改滑块设置较小的费用,但如果价格太低于平均市场价格,我们的交易可能需要更长时间才能完成。 点击部署并输入您的密码。 几秒钟后,我们将被重定向到仪表板,在最新的交易中,您将看到一行“创建合同”。 等待几秒钟让某人选择你的交易,然后你会看到一个缓慢的蓝色矩形,表示有多少其他节点已经看到你的交易并确认了它们。 您拥有的确认越多,您的代码已部署的可信度就越高。
点击管理页面的链接,您可以使用新创建的货币执行任何操作。
在左侧的"READ FROM CONTRACT"中,您可以获得所有可用于从合同中读取信息的选项和功能。 如果您的货币拥有所有者,它将在此处显示其地址。 复制该地址并将其粘贴到余额中,它将向您显示所有帐户的余额(您的余额也会自动显示在具有货币的其他帐户页面上)。
在右侧的“WRITE TO CONTRACT”下,我们可以更改区块链的功能。 这些操作将耗费 gas。 如果您创建了允许您创建新货币的合同,则应该有一个名为“Mint Token”的功能。 选择它。
选择创建新货币的地址,然后选择金额(如果您将小数点设置为 2,则在金额后添加 2 个零,以创建正确数量)。在执行从中选择设置为所有者的帐户,将 Ether 数量保留为零,然后按执行。
经过几次确认后,收款人余额将会更新以反映新的金额。但是您的收件人钱包可能不会自动显示:为了了解自定义货币,钱包必须手动将其添加到监视列表中。复制您的货币地址(在管理页面,按复制地址)并将其发送给您的收件人。如果他们还没有进入合约标签,请按 Watch Token,然后在那里添加地址。最终用户可以自定义显示的名称,符号和小数量,特别是如果他们有其他类似(或相同)名称的令牌。主图标不可更改,用户在发送和接收令牌时应该注意它们,以确保它们处理的是实际交易,而不是一些高仿的货币。
使用你的货币
部署货币后,它们将被添加到您可以看到的货币列表中,并且总余额将显示在您的帐户中。
为了发送货币,只需转到发送页面,然后选择一个包含货币的帐户。
账户中的货币将在 Ether 下面列出。
选择它们,然后输入要发送的货币数量。
如果您想添加其他人的货币,只需转到合同标签并点击监视货币。
例如,要将 Unicorn 货币添加到监视列表中,只需添加地址 0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7,其余信息将自动加载。
点击确定,您的货币将被添加。
注:独角兽货币是专为那些捐赠给由以太坊基金会控制的地址 0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359 的人创建的纪念品。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 智能合约简易部署教程
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论