Home > Technology peripherals > It Industry > Building Ethereum DApps: Whitelisting & Testing a Story DAO

Building Ethereum DApps: Whitelisting & Testing a Story DAO

Lisa Kudrow
Release: 2025-02-16 12:24:15
Original
175 people have browsed it

Building Ethereum DApps: Whitelisting & Testing a Story DAO

Key Points

  • Story DAO uses OpenZeppelin's Ownable contract to ensure that only the owner can perform management functions, thereby enhancing security and control over DApp operations.
  • Story DAO contracts have adjustable fee and duration parameters and are equipped with security measures to prevent unauthorized changes, ensuring that only the owner can modify critical settings.
  • Whitelist management in Story DAO is implemented through paid features that allow automatic and conditional access based on the sender's contribution.
  • Integrated testing strategies, including Solidity and JavaScript testing, are critical to verifying the functionality and security of Story DAO, ensuring robust operation before deployment.
  • The deployment process of Story DAO is simplified by Truffle, with specific migration scripts and configurations, which facilitates a smooth transition from the development environment to the production environment.

The third part of this tutorial series describes building a DApp using Ethereum, where we build and deploy the token to the Ethereum test network Rinkeby. In this section, we will start writing Story DAO code.

We will guide us using the criteria listed in the introductory article.

Contract Overview

Let's create a new contract StoryDao.sol, whose framework is as follows:

pragma solidity ^0.4.24;

import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol";

contract StoryDao is Ownable {
    using SafeMath for uint256;

    mapping(address => bool) whitelist;
    uint256 public whitelistedNumber = 0;
    mapping(address => bool) blacklist;
    event Whitelisted(address addr, bool status);
    event Blacklisted(address addr, bool status);

    uint256 public daofee = 100; // 百分之几,即 100 为 1%
    uint256 public whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币

    event SubmissionCommissionChanged(uint256 newFee);
    event WhitelistFeeChanged(uint256 newFee);

    uint256 public durationDays = 21; // 故事章节持续时间(天)
    uint256 public durationSubmissions = 1000; // 故事章节持续时间(条目)

    function changedaofee(uint256 _fee) onlyOwner external {
        require(_fee <= 1000); // 限制最大费用为 10%
        daofee = _fee;
        emit SubmissionCommissionChanged(_fee);
    }

    function changewhitelistfee(uint256 _fee) onlyOwner external {
        require(_fee > 0); // 确保费用大于 0
        whitelistfee = _fee;
        emit WhitelistFeeChanged(_fee);
    }


    function changeDurationDays(uint256 _days) onlyOwner external {
        require(_days >= 1);
        durationDays = _days;
    }

    function changeDurationSubmissions(uint256 _subs) onlyOwner external {
        require(_subs > 99);
        durationSubmissions = _subs;
    }
}
Copy after login
Copy after login
Copy after login

We import SafeMath to do safe calculations again, but this time we also use Zeppelin’s Ownable contract, which allows someone to “own” the story and perform certain administrator-only functions. Simply put, it's enough for our StoryDao to be Ownable; feel free to check the contract to understand how it works.

We also use the onlyOwner modifier in this contract. Function modifiers are basically extensions and plug-ins for functions. The onlyOwner modifier looks like this:

modifier onlyOwner() {
  require(msg.sender == owner);
  _;
}
Copy after login
Copy after login
Copy after login

When onlyOwner is added to the function, the body of the function will be pasted to where the _; part is located, and the previous part will be executed first. Therefore, by using this modifier, the function automatically checks whether the message sender is the owner of the contract and then continues as usual if it is true. If not, it will crash.

By using the onlyOwner modifier on functions that change the fees and other parameters of Story DAO, we ensure that only administrators can make these changes.

Test

Let's test the initial function.

If it does not exist, create a folder test. Then create the files TestStoryDao.sol and TestStoryDao.js in it. Since there is no native method in Truffle to test exceptions, helpers/expectThrow.js are also created using the following:

