Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to build and test DAO smart contracts

2025-04-07 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/01 Report--

This article mainly introduces "how to build and test DAO smart contracts". In daily operation, I believe many people have doubts about how to build and test DAO intelligent contracts. The editor consulted all kinds of materials and sorted out simple and easy-to-use operation methods. I hope it will be helpful to answer the doubts about "how to build and test DAO smart contracts". Next, please follow the editor to study!

Add tokens

For a contract that can interact with another contract, it needs to know the interface of the other contract-- the available functions. Because our TNS token has a fairly simple interface, we can include it in DAO's smart contract, on top of the contract StoryDao declaration, and in our import statement:

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 _ uint256) uint256 (amount); amount (uint256 _ amount, amount _ public returns) public returns (bool) Function getLockedAmount (address _ owner) view public returns (uint256); function getUnlockedAmount (address _ owner) view public returns (uint256);}

Note that we don't need to paste the "contents" of functions, we just need to paste their signatures (skeletons). This is all you need to interact with each other.

Now we can use these functions in the DAO contract. The plan is as follows:

Start the token (we have already done so).

Start DAO from the same address.

Send all tokens from the token initiator to DAO, and then transfer ownership to DAO itself through a contract.

At this point, DAO owns all the tokens and can use the send function to sell them to people, or you can use the approval function (useful during the voting) to keep them for expenditure.

But how does DAO know the address where tokens are deployed? We tell it.

First, we add a new variable at the top of the DAO contract:

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);}

Constructors are functions that are called automatically when a contract is deployed. It is useful for initializing link contracts, default values and equivalents. In our example, we will use it to use and save the address of the TNS token. The require check is to ensure that the address of the token is valid.

As we deal with it, let's add a function that allows the user to check the number of tokens for sale in DAO, as well as the function changed to another token, if there is a problem and such a change is needed. This change also requires an event, so we need to add it as well.

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 does not change the state of the blockchain; it does not change any values. This means that it is a free, read-only function call to the blockchain: it does not need to pay for transactions. It also returns the marked balance as a number, so you need to declare it with returns (uint256) on the signature of the function. Tokens have a balanceOf function (see the interface we pasted above), which takes an argument-- the address to check its balance. We are checking the balance of DAO, and we change "this" into an address ().

The token address change feature allows the admin to change the token address. It has the same logic as the constructor.

Let's see how we can get people to buy tokens now.

Buy tokens

According to the previous part of the series, users can buy tokens in the following ways:

If it is already on the whitelist, please use the backup function. In other words, simply send the ether to the DAO contract.

The cost of using the whitelistAddress function to send more than the whitelist.

Call the buyTokens function directly.

But there is a warning. When someone calls the buyTokens function from outside, if there are not enough tokens to sell in the DAO, we want it to fail. But when someone buys a token through the whitelist feature by sending too many ethers in the first whitelist attempt, we don't want it to fail because the whitelist process will be canceled. Deals in Ethernet Square either have to succeed or get nothing. So we're going to make two buyTokens functions.

/ / This goes at the top of the contract with other propertiesuint256 public tokenToWeiRatio = 10000 X 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); }} 因此,存在1亿个TNS代币。如果我们为每个以太设置10000个代币的价格,则每个代币的价格降至4-5美分,这是可以接受的。 这些函数在对违禁用户和其他因素进行完整性检查后进行一些计算,并立即将代币发送给买方,买方可以按照自己的意愿开始使用它们--无论是投票还是在交易所销售。如果DAO中的代币数量少于买方试图购买的代币,则退还买方。 部分token.transfer(_buyer, tokens)是我们使用TNS代币合约来启动从当前位置(DAO)到目标_buyer的tokens金额。 现在我们知道人们可以获得代币,让我们看看我们是否可以实施提交。 结构和提交 根据我们的介绍帖子,提交一个条目将花费0.0001 eth倍于故事中的条目数量。我们只需要计算未删除的提交(因为提交可以删除),所以让我们添加这个所需的属性和一个方法来帮助我们。 uint256 public submissionZeroFee = 0.0001 ether;uint256 public nonDeletedSubmissions = 0;function calculateSubmissionFee() view internal returns (uint256) { return submissionZeroFee * nonDeletedSubmissions;} 注意:Solidity具有内置时间和以太单位。在这里阅读更多相关信息。 此费用只能由业主更改,但只能降低。为了增加,需要投票。让我们写下减函数: function lowerSubmissionFee(uint256 _fee) onlyOwner external { require(_fee < submissionZeroFee, "New fee must be lower than old fee."); submissionZeroFee = _fee; emit SubmissionFeeChanged(_fee);} 我们发出一个事件来通知所有观察客户费用已经改变,所以让我们声明这个事件: event SubmissionFeeChanged(uint256 newFee); 提交可以是最多256个字符的文本,并且相同的限制适用于图像。只有他们的类型改变。这是自定义结构的一个很好的用例。让我们定义一个新的数据类型。 struct Submission { bytes content; bool image; uint256 index; address submitter; bool exists;} 这就像我们智能合约中的"对象类型"。该对象具有不同类型的属性。content是bytes类型值。image属性是一个布尔值,表示它是否是图像(true/false)。index是一个数字等于提交时的顺序数字; 它在所有提交列表中的索引(0,1,2,3 ......)。submitter是提交条目的帐户的地址,并且exists标志,因为在映射中,即使密钥尚不存在,所有密钥的所有值都被初始化为默认值(false)。 换句话说,当你有一个address =>

