Chapter 6. Writing more complex smart contracts

This chapter covers

  • Building a crowdsale management Dapp showing how to structure complex contracts
  • Extending the crowdsale management Dapp through single and multiple inheritance

The purpose of the previous chapter was to give you a foundation in Solidity, so I focused mainly on the basic syntax that the language offers. In the next two chapters, I’ll introduce more advanced object-oriented (OO) features. I’ll start with inheritance in chapter 6 and follow with abstract contracts and interfaces in chapter 7. These OO features allow you to reduce code duplication and make your contracts more composable.

The contract side of real-world Dapps is generally more complex than the single-contract Dapp you’ve seen so far with SimpleCoin. They often span many contracts interacting with each other, with each contract being a concrete instance of a potentially complex inheritance structure. In this chapter, I’ll help you build Simple-Crowdsale, a basic crowdsale management Dapp. A crowdsale is the process through which investors fund a Dapp by buying tokens issued by the organization that’s developing it. This sample application will give you an idea of how complex the smart contract layer of a realistic Dapp can be and how inheritance, abstract classes, and interfaces can help you model it appropriately.

I’ll try to keep my presentation of inheritance and polymorphism as pragmatic as possible, so if you’re among the readers who need a refresher on object-oriented programming, you’ll still be able to follow easily. I’ll start by building an application made of a simple contract, and I’ll keep extending it throughout this chapter by introducing all the object features I’ve mentioned, bit by bit.

6.1. Introducing SimpleCrowdsale, a crowdsale contract

I bet you’ve heard the term crowdfunding, which is a way of funding a project or a cause through relatively small contribution amounts from a relatively large number of people. You might have even invested some funds toward the design of a new cool gadget at Kickstarter.com, Indiegogo.com, or Microventures.com. If so, depending on the amount of money you contributed and whether the project was successful, you might have been given an early version of the gadget, or you might have been given a considerable discount on the final official price of the product. This type of scheme is called reward-based crowdfunding.

Lately, a new crowdfunding scheme called crowdsale has emerged, mainly geared toward the funding of startup companies. Rather than being given a discounted product or service in recognition for your early contribution, you’re offered some equity in the venture, generally in the form of a monetary token similar to SimpleCoin, whose value the organizers can set before the sale starts. Alternatively, the value can be determined dynamically during the campaign, depending on market factors such as initial token supply and actual demand. Often, the token or coin crowdsale is called an initial coin offering (ICO), an expression that mirrors the more conventional initial public offering (IPO) of shares by companies that enter the stock market for the first time.

In this section, you’ll build SimpleCrowdsale, a decentralized crowdsale management application that will teach you how to design Dapps based on multiple Solidity contracts and libraries. In a nutshell, the following list describes the minimum functionality a crowdsale contract generally provides, as illustrated in the diagram of the core crowdsale workflow in figure 6.1:

  • It manages the funding that crowdsale investors provide during the funding stage, generally in the form of cryptocurrency. It also converts the cryptocurrency received into tokens and assigns them to the respective investors.
  • If the crowdsale objectives, such as a minimum investment target or a time limit, have been met, it releases the tokens to the investors. The organization developing the Dapp keeps the Ether collected and will use it to fund project costs. A token bonus might be granted to the organizers, the development team, or other parties involved with the token sale. Releasing tokens means activating them so they can be used. Investors can exchange tokens through a token exchange for real cash as soon as the token has become profitable with respect to the initial investment. This process is similar to that of a company that goes public and issues shares to investors in exchange for cash, which it will then use to fund its activities. Investors can subsequently trade their shares in a secondary market, and those shares can become more or less valuable depending on the success of the company.
  • If the crowdsale is unsuccessful—the target investment isn’t met, for example—the contract allows the investors to have their investments refunded.
Figure 6.1. The core crowdsale workflow: 1) investors book crowdsale tokens by paying for them in cryptocurrency; 2) if the crowdsale has met the minimum funding target, tokens are released to the investors; a token bonus might be granted to the organizers, the development team, or other parties involved with the token sale; and the project organization keeps the Ether received and will use it to fund project costs; 3) if the crowdsale is unsuccessful, investors can be refunded.

listing 6.1 gives you an idea of the functions a crowdsale Solidity contract would need to fulfill these requirements. (Don’t enter the code in Remix yet!) In case you’re wondering, onlyOwner is the same modifier I introduced earlier in 5.4.2 for SimpleCoin: only the contract owner is allowed to execute the finalize() function.

Listing 6.1. Core functionality a crowdsale contract provides
contract SimpleCrowdsale {    
    function invest(address _beneficiary) 
       public payable {}                      1
    function finalize() onlyOwner public {}   2
    function refund() public {}               3
}

  • 1 Allows an investor to book crowdsale tokens. (No parameter is necessary to specify the amount of Ether being invested because it’s being sent through the msg.value property.)
  • 2 Allows the crowdsale organizer, who is the contract owner, to release tokens to the investors, in case of successful completion, and grant a bonus to the development team, if applicable
  • 3 Allows an investor to get a refund in case of unsuccessful completion

Let’s build a basic implementation of this functionality, which we’ll use as a starting point for a later discussion on advanced object-oriented features. I’m not expecting you to start entering the code into Remix until you reach 6.1.9, as I’ll be refactoring the code progressively to explain concepts step-by-step. Then I’ll recap everything in listing 6.5, which is fully executable. But if you want to give it a try as I go along, you’re welcome to.

6.1.1. State variables

A crowdsale contract needs to maintain some configuration regarding the funding period during which investment contributions are accepted, the price of the token being sold, the minimum investment objective, and the address of the account accepting the investments. It also needs to keep a record of the contributions that investors submit. This data should be visible from the whole contract, so you should express it in the following state variables:

uint256 public startTime;                 1
uint256 public endTime;                   2
uint256 public weiTokenPrice;             3
uint256 public weiInvestmentObjective;    4
  
mapping (address => uint256) 
   public investmentAmountOf;             5
uint256 public investmentReceived;        6
uint256 public investmentRefunded;        7