export default async promise => {
    try {
      await promise;
    } catch (error) {
      const invalidOpcode = error.message.search('invalid opcode') >= 0;
      const outOfGas = error.message.search('out of gas') >= 0;
      const revert = error.message.search('revert') >= 0;
      assert(
        invalidOpcode || outOfGas || revert,
        'Expected throw, got \'' + error + '\' instead',
      );
      return;
    }
    assert.fail('Expected throw not received');
  };
Copy after login
Copy after login

Note: Solidity testing is usually used to test low-level, contract-based functions, that is, the internal structure of smart contracts. JS testing is often used to test whether a contract can interact correctly from the outside, which is what our end users will do.

In TestStoryDao.sol, put the following content:

pragma solidity ^0.4.24;

import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol";

contract StoryDao is Ownable {
    using SafeMath for uint256;

    mapping(address => bool) whitelist;
    uint256 public whitelistedNumber = 0;
    mapping(address => bool) blacklist;
    event Whitelisted(address addr, bool status);
    event Blacklisted(address addr, bool status);

    uint256 public daofee = 100; // 百分之几,即 100 为 1%
    uint256 public whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币

    event SubmissionCommissionChanged(uint256 newFee);
    event WhitelistFeeChanged(uint256 newFee);

    uint256 public durationDays = 21; // 故事章节持续时间(天)
    uint256 public durationSubmissions = 1000; // 故事章节持续时间(条目)

    function changedaofee(uint256 _fee) onlyOwner external {
        require(_fee <= 1000); // 限制最大费用为 10%
        daofee = _fee;
        emit SubmissionCommissionChanged(_fee);
    }

    function changewhitelistfee(uint256 _fee) onlyOwner external {
        require(_fee > 0); // 确保费用大于 0
        whitelistfee = _fee;
        emit WhitelistFeeChanged(_fee);
    }


    function changeDurationDays(uint256 _days) onlyOwner external {
        require(_days >= 1);
        durationDays = _days;
    }

    function changeDurationSubmissions(uint256 _subs) onlyOwner external {
        require(_subs > 99);
        durationSubmissions = _subs;
    }
}
Copy after login
Copy after login
Copy after login

This will check that the StoryDao contract is properly deployed with the correct fee and duration figures. The first line ensures that it is deployed by reading it from the deployed address list, and the last section makes some assertions - check if the declaration is true or false. In our example, we compare the number to the initial value of the deployed contract. Whenever it is "true", the Assert.equals section will issue an event that states "true", which is what Truffle is listening on during testing.

In TestStoryDao.js, put the following content:

modifier onlyOwner() {
  require(msg.sender == owner);
  _;
}
Copy after login
Copy after login
Copy after login

In order for our tests to run successfully, we also need to tell Truffle that we want to deploy StoryDao - because it won't do that for us. So let's create 3_deploy_storydao.js in the migration using almost the same content as the migration we wrote before:

export default async promise => {
    try {
      await promise;
    } catch (error) {
      const invalidOpcode = error.message.search('invalid opcode') >= 0;
      const outOfGas = error.message.search('out of gas') >= 0;
      const revert = error.message.search('revert') >= 0;
      assert(
        invalidOpcode || outOfGas || revert,
        'Expected throw, got \'' + error + '\' instead',
      );
      return;
    }
    assert.fail('Expected throw not received');
  };
Copy after login
Copy after login

At this point, we should also update (or create, if not) the package.json file in the root of the project folder, containing the dependencies we need so far, and what we may need in the future:

pragma solidity ^0.4.24;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/StoryDao.sol";

