da-dapps
In part 4 of this tutorial series on building DApps with Ethereum, we started building and testing our DAO contract. Now let’s go one step further and handle adding content and tokens to the story, as per our introduction.
在本教程系列的第4部分中 ,该系列教程介绍了如何使用以太坊构建DApp,并开始测试DAO合同。 现在,按照我们的介绍 ,让我们更进一步,并为故事添加内容和令牌。
添加令牌 (Adding Tokens)
For a contract to be able to interact with another contract, it needs to be aware of that other contract’s interface — the functions available to it. Since our TNS token has a fairly straightforward interface, we can include it as such in the contract of our DAO, above the contract StoryDao
declaration and under our import
statements:
为了使一个合同能够与另一个合同进行交互,它需要知道该另一个合同的界面-可用的功能。 由于我们的TNS令牌具有相当简单的界面,因此我们可以将其原样包含在DAO的contract StoryDao
声明的contract StoryDao
和import
声明下:
contract LockableToken is Ownable {
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);
function approveAndCall(address _spender, uint256 _value, bytes _data) public payable returns (bool);
function transferAndCall(address _to, uint256 _value, bytes _data) public payable returns (bool);
function transferFromAndCall(address _from, address _to, uint256 _value, bytes _data) public payable returns (bool);
function increaseLockedAmount(address _owner, uint256 _amount) public returns (uint256);
function decreaseLockedAmount(address _owner, uint256 _amount) public returns (uint256);
function getLockedAmount(address _owner) view public returns (uint256);
function getUnlockedAmount(address _owner) view public returns (uint256);
}
Notice that we don’t need to paste in the “meat” of the functions, but only their signatures (skeletons). This is all that’s needed to interact between contracts.
注意,我们不需要粘贴函数的“肉”,而只需粘贴它们的签名(骨架)。 这就是合同之间进行交互所需要的全部。
Now we can use these functions in the DAO contract. The plan is as follows:
现在我们可以在DAO合同中使用这些功能。 该计划如下:
- launch the token (we already did this) 启动令牌(我们已经做到了)
- launch the DAO from the same address 从同一地址启动DAO
- send all tokens from the token-launcher to the DAO, then transfer ownership over the contract to the DAO itself 将所有令牌从令牌发布者发送到DAO,然后将合同所有权转移到DAO本身
- at this point the DAO owns all tokens and can sell them to people using the transfer function, or can reserve them for spending using the approve function (useful during votes), etc. 在这一点上,DAO拥有所有令牌,并可以使用转移功能将其出售给人们,或者可以使用批准功能(在投票期间有用)等将其保留以用于支出。
But how does the DAO know which address the token is deployed on? We tell it.
但是DAO如何知道令牌部署在哪个地址上? 我们告诉它。
First, we add a new variable at the top of the DAO contract:
首先,我们在DAO合同的顶部添加一个新变量:
LockableToken public token;
Then, we add some functions:
然后,我们添加一些功能:
constructor(address _token) public {
require(_token != address(0), "Token address cannot be null-address");
token = LockableToken(_token);
}
The constructor is the function which gets called automatically when a contract is deployed. It’s useful for initializing values like linked contracts, default values, etc. In our case, we’ll use it to consume and save the address of the TNS token. The require
check is there to make sure the token’s address is valid.
构造函数是在部署合同时自动调用的函数。 对于初始化链接合同,默认值等值非常有用。在我们的案例中,我们将使用它来消耗和保存TNS令牌的地址。 那里require
检查以确保令牌的地址有效。
While we’re at it, let’s add a function that lets users check how many tokens remain for sale in the DAO, and the ability to change to another token should something go wrong and such a change be required. This change deserves an event, too, so let’s add that in as well.
在此过程中,让我们添加一个功能,该功能使用户可以检查DAO中还有多少代币待售,并可以在出现问题并需要进行此类更改时更改为另一个代币。 此更改也应引起一个事件,因此我们也将其添加进来。
event TokenAddressChange(address token);
function daoTokenBalance() public view returns (uint256) {
return token.balanceOf(address(this));
}
function changeTokenAddress(address _token) onlyOwner public {
require(_token != address(0), "Token address cannot be null-address");
token = LockableToken(_token);
emit TokenAddressChange(_token);
}
The first function is set to view
because it doesn’t change the state of the blockchain; it doesn’t alter any values. This means it’s a free, read-only function call to the blockchain: it doesn’t need a paid transaction. It also returns the balance of tokens as a number, so this needs to be declared on the function’s signature with returns (uint256)
. The token has a balanceOf
function (see the interface we pasted in above) and it accepts one parameter — the address whose balance to check. We’re checking our (this) DAO’s balance, so “this”, and we turn “this” into an address with address()
.
第一个功能设置为view
因为它不会更改区块链的状态。 它不会改变任何值。 这意味着它是对区块链的免费,只读函数调用:它不需要付费交易。 它还会以数字形式返回令牌的余额,因此需要在带有returns (uint256)
的函数签名上进行声明。 令牌具有balanceOf
函数(请参阅我们上面粘贴的界面),并且它接受一个参数-要检查其余额的地址。 我们正在检查DAO的余额,即“ this”,然后将“ this”转换为具有address()
。
The token address changing function allows the owner (admin) to change the token contract. It’s identical to the logic of the constructor.
令牌地址更改功能允许所有者(管理员)更改令牌合同。 它与构造函数的逻辑相同。
Let’s see how we can let people buy the tokens now.
让我们看看如何让人们现在购买代币。
购买代币 (Buying Tokens)
As per the previous part of the series, users can buy tokens by:
按照本系列的前一部分,用户可以通过以下方式购买代币:
- Using the fallback function if already whitelisted. In other words, just sending ether to the DAO contract. 使用后备功能(如果已列入白名单)。 换句话说,只需将以太币发送给DAO合同。
Using the
whitelistAddress
function by sending more than the fee required for whitelisting.通过发送比白名单所需的费用更多的
whitelistAddress
使用whitelistAddress
函数。- Calling the buyTokens function directly. 直接调用buyTokens函数。
There is a caveat, however. When someone calls the buyTokens
function from the outside, we want it to fail if there aren’t enough tokens in the DAO to sell. But when someone buys tokens via the whitelist function by sending in too much in the first whitelisting attempt, we don’t want it to fail, because then the whitelisting process will get canceled as everything fails at once. Transactions in Ethereum are atomic: either everything has to succeed, or nothing. So we’ll make two buyTokens
functions.
但是,有一个警告。 当有人从外部调用buyTokens
函数时,如果DAO中没有足够的代币来出售,我们希望它失败。 但是,当某人通过在第一次白名单尝试中发送太多来通过白名单功能购买令牌时,我们不希望它失败,因为那样之后,白名单的过程将被取消,因为所有操作都会立即失败。 以太坊的交易是原子的:要么一切都必须成功,要么一无所有。 因此,我们将创建两个buyTokens
函数。
// This goes at the top of the contract with other properties
uint256 public tokenToWeiRatio = 10000;
function buyTokensThrow(address _buyer, uint256 _wei) external {
require(whitelist[_buyer], "Candidate must be whitelisted.");
require(!blacklist[_buyer], "Candidate must not be blacklisted.");
uint256 tokens = _wei * tokenToWeiRatio;
require(daoTokenBalance() >= tokens, "DAO must have enough tokens for sale");
token.transfer(_buyer, tokens);
}
function buyTokensInternal(address _buyer, uint256 _wei) internal {
require(!blacklist[_buyer], "Candidate must not be blacklisted.");
uint256 tokens = _wei * tokenToWeiRatio;
if (daoTokenBalance() < tokens) {
msg.sender.transfer(_wei);
} else {
token.transfer(_buyer, tokens);
}
}
So, 100 million TNS tokens exist. If we set a price of 10000 tokens per one ether, that comes down to around 4–5 cents per token, which is acceptable.
因此,存在1亿个TNS令牌。 如果我们将每枚以太币10000个代币的价格定下来,那么每个代币的价格将降至4-5美分左右,这是可以接受的。
The functions do some calculations after doing sanity checks against banned users and other factors, and immediately send the tokens out to the buyer, who can start using them as they see fit — either for voting, or for selling on exchanges. If there’s fewer tokens in the DAO than the buyer is trying to buy, the buyer is refunded.
该功能在对违禁用户和其他因素进行完备性检查之后进行一些计算,然后立即将令牌发送给买方,买方可以根据自己的意愿开始使用令牌-进行投票或在交易所出售。 如果DAO中的代币数量少于买家要购买的数量,则买家将获得退款。
The part token.transfer(_buyer, tokens)
is us using the TNS token contract to initiate a transfer from the current location (the DAO) to the destination _buyer
for amount tokens
.
部分token.transfer(_buyer, tokens)
是我们使用TNS令牌合约来发起从当前位置(DAO)到目的地_buyer
的数量tokens
_buyer
。
Now that we know people can get their hands on the tokens, let’s see if we can implement submissions.
现在我们知道人们可以使用令牌了,让我们看看是否可以实现提交。
结构和提交 (Structs and Submissions)
As per our intro post, submitting an entry will cost 0.0001 eth times the number of entries in the story already. We only need to count non-deleted submissions (because submissions can be deleted) so let’s add the properties required for this and a method to help us.
根据我们的介绍性帖子,提交条目的费用将是故事中已有条目数量的0.0001 eth倍。 我们只需要计算未删除的提交(因为可以删除提交),因此让我们添加为此所需的属性和一种可以帮助我们的方法。
uint256 public submissionZeroFee = 0.0001 ether;
uint256 public nonDeletedSubmissions = 0;
function calculateSubmissionFee() view internal returns (uint256) {
return submissionZeroFee * nonDeletedSubmissions;
}
Note: Solidity has built in time and ether units. Read more about them here.
注意:Solidity内置了时间和以太单位。 在此处阅读有关它们的更多信息。
This fee can only be changed by the owner, but only lowered. For increasing, it needs a vote. Let’s write the decrease function:
此费用只能由所有者更改,但只能降低。 要增加,它需要投票。 让我们编写减少函数:
function lowerSubmissionFee(uint256 _fee) onlyOwner external {
require(_fee < submissionZeroFee, "New fee must be lower than old fee.");
submissionZeroFee = _fee;
emit SubmissionFeeChanged(_fee);
}
We’re emitting an event to notify all observing clients that the fee has been changed, so let’s declare that event:
我们正在发出一个事件,通知所有观察的客户费用已更改,因此让我们声明该事件:
event SubmissionFeeChanged(uint256 newFee);
A submission can be text of up to 256 characters, and the same limit applies to images. Only their type changes. This is a great use case for a custom struct. Let’s define a new data type.
提交内容最多可为256个字符的文本,并且图像的限制相同。 只有它们的类型改变。 对于自定义结构来说,这是一个很好的用例。 让我们定义一个新的数据类型。
struct Submission {
bytes content;
bool image;
uint256 index;
address submitter;
bool exists;
}
This is like an “object type” in our smart contract. The object has properties of different types. The content
is a bytes
type value. The image
property is a boolean denoting if it’s an image (true/false). The index
is a number equal to the ordinary number of the submission; its index in the list of all submissions (0, 1, 2, 3 …). The submitter
is an address of the account which submitted the entry, and the exists
flag is there because in mappings all the values of all keys are initialized to default values (false) even if keys don’t exist yet.
这就像我们智能合约中的“对象类型”。 该对象具有不同类型的属性。 content
是bytes
类型值 。 image
属性是一个布尔值,表示它是否是图像(真/假)。 index
是等于提交的普通编号的数字; 它在所有提交清单中的索引(0、1、2、3…)。 submitter
是提交条目的帐户的地址,并且exists
标志是因为在映射中,即使键还不存在,所有键的所有值也会被初始化为默认值(false)。
In other words, when you have a mapping of address => bool
, that mapping is already going to have all the addresses in the world set to “false”. That’s just how Ethereum works. So by checking if the submission exists at a certain hash, we’d get “yes”, whereas the submission might not be there at all. The exists flag helps with that. It lets us check that the submission is there and it exists — that is, was submitted and not just implicitly added by the EVM. Furthermore, it makes “deleting” entries later on much easier.
换句话说,当您具有一个address => bool
映射时,该映射已经将世界上所有的地址都设置为“ false”。 这就是以太坊的工作方式。 因此,通过检查提交是否存在于特定哈希值中,我们将获得“是”,而提交可能根本不存在。 存在标志可以帮助实现这一点。 它使我们可以检查提交是否存在并且是否存在-即已提交,而不仅仅是由EVM隐式添加。 此外,它使以后“删除”条目变得更加容易。
Note: technically, we could also check to make sure that the submitter’s address isn’t a zero-address.
注意:从技术上讲,我们还可以检查以确保提交者的地址不是零地址。
While we’re here, let’s define two events: one for deleting an entry, one for creating it.
在这里,我们定义两个事件:一个用于删除条目,一个用于创建条目。
event SubmissionCreated(uint256 index, bytes content, bool image, address submitter);
event SubmissionDeleted(uint256 index, bytes content, bool image, address submitter);
There’s a problem, though. Mappings in Ethereum are not iterable: we can’t loop through them without significant hacking.
不过有一个问题。 以太坊中的映射不是可迭代的:没有大量的黑客攻击我们就无法遍历它们。
To loop through them all, we’ll create an array of identifiers for these submissions wherein the keys of the array will be the index of the submission, while the values will be unique hashes we’ll generate for each submission. Solidity provides us with the keccak256
hashing algorithm for generating hashes from arbitrary values, and we can use that in tandem with the current block number to make sure an entry isn’t duplicated in the same block and get some degree of uniqueness for each entry. We use it like this: keccak256(abi.encodePacked(_content, block.number));
. We need to encodePacked
the variables passed to the algorithm because it needs a single parameter from us. That’s what this function does.
为了遍历它们,我们将为这些提交创建一个标识符数组,其中数组的键将是提交的索引,而值将是我们将为每个提交生成的唯一哈希。 keccak256
为我们提供了keccak256
哈希算法,用于从任意值生成哈希值,并且我们可以将其与当前块号结合使用,以确保条目不会在同一块中重复,并为每个条目获得一定程度的唯一性。 我们像这样使用它: keccak256(abi.encodePacked(_content, block.number));
。 我们需要对传递给该算法的变量进行encodePacked
,因为它需要我们提供一个参数。 这就是该功能的作用。
We also need to store the submissions somewhere, so let’s define two more contract variables.
我们还需要将提交的内容存储在某个地方,因此让我们定义另外两个合同变量。
mapping (bytes32 => Submission) public submissions;
bytes32[] public submissionIndex;
Okay, let’s try and build the createSubmission
function now.
好的,让我们现在尝试构建createSubmission
函数。
function createSubmission(bytes _content, bool _image) external payable {
uint256 fee = calculateSubmissionFee();
require(msg.value >= fee, "Fee for submitting an entry must be sufficient.");
bytes32 hash = keccak256(abi.encodePacked(_content, block.number));
require(!submissions[hash].exists, "Submission must not already exist in same block!");
submissions[hash] = Submission(
_content,
_image,
submissionIndex.push(hash),
msg.sender,
true
);
emit SubmissionCreated(
submissions[hash].index,
submissions[hash].content,
submissions[hash].image,
submissions[hash].submitter
);
nonDeletedSubmissions += 1;
}
Let’s go through this line by line:
让我们逐行浏览以下内容:
function createSubmission(bytes _content, bool _image) external payable {
The function accepts bytes of content (bytes is a dynamically sized array of bytes, useful for storing arbitrary amounts of data) and a boolean flag for whether or not this input is an image. The function is only callable from the outside world and is payable which means it accepts Ether alongside the transaction call.
该函数接受内容的字节(bytes是一个动态大小的字节数组,用于存储任意数量的数据)和一个布尔标志,用于确定此输入是否为图像。 该功能只能从外部调用,并且是有偿的,这意味着它与交易调用一起接受以太币。
uint256 fee = calculateSubmissionFee();
require(msg.value >= fee, "Fee for submitting an entry must be sufficient.");
Next, we calculate how much it costs to submit a new entry and then check if the value passed along with the transaction is equal to or greater than the fee.
接下来,我们计算提交新条目的成本,然后检查与交易一起传递的价值是否等于或大于费用。
bytes32 hash = keccak256(abi.encodePacked(_content, block.number));
require(!submissions[hash].exists, "Submission must not already exist in same block!");
We then calculate the hash of this entry (bytes32
is a fixed-size array of 32 bytes, so 32 characters which is also the length of the output of keccak256
). We use this hash to find out if a submission with that hash already exists and cancel everything if it does.
然后,我们计算该条目的哈希值( bytes32
是32个字节的固定大小的数组,所以32个字符,这也是keccak256
输出的keccak256
)。 我们使用该哈希值来查找是否已经存在带有该哈希值的提交,如果存在则取消所有操作。
submissions[hash] = Submission(
_content,
_image,
submissionIndex.push(hash),
msg.sender,
true
);
This part creates a new submission at the hash location in the submissions
mapping. It simply passes in the values via a new struct as defined above in the contract. Note that while you might be used to the new
keyword from other languages, it isn’t necessary (or allowed) here. We then emit the event (self-explanatory) and finally, there’s nonDeletedSubmissions += 1;
: this is what increases the fee for the next submission (see calculateSubmissionFee
).
此部分在submissions
映射中的哈希位置创建一个新的submissions
。 它只是简单地通过合同中上面定义的新结构传递值。 请注意,尽管您可能已经习惯了其他语言的new
关键字,但此处没有必要(或不允许使用)。 然后,我们发出事件(不言自明),最后,有nonDeletedSubmissions += 1;
:这就是增加下一次提交费用的原因(请参阅calculateSubmissionFee
)。
But there’s a lot of logic missing here. We still need to:
但是这里缺少很多逻辑。 我们仍然需要:
- account for images, and 占图像,并且
- check for whitelist/blacklist presence and 1 TNS token ownership on submitting accounts. 在提交帐户时检查白名单/黑名单的存在以及1个TNS令牌的所有权。
Let’s do images first. Our original plan said that an image can only be submitted every 50 texts. We’ll need two more contract properties:
让我们先做图像。 我们最初的计划是每50个文本只能提交一张图片。 我们将需要另外两个合同属性:
uint256 public imageGapMin = 50;
uint256 public imageGap = 0;
Surely you can already assume how we’re going to handle this? Let’s add the following into our createSubmission
method, immediately before creating the new submission with submissions[hash] = ...
.
当然,您已经可以假设我们将如何处理此问题? 让我们在创建带有submissions[hash] = ...
的新提交之前,立即将以下内容添加到createSubmission
方法中。
if (_image) {
require(imageGap >= imageGapMin, "Image can only be submitted if more than {imageGapMin} texts precede it.");
imageGap = 0;
} else {
imageGap += 1;
}
Extremely simple: if the entry is supposed to be an image, then first check that the gap between images is more than 49 and reset it to 0 if it is. Otherwise, increase the gap by one. Just like that, every 50th (or more) submission can now be an image.
非常简单:如果条目应该是图像,则首先检查图像之间的间隙是否大于49,然后将其重置为0。 否则,将差距增加一。 这样,每50个(或更多)提交的内容现在都可以成为图像。
Finally, let’s do the access check. We can put this code before the fee calculation and immediately after the entry point to the function, because access checking should happen first.
最后,让我们进行访问检查。 我们可以将此代码放在费用计算之前和函数的入口点之后,因为访问检查应首先进行。
require(token.balanceOf(msg.sender) >= 10**token.decimals());
require(whitelist[msg.sender], "Must be whitelisted");
require(!blacklist[msg.sender], "Must not be blacklisted");
The first line checks if the message sender has more tokens than 10 to the power of the number of decimals in a token’s contract (because we can change the token address, so it’s possible another token will take our token’s place later on and that one might not have 18 decimals!). In other words, 10**token.decimals
is, in our case, 10**18
, which is 1 000 000 000 000 000 000
, or 1 followed by 18 zeroes. If our token has 18 decimals, that’s 1.000000000000000000
, or one (1) TNS token. Note that your compiler or linter might give you some warnings when analyzing this code. That’s because the decimals
property of the token is public, so its getter function is auto-generated (decimals()
), but it’s not explicitly listed in the interface of the token which we listed at the top of the contract. To get around this, we can change the interface by adding this line:
第一行检查消息发件人的令牌数量是否超过令牌合同中小数位数的10(因为我们可以更改令牌地址,因此有可能另一个令牌稍后将取代我们令牌的位置,并且可能没有18位小数!)。 换句话说, 10**token.decimals
是,在我们的情况下, 10**18
,这是1 000 000 000 000 000 000
,或1,随后18个零。 如果我们的令牌有18位小数, 1.000000000000000000
或一(1)个TNS令牌。 请注意,在分析此代码时,编译器或linter可能会给您一些警告。 那是因为令牌的decimals
属性是公共的,因此它的getter函数是自动生成的( decimals()
),但是未在合约顶部列出的令牌接口中明确列出。 为了解决这个问题,我们可以通过添加以下行来更改界面:
function decimals() public view returns (uint256);
One more thing: since there’s an owner fee for using the contract currently set as 1%, let’s put aside the amount that the owner can withdraw and keep the rest in the DAO. The easiest way to do this is to track how much the owner can withdraw and increase that number after every submission’s creation. Let’s add a new property to the contract:
还有一件事:由于目前将合同使用费设为1%,因此需要支付所有者费用,因此,我们将所有者可以提取的金额放在一边,并将其余的保留在DAO中。 最简单的方法是跟踪创建每个提交后所有者可以撤回的金额并增加该数目。 让我们向合同添加一个新属性:
uint256 public withdrawableByOwner = 0;
And then add this to the end of our createSubmission
function:
然后将其添加到我们的createSubmission
函数的末尾:
withdrawableByOwner += fee.div(daofee);
We can let the owner withdraw with a function like this:
我们可以使用以下函数让所有者退出:
function withdrawToOwner() public {
owner.transfer(withdrawableByOwner);
withdrawableByOwner = 0;
}
This sends the allowed amount to the owner and resets the counter to 0. In case the owner doesn’t want to withdraw the whole amount, we can add another function for that edge case:
这会将允许的金额发送给所有者,并将计数器重置为0。如果所有者不想提取全部金额,我们可以为这种极端情况添加另一个功能:
function withdrawAmountToOwner(uint256 _amount) public {
uint256 withdraw = _amount;
if (withdraw > withdrawableByOwner) {
withdraw = withdrawableByOwner;
}
owner.transfer(withdraw);
withdrawableByOwner = withdrawableByOwner.sub(withdraw);
}
Since we’ll be referencing submissions by their hashes often, let’s write a function which checks if a submission exists so we can replace our submissions[hash].exists
checks with it:
由于我们经常使用散列来引用提交,因此我们编写一个函数来检查提交是否存在,以便我们替换submissions[hash].exists
进行检查:
function submissionExists(bytes32 hash) public view returns (bool) {
return submissions[hash].exists;
}
Some other helper functions for reading submissions will also be necessary:
还需要其他一些帮助功能来阅读提交内容:
function getSubmission(bytes32 hash) public view returns (bytes content, bool image, address submitter) {
return (submissions[hash].content, submissions[hash].image, submissions[hash].submitter);
}
function getAllSubmissionHashes() public view returns (bytes32[]) {
return submissionIndex;
}
function getSubmissionCount() public view returns (uint256) {
return submissionIndex.length;
}
This is self explanatory. getSubmission
fetches the submission data, getAllSubmissionHashes
fetches all the unique hashes in the system, and getSubmissionCount
lists how many submissions there are in total (including deleted ones). We use a combination of these functions on the client side (in the UI) to fetch the content.
这是不言自明的。 getSubmission
提取提交数据, getAllSubmissionHashes
提取系统中所有唯一的哈希,而getSubmissionCount
列出总共有多少个提交(包括已删除的)。 我们在客户端(在UI中)使用这些功能的组合来获取内容。
The full createSubmission
function now looks like this:
现在完整的createSubmission
函数createSubmission
:
function createSubmission(bytes _content, bool _image) storyActive external payable {
require(token.balanceOf(msg.sender) >= 10**token.decimals());
require(whitelist[msg.sender], "Must be whitelisted");
require(!blacklist[msg.sender], "Must not be blacklisted");
uint256 fee = calculateSubmissionFee();
require(msg.value >= fee, "Fee for submitting an entry must be sufficient.");
bytes32 hash = keccak256(abi.encodePacked(_content, block.number));
require(!submissionExists(hash), "Submission must not already exist in same block!");
if (_image) {
require(imageGap >= imageGapMin, "Image can only be submitted if more than {imageGapMin} texts precede it.");
imageGap = 0;
} else {
imageGap += 1;
}
submissions[hash] = Submission(
_content,
_image,
submissionIndex.push(hash),
msg.sender,
true
);
emit SubmissionCreated(
submissions[hash].index,
submissions[hash].content,
submissions[hash].image,
submissions[hash].submitter
);
nonDeletedSubmissions += 1;
withdrawableByOwner += fee.div(daofee);
}
删除中 (Deleting)
So what about deleting submissions? That’s easy enough: we just switch the exists
flag to false
!
那么删除提交内容呢? 这很容易:我们只需将exists
标志切换为false
!
function deleteSubmission(bytes32 hash) internal {
require(submissionExists(hash), "Submission must exist to be deletable.");
Submission storage sub = submissions[hash];
sub.exists = false;
deletions[submissions[hash].submitter] += 1;
emit SubmissionDeleted(
sub.index,
sub.content,
sub.image,
sub.submitter
);
nonDeletedSubmissions -= 1;
}
First, we make sure the submission exists and isn’t already deleted; then we retrieve it from the storage. Next we set its exists
flag to false, increase the number of deletions in the DAO for that address by 1 (useful when tracking how many entries a user has had deleted for them later on; this can lead to a blacklist!), and we emit a deletion event.
首先,我们确保提交的内容存在并且尚未删除; 然后我们从存储中检索它。 接下来,我们将其exists
标志设置为false,将该地址中DAO中的删除次数增加1(在跟踪用户以后为该用户删除了多少条目时很有用;这可能会导致黑名单!),然后我们发出删除事件。
Finally, we reduce the new submission creation fee by reducing the number of non-deleted submissions in the system. Let’s not forget to add a new property to our contract as well — one to track these deletions:
最后,我们通过减少系统中未删除的提交数量来减少新的提交创建费用。 我们也不要忘记在合同中添加新属性-跟踪这些删除操作的属性:
mapping (address => uint256) public deletions;
部署变得更加复杂 (Deployments Get More Complicated)
Now that we’re using tokens in another contract, we need to update the deployment script (3_deploy_storydao
) to pass the token’s address into the constructor of the StoryDao, like so:
现在,我们在另一个合同中使用令牌,我们需要更新部署脚本( 3_deploy_storydao
),以将令牌的地址传递到StoryDao的构造函数中,如下所示:
var Migrations = artifacts.require("./Migrations.sol");
var StoryDao = artifacts.require("./StoryDao.sol");
var TNSToken = artifacts.require("./TNSToken.sol");
module.exports = function(deployer, network, accounts) {
if (network == "development") {
deployer.deploy(StoryDao, TNSToken.address, {from: accounts[0]});
} else {
deployer.deploy(StoryDao, TNSToken.address);
}
};
Read more about configuring deployments here.
在此处阅读有关配置部署的更多信息。
结论 (Conclusion)
In this part, we added the ability for participants to buy tokens from our DAO and to add submissions into the story. Another part of the DAO contract remains: voting and democratization. That’s what we’ll handle in the next post.
在这一部分中,我们增加了参与者可以从DAO购买代币并将故事添加到故事的功能。 DAO合同的另一部分仍然是:投票和民主化。 这就是我们在下一篇文章中要处理的。
翻译自: https://www.sitepoint.com/building-ethereum-dapps-cross-contract-communication-token-selling/
da-dapps