400-638-8808
|
微信公众号
本文参考 ethereum token ,以发行数字货币为案例,介绍solidity语言和以太坊编程。教大家如何在以太坊上发行数字货币?
一、数字货币
在以太坊,Token可以用来表示各种可以交易的商品: 数字货币、积分、证书、IOU、游戏虚拟物品等。这些Token在实现上有一些共性,可以提炼成通用接口,这样以太坊钱包和其他的一些应用也可以兼容使用这些共性。
1.1 Token的一个极简实现
要实现一个标准的合约,会相当复杂。我们由浅入深,先实现一个非常简单的合约。
pragma solidity ^0.4.20;
contract MyToken {
/* Map对象用来记录账户的余额, key是账户地址,value是余额 */
mapping (address => uint256) public balanceOf;
/* 构造函数,执行初始化操作,给虚币发行人,即默认为当前代码的发布人,设置一定的初始虚币数量。 */
function MyToken(
uint256 initialSupply
) public {
balanceOf[msg.sender] = initialSupply; // 初始虚币数据全部授予发行者
}
/* 转账 */
function transfer(address _to, uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value); // 检查发送者的账户的余额是否足够
require(balanceOf[_to] + _value >= balanceOf[_to]); // 确认接受者的账户余额不会溢出。严谨!
balanceOf[msg.sender] -= _value; // 扣减发送者的余额
balanceOf[_to] += _value; // 增加接受者的余额
return true;
}
}
1.2 阅读代码
我们从最简单的合约代码开始。
contract MyToken {
/* This creates an array with all balances */
mapping (address => uint256) public balanceOf;
}
这里定义了一个余额Map,注意的要点:
address: 是指账户地址,16进制格式的。
uint256: 2^256 = 10^77 无符号整数,指账户余额,基本能满足各路壕的需求。
public: 所有账户余额都是公开的。 链上任何人都可以查询其他人的账户余额。
如果这个合约能够正常发布出来,它就可以正常的工作,但是没什么用。 只是一个能够查询余额的合约,其他什么事都做不了,包括获取一个数字货币。 所有账户余额都返回0。 所以我们需要在启动的时候,预置一些数字货币进来。 于是有这么几行代码:
function MyToken() public {
balanceOf[msg.sender] = 21000000;
}
这是一个构造函数,方法名和类名是一样的。 构造函数仅仅在这个合约被部署到网络上的时候运行一次。它将给msg.sender,即发布这个合约的人,设置余额。 21000000是一个任意设定的数字。 当然,也可以将这个数字作为参数来传进来。
function MyToken(uint256 initialSupply) public {
balanceOf[msg.sender] = initialSupply;
}
这样在部署的时候,比如使用Ethereum Wallet来部署,就他会让你选择这个初始参数的值。
现在至少已经有一个账户拥有初始数字货币余额了。接下来就需要添加一个方法,让这些数字货币能够交易。
/* Send coins */
function transfer(address _to, uint256 _value) public {
/* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
这是一个相当直观的方法:
_to: 收款人的账户地址;
_value: 送出的金额。
实现也很简单,就是增减收付款人的账户余额。 但这个实现是有问题的,如果出款人账户余额不足怎么办? 还有就是如果收款人是个土豪,再接收一笔巨款,就把账户打爆了,怎么办? 所以我们需要增加一个校验。 校验的代码并不复杂,问题是,如果校验发现参数确实有问题,那应该怎么办? 在以太坊上,终止程序执行有两种方法:
直接通过return来返回某个错误信息。 这会节省一点gas,但也有个头疼的问题, 已经执行的代码产生的任何变更,都会被保存到链上。
通过throw 来抛出异常, 这会将本次产生的所有变更都回滚,但也会导致发送者的gas都被消耗。 不过Wallet能够检测出来可能抛的异常,他会显示一个警告,避免以太币被浪费掉。
function transfer(address _to, uint256 _value) public {
/* Check if sender has balance and for overflows */
require(balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]);
/* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
这个合约本身还需要一些基本信息。 以后我们会通过Token注册表来处理这些合约,现在我们先直接上代码吧。
string public name;
string public symbol;
uint8 public decimals;
修正下构造函数,添加上述合约信息。
/* Initializes contract with initial supply tokens to the creator of the contract */
function MyToken(uint256 initialSupply, string tokenName, string tokenSymbol, uint8 decimalUnits) public {
balanceOf[msg.sender] = initialSupply; // Give the creator all initial tokens
name = tokenName; // Set the name for display purposes
symbol = tokenSymbol; // Set the symbol for display purposes
decimals = decimalUnits; // Amount of decimals for display purposes
}
最后,我们需要设置一些“事件”。 这是以太坊特有的,它可以让以太坊钱包这样的应用来跟踪合约上发生的活动。 所有事件的第一个字符必须是大写的,并且放在合约代码的起始部分。
event Transfer(address indexed from, address indexed to, uint256 value);
这里我们设置了一个转账的事件。然后在转账代码实现的最后,发出这个事件。
And then you just need to add these two lines inside the “transfer” function:
/* Notify anyone listening that this transfer took place */
emit Transfer(msg.sender, _to, _value);
这样,一个合约就开发完成了。
1.3 关于注释
和java一样,solidity也支持注释标签,比如@notice和@param。这些注释可以让以太坊钱包等应用可以将注释的内容显示给用户。 这里有关于注释的详细说明。
二、改进一下
现在这个加密货币可以上线了。可我们总要折腾,把这个货币做的更有意思一点。
2.1 完善基础功能
在基础功能上,我们还有很多事情要做,比如approve, sendFrom等功能。
考虑这么一个场景: 把数字货币卖给交易所,不可能就把数字货币发送到交易所给的账号就完事了, 他们还得知道是谁发的数字货币,也不会去订阅这些事件。 所以,我们需要增加如下支持:
增加批准approve功能,批准交易所从你的账户里面扣款。
或者更进一步,提供approveAndCall功能,在approve之后,通知交易所做后续的事情。
在这些待实现的功能里面, 转账是最基本的公用功能。 我们把它修改为内部函数,供其他方法实现时调用。
/* Internal transfer, can only be called by this contract */
function _transfer(address _from, address _to, uint _value) internal {
require (_to != 0x0); // Prevent transfer to 0x0 address. Use burn() instead
require (balanceOf[_from] >= _value); // Check if the sender has enough
require (balanceOf[_to] + _value >= balanceOf[_to]); // Check for overflows
require(!frozenAccount[_from]); // Check if sender is frozen
require(!frozenAccount[_to]); // Check if recipient is frozen
balanceOf[_from] -= _value; // Subtract from the sender
balanceOf[_to] += _value; // Add the same to the recipient
emit Transfer(_from, _to, _value);
}
这是一个非常危险的方法,一不小心钱就没了。 所以调用这个transfer方法需要非常谨慎,确保要经过许可才可以。
2.2 集中管理
所有的Dapps默认是非集中式管理。 这并非意味着不能做集中管理。 如果需要控制能够获取数字货币的人, 控制数字货币的使用,这就得需要集中式的管理。 我们可以用一个账户,或者一个合约来控制, 使用的策略,可以是民主的投票,也可以采用限制数字货币所有者的权力的方式。 针对这些场景,我们需要使用到合约的一个非常有用的属性: inheritance。 合约的继承属性使得它可以得到父合约的所有特性,而且不需要重新定义。
contract owned {
address public owner;
function owned() {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) onlyOwner {
owner = newOwner;
}
}
这里创建一个非常简单的,申明数字货币owner的合约。 然后就把自己发行的数字货币关联过来:
contract MyToken is owned {
/* the rest of the contract as usual */
这意味着在MyToken里面所有的方法都可以使用owner这个属性和可以继承的方法onlyOwner。 并且可以调用transferOwnership这个方法来修改所有者。 注意一下onlyOwner的实现,有一个“-”标识,子类需要覆盖实现这个方法时,子类的函数体将放在这里。 可以在MyToken的构造函数里面增加这个参数来设置owner:
function MyToken(
uint256 initialSupply,
string tokenName,
uint8 decimalUnits,
string tokenSymbol,
address centralMinter
) {
if(centralMinter != 0 ) owner = centralMinter;
}
2.3 印钞造币
在现实中,货币的总量不是恒定的,往往会随着经济的发展而不断地增加。 央行会不断注入流动性, 印钞厂开足马力印刷钞票… 就数字货币而言,我们也希望能够像央行那样进行管控,根据价格的增减而控制货币总量。 我们来看看这个怎么做。
首先,我们需要有一个字段来存储总供应链:totalSupply, 并且在构造函数中初始化:
contract MyToken {
uint256 public totalSupply;
function MyToken(...) {
totalSupply = initialSupply;
...
}
...
}
添加一个方法来印钞造币:
function mintToken(address target, uint256 mintedAmount) onlyOwner {
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;
emit Transfer(0, owner, mintedAmount);
emit Transfer(owner, target, mintedAmount);
}
注意,这个方法后面的onlyOwner, 表示它将覆盖实现父类中的 modifier onlyOwner 这个方法。 这是和其他语言不一样的地方,可以对覆盖的方法重新命名,甚至修改输入输出参数。 在编译时,这个方法的实现会替换到onlyOwner的”_“位置。
2.4 冻结资产
在实践中,有时候需要对数字货币做一些管控,冻结或者解冻账户就是常见的操作。
mapping (address => bool) public frozenAccount;
event FrozenFunds(address target, bool frozen);
function freezeAccount(address target, bool freeze) onlyOwner {
frozenAccount[target] = freeze;
emit FrozenFunds(target, freeze);
}
在这个实现中,我们假定所有账户刚开始都是未冻结状态。 之后,可以通过freezeAccount指令来冻结账户,或者解冻账户。 此外,我们还需要修改下transfer方法,转账之前,需要确认账户是否没有冻结。
function transfer(address _to, uint256 _value) {
require(!frozenAccount[msg.sender]);
这样,冻结后, 账户的余额是不变的,但无法执行转账操作。 这种方式是默认账户都是未冻结状态,可以转账。 还有一个场景是默认账户都是冻结的,只有白名单中的账户才可以转账。这种情况下,我们只要把frozenAccount方法替换成approvedAccount,并将上述实现替换成验证账户:
require(approvedAccount[msg.sender]);
2.5 买卖货币
到目前为止,我们这个新币种还是自己内部循环使用,体现不出价值来。 最简单的给数字货币定价的方法,就是和以太币挂钩起来。 提供一个方法,可以在市场上以市场价自动买卖,就和本外币交换一样。
首先,我们需要定一个买卖的牌价:
uint256 public sellPrice;
uint256 public buyPrice;
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}
对于币值波动不大的货币,这种做法是可以接受的。毕竟每一次调整币值都需要耗费一定的以太币。 如果要设置固定的币值, 可以考虑研究下 standard data feeds 然后实现下货币买卖的功能:
function buy() payable returns (uint amount){
amount = msg.value / buyPrice; // calculates the amount
_transfer(this, msg.sender, amount);
return amount;
}
function sell(uint amount) returns (uint revenue){
require(balanceOf[msg.sender] >= amount); // checks if the sender has enough to sell
balanceOf[this] += amount; // adds the amount to owners balance
balanceOf[msg.sender] -= amount; // subtracts the amount from sellers balance
revenue = amount * sellPrice;
msg.sender.transfer(revenue); // sends ether to the seller: its important to do this last to prevent recursion attacks
Transfer(msg.sender, this, amount); // executes an event reflecting on the change
return revenue; // ends function and returns
}
注意,这种方式并不会创建新的数字货币, 只是在原始账户和数字货币owner之间做了转账。 合约既可以持有自己的token,也可以持有以太币。 但不管是设置币值,还是投放新币,都无法接触真正的银行发行的货币,或者以太币。 合约能做的事情就是在自己的货币体系中转移货币。
注意, 币值并非以Ether为单位来设置,一般是使用wei单位,即以太币的最小单位来设置。 一个ether等于10^18个wei,所以如果货币是以ether来定价,那需要加18个0转成wei定价。 在创建合约的时候,注意要凑集足够的以太币作为准备金来应对可能的兑换,否则这个货币会由于无法兑换而没人买了。 这个例子描述了一个简单的中心化的交易中心来买卖数字货币。一个更有意思的市场应该是任何人都可以出不同的价格来买卖货币。
2.6 Gas自动支付
在以太坊体系中,每一次交易或者合约都需要支付一定的费用(Gas), 以后有可能会调整这个策略。 到目前为止,挖矿费用还只能通过以太币来支付,这样所有的数字货币用户就需要使用以太币来完成合约或者交易。 这样的话,用户体验就不好了。我们希望数字货币用户在交易时,也能够直接使用数字货币,不需要去考虑以太币、区块链以及如何获取以太币。 一个可行的方法是需要自动监测账户余额,如果余额不足,就禁止交易。
这样,需要首先创建一个阈值变量,并提供一个方法来修改这个变量。 这个变量初始值设置为5 finney (0.005 Ether),即约等于一笔交易用的Gas。
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);
}
或者直接把费用支付给sender。
/* Send coins */
function transfer(address _to, uint256 _value) {
...
if(_to.balance<minBalanceForAccounts)
_to.send(sell((minBalanceForAccounts - _to.balance) / sellPrice));
}
这就确保了这些账户不会由于余额不足而导致交易失败。
2.7 Proof of Work
在工作量证明体系中(POW, Proof of Work), 一个简单的方法是实现和以太币一起“合并挖矿”,即矿工不仅获得以太币简历,也会同时得到新数字货币的奖励。 参考这里special keyword coinbase 来了解如何获取某个入链区块的矿工账户。
function giveBlockReward() {
balanceOf[block.coinbase] += 1;
}
当然,也可以尝试去设置一些数据难题,任何人解决这个问题就可以获取数字货币奖励。 在下一个例子中,你需要计算一个挑战数据的立方根, 计算成功后,获取奖励,并设置下一个挑战数据。
uint public currentChallenge = 1; // 需要计算出这个数据的立方根?
function rewardMathGeniuses(uint answerToCurrentReward, uint nextChallenge) {
require(answerToCurrentReward**3 == currentChallenge); // If answer is wrong do not continue
balanceOf[msg.sender] += 1; // Reward the player
currentChallenge = nextChallenge; // Set the next challenge
}
当然,计算立方根,对人来说可能比较难,但对计算机来说是很容易的事情。 由于最后一个赢家可以选择下一个挑战数据,他们可以选择一个对自己有利的数据,这样对其他矿工来说是不公平的。 这种POW的计算公式,最好是选择对计算机来说,有计算难度,但是很容易验证计算结果的公式。 比特币和以太坊使用的计算哈希值的方式可以说是目前最好的数据难题了。 如果我们要在新的币种中使用哈希计算作为数据难题, 可以参考如下代码:
bytes32 public currentChallenge; // The coin starts with a challenge
uint public timeOfLastProof; // Variable to keep track of when rewards were given
uint public difficulty = 10**32; // Difficulty starts reasonably low
function proofOfWork(uint nonce){
bytes8 n = bytes8(sha3(nonce, currentChallenge)); // Generate a random hash based on input
require(n >= bytes8(difficulty)); // Check if its under the difficulty
uint timeSinceLastProof = (now - timeOfLastProof); // Calculate time since last reward was given
require(timeSinceLastProof >= 5 seconds); // Rewards cannot be given too quickly
balanceOf[msg.sender] += timeSinceLastProof / 60 seconds; // The reward to the winner grows by the minute
difficulty = difficulty * 10 minutes / timeSinceLastProof + 1; // Adjusts the difficulty
timeOfLastProof = now; // Reset the counter
currentChallenge = sha3(nonce, currentChallenge, block.blockhash(block.number - 1)); // Save a hash that will be used as the next proof
}
需要修改下构造函数, 添加初始值:
timeOfLastProof = now;
一旦这个合约开始上线运行, 需要选择一个工作量证明公式, 并设置合适的 nonce 值。 如果收到 “Data cant be execute” , 则说明 nounce设置不合理,重新调整下。 直到账户能够每分钟收到1个数字货币为止,之后计算难度会每10分钟自动调整一次。 这就是挖矿的过程。
2.8 改进发行机制
最终版本的代码如下:
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) external; }
/******************************************/
/* ERC20标准的虚币 */
/******************************************/
contract TokenERC20 {
string public name; // public属性,数字货币名称
string public symbol;// 数字货币的符号
uint8 public decimals = 18; // 18 decimals is the strongly suggested default, avoid changing it
uint256 public totalSupply; //货币总供应量
mapping (address => uint256) public balanceOf; // 账户-余额 映射关系
mapping (address => mapping (address => uint256)) public allowance; // 二维数据[a][b]=amount,指账户a授权给b可动用的金额为amount。
/**
*
* 转账的区块链事件。
*
**/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
*
* 批准转账申请的区块链事件
*
**/
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
/**
*
* 发行更多货币的区块链事件
*
**/
event Burn(address indexed from, uint256 value);
/**
* 构造函数,用来执行初始化操作。
*
* 初始化合约,并给合约的创建者提供一定数额的数字货币
*/
function TokenERC20(
uint256 initialSupply,
string tokenName,
string tokenSymbol
) public {
totalSupply = initialSupply * 10 ** uint256(decimals); // Update total supply with the decimal amount
balanceOf[msg.sender] = totalSupply; // 给创建者所有初始货币;
name = tokenName; // 设置本数字货币的显示名称。
symbol = tokenSymbol; // 设置本数据货币的显示符号。
}
/**
* Internal transfer, only can be called by this contract
*/
function _transfer(address _from, address _to, uint _value) internal {
// 避免转账到0x0的空地址。
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;
// 发送转账事件
emit Transfer(_from, _to, _value);
// 确保转账前后两个账户总和是一样的。
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
/**
* 转账
*
* 从当前账户转出给定金额到收款人账户上
*
* @param _to 收款人账户地址
* @param _value 金额,指当前货币单位
*/
function transfer(address _to, uint256 _value) public returns (bool success) {
_transfer(msg.sender, _to, _value);
return true;
}
/**
* 转账
*
* 从出款人账户转出给定金额到收款人账户
*
* @param _from 出款人账户地址
* @param _to 收款人账户地址
* @param _value 转账金额
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= allowance[_from][msg.sender]); // 检查_from账户中可以由发起人动用的金额。
allowance[_from][msg.sender] -= _value; // 扣减授权金额
_transfer(_from, _to, _value); //执行转账
return true;
}
/**
* 授权某账户允许它从出款人账户上扣款。
*
*
* @param _spender 允许扣款的账户
* @param _value 允许扣除的最高金额。
*/
function approve(address _spender, uint256 _value) public
returns (bool success) {
allowance[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
/**
* 批准并执行从收款账户发起的、从本账户扣款并转账的申请。
* 允许 收款账户可以得到比预定金额更多以的币值, 并通知对方。
*
* @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;
}
}
/**
* 销毁货币
*
* 从当前账户中销毁 `_value` 额度的货币,本操作不可逆转。
*
* @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中销毁 `_value` 额度的货币,本操作不可逆转。
*
* @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; // 扣减授权余额。
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); // 禁止向0x0账户转账。
require (balanceOf[_from] >= _value); // 检查出款账户余额是否足够
require (balanceOf[_to] + _value >= balanceOf[_to]); // 检查收款账户余额避免溢出。
require(!frozenAccount[_from]); // 检查出款账户是否被冻结
require(!frozenAccount[_to]); // 检查收款账户是否被冻结
balanceOf[_from] -= _value; // 扣减出款账户余额
balanceOf[_to] += _value; // 增加收款账户余额
emit Transfer(_from, _to, _value);
}
/// @notice 给目标账户发送 挖矿津贴。
/// @param target 目标账户
/// @param mintedAmount 挖矿津贴
function mintToken(address target, uint256 mintedAmount) onlyOwner public {
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;
emit Transfer(0, this, mintedAmount);
emit Transfer(this, target, mintedAmount);
}
/// @notice 冻结/解冻账户,冻结后,账户就无法收发虚币了。
/// @param target 待冻结的账户
/// @param freeze 冻结/解冻
function freezeAccount(address target, bool freeze) onlyOwner public {
frozenAccount[target] = freeze;
emit FrozenFunds(target, freeze);
}
/// @notice 设置虚币对以太币的买卖汇率
/// @param newSellPrice 卖出价
/// @param newBuyPrice 买入价
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner public {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}
/// @notice 买入。注意这里使用的是payable接口,调用时候,需要输入买入金额(以太币wei为单位),买入成功后,当前账户增加了amount个虚币,所使用的以太币存入到当前合约所在的地址中(不是owner账户)。
function buy() payable public {
uint amount = msg.value / buyPrice; // calculates the amount
_transfer(owner, msg.sender, amount); // makes the transfers
}
/// @notice 卖出。
/// @param amount 卖出的数量
function sell(uint256 amount) public {
address myAddress = this;
require(myAddress.balance >= amount * sellPrice); // 检查是否有足够的卖出余额
_transfer(msg.sender, owner, amount); // 转账
msg.sender.transfer(amount * sellPrice); // 必须在最后一步执行以太币的转账操作,防止recursion attacks。注意,这里是从当前合约所在的地址上转出资金给卖出者。
}
天下数据IDC提供香港服务器、美国服务器等全球海外服务器租用托管,是区域链、数字货币、加密货币、直销、流媒体、外贸、游戏等服务器解决方案首选品牌。天下数据已为多家企业提供区块链服务器租用托管解决方案,为他们的区块链安全提供支持!具体详询在线客服!
天下数据手机站 关于天下数据 联系我们 诚聘英才 付款方式 帮助中心 网站备案 解决方案 域名注册 网站地图
天下数据18年专注海外香港服务器、美国服务器、海外云主机、海外vps主机租用托管以及服务器解决方案-做天下最好的IDC服务商
《中华人民共和国增值电信业务经营许可证》 ISP证:粤ICP备07026347号
朗信天下发展有限公司(控股)深圳市朗玥科技有限公司(运营)联合版权
深圳总部:中国.深圳市南山区深圳国际创新谷6栋B座10层 香港总部:香港上環蘇杭街49-51號建安商業大廈7樓
7×24小时服务热线:4006388808香港服务电话:+852 67031102
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品