contract TestStoryDao {
    function testDeploymentIsFine() public {
        StoryDao sd = StoryDao(DeployedAddresses.StoryDao());

        uint256 daofee = 100; // 百分之几,即 100 为 1%
        uint256 whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币

        uint256 durationDays = 21; // 故事章节持续时间(天)
        uint256 durationSubmissions = 1000; // 故事章节持续时间(条目)

        Assert.equal(sd.daofee(), daofee, "初始 DAO 费用应为 100");
        Assert.equal(sd.whitelistfee(), whitelistfee, "初始白名单费用应为 0.01 以太币");
        Assert.equal(sd.durationDays(), durationDays, "初始天数持续时间应设置为 3 周");
        Assert.equal(sd.durationSubmissions(), durationSubmissions, "初始提交持续时间应设置为 1000 个条目");
    }
}
Copy after login

and .babelrc files containing the following:

import expectThrow from './helpers/expectThrow';

const StoryDao = artifacts.require("StoryDao");

contract('StoryDao Test', async (accounts) => {
    it("should make sure environment is OK by checking that the first 3 accounts have over 20 eth", async () =>{
        assert.equal(web3.eth.getBalance(accounts[0]).toNumber() > 2e+19, true, "Account 0 has more than 20 eth");
        assert.equal(web3.eth.getBalance(accounts[1]).toNumber() > 2e+19, true, "Account 1 has more than 20 eth");
        assert.equal(web3.eth.getBalance(accounts[2]).toNumber() > 2e+19, true, "Account 2 has more than 20 eth");
    });

    it("should make the deployer the owner", async () => {
        let instance = await StoryDao.deployed();
        assert.equal(await instance.owner(), accounts[0]);
    });

    it("should let owner change fee and duration", async () => {
        let instance = await StoryDao.deployed();

        let newDaoFee = 50;
        let newWhitelistFee = 1e+10; // 1 ether
        let newDayDuration = 42;
        let newSubsDuration = 1500;

        instance.changedaofee(newDaoFee, {from: accounts[0]});
        instance.changewhitelistfee(newWhitelistFee, {from: accounts[0]});
        instance.changeDurationDays(newDayDuration, {from: accounts[0]});
        instance.changeDurationSubmissions(newSubsDuration, {from: accounts[0]});

        assert.equal(await instance.daofee(), newDaoFee);
        assert.equal(await instance.whitelistfee(), newWhitelistFee);
        assert.equal(await instance.durationDays(), newDayDuration);
        assert.equal(await instance.durationSubmissions(), newSubsDuration);
    });

    it("should forbid non-owners from changing fee and duration", async () => {
        let instance = await StoryDao.deployed();

        let newDaoFee = 50;
        let newWhitelistFee = 1e+10; // 1 ether
        let newDayDuration = 42;
        let newSubsDuration = 1500;

        await expectThrow(instance.changedaofee(newDaoFee, {from: accounts[1]}));
        await expectThrow(instance.changewhitelistfee(newWhitelistFee, {from: accounts[1]}));
        await expectThrow(instance.changeDurationDays(newDayDuration, {from: accounts[1]}));
        await expectThrow(instance.changeDurationSubmissions(newSubsDuration, {from: accounts[1]}));
    });

    it("should make sure the owner can only change fees and duration to valid values", async () =>{
        let instance = await StoryDao.deployed();

        let invalidDaoFee = 20000;
        let invalidDayDuration = 0;
        let invalidSubsDuration = 98;

        await expectThrow(instance.changedaofee(invalidDaoFee, {from: accounts[0]}));
        await expectThrow(instance.changeDurationDays(invalidDayDuration, {from: accounts[0]}));
        await expectThrow(instance.changeDurationSubmissions(invalidSubsDuration, {from: accounts[0]}));
    })
});
Copy after login

We also need to require Babel in the Truffle configuration so that it knows that it should be used when compiling tests.

Note: Babel is an add-on to NodeJS that allows us to use the next-generation JavaScript in the current generation of NodeJS, so we can write imports and other content. If you don't understand this, just ignore it and paste it verbatim. After installation, you may never have to deal with this issue again.

var Migrations = artifacts.require("./Migrations.sol");
var StoryDao = artifacts.require("./StoryDao.sol");