When bool is mapped, the mapping has set all addresses in the world to "false". This is how Etay Square works. Therefore, by checking whether the submission exists in a hash, we get a "yes", and the submission may not exist at all. The existence of flags helps this. It lets us check to see if the submission exists and exists-- that is, the submission, rather than being implicitly added by EVM. In addition, it makes it easier to "delete" entries later.

Note: technically, we can also check to make sure that the submitter's address is not zero.

While we are here, let's define two events: one for deleting entries and one for creating entries.

Event SubmissionCreated (uint256 index, bytes content, bool image, address submitter); event SubmissionDeleted (uint256 index, bytes content, bool image, address submitter)

But there is one problem. The mappings in Ethernet Square are not iterative: we cannot traverse them without a serious hacker attack.

To traverse them, we will create an array of identifiers for these commits, where the key of the array will be the index of the commit, and the value will be the unique hash value we will generate for each commit. Keccak256 provides us with a keccak256 hash algorithm for generating hashes from arbitrary values, which we can use with the current block number to ensure that entries are not repeated in the same block and to achieve a certain degree of uniqueness for each entry. We use it like this: keccak256 (abi.encodePacked (_ content, block.number));. We need the variable passed by encodePacked to the algorithm because it requires one of our parameters. That's what this function does.

We also need to store the submission somewhere, so let's define two more contract variables.

Mapping (bytes32 = > Submission) public submissions;bytes32 [] public submissionIndex

Okay, let's try to build the createSubmission function now.

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] .requests, "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.content, submissions.image, submissions[ hash] .submitter); nonDeletedSubmissions + = 1;}

Let's explain line by line:

Function createSubmission (bytes _ content, bool _ image) external payable {

This function accepts the contents of bytes (a dynamic size byte array that is useful for storing any amount of data) and a Boolean flag indicating whether the input is an image. This function can only be called from the outside world and should be paid, which means that it accepts the ether when the transaction is called.

Uint256 fee = calculateSubmissionFee (); require (msg.value > = fee, "Fee for submitting an entry must be sufficient.")

Next, we calculate the cost of submitting the new item, and then check to see if the value sent with the transaction is equal to or greater than the expense.

Bytes32 hash = keccak256 (abi.encodePacked (_ content, block.number)); require (! submissions[ hash] .requests, "Submission must not already exist in same block!")

Then we calculate the hash value of this entry (bytes32 is a 32-byte fixed-size array, so 32 characters are also keccak256 output). We use this hash to find out if a commit with that hash already exists, and if so, cancel everything.

Submissions [hash] = Submission (_ content, _ image, submissionIndex.push (hash), msg.sender, true)

This section creates a new submission at the hash location in the submissions map. It simply passes the value through the new structure defined above in the contract. Please note that although you may be used to using the new keyword in other languages, it is not necessary (or allowed) here. Then we send out the event (self-evident), and finally, nonDeletedSubmissions + = 1 position: this is the reason for increasing the cost of the next submission (see calculateSubmissionFee).

But there is a lack of logic here. We still need:

Account of the image

Check the whitelist / blacklist existence of the submitted account and the ownership of 1 TNS token.

Let's do the image first. Our original plan indicated that only one image could be submitted for every 50 texts. We also need two more contract attributes:

Uint256 public imageGapMin = 50 int256 public imageGap = 0

Of course, you can already assume how we will deal with this problem. Let's create a new submissions [hash] =. Add the following to our createSubmission method immediately before

If (_ image) {require (imageGap > = imageGapMin, "Image can only be submitted if more than {imageGapMin} texts precede it."); imageGap = 0;} else {imageGap + = 1;}

Very simple: if the entry should be an image, first check to see if the gap between the images is more than 49, and if so, reset it to 0. Otherwise, increase the gap by one. Just like that, every 50 (or more) submissions of existing content can become an image.

Finally, let's do a visit check. We can place this code before the cost calculation and immediately after the function entry point, because the access check should occur 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 to see if the message sender has more tokens than the token contract (because we can change the token address, so maybe another token will use our token later, and there may be no 18 decimal places. ). In other words, in our example, 10**token.decimals is 10 percent 18, that is, 1000 000 000 000, 1 followed by 18 zeros. If our tokens have 18 decimal places, it is 1.000000000000000000, or one (1) TNS tokens. Please note that your compiler or linter may give you some warnings when analyzing this code. This is because the decimals attribute of the token is public, so its getter function is automatically generated by decimals (), but it is not explicitly listed in the interface of the token we listed at the top of the contract. To solve this problem, we can change the interface by adding the following line:

Function decimals () public view returns (uint256)

One more thing: because we use the owner's fee for the contract currently set at 1%, let's give up the amount that the owner can withdraw and keep the rest in DAO. The easiest way is to keep track of how much the owner can extract and increase that number after each submission is created. Let's add a new attribute to the contract:

Uint256 public withdrawableByOwner = 0

Then add it to the end of our createSubmission function:

WithdrawableByOwner + = fee.div (daofee)

We can exit the owner through this feature:

Function withdrawToOwner () public {owner.transfer (withdrawableByOwner); withdrawableByOwner = 0;}

This sends the allowed amount to the owner and resets the counter to 0. 0. If the owner does not want to withdraw the full amount, we can add another function for the case:

Function withdrawAmountToOwner (uint256 _ amount) public {uint256 withdraw = _ amount; if (withdraw > withdrawableByOwner) {withdraw = withdrawableByOwner;} owner.transfer (withdraw); withdrawableByOwner = withdrawableByOwner.sub (withdraw);}

Since we often commit through hash references, let's write a function to check whether the submission exists so that we can replace our submissions[ hash] .submission check:

Function submissionExists (bytes32 hash) public view returns (bool) {return submissions[ hash] .operations;}

Some other helper functions are needed to read the submission:

Function getSubmission (bytes32 hash) public view returns (bytes content, bool image, address submitter) {return (submissions.content, submissions.image, submissions.submitter);} function getAllSubmissionHashes () public view returns (bytes32 []) {return submissionIndex;} function getSubmissionCount () public view returns (uint256) {return submissionIndex.length;}

GetSubmission gets the submission data, getAllSubmissionHashes gets all the unique hashes in the system, and getSubmissionCount lists the total number of submissions (including deleted submissions). We use a combination of these functions on the client side (in UI) to get the content.

The complete createSubmission function now looks like this:

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.content, submissions.image, submissions.submitter); nonDeletedSubmissions + = 1; withdrawableByOwner + = fee.div (daofee);} delete

What about deleting the submission? It's easy: we just switch the exists flag to 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 that the submission exists and has not been deleted; then we retrieve it from the storage. Next, we set its exists flag to false, increasing the number of deletions in the DAO of that address by 1 (useful when tracking the number of entries that users delete later; this could lead to a blacklist! We issue a delete event

Finally, we reduce the cost of creating new submissions by reducing the number of undeleted submissions in the system. Let's not forget to add a new attribute to our contract: one to track these deletions.

Mapping (address = > uint256) public deletions; deployment becomes more complex

Now that we use tokens in another contract, we need to update the deployment script (3_deploy_storydao) to pass the address of the token to the constructor of StoryDao, as follows:

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);}} At this point, the study on "how to build and test DAO smart contracts" is over. I hope to be able to solve your doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 270

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report