bool public isFinalized;                  8
bool public isRefundingAllowed;           9
address public owner;                     10
SimpleCoin public crowdsaleToken;         11

  • 1 Start time, in UNIX epoch, of the crowdsale funding stage
  • 2 End time, in UNIX epoch, of the crowdsale funding stage
  • 3 Price of the token being sold
  • 4 Minimum investment objective, which defines if the crowdsale is successful
  • 5 Amount of Ether received from each investor
  • 6 Total Ether received from the investors
  • 7 Total Ether refunded to the investors
  • 8 Flag indicating if the contract has been finalized
  • 9 Flag indicating whether refunding is allowed
  • 10 Account of the crowdsale contract owner
  • 11 Instance of the contract of the token being sold. (You’ll use SimpleCoin as we left it in chapter 5 for the moment.)

6.1.2. Constructor

The contract constructor should take all the input configurations I’ve described, validate them, and instantiate the contract of the token being sold. To make things easy, you’ll use SimpleCoin, because you’re already familiar with it. You shouldn’t find anything surprising in the following code:

constructor(uint256 _startTime, uint256 _endTime, 
    uint256 _weiTokenPrice, uint256 _etherInvestmentObjective) public
{
    require(_startTime >= now);                        1
    require(_endTime >= _startTime);                   1
    require(_weiTokenPrice != 0);                      1
    require(_etherInvestmentObjective != 0);           1

    startTime = _startTime;                            2
    endTime = _endTime;                                2
    weiTokenPrice = _weiTokenPrice;                    2
    weiInvestmentObjective =                           2
 _etherInvestmentObjective * 1000000000000000000;    2

    crowdsaleToken = new SimpleCoin(0);                3
    isFinalized = false;
    isRefundingAllowed = false;
    owner = msg.sender;                                4
}

  • 1 Validates input configurations
  • 2 Sets input configurations into state variables
  • 3 Instantiates the contract of the token being sold in the crowdsale
  • 3 Sets the contract owner, as seen in SimpleCoin

6.1.3. Implementing invest()

This is the most important function of the contract. Its purpose is to accept Ether funds from investors and convert them into crowdsale tokens. These tokens won’t be released to their respective owners, though, until the crowdsale has completed successfully.

As you can see in the following implementation, you’ve declared the invest() function as payable and placed validation logic and token conversion logic in three internal functions. You’ve also declared a couple of events that these functions raise:

event LogInvestment(address indexed investor, uint256 value);
event LogTokenAssignment(address indexed investor, uint256 numTokens);

function invest() public payable {                     1
    require(isValidInvestment(msg.value));             2
    
    address investor = msg.sender;
    uint256 investment = msg.value;
    
    investmentAmountOf[investor] += investment;        3
    investmentReceived += investment;                  3
    
    assignTokens(investor, investment);                4
    emit LogInvestment(investor, investment);          5
}

function isValidInvestment(uint256 _investment) 
    internal view returns (bool) {                     6
    bool nonZeroInvestment = _investment != 0;         7
    bool withinCrowdsalePeriod = 
       now >= startTime && now <= endTime;             8
        
    return nonZeroInvestment && withinCrowdsalePeriod;
}

function assignTokens(address _beneficiary, 
    uint256 _investment) internal {

    uint256 _numberOfTokens = 
       calculateNumberOfTokens(_investment);           9
    
    crowdsaleToken.mint(_beneficiary, 
       _numberOfTokens);                               10
}

function calculateNumberOfTokens(uint256 _investment) 
    internal returns (uint256) {
    return _investment / weiTokenPrice;                11
}

  • 1 Declares the invest() function as payable to accept Ether
  • 2 Checks if the investment is valid
  • 3 Records the investment that each investor contributes and the total investment
  • 4 Converts the Ether investment into crowdsale tokens
  • 5 Logs the investment event
  • 6 Validates the investment
  • 7 Checks that this is a meaningful investment
  • 8 Checks that this is taking place during the crowdsale funding stage
  • 9 Calculates the number of tokens corresponding to the investment
  • 10 Generates the tokens in the investor account
  • 11 Calculates the number of tokens

6.1.4. Implementing finalize()

The purpose of the finalize() function is to execute the closing actions of a crowdsale. If the crowdsale has met its minimum investment objective, the contract releases the tokens to the investors so they can be used. Additionally, a token bonus that depends on the total investment collected could be assigned and released to the development team. On the other hand, if the crowdsale is unsuccessful, it moves to a refunding state, and the investors are allowed to get their investments refunded to their accounts.

Before tokens are released to the investors, they should stay locked down, in an unusable state: token owners shouldn’t be able to perform any operations on them, such as transferring them to other accounts. The contract should only release the initially locked tokens if the crowdsale is successful.

As you know, SimpleCoin operations (apart from minting(), which is restricted to the contract owner) aren’t constrained in any way and don’t depend on the contract owner unlocking any functionality. Consequently, SimpleCoin, as it stands, isn’t suitable for a crowdsale. It seems you must create a modified version of SimpleCoin—say, ReleasableSimpleCoin—whose operations, such as transfer() and transferFrom(), aren’t allowed to work unless the token has been released, as shown in the following listing.

Listing 6.2. ReleasableSimpleCoin, with locked transfer() and transferFrom()
contract ReleasableSimpleCoin {
    bool public released = false;                     1

    ...                                                 2

    function release() onlyOwner {                    3
        released = true;
    }

    function transfer(address _to, uint256 _amount) public {
        require(_to != 0x0); 
        require(coinBalance[msg.sender] > _amount);
        require(coinBalance[_to] + _amount >= coinBalance[_to] );
         
        if (released ) {                              4
            coinBalance[msg.sender] -= _amount;  
            coinBalance[_to] += _amount;   
            emit Transfer(msg.sender, _to, _amount); 

            return true; 
        }
        revert();                                     5
    }

    function transferFrom(address _from, address _to, uint256 _amount) 
        public returns (bool success) {

        require(_to != 0x0);  
        require(coinBalance[_from] > _amount); 
        require(coinBalance[_to] + _amount >= coinBalance[_to] ); 
        require(_amount <= allowance[_from][msg.sender]);  

        if (released ) {                              4
            coinBalance[_from] -= _amount; 
            coinBalance[_to] += _amount; 
            allowance[_from][msg.sender] -= _amount;
            emit Transfer(_from, _to, _amount);

            return true;
        }
        revert();                                     5
        
    }
    

    ...                                                 5
    
}

  • 1 Flag determining whether the token is released
  • 2 Same SimpleCoin code as before, omitted for brevity. (In case you’re expecting inheritance..., it hasn’t come yet.)
  • 3 New function to release the coin. (Only the contract owner can call it.)
  • 4 Now the transfer logic can be executed only if the token has been released.
  • 5 If the token hasn’t been released, the state is reverted.