module.exports = function(deployer, network, accounts) {
  if (network == "development") {
    deployer.deploy(StoryDao, {from: accounts[0]});
  } else {
    deployer.deploy(StoryDao);
  }
};
Copy after login

Now, finally run truffle test. The output should be similar to this:

Building Ethereum DApps: Whitelisting & Testing a Story DAO

For more information on testing, see this tutorial, which we have prepared specifically to cover testing of smart contracts.

In the subsequent section of this course, we will skip the tests because typing them verbatim will make the tutorial too long, but please refer to the project's final source code to check all tests. The process we just finished has an environment set up for the tests, so you can write the tests without further setup.

Whitelist

Let's build a whitelisting mechanism that allows users to participate in building stories. Add the following function framework to StoryDao.sol:

{
  "name": "storydao",
  "devDependencies": {
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-stage-2": "^6.24.1",
    "babel-preset-stage-3": "^6.17.0",
    "babel-polyfill": "^6.26.0",
    "babel-register": "^6.23.0",
    "dotenv": "^6.0.0",
    "truffle": "^4.1.12",
    "openzeppelin-solidity": "^1.10.0",
    "openzeppelin-solidity-metadata": "^1.2.0",
    "openzeppelin-zos": "",
    "truffle-wallet-provider": "^0.0.5",
    "ethereumjs-wallet": "^0.6.0",
    "web3": "^1.0.0-beta.34",
    "truffle-assertions": "^0.3.1"
  }
}
Copy after login

Unnamed function function() is called a fallback function, which is called when funds are sent to this contract but no specific instructions (i.e. no other function is specifically called). This allows people to join StoryDao by sending Ether to DAO only and to whitelist or buy tokens immediately based on whether they are whitelisted or not.

The whitelistSender function is used to whitelist and can be called directly, but we will make sure that the fallback function automatically calls it after receiving some ether, provided the sender is not whitelisted yet. The whitelistAddress function is declared public because it should also be called from other contracts, while the fallback function is external because funds are only sent from external addresses to this address. The contract that calls this contract can easily call the required functions directly.

Let's deal with the fallback function first.

pragma solidity ^0.4.24;

import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol";

contract StoryDao is Ownable {
    using SafeMath for uint256;

    mapping(address => bool) whitelist;
    uint256 public whitelistedNumber = 0;
    mapping(address => bool) blacklist;
    event Whitelisted(address addr, bool status);
    event Blacklisted(address addr, bool status);

    uint256 public daofee = 100; // 百分之几,即 100 为 1%
    uint256 public whitelistfee = 10000000000000000; // 以 Wei 为单位,这是 0.01 以太币

    event SubmissionCommissionChanged(uint256 newFee);
    event WhitelistFeeChanged(uint256 newFee);

    uint256 public durationDays = 21; // 故事章节持续时间(天)
    uint256 public durationSubmissions = 1000; // 故事章节持续时间(条目)

    function changedaofee(uint256 _fee) onlyOwner external {
        require(_fee <= 1000); // 限制最大费用为 10%
        daofee = _fee;
        emit SubmissionCommissionChanged(_fee);
    }

    function changewhitelistfee(uint256 _fee) onlyOwner external {
        require(_fee > 0); // 确保费用大于 0
        whitelistfee = _fee;
        emit WhitelistFeeChanged(_fee);
    }


    function changeDurationDays(uint256 _days) onlyOwner external {
        require(_days >= 1);
        durationDays = _days;
    }

    function changeDurationSubmissions(uint256 _subs) onlyOwner external {
        require(_subs > 99);
        durationSubmissions = _subs;
    }
}
Copy after login
Copy after login
Copy after login
We check if the sender is not whitelisted yet and delegates the call to the whitelistAddress function. Note that we commented out the buyTokens function because we don't have it yet.

Next, let's deal with the whitelist.

modifier onlyOwner() {
  require(msg.sender == owner);
  _;
}
Copy after login
Copy after login
Copy after login
Note that this function accepts an address as an argument, rather than extracting it from a message (from a transaction). This has the added benefit of being able to whitelist others if someone can’t afford to join the DAO, for example.

