以太坊dapp
In part 1 of this tutorial series on building DApps with Ethereum, we bootstrapped two versions of a local blockchain for development: a Ganache version, and a full private PoA version.
在本教程系列中,用以太坊构建DApps的第1部分中 ,我们介绍了用于开发的本地区块链的两个版本:Ganache版本和完整的私有PoA版本。
In this part, we’ll dive right into it and build and deploy our TNS token — the token users will use to vote on proposals in the Story DAO.
在这一部分中,我们将深入研究它,并构建和部署我们的TNS令牌-令牌用户将用于对Story DAO中的提案进行投票。
Have a Ganache version up and running, as per the previous part. Alternatively, have any local version of a blockchain running if you’re not following along from the first part, but make sure you can connect to it with tools we’ll need.
按照上一部分,启动并运行Ganache版本。 另外,如果您不遵循第一部分的内容,请运行任何本地版本的区块链,但请确保可以使用我们需要的工具连接到该区块链。
We’ll assume you have a working private blockchain and the ability to type commands into its console and the operating system’s terminal via the Terminal app or, on Windows, an app like Git Bash, Console, CMD Prompt, Powershell, etc.
我们假设您有一个正常工作的私有区块链,并且能够通过终端应用程序或在Windows上通过Git Bash,控制台,CMD Prompt,Powershell等应用程序在其控制台和操作系统终端中键入命令。
To develop our application, we can use one of several frameworks and starter kits at our disposal: Dapp, eth-utils, Populus, Embark … and so on. But we’ll go with the current king of the ecosystem, Truffle.
要开发我们的应用程序,我们可以使用一些框架和入门工具包之一: Dapp , eth-utils , Populus , Embark …等等。 但是,我们将继续使用当前的生态系统之王Truffle 。
Install it with the following:
通过以下方式安装:
npm install -g truffle
This will make the truffle
command available everywhere. Now we can start the project with truffle init
.
这将使truffle
命令随处可用。 现在我们可以使用truffle init
启动项目。
Let’s get right into it and build our token. It’ll be a somewhat standard cookie-cutter ERC20 token with a twist. (You’ll see which twist lower in this post.) First, we’ll pull in some dependencies. The OpenZeppelin libraries are battle-tested high quality solidity contracts usable for extending and building contracts from.
让我们进入它并构建我们的令牌。 这将是一个有点扭曲的标准饼干切割机ERC20令牌。 (您将在这篇文章中看到哪一个更低的角度。)首先,我们将引入一些依赖性。 OpenZeppelin库是经过实践检验的高质量实体合同,可用于扩展和构建合同。
npm install openzeppelin-solidity
Next, let’s create a new token file:
接下来,让我们创建一个新的令牌文件:
truffle create contract TNSToken
The default template that truffle generates here is a little out of date, so let’s get it updated:
松露在此处生成的默认模板有些过时了,因此让我们对其进行更新:
pragma solidity ^0.4.24;
contract TNStoken {
constructor() public {
}
}
Up until now, the constructor of the token contract was supposed to be called the same as the contract itself, but for clarity it was changed to constructor
. It should also always have a modifier telling the compiler who is allowed to deploy and interact with this contract (public
meaning everyone).
到目前为止,令牌合约的构造函数应该被称为与合约本身相同,但为清楚起见,将其更改为constructor
。 它还应始终有一个修饰符,告诉编译器允许谁部署此合同并与之交互( public
意义是每个人)。
The only Zeppelin contract we’ll be using in this case is their SafeMath
contract. In Solidity, we import contracts with the import
keyword, while the compiler will generally not require a full path, only a relative one, like so:
在这种情况下,我们将使用的唯一Zeppelin合同是他们的SafeMath
合同。 在Solidity中,我们使用import
关键字导入合同,而编译器通常不需要完整路径,而仅需要相对路径,如下所示:
pragma solidity ^0.4.24;
import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
contract TNStoken {
using SafeMath for uint256;
constructor() public {
}
}
So, what is SafeMath
? Long ago, there was an issue of 184 billion bitcoins being created because of a math problem in code. To prevent issues even remotely similar to these (not that this one in particular is possible in Ethereum), the SafeMath library exists. When two numbers are of the MAX_INT
size (i.e. the maximum possible number in an operating system), summing them up would make the value “wrap around” to zero, like a car’s odometer being reset to 0 after reaching 999999 kilometers. So the SafeMath library has functions like these:
那么,什么是SafeMath
? 很久以前,由于代码中的数学问题, 创造了1,840亿个比特币的发行。 为了防止甚至与这些远程相似的问题(不是以太坊特别可能发生此问题),存在SafeMath库。 当两个数字具有MAX_INT
大小(即,操作系统中的最大可能数字)时,将它们相加将使“回绕”值变为零,就像汽车的里程表在达到999999公里后重置为0。 因此,SafeMath库具有以下功能:
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
assert(c >= a);
return c;
}
This function prevents this issue: it checks whether the sum of two numbers is still bigger than each of the two operands.
此函数可防止出现此问题:它检查两个数字的和是否仍大于两个操作数的每个。
While it’s not too easy to make such silly mistakes when writing Solidity contracts, it’s still better to be safe than sorry.
尽管在编写Solidity合同时犯下如此愚蠢的错误并非易事,但还是要比后悔更安全。
By using SafeMath for uint256
, we replace the standard uint256 numbers in Solidity (256bit unsigned — a.k.a. positive-only — whole numbers) with these “safe” versions. Instead of summing numbers like this: sum = someBigNumber + someBiggerNumber
, we’ll be summing them like this: sum = someBigNumber.add(someBiggerNumber)
, thereby being safe in our calculations.
通过using SafeMath for uint256
,我们用这些“安全”版本替换了Solidity中的标准uint256数字(256位无符号(也称为正数)整数)。 而不是像这样求和: sum = someBigNumber + someBiggerNumber
,我们将像这样对它们求和: sum = someBigNumber.add(someBiggerNumber)
,因此在我们的计算中是安全的。
With our math made safe, we can create our token.
通过安全地进行数学运算,我们可以创建令牌。
ERC20 is a standard with a well-defined interface, so for reference, let’s add it into the contract. Read about the token standards here.
ERC20是具有明确定义的接口的标准,因此作为参考,让我们将其添加到合同中。 在此处阅读有关令牌标准的信息 。
So the functions that an ERC20 token should have are:
因此,ERC20令牌应具有的功能是:
pragma solidity ^0.4.24;
import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
contract ERC20 {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
function allowance(address owner, address spender) public view returns (uint256);
function transferFrom(address from, address to, uint256 value) public returns (bool);
function approve(address spender, uint256 value) public returns (bool);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract TNStoken {
using SafeMath for uint256;
constructor() public {
}
}
This might seem complex, but it’s actually very simple. This is a “directory” of functions our token needs to have, and we’ll build them one by one, explaining what each of them means. Consider the above an interface for our token. We’ll see how and why this is useful when we create the Story DAO application.
这看起来很复杂,但实际上非常简单。 这是令牌需要具有的功能的“目录”,我们将逐一构建它们,解释它们各自的含义。 考虑上面的令牌接口 。 我们将看到在创建Story DAO应用程序时该功能有用以及为什么有用。
Let’s start. A token is actually just a “spreadsheet” in the Ethereum blockchain, like this:
开始吧。 令牌实际上只是以太坊区块链中的“电子表格”,如下所示:
| Name | Amount |
|:--|:--|
| Bruno | 4000 |
| Joe | 5000 |
| Anne | 0 |
| Mike | 300 |
So let’s create a mapping
, which is essentially exactly like a spreadsheet in the contract:
因此,让我们创建一个mapping
,该mapping
本质上就像合同中的电子表格一样:
mapping(address => uint256) balances;
According to the interface above, this needs to be accompanied by a balanceOf
function, which can read this table:
根据上面的接口,这需要伴随有balanceOf
函数,该函数可以读取此表:
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
}
The function balanceOf
accepts one argument: _owner
is public (can be used by anyone), is a view
function (meaning it’s free to use — does not require a transaction), and returns a uint256
number, the balance of the owner of the address sent in. Everyone’s balance of tokens is publicly readable.
该函数balanceOf
接受一个参数: _owner
是公共的(任何人都可以使用),是一个view
函数(意味着它可以免费使用-不需要交易),并返回一个uint256
数字,即地址所有者的余额每个人的令牌余额都是公开可读的。
Knowing the total supply of the token is important for its users and for coin tracking applications, so let’s define a contract property (variable) to track this and another free function through which to read this:
知道令牌的总供应量对其用户和硬币跟踪应用程序都很重要,因此让我们定义一个合同属性(变量)来跟踪此令牌,并定义另一个免费函数以读取令牌:
uint256 totalSupply_;
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
Next, let’s make sure the owner of a number of tokens can transfer them to someone else. We’ll also want to know when a transfer has occurred, so we’ll define a Transfer event as well. A Transfer event lets us listen for transfers in the blockchain via JavaScript, so that our applications can be aware of when these events are emitted instead of constantly manually checking if a transfer happened. Events are declared alongside variables in a contract, and emitted with the emit
keyword. Let’s add the following into our contract now:
接下来,让我们确保多个令牌的所有者可以将其转让给其他人。 我们还将想知道何时发生转移,因此我们还将定义一个Transfer事件。 转移事件使我们能够通过JavaScript侦听区块链中的转移,因此我们的应用程序可以知道何时发出这些事件,而不必不断手动检查转移是否发生。 事件是在合同中与变量一起声明的,并通过emit
关键字emit
。 现在,将以下内容添加到我们的合同中:
event Transfer(address indexed from, address indexed to, uint256 value);
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
return true;
}
This function accepts two arguments: _to
, which is the destination address which will receive the tokens, and value
, which is the number of tokens. It’s important to remember that value
is the number of the smallest units of the token, not the whole units. So if a token is declared to have 10 decimals, then in order to send one token you would send 10000000000. This level of granularity lets us transact minuscule amounts.
此函数接受两个参数: _to
是接收令牌的目标地址,而value
是令牌的数目。 重要的是要记住, value
是令牌的最小单位数,而不是整个单位。 因此,如果声明一个令牌有10个小数位,则要发送一个令牌,您将发送10000000000。这种粒度级别使我们可以处理少量的事务。
The function is public, meaning anyone can use it — both other contracts and users — and it returns true
if the operation was successful.
该功能是公共的,这意味着任何人都可以使用它(其他合同和用户),并且如果操作成功,它将返回true
。
The function then does some sanity checks. First, it checks that the destination address isn’t a null address. In other words, tokens must not be sent into oblivion. Next, it checks if the sender is even allowed to send this many tokens by comparing their balance (balances[msg.sender]
) with the value passed in for sending. If any of these checks fail, the function will reject the transaction and fail. It will refund any tokens sent, but the gas spent on executing the function up until that point will have been spent.
然后,该功能进行一些完整性检查。 首先,它检查目标地址不是空地址。 换句话说,令牌不能被遗忘。 接下来,它通过比较令牌的余额( balances[msg.sender]
)与传递的值来检查是否甚至允许发送者发送这么多令牌。 如果这些检查中的任何一个失败,该函数将拒绝该事务并失败。 它将退还已发送的所有令牌,但是直到该点为止在执行该功能上所花费的气体都会被消耗掉。
The next two lines subtract the amount of tokens from the sender’s balance and add that amount to the destination’s balance. Then the event is emitted with emit
, and some values are passed in: the sender, the recipient, and the amount. Any client subscribed to Transfer events on this contract will now be notified of this event.
接下来的两行从发送者的余额中减去令牌的数量,并将该数量添加到目的地的余额中。 然后,该事件将通过emit
,并传递一些值:发送者,接收者和金额。 现在,将在此合同上通知任何订阅了此合同上的转移事件的客户。
Okay, now our token holders can send tokens around. Believe it or not, that’s all you need for a basic token. But we’re going beyond that and adding some more functionality.
好的,现在我们的令牌持有者可以发送令牌了。 信不信由你,这就是您需要基本令牌的全部。 但是,我们不仅仅局限于此,还添加了更多功能。
Sometimes a third party may be given permission to withdraw from another account’s balance. This is useful for games which might facilitate in-game purchases, decentralized exchanges, and more. We do this by building a multi-dimensional mapping
called allowance
, which stores all such permissions. Let’s add the following:
有时,第三方可能会被允许从另一个帐户的余额中提款。 这对于可能有助于游戏内购买,分散交易等的游戏很有用。 为此,我们构建了一个称为allowance
的多维mapping
,该mapping
存储了所有此类权限。 让我们添加以下内容:
mapping (address => mapping (address => uint256)) internal allowed;
event Approval(address indexed owner, address indexed spender, uint256 value);
The event is there so that apps listening can know when someone has pre-approved spending of their balance by someone else — a useful feature, and part of the standard.
该事件在那里发生,以便监听的应用程序可以知道某人何时已预先批准了某人的余额支出,这是一项有用的功能,并且是标准的一部分。
The mapping combines addresses with another mapping, which combines addresses with numbers. It basically forms a spreadsheet like this one:
该映射将地址与另一个映射相结合,后者将地址与数字相结合。 它基本上形成了一个像这样的电子表格:
So Bob’s balance may be spent by Mary up to 1000 tokens and Billy up to 50 tokens. Mary’s balance can be spent by Bob up to 750 tokens. Billy’s balance can be spent up to 300 tokens by Mary and 1500 by Joe.
因此Bob的余额可能由Mary最多消费1000个令牌,而Billy最多花费50个令牌。 Bob最多可以用Mary的余额消费750个令牌。 Billy的余额最多可以由Mary消费300个代币,由Joe消费1500个。
Given that this mapping is internal
, it can only be used by functions in this contract and contracts that use this contract as a base.
鉴于此映射是internal
映射,因此只能由该合同中的功能以及以该合同为基础的合同使用。
To approve someone else spending from your account, you call the approve
function with the address of the person allowed to spend your tokens, the amount they are allowed to spend, and in the function you emit an Approval
event:
要批准其他人从您的帐户中支出,请调用approve
功能,其中包含允许花费您的代币的人的地址,允许他们花费的金额,并在该函数中发出Approval
事件:
function approve(address _spender, uint256 _value) public returns (bool) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
We also need a way to read how much a user can spend from another user’s account:
我们还需要一种方法来读取一个用户可以从另一个用户的帐户中支出多少:
function allowance(address _owner, address _spender) public view returns (uint256) {
return allowed[_owner][_spender];
}
So it’s another read only
function (view
) which means it’s free to execute. It simply reads the remaining withdrawable balance.
因此,这是另一个read only
函数( view
),这意味着它可以自由执行。 它仅读取剩余的可提取余额。
So how does one send for someone else? With a new transferFrom
function:
那么,一个人如何寄给别人呢? 使用新的transferFrom
函数:
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
emit Transfer(_from, _to, _value);
return true;
}
As before, there are sanity checks: the destination address must not be a null-address, so no sending tokens to a black hole. The value being transferred also needs to be less than or equal to not only the current balance of the account the value is being transferred from, but also the balance the message sender (the address initiating this transaction) is still allowed to spend for them.
像以前一样,进行完整性检查:目标地址不能为空地址,因此不向黑洞发送令牌。 转移的价值还不仅需要小于或等于从其转移账户的当前余额,而且还必须允许消息发送者(发起此交易的地址)为他们花费的余额。
Next, the balance is updated and the allowed balance is brought into sync with that before emitting the event about the Transfer.
接下来,更新余额,并在发出有关转让的事件之前,使允许的余额与余额保持同步。
Note: it’s possible for the token holder to spend tokens without the allowed
mapping being updated. This can happen if the token holder sends tokens around manually using transfer
. In that case, it’s possible that the holder will have fewer tokens than the allowance dictates a third party can spend for them.
注意:令牌持有者有可能在不更新allowed
映射的情况下花费令牌。 如果令牌持有者使用transfer
手动发送令牌,则会发生这种情况。 在这种情况下,持有人的代币数量可能会少于配额规定的第三方可以为之花费的数量。
With approvals and allowances in place, we can also create functions that let a token holder increase or decrease someone’s allowance, rather than overwrite the value entirely. Try doing this as an exercise, then refer to source code below for the solution.
有了批准和津贴,我们还可以创建功能,让代币持有人增加或减少某人的津贴,而不是完全覆盖价值。 尝试做为练习,然后参考下面的源代码以获取解决方案。
function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
allowed[msg.sender][_spender] = (
allowed[msg.sender][_spender].add(_addedValue));
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
uint oldValue = allowed[msg.sender][_spender];
if (_subtractedValue > oldValue) {
allowed[msg.sender][_spender] = 0;
} else {
allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
}
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
So far, we’ve merely built a token “contract”. But what is this token? What’s it called? How many decimals does it have? How do we use it?
到目前为止,我们仅建立了一个令牌“合同”。 但是这个令牌是什么? 它叫什么? 它有多少个小数? 我们如何使用它?
At the very beginning, we defined a constructor
function. Now, let’s finish its body and add the attributes name
, symbol
and decimals
:
在开始时,我们定义了一个constructor
函数。 现在,让我们完成其主体,并添加属性name
, symbol
和decimals
:
string public name;
string public symbol;
uint8 public decimals;
constructor(string _name, string _symbol, uint8 _decimals, uint256 _totalSupply) public {
name = _name;
symbol = _symbol;
decimals = _decimals;
totalSupply_ = _totalSupply;
}
Doing it like this lets us reuse the contract later for other tokens of the same type. But seeing as we know exactly what we’re building, let’s hard-code those values:
这样做可以让我们稍后将合同重用于其他相同类型的令牌。 但是,既然我们确切知道要构建的内容,就可以对这些值进行硬编码:
string public name;
string public symbol;
uint8 public decimals;
constructor() public {
name = "The Neverending Story Token;
symbol = "TNS";
decimals = 18;
totalSupply_ = 100 * 10**6 * 10**18;
}
These details are read by the various Ethereum tools and platforms when displaying the token’s information. The constructor function is automatically called when a contract is deployed to an Ethereum network, so these values will be auto-configured at deploy-time.
在显示令牌信息时,各种以太坊工具和平台会读取这些详细信息。 将合同部署到以太坊网络时会自动调用构造函数,因此这些值将在部署时自动配置。
A word about totalSupply_ = 100 * 10**6 * 10**18;
: this is just a way to make it easier for humans to read the number. Since all transfers in Ethereum are done with the smallest unit of ether or token (including decimals) the smallest unit is 18 decimals deep into the decimal point. This is why a single TNS token is 1 * 10**18*
. Furthermore, we want 100 million of them, so that’s 100 * 10**6
or 100*10*10*10*10*10*10
. This makes the number much more readable than 100000000000000000000000000
.
关于totalSupply_ = 100 * 10**6 * 10**18;
:这只是一种使人类更容易阅读数字的方式。 由于以太坊中的所有转移都是以最小的以太或令牌单位(包括小数)完成的,因此最小的单位是小数点后18位小数。 这就是为什么单个TNS令牌为1 * 10**18*
。 此外,我们需要1亿个,即100 * 10**6
或100*10*10*10*10*10*10
。 这使得该数字比100000000000000000000000000
更具可读性。
Alternatively, we can just extend the Zeppelin contract, modify some attributes, and we have our token. That’s what most people do, but when dealing with software that potentially handles millions of other people’s money, I personally tend to want to know exactly what I put in the code, so blind code reuse is minimal in my personal case.
另外,我们可以扩展Zeppelin合同,修改某些属性,然后获得令牌。 多数人就是这样做的,但是在处理可能处理数百万他人金钱的软件时,我个人往往想确切地知道我在代码中放了什么,因此在我个人情况下,盲目代码重用是最少的。
pragma solidity ^0.4.24;
import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../node_modules/openzeppelin-solidity/contracts/token/ERC827/ERC20Token.sol";
contract TNStoken is ERC20Token {
using SafeMath for uint256;
string public name;
string public symbol;
uint8 public decimals;
uint256 totalSupply_;
constructor() public {
name = "The Neverending Story Token";
symbol = "TNS";
decimals = 18;
totalSupply_ = 100 * 10**6 * 10**18;
}
}
In this case, we use the is
notation to declare that our token is ERC20Token
. This makes our token extend the ERC20 contract, which in turn extends StandardToken, and so on …
在这种情况下,我们使用is
表示法声明我们的令牌is ERC20Token
。 这使我们的令牌扩展了ERC20合同,从而又扩展了StandardToken,依此类推……
Either way, our token is ready now. But who gets how many tokens to start with and how?
无论哪种方式,我们的令牌现在都准备就绪。 但是谁能获得多少代币以及如何开始呢?
Let’s give the maker of the contract all the tokens. Otherwise, the tokens won’t be sendable to anyone. Update the constructor
by adding the following line to the end of it:
让我们给合同制定者所有代币。 否则,令牌将无法发送给任何人。 通过在constructor
的末尾添加以下行来更新constructor
:
balances[msg.sender] = totalSupply_;
Seeing as we intend to use the tokens as voting power (i.e. how many tokens you lock during voting represents how powerful your vote is), we need a way to prevent users from sending them around after having voted, else our DAO would be susceptible to a Sybil attack — a single person with a million tokens could register 100 addresses and achieve the voting power of 100 million tokens by just sending them around to different addresses and re-voting with a new address. Thus, we’ll prevent transferring exactly as many tokens as a person has devoted to a vote, cumulatively for each vote on each proposal. That’s the twist we mentioned at the start of this post. Let’s add the following event into our contract:
鉴于我们打算将令牌用作投票权(即投票期间锁定的令牌数量代表您的投票有多强大),我们需要一种方法来防止用户投票后将其发送出去,否则我们的DAO容易受到攻击一次Sybil攻击 -一个拥有100万个令牌的人可以注册100个地址,只需将它们发送到不同的地址并用新地址重新投票,即可获得1亿个令牌的投票权。 因此,对于每个提案的每次投票,我们都将避免完全转移与某人投入投票一样多的令牌。 这就是我们在本文开头提到的内容。 让我们在合同中添加以下事件:
event Locked(address indexed owner, uint256 indexed amount);
Then let’s add the locking methods:
然后让我们添加锁定方法:
function increaseLockedAmount(address _owner, uint256 _amount) onlyOwner public returns (uint256) {
uint256 lockingAmount = locked[_owner].add(_amount);
require(balanceOf(_owner) >= lockingAmount, "Locking amount must not exceed balance");
locked[_owner] = lockingAmount;
emit Locked(_owner, lockingAmount);
return lockingAmount;
}
function decreaseLockedAmount(address _owner, uint256 _amount) onlyOwner public returns (uint256) {
uint256 amt = _amount;
require(locked[_owner] > 0, "Cannot go negative. Already at 0 locked tokens.");
if (amt > locked[_owner]) {
amt = locked[_owner];
}
uint256 lockingAmount = locked[_owner].sub(amt);
locked[_owner] = lockingAmount;
emit Locked(_owner, lockingAmount);
return lockingAmount;
}
Each method makes sure that no illegal amount can be locked or unlocked, and then emits an event after altering the locked amount for a given address. Each function also returns the new amount that’s now locked for this user. This still doesn’t prevent sending, though. Let’s modify transfer
and transferFrom
:
每种方法都确保不会锁定或解锁任何非法金额,然后在更改给定地址的锁定金额后发出事件。 每个函数还返回现在已为此用户锁定的新金额。 不过,这仍然不会阻止发送。 让我们修改transfer
和transferFrom
:
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender] - locked[msg.sender]); // <-- THIS LINE IS DIFFERENT
// ...
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[_from] - locked[_from]);
require(_value <= allowed[_from][msg.sender] - locked[_from]); // <-- THIS LINE IS DIFFERENT
// ...
Finally, we need to know how many tokens are locked or unlocked for a user:
最后,我们需要知道用户锁定或解锁了多少个令牌:
function getLockedAmount(address _owner) view public returns (uint256) {
return locked[_owner];
}
function getUnlockedAmount(address _owner) view public returns (uint256) {
return balances[_owner].sub(locked[_owner]);
}
That’s it: our token is now lockable from the outside, but only by the owner of the token contract (which will be the Story DAO we’ll build in the coming tutorials). Let’s make the token contract Ownable — i.e. allow it to have an owner. Import the Ownable
contract with import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol";
and then change this line:
就是这样:我们的令牌现在可以从外部锁定,但只能由令牌合约的所有者(这将是我们将在接下来的教程中构建的Story DAO)锁定。 让我们将令牌合同设为Ownable-即允许它拥有一个所有者。 通过import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol";
导入Ownable
合同import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol";
然后更改此行:
contract StoryDao {
… to be this:
……就是这样:
contract StoryDao is Ownable {
The full code of the token with comments for custom functions at this point looks like this:
此时,令牌的完整代码以及对自定义函数的注释如下所示:
pragma solidity ^0.4.24;
import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol";
contract TNStoken is Ownable {
using SafeMath for uint256;
mapping(address => uint256) balances;
mapping(address => uint256) locked;
mapping (address => mapping (address => uint256)) internal allowed;
uint256 totalSupply_;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event Locked(address indexed owner, uint256 indexed amount);
string public name;
string public symbol;
uint8 public decimals;
constructor() public {
name = "The Neverending Story Token";
symbol = "TNS";
decimals = 18;
totalSupply_ = 100 * 10**6 * 10**18;
balances[msg.sender] = totalSupply_;
}
/**
@dev _owner will be prevented from sending _amount of tokens. Anything
beyond this amount will be spendable.
*/
function increaseLockedAmount(address _owner, uint256 _amount) public onlyOwner returns (uint256) {
uint256 lockingAmount = locked[_owner].add(_amount);
require(balanceOf(_owner) >= lockingAmount, "Locking amount must not exceed balance");
locked[_owner] = lockingAmount;
emit Locked(_owner, lockingAmount);
return lockingAmount;
}
/**
@dev _owner will be allowed to send _amount of tokens again. Anything
remaining locked will still not be spendable. If the _amount is greater
than the locked amount, the locked amount is zeroed out. Cannot be neg.
*/
function decreaseLockedAmount(address _owner, uint256 _amount) public onlyOwner returns (uint256) {
uint256 amt = _amount;
require(locked[_owner] > 0, "Cannot go negative. Already at 0 locked tokens.");
if (amt > locked[_owner]) {
amt = locked[_owner];
}
uint256 lockingAmount = locked[_owner].sub(amt);
locked[_owner] = lockingAmount;
emit Locked(_owner, lockingAmount);
return lockingAmount;
}
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender] - locked[msg.sender]);
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
return true;
}
function approve(address _spender, uint256 _value) public returns (bool) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[_from] - locked[_from]);
require(_value <= allowed[_from][msg.sender] - locked[_from]);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
emit Transfer(_from, _to, _value);
return true;
}
function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
allowed[msg.sender][_spender] = (
allowed[msg.sender][_spender].add(_addedValue));
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
uint oldValue = allowed[msg.sender][_spender];
if (_subtractedValue > oldValue) {
allowed[msg.sender][_spender] = 0;
} else {
allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
}
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
/**
@dev Returns number of tokens the address is still prevented from using
*/
function getLockedAmount(address _owner) public view returns (uint256) {
return locked[_owner];
}
/**
@dev Returns number of tokens the address is allowed to send
*/
function getUnlockedAmount(address _owner) public view returns (uint256) {
return balances[_owner].sub(locked[_owner]);
}
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
}
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function allowance(address _owner, address _spender) public view returns (uint256) {
return allowed[_owner][_spender];
}
}
This part helped us get through building a basic token that we’ll use as a participation/share token in The Neverending Story. While the token has utility, it is by its definition of being an asset that controls decisions of a greater body a security token. Be mindful of the difference.
这部分内容帮助我们构建了一个基本的令牌,该令牌将用作“永无止境的故事”中的参与/共享令牌。 尽管令牌具有效用 ,但根据其定义,它是一种资产,可控制安全性令牌的更大主体的决策。 注意差异 。
In the next part of this series, we’ll learn how to compile, deploy and test this token.
在本系列的下一部分中,我们将学习如何编译,部署和测试该令牌。
翻译自: https://www.sitepoint.com/building-ethereum-dapps-tns-tokens/
以太坊dapp