6.1.5. Small detour: Introducing inheritance

The ReleasableSimpleCoin implementation in listing 6.2 works but has a major problem: it’s duplicating most of SimpleCoin’s code, with obvious maintenance disadvantages. If you decide to make a change in SimpleCoin, you must remember to replicate the same change in ReleasableSimpleCoin, which is time-consuming and error-prone. A way to avoid this is to derive ReleasableSimpleCoin from SimpleCoin through single inheritance and introduce the isReleased modifier, as shown in the following listing.

Listing 6.3. ReleasableSimpleCoin inherited from SimpleCoin
pragma solidity ^0.4.18;
import "./Listing5_8_SimpleCoin.sol";                       1
contract ReleasableSimpleCoin is SimpleCoin {               2
    bool public released = false;                           3

    modifier isReleased() {                                 4
        if(!released) {
            revert();
        }
 
        _;
    }

    constructor(uint256 _initialSupply) 
        SimpleCoin(_initialSupply) public {}                5

    function release() onlyOwner public {                   6
        released = true;
    }

    function transfer(address _to, uint256 _amount) 
        isReleased public {                                 7
        super.transfer(_to, _amount);                       8
    }

    function transferFrom(address _from, address _to, uint256 _amount)  
        isReleased public returns (bool) {                  7
        super.transferFrom(_from, _to, _amount);            8
    }  
}

  • 1 Directive to reference the file where SimpleCoin is defined (listing 5.8 downloadable from the book website)
  • 2 Makes ReleasableSimpleCoin inherited from SimpleCoin
  • 3 Flag determining whether the token is released
  • 4 Modifier encapsulating check on released flag
  • 5 Calls the base constructor to initialize the initialSupply state variable in SimpleCoin
  • 6 New function to release the coin. (Only the contract owner can call it.)
  • 7 Overrides the original implementation. (Thanks to the isReleased modifier, this can be called successfully only if the token has been released.)
  • 8 You call the original SimpleCoin implementation through super. The original implementation is now constrained by the isReleased modifier, though.