We start with some sanity checks function: the sender has to be whitelisted or blacklisted (banned) and must have sent enough fees to pay. If these conditions are satisfactory, the address is added to the whitelist, a Whitelisted event is issued, and finally, if the number of ethers sent is greater than the number of ethers required to pay the whitelist fee, the remainder will be used to purchase the tokens.

Note: We use sub instead of - to subtract, because this is the SafeMath function for safe calculations.

As long as users send 0.01 Ether or more Ether to the StoryDao contract, they can now whitelist themselves or others.

Conclusion

In this tutorial, we built the initial part of DAO, but there is still a lot of work to do. Stay tuned: In the next section, we will deal with adding content to the story!

FAQs on building Ethereum DApps and Whitelists

What are the prerequisites for building an Ethereum DApp?

Before starting to build an Ethereum DApp, you need to have a basic understanding of blockchain technology, Ethereum and smart contracts. You should also be familiar with programming languages ​​such as JavaScript and Solidity, which are used to write smart contracts on Ethereum. Additionally, you need to install tools such as Node.js, Truffle, Ganache, and MetaMask that are critical to developing and testing DApps.

How does the whitelisting process work in DApp?

Whitelists are security measures in DApps that restrict access to certain features or areas of the application. It involves creating a list of approved addresses that allow interaction with DApps. Only transactions initiated from these addresses will be accepted, while others will be rejected. This helps prevent unauthorized access and malicious activity.

What is the role of smart contracts in DApps?

Smart contracts are self-executing contracts, and their agreement terms are written directly into the code. They play a vital role in DApps because they automate the execution of business logic on the blockchain. They ensure transparency, security, and immutability because once deployed, they cannot be changed or tampered with.

How to test my DApp?

Testing is a crucial part of DApp development to ensure its functionality and security. You can use tools like Truffle and Ganache to test. Truffle provides Ethereum with a development environment, testing framework, and asset pipeline, while Ganache allows you to create a private Ethereum blockchain for testing.

What is DAO and how does it have to do with DApp?

DAO represents decentralized autonomous organization. It is an organization type represented by rules encoded as computer programs that are transparent, controlled by organizational members and not affected by central government. DAO's financial transactions and rules are kept on the blockchain, which makes it a DApp.

How to ensure the security of my DApp?

Ensure the security of the DApp involves a variety of practices. This includes writing secure smart contracts, thorough testing, performing security audits, and keeping software and dependencies up to date. It is also important to follow best practices for secure coding and stay abreast of the latest security vulnerabilities and threats in the blockchain space.

What is MetaMask and why is it important in DApp development?

MetaMask is a browser extension that allows you to interact with the Ethereum blockchain and DApps directly from your browser. It can also serve as an Ethereum wallet to manage your Ethereum and ERC-20 tokens. It is important in DApp development because it provides users with a user-friendly interface so that users can interact with your DApp without running a full Ethereum node.

How to deploy my DApp?

Once you have developed and tested your DApp, you can deploy it to the Ethereum mainnet or testnet. This involves compiling your smart contracts, deploying them to the blockchain, and connecting your DApps to those contracts. You can complete this process using tools such as Truffle and Infura.

What are the challenges of DApp development?

DApp development faces some challenges. This includes dealing with scalability issues of the Ethereum network, ensuring the security of DApps, managing volatile gas prices for transactions, and providing a user-friendly interface. It also requires keeping an eye on rapidly evolving blockchain technologies and regulations.

How to update my DApp after deployment?

Updating DApps after deployment can be challenging because smart contracts on the blockchain are immutable. However, you can design upgradeable contracts by separating data and logic into different contracts or upgrading contracts using delegated calls. Planning for upgrades and changes during the design phase of the DApp is very important.

The above is the detailed content of Building Ethereum DApps: Whitelisting & Testing a Story DAO. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template