The ReleasableSimpleCoin contract has given you a concrete example of single inheritance in Solidity. Let’s summarize the main Solidity keywords involved in inheritance:

  • A contract is derived from another contract with the is keyword, as in
    Contract ReleasableSimpleCoin is SimpleCoin
  • The constructor of the derived contract must feed the base constructor all the necessary parameters, as shown here:
    function ReleasableSimpleCoin(uint256 _initialSupply)
      SimpleCoin(_initialSupply)  {}
  • A derived contract inherits all the public and internal functions from the parent contract. It’s possible to override a function by reimplementing it in the child contract, as in
    function transfer(address _to, uint256 _amount) isReleased public { ...
  • An overridden function can call the original implementation from the base contract using the super keyword, as in
    super.transfer(_to, _amount);

If you’re not entirely familiar with object-oriented terminology, table 6.1 can help you understand it.

Table 6.1. Inheritance terminology

Definition

Explanation

To derive a contract To inherit a contract—Acquire public and internal state variables and functions from the parent contract.
Derived contract Inherited contract, subcontract, or child contract.
Base contract Parent contract.
Overridden function Function reimplemented in a derived contract—The overridden function is used instead of the parent one when the derived contract calls the function.

You can see the inheritance relationship between ReleasableSimpleCoin and Simple-Coin in the contract diagram in figure 6.2. I’ve used the same drawing convention used in UML class diagrams. Although you may be able to understand UML class diagrams intuitively, if you’re not familiar with them and want to learn more, check out the related sidebar.

Figure 6.2. Contract diagram illustrating the token contract hierarchy: SimpleCoin is a base contract, and ReleasableSimpleCoin has been inherited from it.

UML class diagrams

The Unified Modeling Language (UML) is a general-purpose modeling language that aims at standardizing the visualization of the design of a software system. It was developed in the 1990s by Grady Booch, Ivar Jacobson, and James Rumbaugh at Rational Software. It became an ISO standard in 2005.

UML covers a wide range of diagrams, including these categories:

  • Behavior diagrams—Describe how a system works (how the components of the system interact with each other and with external agents)
  • Structure diagrams—Describe the structure of the system at different levels (packages to be deployed, components within a package, and object-oriented classes within a component)

In this book, I’ll only show you contract diagrams, which, given the similarity between the concepts of contract and class, are based on UML class diagrams, the lowest-level structure diagrams.

A contract diagram describes the content of a contract (state variables and functions) and the two main relationships between contracts:

  • Generalization—A contract is inherited from a more general contract (its base or parent contract) or, as you’ll learn later, from an interface.
  • Dependency—One of the state variables is an instance of another contract.

I’ve annotated every section of text and every symbol in the sample contract diagram you see here, so you can refer back to it if you have any trouble understanding the contract diagrams that follow in the coming pages. I’ll explain new symbols as I introduce them in the coming sections.

Contract diagram, with symbols and conventions adapted from the UML class diagram

If you want to learn more about UML diagrams, I encourage you to look up the UML Quick Reference Card, at http://tnerual.eriogerg.free.fr/umlqrc.pdf, or this convenient UML Cheatsheet: http://mng.bz/jO5p. The best book on UML is The Unified Modeling Language User Guide, by Grady Booch, et al, published by Addison-Wesley.

6.1.6. Implementing finalize(), take 2

Now that you’ve implemented a token suitable for a crowdsale, you can go back to the state variables section and replace the definition of crowdsaleToken with

ReleasableSimpleCoin public crowdsaleToken;

and amend the token instantiation in the crowdsale constructor with

crowdsaleToken = new ReleasableSimpleCoin(0);

Now I can show you a possible implementation for the finalize() function:

function finalize() onlyOwner public {                1
    if (isFinalized) revert();                        2

    bool isCrowdsaleComplete = now > endTime;         3
    bool investmentObjectiveMet = 
       investmentReceived >= weiInvestmentObjective;  3
        
    if (isCrowdsaleComplete)
    {     
        if (investmentObjectiveMet)
            crowdsaleToken.release();                 4
        else 
            isRefundingAllowed = true;                5

        isFinalized = true;
    }               
}

  • 1 Allows only the crowdsale contract owner to call finalize()
  • 2 Prevents calling finalize() on a finalized contract
  • 3 Conditions determining if a crowdsale has been successful
  • 4 Releases crowdsale tokens so investors can use them
  • 5 Allows investors to get refunded if the funding objective hasn’t been met

As I mentioned earlier, onlyOwner is the same modifier I introduced in SimpleCoin at the end of chapter 5 to restrict the execution of some functions only to the contract owner:

  modifier onlyOwner {
    if (msg.sender != owner) revert();

    _;
  }

6.1.7. Implementing refund()

The last function you must implement to complete the first version of your crowdsale contract is refund(), which investors would call after an unsuccessful crowdsale:

event Refund(address investor, uint256 value);

function refund() public {
    if (!isRefundingAllowed) revert();                   1

    address investor = msg.sender;
    uint256 investment = investmentAmountOf[investor];
    if (investment == 0) revert();                       2
    investmentAmountOf[investor] = 0;                    3
    investmentRefunded += investment;
    emit Refund(msg.sender, investment);

    if (!investor.send(investment)) revert();            4
}

  • 1 Only allows refunding if this has been allowed at the crowdsale finalization
  • 2 Only allows refunding if the investor has contributed a meaningful amount
  • 3 Keeps a record of all refunds
  • 4 Transfers Ether back to the investor and handles possible transfer error
Note

I’ve decided to refund investors through send() rather than transfer(), only because transfer() has some quirks in Remix (at the time of writing) and might generate unwanted error messages that would slow down your learning experience. In a production environment, transfer() is recommended.

6.1.8. Eliminating code duplication with inheritance

As I noted, SimpleCoin and SimpleCrowdsale use the same onlyOwner modifier to restrict access to some operations to only the contract owner. The advantage is that you’re using onlyOwner consistently across the two contracts. The downside is that you had to introduce the owner state variable and implement the onlyOwner modifier in both contracts. Wouldn’t it be nice to place this modifier somewhere so you could then drop it into both SimpleCoin and SimpleCrowdsale without introducing code duplication? Fortunately, you can by encapsulating the ownership state and onlyOwner modifier into a separate contract, called Ownable, as shown in the following listing.

Listing 6.4. Ownable contract extracted from SimpleCrowdsale and SimpleCoin
pragma solidity ^0.4.18;
contract Ownable {
    address public owner;                1


    constructor() public {
        owner = msg.sender;              2
    }

    modifier onlyOwner() {
        require(msg.sender == owner);    3
        _;
    }
}

  • 1 Keeps the address of the contract owner in a state variable
  • 2 Assigns the contract owner at construction
  • 3 Checks if the function caller using this modifier is the owner

Now you can remove the owner state variable and the onlyOwner() modifier from both SimpleCoin and SimpleCrowdsale and inherit both contracts from Ownable, as shown in the contract diagram in figure 6.3 and in this code:

SimpleCoin is Ownable {
... 
}

SimpleCrowdsale is Ownable {
... 
}
Figure 6.3. After moving ownership functionality into the Ownable contract, both SimpleCoin and Simple-Crowdsale can still use the onlyOwner modifier by inheriting it from Ownable.

You can see the refactored SimpleCoin contract inheriting from Ownable in appendix A. Congratulations! You’ve completed your first implementation of a crowdsale contract, which you can fully appreciate in the contract diagram in figure 6.4 and in listing 6.5.

Note

The arrow between SimpleCrowdsale and ReleasableSimpleCoin in figure 6.4 is a UML symbol that stands for depends on. I’ve included the arrow because the SimpleCrowdsale contract has a state variable of type Releasable-SimpleCoin, so it depends on ReleasableSimpleCoin.

Figure 6.4. Crowdsale’s contract diagram, including Ownable contract and token contract. Both SimpleCrowdsale and SimpleCoin are inherited from Ownable. SimpleCrowdsale has a ReleasableSimpleCoin state variable, so it depends on ReleasableSimpleCoin. (If you’re unfamiliar with objectoriented class diagrams, the hollow-headed arrow means “inherited from,” and the filled arrow means “depends on.”)

Listing 6.5. Initial implementation of a crowdsale contract
pragma solidity ^0.4.24;

import "./Listing6_3_ReleasableSimpleCoin.sol";       1
import "./Listing6_4_Ownable.sol";                    1

contract SimpleCrowdsale is Ownable {
    uint256 public startTime;
    uint256 public endTime; 
    uint256 public weiTokenPrice;
    uint256 public weiInvestmentObjective;
       
    mapping (address => uint256) public investmentAmountOf;
    uint256 public investmentReceived;
    uint256 public investmentRefunded;
    
    bool public isFinalized;
    bool public isRefundingAllowed; 

    ReleasableSimpleCoin public crowdsaleToken; 
    
    constructor(uint256 _startTime, uint256 _endTime, 
      uint256 _weiTokenPrice, 
      uint256 _weiInvestmentObjective) 
      payable public
    {
        require(_startTime >= now);
        require(_endTime >= _startTime);
        require(_weiTokenPrice != 0);
        require(_weiInvestmentObjective != 0);
        
        startTime = _startTime;
        endTime = _endTime;

        weiTokenPrice = _weiTokenPrice;
        weiInvestmentObjective = _weiInvestmentObjective;
    
        crowdsaleToken = new ReleasableSimpleCoin(0);
        isFinalized = false;
    } 
    
    event LogInvestment(address indexed investor, uint256 value);
    event LogTokenAssignment(address indexed investor, uint256 numTokens);
    event Refund(address investor, uint256 value);
    
    function invest() public payable {
        require(isValidInvestment(msg.value)); 
        
        address investor = msg.sender;
        uint256 investment = msg.value;
        
        investmentAmountOf[investor] += investment; 
        investmentReceived += investment; 
        
        assignTokens(investor, investment);
        emit LogInvestment(investor, investment);      
    }

    function isValidInvestment(uint256 _investment) 
        internal view returns (bool) {
        bool nonZeroInvestment = _investment != 0;
        bool withinCrowdsalePeriod = now >= startTime && now <= endTime; 
        
        return nonZeroInvestment && withinCrowdsalePeriod;
    }
    
    function assignTokens(address _beneficiary, 
        uint256 _investment) internal {
    
        uint256 _numberOfTokens = calculateNumberOfTokens(_investment); 
        
        crowdsaleToken.mint(_beneficiary, _numberOfTokens);
    }
    
    function calculateNumberOfTokens(uint256 _investment) 
        internal returns (uint256) {
        return _investment / weiTokenPrice; 
    }
    
    function finalize() onlyOwner public {
        if (isFinalized) revert();
    
        bool isCrowdsaleComplete = now > endTime; 
        bool investmentObjectiveMet = 
    investmentReceived >= weiInvestmentObjective;
            
        if (isCrowdsaleComplete)
        {     
            if (investmentObjectiveMet)

                crowdsaleToken.release();
            else 
                isRefundingAllowed = true;
    
            isFinalized = true;
        }               
    }
    
    function refund() public {
        if (!isRefundingAllowed) revert();
    
        address investor = msg.sender;
        uint256 investment = investmentAmountOf[investor];
        if (investment == 0) revert();
        investmentAmountOf[investor] = 0;
        investmentRefunded += investment;
        emit Refund(msg.sender, investment);

        if (!investor.send(investment)) revert();
    }    
}

  • 1 References Solidity code from other files or from other Remix code tabs

6.1.9. Running SimpleCrowdsale

You’ve just started implementing SimpleCrowdsale, but if you want to see what you’ve built so far in action, copy listing 6.5 into a new Remix code tab (perhaps named Listing6_5_SimpleCrowdsale.sol). Make sure you’ve placed the dependent code specified in the import directives in related code tabs named appropriately:

  • Listing5_8_SimpleCoin.sol (needed for Listing6_3_ReleasableSimpleCoin.sol)
  • Listing6_3_ReleasableSimpleCoin.sol
  • Listing6_4_Ownable.sol

(You can find these files in the code downloadable from the book website.)

Disabling date checks temporarily

Before instantiating SimpleCrowdsale, I recommend you pick version 0.4.24 in the Remix Compiler tab because this is the Solidity version I’ve used. I also suggest you temporarily disable checks on startDate and endDate to make your interaction with Remix easier. Modify this line in isValidInvestment()

bool withinCrowdsalePeriod = now >= startTime && now <= endTime; 

to read

bool withinCrowdsalePeriod = true;

and this line in finalize()

bool isCrowdsaleComplete = now > endTime; 

to read

bool isCrowdsaleComplete = true;
Instantiating the contract

Now you can feed SimpleCrowdsale’s constructor parameters in the text box next to the Deploy button. For example, I’ll set a token price of 2,000,000,000,000,000 Wei and a funding objective of 15,000 Ether as follows:

2003526559, 2003526600, 2000000000000000, 15000

Because of your earlier modification, the 2003526559 and 2003526600 start and end dates will be ignored.

Now pick SimpleCrowdsale from the contract drop-down list and click Deploy. If SimpleCrowdsale doesn’t appear, click the Compile tab and then Start to Compile. Take a note of which account is currently selected in the Account drop-down list at the top of the screen: this will be the contract owner of both SimpleCrowdsale and ReleasableSimpleCoin. SimpleCrowdsale will be activated, and a number of buttons corresponding to its state variables and functions will appear at the bottom of the screen.

Investing into the crowdsale

You can simulate investors’ activity by calling the invest() function as follows:

  1. Click the SimpleCrowdsale drop-down in the Deployed Contracts bottom section generated after you clicked Deploy earlier.
  2. Pick an Account from the drop-down list—perhaps the one starting with 0x147.
  3. Enter an amount in the Value box at the top of the screen, specify the unit (100 Ether, for example), and click the invest button in the SimpleCrowdsale panel within the Deployed Contract bottom section. (You can see this button in figure 6.5.)
  4. Check the total investment received by clicking investmentReceived.

Repeat these three steps with different accounts and investment amounts.

Checking that tokens have been assigned to investors

Although investors at this point have received a number of tokens corresponding to the amount of Ether contributed, they aren’t able to transfer them yet because the owner hasn’t finalized the crowdsale. If you want to check that tokens have been assigned to each investor, you must activate the ReleasableSimpleCoin contract instance referenced in SimpleCrowdsale.

First of all, get its address by clicking crowdsaleToken. Copy this address and paste it in the textbox next to the Activate button (not wrapped with double quotes), as shown in figure 6.5 (but your address will be different):

Figure 6.5. Place in Releasable-SimpleCoin’s At Address text box the address from SimpleCrowdsale's crowdsaleToken state variable.

Now click At Address, and a new panel showing ReleasableSimpleCoin’s functionality, mostly inherited from SimpleCoin, will appear at the bottom part of the screen, as shown in figure 6.6. You must click ReleasableSimpleCoin to expand the panel.

You can now check the number of tokens assigned to each investor in the following way:

  1. Pick the address from the Account drop-down corresponding to an investor you want to check, copy it by clicking the copy icon next to it, and paste it into the text box next to the coinBalance button. (Remember to wrap it with double quotes.)
  2. Click coinBalance, and the expected number of tokens will appear next to it.

Repeat this for all accounts in the drop-down. In my case, I have the token breakdown shown in table 6.2.

Figure 6.6. ReleasableSimpleCoin's state variables and functions

Table 6.2. Number of tokens assigned to each investor

Investor account

Number of tokens

0xca35b7d915458ef540ade6068dfe2f44e8fa733c 10,100,000
0x14723a09acff6d2a60dcdf7aa4aff308fddc160c 150,000
0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db 653,500
0x583031d1113ad414f02576bd6afabfb302140225 284,000
0xdd870fa1b7c4700f2bd7f44238821c26f7392148 56,000
Checking that tokens are still locked

As I explained, tokens that get assigned to investors during the crowdsale are unusable until it’s been finalized successfully. This means an investor won’t be able to transfer tokens to another account. Let’s check that this is the case by trying to transfer some tokens from one account to another.

Pick an account from the Account drop-down, copy its address, and paste it into the transfer text box (wrapped, as usual, with double quotes). This will be the destination account for the token transfer. Then pick a different account from the Account drop-down. This will be the source of the token transfer.

Click Transfer. Now check the number of tokens associated with the source and destination account again, as you did earlier. You’ll notice they haven’t changed. Because the value of released is still false (the crowdsale hasn’t been finalized yet), the transfer won’t take place (but no error will be thrown), as you can see in the code extract from transfer():

function transfer(address _to, uint256 _amount) public {
    ...
         
    if (released) {                             1
        coinBalance[msg.sender] -= _amount;  
        coinBalance[_to] += _amount;   
        emit Transfer(msg.sender, _to, _amount);  
    }
}

  • 1 The value of released is still false, so the token transfer isn’t executed.
Finalizing a successful crowdsale

If you want to test a successful crowdsale, make sure the amount of investment is above the investment objective (15,000 Ether in my example, equivalent to 15,000,000,000,000,000,000,000 Wei). Bear in mind, the amount shown next to the investmentReceived button is expressed in Wei. You can reach it quickly by making a few large Ether investments. Once the investment received is higher than the funding objective, select the SimpleCrowdsale contract owner account (the account you used to instantiate SimpleCrowdsale) and click Finalize.

Verify the crowdsale has been finalized by clicking isFinalized. The value displayed should be true. Verify ReleasableSimpleCoin has been released by clicking Released. The value displayed again should be true.

Now you can try to make a token transfer, as you did earlier. This time, though, the transfer will work because the value of released is now true and the transfer logic can get executed. You can verify that the number of tokens associated with the source and destination accounts have changed accordingly. In my case, I moved three coins from the account starting with 0x1472 to the account starting with 0x4b089. You can see their amended balances in table 6.3.

Table 6.3. Amended ReleasableSimpleCoin token balance after a token transfer

Investor account

Number of tokens

0xca35b7d915458ef540ade6068dfe2f44e8fa733c 10,100,000
0x14723a09acff6d2a60dcdf7aa4aff308fddc160c 149,997
0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db 653,503
0x583031d1113ad414f02576bd6afabfb302140225 284,000
0xdd870fa1b7c4700f2bd7f44238821c26f7392148 56,000
Finalizing an unsuccessful crowdsale

If you’re up for it, you can also test the scenario of an unsuccessful crowdsale. I recommend you restart from scratch and go through all the steps you took earlier up to the point you contribute Ether to the crowdsale through the invest() function from various investor accounts. This time, though, don’t reach the funding objective.

Now, when you call finalize() by clicking the related button, fundingObjectiveMet is false. Consequently, the isRefundingAllowed gets enabled, as you can see in this extract from the finalize() code:

if (isCrowdsaleComplete)
{     
    if (investmentObjectiveMet)
        crowdsaleToken.release();
    else 
        isRefundingAllowed = true;
    
    isFinalized = true;
}    

You can double-check this is the case by clicking isRefundingAllowed. With that enabled, you’ll be able to successfully call the refund() function, as you’ll see next. But before doing so, as an exercise I invite you to test that tokens haven’t been released and it isn’t possible to transfer them from one account to another, as you saw earlier.

Getting a refund

Following unsuccessful finalization, investors are allowed to get refunded. Pick an address from the Account drop-down, check the amount of Ether associated with it (shown next to the address), and click Refund. You’ll see that the Ether value next to the account address will increase because it has been transferred from the crowdsale contract back to the investor.

In the next few sections, you’ll improve the functionality of the crowdsale contract by taking advantage of Solidity object-oriented features, such as single inheritance, which you’ve already used in the ReleasableSimpleCoin contract, multiple inheritance, and abstract classes.

6.2. Extending functionality with inheritance

The current crowdsale implementation assumes the price of the token being bought by the investors is fixed throughout the crowdsale, from start to end. A way to incentivize and reward early investors is to provide tranche-based pricing.

6.2.1. Implementing a new token pricing contract with inheritance

A tranche is a certain amount of total investment received. A different token price applies to each tranche, as shown in figure 6.7, so that early investors are attracted with a lower initial price. As the sale progresses, the token price rises when moving from one tranche to the next and eventually becomes constant after the minimum funding target has been met.

Figure 6.7. Tranche-based token pricing. The total investment up to the minimum funding objective is divided into several tranches, each with a different token price. The token price rises as the total investment received moves from one tranche to the next.

In the example shown in figure 6.7, the minimum funding target has been set at 15,000 Ether, and the organizers have decided to create four tranches, as defined in table 6.4.

Table 6.4. Token-based pricing: example of different prices at different points in the funding process

Total investment received (in Ether)

Token price (in Ether)

Above 15,000 0.005
From 10,000 to 15,000 0.004
From 3,000 to 10,000 0.003
From 0 to 3,000 0.002
Tranche-based token pricing logic

Now that you understand the principle of tranche-based pricing, let’s see how you can implement it in Solidity. If you look back at listing 6.5, you’ll notice that the only function that’s directly accessing the token price is

function calculateNumberOfTokens(uint256 _investment) 
    internal returns (uint256) {
    return _investment / weiTokenPrice;         1
}

  • 1 weiTokenPrice is fixed because it isn’t modified during the crowdsale.

You’ll also notice that the value of weiTokenPrice is never modified from within the crowdsale contract, so you can consider it fixed throughout the whole crowdsale.

Here’s a small challenge: How would you modify the implementation of calculate-NumberOfTokens() so the pricing becomes tranche-based according to the tranches defined in table 6.4? Here are some hints you could consider before attempting a solution:

  • You could configure the tranches with a struct type, such as
    struct Tranche {
        uint256 weiHighLimit;      1
        uint256 weiTokenPrice;     2
    }

    • 1 Higher funding limit of the tranche
    • 2 Token price associated with the tranche
  • You could store the entire tranche structure in a state variable defined with the following mapping
    mapping(uint256 => Tranche) public trancheStructure;
    and initialize the state variable in the contract constructor as follows
    trancheStructure[0] = Tranche(3000 ether, 0.002 ether);
    trancheStructure[1] = Tranche(10000 ether, 0.003 ether);
    trancheStructure[2] = Tranche(15000 ether, 0.004 ether);
    trancheStructure[3] = Tranche(1000000000 ether, 0.005 ether);
    with the tranche limits and token price defined in Ether.
  • You could maintain the current active tranche in a state variable
    uint256 currentTrancheLevel;
    and you’d initialize this state variable in the constructor as follows:
    currentTrancheLevel = 0;
  • You could update currentTrancheLevel and weiTokenPrice within calculate-NumberOfTokens().

I strongly encourage you to give tranche-based pricing a try! Finished? You can compare your modified calculateNumberOfTokens() with that in the following listing.

Listing 6.6. calculateNumberOfTokens() based on tranche-based pricing
function calculateNumberOfTokens(uint256 investment) 
    internal returns (uint256) {
    updateCurrentTrancheAndPrice();          1
    return investment / weiTokenPrice; 
}

function updateCurrentTrancheAndPrice()      2
    internal {
    uint256 i = currentTrancheLevel;
  
    while(trancheStructure[i].weiHighLimit 
       < investmentReceived)                 3
        ++i;
      
    currentTrancheLevel = i;

    weiTokenPrice = 
      trancheStructure[currentTrancheLevel]
         .weiTokenPrice;                     4
}

  • 1 The only change to calculateNumberOfTokens()
  • 2 Updates the current tranche and, consequently, the current token price
  • 3 Tests tranches to identify where investmentReceived falls
  • 4 Updates weiTokenPrice with the value from the current tranche-based pricing crowdsale contract

As you can see in listing 6.6, only one extra function is necessary to calculate the token price based on a tranche basis. The rest of the code stays unaltered. I’m sure you might be wondering where you should place the amended calculateNumberOfTokens() and new updateCurrentTrancheAndPrice() functions. The simple answer is in a new crowdsale contract called TranchePricingCrowdsale, because you might still want to use the flat token pricing in other crowdsales. The next question is should I copy Simple-Crowdsale’s code, paste it into TranchePricingCrowdsale, and apply in it the modifications from listing 6.6? The answer is no, you shouldn’t! As I explained when you created ReleasableSimpleCoin based on SimpleCoin, you’ll be inheriting TranchePricingCrowdsale from SimpleCrowdsale, as shown in the following listing.

Listing 6.7. TranchePricingCrowdsale derived from SimpleCrowdsale
pragma solidity ^0.4.24;

import "./Listing6_5_SimpleCrowdsale.sol";


contract TranchePricingCrowdsale 
   is SimpleCrowdsale  {                                1

   struct Tranche {
     uint256 weiHighLimit;
     uint256 weiTokenPrice;
   }
    
   mapping(uint256 => Tranche) 
      public trancheStructure;                          2
   uint256 public currentTrancheLevel;                  3

   constructor(uint256 _startTime, uint256 _endTime, 
      uint256 _etherInvestmentObjective) 
      SimpleCrowdsale(_startTime, _endTime,
      1, _etherInvestmentObjective)                     4
   payable public
   {
      trancheStructure[0] = Tranche(3000 ether, 
         0.002 ether);                                  5
      trancheStructure[1] = Tranche(10000 ether, 
         0.003 ether);                                  5
      trancheStructure[2] = Tranche(15000 ether, 
         0.004 ether);                                  5
      trancheStructure[3] = Tranche(1000000000 ether, 
         0.005 ether);                                  5
        
      currentTrancheLevel = 0;                          5
   } 
    
   function calculateNumberOfTokens(
      uint256 investment)                               6
      internal returns (uint256) {
      updateCurrentTrancheAndPrice();
      return investment / weiTokenPrice; 
   }

   function updateCurrentTrancheAndPrice()              7
      internal {
      uint256 i = currentTrancheLevel;
      
      while(trancheStructure[i].weiHighLimit < investmentReceived) 
        ++i;
          
      currentTrancheLevel = i;

      weiTokenPrice = trancheStructure[currentTrancheLevel].weiTokenPrice;
   }
}

  • 1 TranchePricingCrowdsale is inherited from SimpleCrowdsale.
  • 2 Configuration of tranche structure
  • 3 Current tranche level with respect to the investment received so far
  • 4 Calling constructor on base contractor to complete the contract initialization
  • 5 Initialization of tranche structure. (I’ve hardcoded this for simplicity, but it could be fed through constructor parameters.)
  • 6 Overrides the original calculateNumberOfTokens() implementation present in SimpleCrowdsale
  • 7 New function to update the token price based on the current tranche

By inheriting TranchePricingCrowdsale from SimpleCrowdsale, I’ve shown you another example of single inheritance. I’ll give you a quick summary of the inheritance features of Solidity you can appreciate in TranchePricingCrowdsale. This is almost a repeat of what you’ve already seen in ReleasableSimpleCoin, and hopefully it will help you consolidate the concepts:

  • TranchePricingCrowdsale is inherited from SimpleCrowdsale with the is keyword:
    contract TranchePricingCrowdsale is SimpleCrowdsale
  • You’ve added additional state variables trancheStructure and currentTranche-Level to the inherited contract to handle tranche-specific functionality.
  • TranchePricingCrowdsale’s constructor sets tranche-related state but also feeds the base SimpleCrowdsale constructor with the required parameters:
    TranchePricingCrowdsale(uint256 _startTime, uint256 _endTime, 
            uint256 _weiTokenPrice, uint256 _etherInvestmentObjective) 
            SimpleCrowdsale(_startTime, _endTime,
               _weiTokenPrice, _etherInvestmentObjective)
        payable public
        {
            trancheStructure[0] = Tranche(3000 ether, 0.002 ether);
            ...
  • You’ve overridden calculateNumberOfTokens()in TranchePricingCrowdsale by providing a new implementation. Contrary to other languages, no special keywords are necessary to override a function in Solidity.

You can see the inheritance relationship between TranchePricingCrowdsale and SimpleCrowdsale in the contract diagram in figure 6.8.

Figure 6.8. Contract diagram illustrating the crowdsale contract hierarchy, with the latest addition of TranchePricing-Crowdsale, derived from SimpleCrowdsale

I invite you to copy TranchePricingCrowdsale’s code from listing 6.7 into a new code tab of Remix and instantiate this contract. Then you can check how the token price rises when the tranche thresholds are hit.

You might have noticed I’ve used the expression single inheritance a few times. The reason I’ve done so is because Solidity also supports multiple inheritance. We’ll explore this in the next section.

6.2.2. Composing functionality with multiple inheritance

Solidity is a young language that’s still in the early stages of its development. Its syntax is continuously improving, and consequently best practice recommendations change frequently. Even the most experienced Solidity developers, who might have a length of experience that would make them barely junior developers in more established languages, have to continuously keep up with learning the latest techniques and recommendations, especially around security.

Realistically, once a contract has been deployed into the public production Ethereum network, it’s unlikely to be modified on a regular basis. But a security flaw might be discovered, either because its developers weren’t aware of a recent recommendation on safer syntax or because someone finds the flaw at the community, node client, or language level.

Given that the possibility of things going wrong is real and the consequent risk of Ether being lost or stolen isn’t negligible, it has become common practice to include various forms of panic buttons in contracts. These range from a pause or halt function, which freezes the state and functionality of the contract until the contract owner decides to switch it on again, to a complete self-destruct function, which transfers the Ether stored in the contract to a safe account before making the contract permanently unusable.

Making a token contract pausable

Enough talking! The following listing shows how to add pausing functionality to ReleasableSimpleCoin, assuming you don’t want to add it to SimpleCoin as well.

Listing 6.8. Adding pausable functionality to ReleasableSimpleCoin
pragma solidity ^0.4.24;
import "./Listing5_8_SimpleCoin.sol";
contract ReleasableSimpleCoin is SimpleCoin { 
    ...                                             1
    
    bool public paused = false;                     2

    modifier whenNotPaused() {
        require(!paused);
        _;
    }

    modifier whenPaused() {
        require(paused);
        _;
    }

    function pause() onlyOwner 
        whenNotPaused public {                      3
        paused = true;
    }

    function unpause() 
        onlyOwner whenPaused public {               3
        paused = false;
    }

    ...                                             4

    function transfer(address _to, uint256 _amount) 
        isReleased whenNotPaused public {           5
        super.transfer(_to, _amount); 
    }

    function transferFrom(address _from, address _to, uint256 _amount) 
        isReleased whenNotPaused 
        public returns (bool) {                     5
        super.transferFrom(_from, _to, _amount); 
    }  
}

  • 1 Same code as before
  • 2 Flag holding the paused state
  • 3 Modifiers holding the paused state
  • 4 Same code as before
  • 5 Guarantees a transfer can only take place when the token contract hasn’t been paused
Extracting pausability functionality

After making ReleasableSimpleCoin pausable, you might want to do the same with SimpleCrowdsale. You might be tempted to duplicate the pausing functionality code you’ve written into SimpleCrowdsale. A smarter way to make both ReleasableSimple-Coin and SimpleCrowdsale pausable—without introducing code duplication—is to extract the pausable functionality from ReleasableSimpleCoin into a separate contract (called Pausable, for example) and then inherit both ReleasableSimpleCoin and SimpleCrowdsale from Pausable.

You can see the new Pausable contract extracted from ReleasableSimpleCoin in the following listing.

Listing 6.9. Pausable contract extracted from ReleasableSimpleCoin
pragma solidity ^0.4.24;
import "./Listing6_4_Ownable.sol";
contract Pausable is Ownable { 
    bool public paused = false;            1

    modifier whenNotPaused() {             2
        require(!paused);
        _;
    }

    modifier whenPaused() {                2
        require(paused);
        _;
    }

    function pause() onlyOwner 
        whenNotPaused public {             3
        paused = true;
    }

    function unpause() onlyOwner 
        whenPaused public {                3
        paused = false;
    }
}

  • 1 State variable holding paused state
  • 2 Modifier allowing function to run depending on paused state
  • 3 Functions changing paused state
Composing base contracts with multiple inheritance

Now you can reapply pausability to ReleasableSimpleCoin by inheriting Releasable-SimpleCoin from Pausable, which, by the way, I’ve inherited from Ownable so it can use the onlyOwner modifier. But, hold on, as you’ll remember, you’re already inheriting ReleasableSimpleCoin from SimpleCoin. Is that a problem? The answer is no. Solidity supports multiple inheritance, so you can inherit a contract from several contracts, as shown here:

contract ReleasableSimpleCoin 
    is SimpleCoin, Pausable {       1
   ...
}

  • 1 ReleasableSimpleCoin is inherited from SimpleCoin and Pausable.

Multiple inheritance can make your code composable because you can create more complex contracts by inheriting from multiple simpler contracts. On the other hand, you should try to compose your contracts so they have only minimal cross dependencies, to avoid circular reference issues.

Note

A circular reference happens when contract C is inherited from contract P, which in turn is inherited indirectly from contract C.

You can see the multiple inheritance relationships present in ReleasableSimpleCoin in figure 6.9.

Figure 6.9. Releasable-SimpleCoin is inherited both from SimpleCoin and Pausable, which is in turn inherited from Ownable.

Making a crowdsale contract pausable

After extracting the pausable functionality into a standalone contract, it’s easy to apply it to other contracts. For example:

contract SimpleCrowdsale is Pausable {
...
}

By inheriting SimpleCrowdsale from Pausable, you’ve also made TranchePricingCrowdsale pausable. You can see the amended crowdsale contract structure in figure 6.10.

Figure 6.10. Amended crowdsale contract structure including Pausable contract

Making a contract destructible

If you’re not comfortable with the Pausable functionality, and you prefer, in case of emergency, to immediately transfer the Ether stored in the contract to a safe address and then destroy the contract so that malicious attackers can no longer manipulate it, you can encapsulate this functionality in a Destructible contract, as shown in the following listing.

Listing 6.10. A Destructible contract
pragma solidity ^0.4.24;
import "./Listing6_4_Ownable.sol";
contract Destructible is Ownable {

   constructor() payable public { } 

   function destroyAndSend(address _recipient) onlyOwner public {
      selfdestruct(_recipient);                                      1
   }
}

  • 1 Destroys the contract after having transferred Ether to a safe specified account using the implicitly declared function selfdestruct() (as explained in 5.3.3)

You can now also make both ReleasableSimpleCoin and SimpleCrowdsale destructible by inheriting them from Destructible, as shown in figure 6.11 and the following snippet:

contract ReleasableSimpleCoin is SimpleCoin, 
   Pausable, Destructible {                   1

   ...
}

contract SimpleCrowdsale is Pausable, 
   Destructible {                             1
   ...
}

  • 1 ReleasableSimpleCoin and SimpleCrowdsale inheriting from Destructible
Figure 6.11. ReleasableSimpleCoin and SimpleCrowdsale are now also Destructible.

So far, you’ve learned how to take advantage of single and multiple inheritance to implement contracts with increasing functionality while avoiding code duplication. In the next chapter, you’ll see how abstract classes and interfaces can help you further toward the maintainability of your contract code base.

Summary

  • The contract side of real-world Ethereum Dapps is generally made of many contracts that interact with each other.
  • A crowdsale management application is an example of a multicontract Dapp.
  • You can extend contract functionality while avoiding code duplication by using inheritance.
  • Multiple inheritance allows you to compose a complex contract from several simpler contracts.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset