Chapter 7. Generalizing functionality with abstract contracts and interfaces

This chapter covers

  • Generalizing contracts by making them abstract
  • Improving contract extensibility through interfaces
  • A summary of Solidity object-oriented features
  • Improving maintainability of utility code using libraries

The previous chapter introduced SimpleCrowdsale, which presented an example of a multicontract decentralized application, with a crowdsale management contract (SimpleCrowdsale) interacting with a token contract (ReleasableSimpleCoin).

You started to extend the functionality of SimpleCrowdsale by adding a new, more complex pricing strategy, and took advantage of contract inheritance to do so. You further extended the functionality by implementing pausability and destructibility as separate contracts and composing them into SimpleCrowdsale through multiple inheritance.

In this chapter, you’ll keep extending SimpleCrowdsale’s functionality by using other object-oriented features, such as abstract contracts and interfaces. I’ll show you how an abstract contract can help you generalize a contract while avoiding code duplication. I’ll also demonstrate how interfaces can add flexibility to the design of your contract so you can choose to plug in one of many possible implementations of a specific aspect of the contract functionality.

As with most languages, you achieve code reuse in Solidity by grouping and organizing functions you use often into shared libraries. I’ll give you an idea of what Solidity libraries look like, and I’ll explain how to call their functions.

You’ll close this chapter by making further improvements to SimpleCoin using some of the object-oriented techniques you’ll have learned in this chapter and the previous chapter. Specifically, I’ll show you how to refactor SimpleCoin so it can comply with the Ethereum ERC20 token standard.

7.1. Making a contract abstract

At the moment, your crowdsale contracts can handle different pricing strategies. You derived TranchedSimpleCrowdsale from SimpleCrowdsale to provide specific (tranche-based) token pricing not available in the parent contract.

7.1.1. Implementing funding limit with inheritance

Let’s imagine a client is interested in your crowdsale contracts, but they’d like a new feature: the ability to cap the crowdsale funding at a maximum total amount above which no further investments are accepted. A quick way to implement this would be to derive a new contract called CappedCrowdsale from SimpleCrowdsale, as shown in the following listing.

Listing 7.1. First implementation of CappedCrowdsale
pragma solidity ^0.4.24;
import "./Listing6_5_SimpleCrowdsale.0.5.sol";

contract CappedCrowdsale is SimpleCrowdsale {
   uint256 fundingCap;                                  1

   constructor(uint256 _startTime, uint256 _endTime, 
      uint256 _weiTokenPrice, uint256 _etherInvestmentObjective, 
      uint256 _fundingCap)
      SimpleCrowdsale(_startTime, _endTime, 
      _weiTokenPrice, _etherInvestmentObjective)        2
      payable public 
   {
      require(_fundingCap > 0);
      fundingCap = _fundingCap;
   }
     
   function isValidInvestment(uint256 _investment)      3
      internal view returns (bool) {
      bool nonZeroInvestment = _investment != 0;        4
      bool withinCrowdsalePeriod = 
         now >= startTime && now <= endTime;            5

      bool isInvestmentBelowCap = investmentReceived + 
         _investment < fundingCap;                      5

      return nonZeroInvestment && withinCrowdsalePeriod 
         && isInvestmentBelowCap;
   }
}

  • 1 State variable for configuring funding cap
  • 2 Configures the rest of the state variables through the base constructor
  • 3 Overrides isValidInvestment()
  • 4 Copies validations from SimpleCrowdsale.isValidInvestment()
  • 5 Checks that the cap limit hasn’t been breached
Issues with the current implementation

This implementation might feel simple and satisfactory at first, but at closer look it has a couple of issues:

  • The implementation of isValidInvestment() has been partially copied from SimpleCrowdsale.isValidInvestment(), creating some code duplication.
  • The current implementation of CappedCrowdsale is inheriting the default token pricing code from SimpleCrowdsale, so you can’t use it as it stands for a capped crowdsale with tranche-based token pricing (because TranchePricingCrowdsale is a child of SimpleCrowdsale).
Removing duplication with a template method

Let’s try to tackle these issues one by one. First, you can avoid the partial code duplication within isValidInvestment()by reimplementing SimpleCrowdsale.isValid-Investment() as

contract SimpleCrowdsale is Ownable {
    ...
    function isValidInvestment(uint256 _investment) 
        internal view returns (bool) {
        bool nonZeroInvestment = _investment != 0;        1
        bool withinCrowdsalePeriod = now >= startTime 
           && now <= endTime;
    
        return nonZeroInvestment && withinCrowdsalePeriod 
           && isFullInvestmentWithinLimit(
           _investment);                                  2
    }
    
    function isFullInvestmentWithinLimit(uint256 _investment)
        internal view returns (bool) {
        return true;                                      3
    }
    ...    
}

  • 1 Previous checks on investment validity
  • 2 Generic check against full investment received so far
  • 3 The default implementation doesn’t perform any check at all; overridden implementations in inherited classes will do, as you’ll see later.

Following this change, CappedCrowdsale no longer needs to override isValid-Investment(), but only isFullInvestmentWithinLimit(), as shown in the following listing.

Listing 7.2. Refactored CappedCrowdsale
pragma solidity ^0.4.18;
import "./Listing7_A_SimpleCrowdsale_forCapped.sol";         1

contract CappedCrowdsale is SimpleCrowdsale {
     uint256 fundingCap;

     function CappedCrowdsale(uint256 _startTime, uint256 _endTime, 
     uint256 _weiTokenPrice, uint256 _etherInvestmentObjective, 
     uint256 _fundingCap) SimpleCrowdsale(_startTime, _endTime,
     _weiTokenPrice, _etherInvestmentObjective)
     payable public 
    {
        require(_fundingCap > 0);
        fundingCap = _fundingCap;
    }
     
    function isFullInvestmentWithinLimit(uint256 _investment) 
        internal view returns (bool) {
        bool check = investmentReceived + _investment
           < fundingCap;                                     2
        return check;
    }
}

  • 1 References a modified version of SimpleCrowdsale implementing isFull-InvestmentWithinLimit
  • 2 This is the check that was being performed previously in the overridden isValidInvestment() function.

The isValidInvestment() function has now become a template method: it’s a high-level function that dictates high-level steps whose logic is delegated to lower level functions, such as isFullInvestmentWithinLimit(), which you override with specific implementations in each derived contract.

Note

A template method is a classic design pattern that appeared in the so-called Gang of Four book: Design Patterns: Elements of Reusable Object-Oriented Software, published by Addison-Wesley.

Limitations of the current funding limit strategy implementation

As I hinted previously, the current functionality to check the cap limit isn’t composable as it stands. If you wanted to cap funding on a crowdsale with tranche-based token pricing, you’d have to implement the CappedTranchePricingCrowdsale contract, as shown in the next listing.

Listing 7.3. Capped funding and tranche-based pricing
pragma solidity ^0.4.24;
import "./Listing7_B_TranchePricingCrowdsale_forCapped.sol";

contract CappedTranchePricingCrowdsale is TranchePricingCrowdsale {
   uint256 fundingCap;                                                1

   constructor(uint256 _startTime, uint256 _endTime, 
      uint256 _etherInvestmentObjective, 
      uint256 _fundingCap)
      TranchePricingCrowdsale(_startTime, _endTime, 
      _etherInvestmentObjective)
      payable public 
   {
      require(_fundingCap > 0);
      fundingCap = _fundingCap;
   }
     
   function isFullInvestmentWithinLimit(uint256 _investment) 
      internal view returns (bool) {
      bool check = investmentReceived + _investment 
         < fundingCap;                                                2
      return check;
   }
}

  • 1 The same state variable introduced in CappedCrowdsale
  • 2 The same implementation written for CappedCrowdsale

As you can see, I’ve copied the code entirely from CappedCrowdsale. This is far from an ideal solution from a maintenance point of view.

Aside from duplication, the current crowdsale contract hierarchy shows another subtler issue: as you can see in figure 7.1, the hierarchy is asymmetrical, and not all contract names tell explicitly what token pricing strategy or funding limit strategy they’re employing. For example, CappedCrowdsale, whose name doesn’t make any reference to any token pricing strategy, inherits from SimpleCrowdsale, which implements fixed token pricing. A more precise name for this contract probably would be CappedFixedPricingCrowdsale. Equally, TranchePricingCrowdsale, whose name doesn’t make any reference to any funding strategy, also inherits from SimpleCrowdsale, which implements an unlimited funding strategy. A more precise name for this contract probably would be UncappedTranchePricingCrowdsale.

7.1.2. Generalizing funding limit strategy with an abstract contract

Now I’ll show you a better solution for implementing CappedCrowdsale and CappedTranchePricingCrowdsale based on the concept of abstract classes. I’ll also show you how abstract classes can make the crowdsale contract hierarchy you saw in figure 7.1 symmetric and more explicit. This involves first encapsulating the funding limit strategy functionality into a completely separate contract, which I’ll sketch for the moment as

Figure 7.1. A UML contract diagram showing asymmetry in the crowdsale contract hierarchy and ambiguity regarding token pricing strategy or funding limit strategy in the name of some contracts. For clarity's sake, I’ve omitted ownable, pausable, and destructible base contracts.

contract FundingLimitStrategy{
    function isFullInvestmentWithinLimit(uint256 _investment, 
        uint256 _fullInvestmentReceived)     
        public view returns (bool);         1
}

  • 1 This is the function that performs the check on the funding cap, although it’s not yet implemented at this stage.

You can imagine this is a base contract for all possible funding limit strategies. Here are two possible funding limit strategies:

  • CappedFundingStrategy—Crowdsale with funding capped by fundingCap limit, as seen earlier in the CappedTranchePricingCrowdsale contract
  • UnlimitedFundingStrategy—Crowdsale with unlimited (or uncapped) funding

You can derive the implementation for a capped crowdsale from FundingLimitStrategy as follows:

contract CappedFundingStrategy is FundingLimitStrategy {
    uint256 fundingCap;                                   1

    constructor(uint256 _fundingCap) public {
        require(_fundingCap > 0);

        fundingCap = _fundingCap;
    }

    function isFullInvestmentWithinLimit(
        uint256 _investment, 
        uint256 _fullInvestmentReceived)                  2
        public view returns (bool) {
        
        bool check = _fullInvestmentReceived + _investment < fundingCap; 
        return check;
    }
}

  • 1 Funding cap limit
  • 2 This is the same implementation you saw earlier in CappedTranche-PricingCrowdsale.

The implementation for an unlimited funding strategy is

contract UnlimitedFundingStrategy is FundingLimitStrategy {
    function isFullInvestmentWithinLimit(
        uint256 _investment, 
        uint256 _fullInvestmentReceived) 
        public view returns (bool) {
        return true;                     1
    }
}

  • 1 No check is performed because the funding is unlimited.

Obviously, you can derive other funding limit strategies from FundingLimitStrategy. For example, you could implement a strategy with a dynamic funding limit, readjusted depending on various factors that might change during the crowdsale.

FundingLimitStrategy is considered an abstract contract because you’ve declared its isFullInvestmentWithinLimit() function but haven’t implemented it. On the other hand, CappedFundingStrategy and UnlimitedFundingStrategy are considered concrete contracts because all of their functions have been implemented.

Definition

A contract is considered abstract if it contains at least one declared but unimplemented function. An abstract contract is used as a base class for other contracts, but it can’t be instantiated. A contract whose functions have all been implemented is considered a concrete contract.

The UML contract diagram in figure 7.2 shows the inheritance hierarchy of funding limit strategy contracts.

Note

You might have noticed that the name of FundingLimitStrategy in figure 7.2 is in italic. This is the UML convention for writing the name of abstract classes.

Figure 7.2. Funding limit strategy contract hierarchy, including an abstract base contract and two concrete child contracts

7.1.3. Improving the token pricing strategy with an abstract contract

You can apply the approach you took to make the funding limit base strategy contract abstract to tidy up the crowdsale contract hierarchy and make the token pricing strategy used by each contract more explicit, as shown in figure 7.3.

Figure 7.3. Making the crowdsale contract hierarchy symmetrical and more explicit. SimpleCrowdsale has been made abstract by pushing the implementation of fixed token pricing down to a new FixedPricingCrowdsale contract.

This is the main change taking place in SimpleCrowdsale:

contract SimpleCrowdsale {
    function calculateNumberOfTokens(uint256 investment) 
       internal returns (uint256) ;                      1
}

  • 1 This function has become abstract and has made SimpleCrowdsale abstract.

The fixed token pricing strategy previously in SimpleCrowdsale has been pushed down to the new FixedPricingCrowdsale contract:

contract FixedPricingCrowdsale is SimpleCrowdsale {     

    constructor(uint256 _startTime, uint256 _endTime, 
        uint256 _weiTokenPrice, uint256 _etherInvestmentObjective, 
        uint256 _fundingCap)
        SimpleCrowdsale(_startTime, _endTime, 
        _weiTokenPrice, _etherInvestmentObjective)
        payable public {
    }


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

  • 1 This formula was in SimpleCrowdsale, and now it has been moved here.

Meanwhile, TranchePricingCrowdsale is unaltered from the previous implementation.

7.1.4. Reimplementing capped crowdsales with no duplication

After having encapsulated the crowdsale funding limit strategy in the FundingLimitStrategy contract hierarchy, and having slightly refactored the crowdsale contract hierarchy, you can attempt to reimplement CappedCrowdsale and CappedTranchePricingCrowdsale, this time avoiding duplication.

First, you have to add the funding limit strategy to SimpleCrowdsale as a state variable: fundingLimitStrategy. You can instantiate a specific funding limit strategy in the constructor through a new function called createFundingLimitStrategy(), which here is declared as abstract and you must implement in the inherited contracts. Then you can use fundingLimitStrategy in the isValidInvestment() function:

contract SimpleCrowdsale is Ownable {
    //...
    
    FundingLimitStrategy internal
        fundingLimitStrategy;                          1
    
    //...
    constructor(...) public {
        ...
        fundingLimitStrategy =
           createFundingLimitStrategy();               2
    }

    //...

    function createFundingLimitStrategy()
        internal returns (FundingLimitStrategy);       3
    
    function isValidInvestment(uint256 _investment) 
        internal view returns (bool) {
        bool nonZeroInvestment = _investment != 0;
        bool withinCrowdsalePeriod = now >= startTime && now <= endTime; 
    
        return nonZeroInvestment && withinCrowdsalePeriod
           && fundingLimitStrategy.
              isFullInvestmentWithinLimit(
                _investment,
                investmentReceived);                  4
    }
    

    //...
}

  • 1 State variable holding the funding limit strategy
  • 2 A specific funding limit strategy is instantiated through createFundingLimitStrategy(), which is declared here as abstract.
  • 3 Instantiates a specific FundingLimitStrategy. It’s abstract, and you must implement it in inherited contracts.
  • 4 The check against the funding limit is performed through the appropriate FundingLimitStrategy contract.

It’s now possible to implement the four concrete crowdsale contracts that result from combining the token pricing strategy and the funding limit strategy in different ways, as shown in the amended crowdsale contract hierarchy in figure 7.4:

Figure 7.4. Reorganized crowdsale contract hierarchy, with two layers of abstract contracts at the top and a bottom layer of concrete contracts encapsulating all combinations of the contract pricing and funding limit options. The FundingLimitStrategy contract hierarchy now encapsulates the checks on the funding limit in an efficient way that avoids code duplication.

  • UnlimitedFixedPricingCrowdsale—Derived from FixedPricingCrowdsale, with UnlimitedFundingStrategy instance
  • CappedFixedPricingCrowdsale—Derived from FixedPricingCrowdsale, with CappedFundingStrategy instance
  • UnlimitedTranchePricingCrowdsale—Derived from TranchePricingCrowdsale, with UnlimitedFundingStrategy instance
  • CappedTranchePricingCrowdsale—Derived from TranchePricingCrowdsale, with CappedFundingStrategy instance
Note

The two intermediate FixedPricingCrowdsale and TranchePricingCrowdsale contracts have become abstract because they don’t implement createFundingLimitStrategy().

The implementation of these four concrete contracts, which you can appreciate in listing 7.4, is succinct. These contracts derive from the abstract crowdsale contract with the relevant token pricing strategy (either FixedPricingCrowdsale or TranchePricingCrowdsale) and implement createFundingLimitStrategy() by returning a specific funding limit strategy. All the work is then delegated to the abstract contracts they derive from.

Listing 7.4. Crowdsale contracts combining different pricing and funding limit options
pragma solidity ^0.4.24;
import "./Listing7_C_FundingStrategies.sol";
import "./Listing7_E_PricingStrategies.sol";

contract UnlimitedFixedPricingCrowdsale is FixedPricingCrowdsale {
    
    constructor(uint256 _startTime, uint256 _endTime, 
    uint256 _weiTokenPrice, uint256 _etherInvestmentObjective)
    FixedPricingCrowdsale(_startTime, _endTime,                1
    _weiTokenPrice, _etherInvestmentObjective)
    payable public  {

    }
    
    function createFundingLimitStrategy() 
        internal returns (FundingLimitStrategy) {
        
        return new UnlimitedFundingStrategy();                 2
    }
}

contract CappedFixedPricingCrowdsale is FixedPricingCrowdsale {
    
    constructor(uint256 _startTime, uint256 _endTime, 
    uint256 _weiTokenPrice, uint256 _etherInvestmentObjective)
    FixedPricingCrowdsale(_startTime, _endTime,                1
    _weiTokenPrice, _etherInvestmentObjective)
    payable public  {
    }
    
    function createFundingLimitStrategy() 
        internal returns (FundingLimitStrategy) {
        
        return new CappedFundingStrategy(10000);               2
    }
}

contract UnlimitedTranchePricingCrowdsale is TranchePricingCrowdsale {
    
    constructor(uint256 _startTime, uint256 _endTime, 
    uint256 _etherInvestmentObjective)
    TranchePricingCrowdsale(_startTime, _endTime,              1
    _etherInvestmentObjective)
    payable public  {
    }
    
    function createFundingLimitStrategy() 
        internal returns (FundingLimitStrategy) {
        
        return new UnlimitedFundingStrategy();                 2
    }
}

contract CappedTranchePricingCrowdsale is TranchePricingCrowdsale {
    
    constructor(uint256 _startTime, uint256 _endTime, 
    uint256 _etherInvestmentObjective)
    TranchePricingCrowdsale(_startTime, _endTime,              1
    _etherInvestmentObjective)
    payable public  {
    }
    
    function createFundingLimitStrategy() 
        internal returns (FundingLimitStrategy) {
        
        return new CappedFundingStrategy(10000);               2
    }
}

  • 1 Only feeds the parent contract
  • 2 The only function implemented; overrides the abstract one in SimpleCrowdsale and provides a specific funding limit strategy

7.2. Allowing multiple contract implementations with interfaces

Before we leave SimpleCrowdsale, I’d like to show you one last object-oriented feature: interfaces. If you’re familiar with other OO languages, you’ll understand immediately how interfaces work in Solidity. If not, I’ll explain them through an example, so you should still be able to pick up the concept quickly.

7.2.1. Setting functional requirements with interfaces

Imagine the client who asked you to customize the crowdsale with the capped funding strategy now wants another change. They’re happy about your crowdsale contract, but they want to support other tokens, not necessarily your ReleasableSimpleCoin. You think this is a fair request that will also provide flexibility to new clients. After analyzing your current code, you realize your crowdsale contracts have only minimal interaction with ReleasableSimpleCoin. The only references to it are in SimpleCrowdsale, the base contract of the hierarchy, as highlighted here:

contract SimpleCrowdsale is Ownable {
    ... 

    ReleasableSimpleCoin public
        crowdsaleToken;                           1
    ...
    
    function SimpleCrowdsale(uint256 _startTime, uint256 _endTime, 
    uint256 _weiTokenPrice, uint256 _etherInvestmentObjective) 
    payable public
    {
        ...
        crowdsaleToken =
           new ReleasableSimpleCoin(0);           2
    } 
    
...
    
    function assignTokens(address _beneficiary, 
        uint256 _investment) internal {
    
        uint256 _numberOfTokens = calculateNumberOfTokens(_investment); 
    
        crowdsaleToken.mint(_beneficiary,
        _numberOfTokens);                         3
    }
    
    ...
    
    function finalize() onlyOwner public {
        ...
            
        if (isCrowdsaleComplete)
        {     
            if (investmentObjectiveMet)
                crowdsaleToken.release();         4
            else 
                isRefundingAllowed = true;
    
            isFinalized = true;
        }               
    }
    

    ...  
}

  • 1 Defines crowdsaleToken as a state variable
  • 2 Initializes crowdsaleToken in the SimpleCrowdsale constructor
  • 3 Mints tokens bought from the investor into their account
  • 4 Releases (unlocks) the token contract so investors can transfer their tokens

From your point of view, as the crowdsale contract developer, you only care that the token used in the crowdsale supports the following two functions:

mint(address _beneficiary, uint256 _numberOfTokens);

release();

Obviously, to be useful to the investor, the token contract should also support at least the following function:

function transfer(address _to, uint256 _amount);

The syntax construct that defines the minimum set of functions the token contract should support is called an interface. The token interface that SimpleCrowdsale would reference would look like this:

interface ReleasableToken {
    function mint(address _beneficiary, uint256 _numberOfTokens) external;
    function release() external;
    function transfer(address _to, uint256 _amount) external;
}

7.2.2. Referencing a contract through an interface

You can define a contract that implements this interface by inheriting from it. Here’s an example:

contract ReleasableSimpleCoin is ReleasableToken {     1
    ...
}

  • 1 ReleasableSimpleCoin already implements ReleasableToken as it stands.

You also can create other implementations:

contract ReleasableComplexCoin is ReleasableToken {
    ...
}

Figure 7.5 shows the relationship between an interface and its implementations. As you can see, you can represent in two ways how a concrete contract implements an interface.

Now you can modify SimpleCrowdsale so that it references the ReleasableToken interface rather than a concrete token contract. You should also instantiate the token contract in an overridden internal function rather than directly in the constructor, as you can see in the following listing.

Figure 7.5. Relationships between an interface and its concrete implementations. You can represent this relationship in two ways. The first one resembles inheritance, with the interface being the parent of its implementations. The second one uses an explicit interface symbol that’s useful if you don’t want to show all the interface implementations.

Listing 7.5. SimpleCrowdsale referencing ReleasableToken
contract SimpleCrowdsale is Ownable {
    ...
    ReleasableToken  public crowdsaleToken;          1
    ...
    
    constructor(uint256 _startTime, uint256 _endTime, 
        uint256 _weiTokenPrice, uint256 _etherInvestmentObjective) 
        payable public {
        ...    
        crowdsaleToken = createToken();              2
        ...
    } 
    
    ...
    
    function createToken() 
        internal returns (ReleasableToken) {
            return new ReleasableSimpleCoin(0);      3
        }
        
    ...   
}

  • 1 Now the crowdsale contract can be any token implementing ReleasableToken.
  • 2 Instantiates the token contract in a function that can be overridden
  • 3 The default implementation that the SimpleCrowdsale abstract contract offers still instantiates ReleasableSimpleCoin.
Note

You also could have declared createToken() as an abstract function within SimpleCrowdsale. This would have been the purest approach, but it would have forced you to implement createToken() in all concrete contracts (such as UnlimitedFixedPricingCrowdsale). The individual implementation of createToken() in each concrete contract would have been the same as in listing 7.5. This duplication might seem unnecessary, though, given that in most cases you’d want to reference ReleasableSimpleCoin anyway. There’s no right or wrong design in this regard, and the solution you choose depends on how you want to balance requirements and technical tradeoffs.

So far, nothing seems to have changed. You start enjoying the benefit of referencing an interface rather than a concrete contract when you implement a crowdsale contract that needs a custom token. Imagine your client wants to use a different token contract, such as

contract ReleasableComplexCoin is ReleasableToken {
...    
}

You can easily implement a new crowdsale contract that supports this token by overriding the createToken() function:

contract UnlimitedFixedPricingCrowdsaleWithComplexCoin 
    is UnlimitedFixedPricingCrowdsale {
    
    constructor(uint256 _startTime, 
    uint256 _endTime, 
    uint256 _weiTokenPrice, uint256 _etherInvestmentObjective)
    UnlimitedFixedPricingCrowdsale(_startTime, _endTime, 
    _weiTokenPrice, _etherInvestmentObjective)
    payable public  {
    }
    
    function createToken()
        internal returns (ReleasableToken) {
            return new ReleasableComplexCoin();        1
        }
}

  • 1 To support a different token contract, you need to override this function.

You can see UnlimitedFixedPricingCrowdsaleWithComplexCoin’s contract diagram in figure 7.6.

Figure 7.6. A contract diagram of UnlimitedFixedPricingCrowdsaleWithComplexCoin. You can appreciate the relationships between the abstract SimpleCrowdsale contract and the ReleasableToken token interface and between the concrete UnlimitedFixedPricing-CrowdsaleWithComplexCoin crowdsale contract and the concrete ReleasableComplexCoin token contract.

As you can see, an interface is a useful construct that increases the flexibility of one element or aspect of your contract (for example, the specific token used in your crowdsale). By referencing an interface rather than a concrete contract (ReleasableToken rather than ReleasableSimpleCoin in our example), your main contract (Simple-Crowdsale) can work seamlessly with any implementation of the interface (ReleasableSimpleCoin or ReleasableComplexCoin, for example). As a result, you’re free to change the behavior of one element of your main contract (in the case we’ve been reviewing, the behavior of the crowdsale token used) without requiring any changes to the contract itself. This ability to switch seamlessly between different implementations of an interface is called polymorphism, and it’s one of the main principles of object-oriented programming.

I’m sure you’re wondering whether you could have achieved the same by making ReleasableToken an abstract contract rather than an interface. You’re right; it would have worked equally well, but by doing so you’d introduce in your code base a contract you don’t fully need yet. What you need at this stage is only the definition of the minimum functionality that SimpleCrowdsale requires to interact with a token. That’s exactly the purpose of an interface.

Before leaving interfaces, let me quickly summarize how they work:

  • An interface defines the set of functions that must be implemented; you must declare them with external visibility:
    interface ReleasableToken {
        function mint(address _beneficiary, uint256 _numberOfTokens) external;
    
        function release() external;
        function transfer(address _to, uint256 _amount) external;
    }
  • A contract implements an interface by inheriting from it:
    contract ReleasableSimpleCoin is ReleasableToken {
        ...
    }
  • A contract must implement all the functions present in the interface it inherits from.

Congratulations! You’ve now completed a full implementation of a crowdsale contract hierarchy. This allows you to set up crowdsales with different token contracts, token pricing strategies, or funding limit strategies. The fruits of your hard work are shown in appendix B. I encourage you to take a break, browse through the code, and try to digest it slowly. You’ll appreciate how all the pieces of the puzzle you’ve built in the last two chapters have come together. I’m sure that by looking at the entire code all the concepts you’ve learned will settle further into your head.

7.3. Real-world crowdsale contracts

Although SimpleCrowdsale is a good starting point, real-world crowdsale contracts can get much more complex because they offer much more functionality, such as the following:

  • A prefunding stage, during which early investors provide initial funding, which gets converted into tokens, possibly at a special price, at the beginning of the public crowdsale
  • More sophisticated token pricing strategies
  • A funding limit based on the number of tokens purchased
  • Finalization strategies, including the possibility of distributing additional tokens to the development team, the organizers, or the legal and advisory team
  • Tokens with various characteristics
Warning

In these two introductory chapters on Solidity, I haven’t touched on security aspects you must be aware of before deploying a contract onto the public Ethereum network. I’ll cover them in chapter 14.

The main objective of SimpleCrowdsale was to teach you inheritance, abstract classes, and interfaces in Solidity through a realistic use case, as well as to give you some technical details of how a crowdsale works. If you’re interested in learning more about how to build a decentralized crowdsale management application, or you’re curious to see how complex a real-world Dapp can be, I strongly encourage you to study the code of the TokenMarketNet ICO[1] GitHub repository, one of the best open source Ethereum crowdsale implementations, at https://github.com/TokenMarketNet/ico.

1

Copyright © 2017 TokenMarket Ltd., Gibraltar, https://tokenmarket.net. Licensed under the Apache License, V2.0.

I encourage you to quickly review the contracts in this repository and compare them with the respective SimpleCrowdsale ones you saw earlier. You’ll realize how complex a real-world Dapp can become both in size (number of contracts) and implementation.

A little challenge

As an exercise, I now leave you to try on your own to implement different finalization strategies, which you could encapsulate in yet another contract hierarchy with a base abstract contract, such as FinalizationStrategy. You could then create a new set of concrete contracts in which you inject such strategy at construction, as you’ve done when injecting the funding strategy.

7.4. Recap of Solidity’s object-oriented features

If you’re still alive after the hard work you’ve done so far in this chapter, I’m going to give you a break now. I’ll summarize and generalize all you’ve learned through Simple-Crowdsale. In case you forget some details, you won’t have to dig through the previous code listings and snippets to find the syntax associated with object-oriented features such as inheritance, abstract classes, and interfaces. Sit back and relax.

7.4.1. Inheritance

Solidity supports multiple inheritance, so a derived contract can inherit state variables and functions from one or more contracts, as shown here:

contract Parent1 {

    int256 public stateVar1;
    bool public stateVar2;

    function initialize() public {}
    function Parent1(int256 param1, bool param2) public {}

    function operation1(int256 opParam1) public returns (int256) {}

}

contract ParentA {

    int256 public stateVarA;
    int16 public stateVarB;

    function initialize() public {}
    function ParentA(int256 paramA, int16 paramB) public {}

    function operationA(bool opParamA) public {}
}


contract Child is Parent1, ParentA {        1

}

  • 1 Child inherits the state variables stateVar1, stateVar2, stateVarA, and stateVarB, and the functions operation1() and operationA() from its parent contracts, Parent1 and ParentA
Calling base constructors

The constructor of a derived contract must call all the parent constructors (in the order you want them to be called):

function Child(int256 p1, bool p2, int256 pA, int16 pB) 
    Parent1(p1, p2) ParentA(pA, pB) {
}
Overriding functions

A derived contract can override any function inherited from its parent contracts by reimplementing it:

contract Child is Parent1, ParentA { 
    ...
    function operationA(bool opParamA) public {     1
    ...
    }
}

  • 1 New implementation that replaces ParentA.operationA()

When a function is called on a contract, its most overridden implementation, at the bottom of the inheritance hierarchy, will get executed.

Calling base functions

An overridden function can call a function located in a base contract, as follows:

contract Child is Parent1, ParentA {
    ...
    function operationA(bool opParamA) public {
        ParentA.operationA(opParamA);             1
    }
}

  • 1 Calls ParentA.operationA()

In some cases, you might want to make sure all the base implementations of a function are called. In those cases, you can call all of them implicitly with the super keyword:

contract Child is Parent1, ParentA { 
    ... 
    
    function initialize() public {
        ...
        super.initialize();        1

    }
    
    ...
}

  • 1 Calls Parent1.initialize() followed by ParentA.initialize()

7.4.2. Abstract contracts

A contract is considered abstract, rather than concrete, if at least one of its functions is abstract, which means it has been declared but not implemented, as is the case with operationA() in this contract:

contract AbstractContract {
    int256 public stateVar;
       
    constructor(int256 param1)  public {
        stateVar = param1;
    }

    function operation1(int256 opParam1, bool opParam2)
        returns (int256) {
        if (opParam2) return opParam1;
    }

    function operationA(int256 opParamA);       1
}

  • 1 This is an abstract function, which makes AbstractContract abstract.

As in other statically typed languages, Solidity abstract contracts can’t be instantiated; they can only be used as base contracts for other abstract or concrete contracts.

7.4.3. Interfaces

Interfaces in Solidity are similar to those offered in Java and C#. An interface defines the set of functions and events that must be implemented in their derived contracts, but it doesn’t provide any implementation. All the functions declared on an interface are abstract, as shown here:

interface SampleInterface {
    function operation1(int256 param1, bool param2) external;

    function operationA(int16 paramA) external;
}

A contract derived from an interface must implement all of its functions, as shown here:

contract SampleContract is SampleInterface {
    function operation1(int256 param1, bool param2) {
                                                      1
    }

    function operationA(int16 paramA) {
                                                      2
    }
}

  • 1 Implementation of operation1
  • 2 Constructor definition
Note

You can’t define any variables, structs, or enums on an interface. Also, you can’t derive an interface from other interfaces.

Although you can avoid code duplication by factoring a good class hierarchy where contracts at the bottom reuse function implementations located in their base classes, often common shareable logic isn’t specific to the domain of a contract hierarchy and has a more generic purpose. For example, functions that manipulate low-level data structures, such as arrays, byte arrays, or strings, in a generic way might be useful in any contract. The naive way to import such functionality is to copy and paste the required functions from a function repository into the contracts that need them. But there’s a much smarter way. Enter libraries.

7.4.4. A word of caution

I’ve presented in this chapter all of Solidity’s object-oriented features, and I’ve shown you how real-world Dapps, such as TokenMarketNet, have been designed using advanced object-oriented principles. But remember that Solidity is meant to be used only to implement smart contracts and not rich general-purpose applications, so in most cases, you might not need a complex OO design after all. People fall into different schools of thought when it comes to designing smart contracts: whereas some are comfortable with taking advantage of Solidity’s rich OO feature set, most Ethereum developers prioritize simplicity and security and are willing to sacrifice long-term maintainability to gain short-term predictability. I believe the latter approach is sensible, especially while you’re still learning this new technology.

7.5. Libraries

A Solidity library is a shared function repository similar in purpose to a Java class package or a .NET class library. The code of a library looks like that of a C# or C++ static class, and it contains a collection of stateless functions. It can also include struct and enum definitions. You can get an idea of what a library looks like in listing 7.6, which shows SafeMath, a collection of functions to execute math operations that include safety checks around incorrect input or overflows. This library is part of OpenZeppelin,[2] an open source framework to build secure smart contracts in Solidity, which aims at standardizing common functionality required by most Solidity developers.

2

Copyright © 2016 Smart Contract Solutions, Inc., http://mng.bz/oNPv, under The MIT License (MIT).

Listing 7.6. SafeMath library for performing checked math operations
library SafeMath {                                    1
    //Taken from: https://github.com/OpenZeppelin/
    function mul(uint256 a, uint256 b)
        public pure returns (uint256) {               2
        if (a == 0) return 0;
        uint256 c = a * b;
        assert(c / a == b);                           3
        return c;
    }

    function div(uint256 a, uint256 b) 
        public pure returns (uint256) {               2
        uint256 c = a / b;
        return c;
    }

    function sub(uint256 a, uint256 b) 
        public pure returns (uint256) {               4
        assert(b <= a);                               5
        return a - b;
    }

    function add(uint256 a, uint256 b) 
        public pure returns (uint256) {               4
        uint256 c = a + b;
        assert(c >= a);                               5
        return c;
    }
}

  • 1 Uses the library keyword instead of the contract keyword
  • 2 Functions in a library are defined exactly as in contracts.
  • 3 Checks on the input or on the result of the arithmetic operation
  • 4 Functions in a library are defined exactly as in contracts.
  • 5 Checks on the input or on the result of the arithmetic operation

A library has the following limitations, compared to a contract:

  • It can’t have state variables.
  • It doesn’t support inheritance.
  • It can’t receive Ether. (You can’t decorate a function with the payable keyword and send Ether to it while invoking it.)

7.5.1. Library function invocation

A contract can reference a local copy of a library (located in the same .sol code file) directly by its name, as shown in listing 7.7. As you can see, you invoke library functions by prefixing them with the library name, like you invoke static functions in other languages. You also have to prefix library structs and enums with the library name.

Listing 7.7. How to call library functions
pragma solidity ^0.4.24;
import './Listing7_6_SafeMath.sol';

contract Calculator {
    function calculateTheta(uint256 a, uint256 b) public returns (uint256) {
        uint256 delta = SafeMath.sub(a, b);                               1
        uint256 beta = SafeMath.add(delta, 
           1000000);                                                      1
        uint256 theta = SafeMath.mul(beta, b);                            1
        
        uint256 result = SafeMath.div(theta, a);                          1
        
        return result;
    }
}

  • 1 SafeMath library functions are all prefixed with SafeMath.

7.5.2. Deployed libraries

True code reuse takes place when only one instance of the library is deployed on the network and all contracts accessing it reference it through its deployment address (conceptually the same as a contract address). Once deployed, the functions of a library are exposed with implicit external visibility to all the contracts referencing it. The usual way of calling a deployed library from a contract is to define a local abstract contract that matches the signature of the deployed library. Then you communicate with the library through this local abstract contract, which acts as a strongly typed proxy to the library, as shown in the following listing. The alternative would be to invoke the library functions directly through call(), but by doing so, you wouldn’t guarantee type safety.

Listing 7.8. How to call library functions from a contract
pragma solidity ^0.4.24;
contract SafeMathProxy {                                  1
    function mul(uint256 a, uint256 b) 
        public pure returns (uint256);                    2
    function div(uint256 a, uint256 b)  
        public pure returns (uint256);                    2
    function sub(uint256 a, uint256 b) 
        public pure returns (uint256);                    2
    function add(uint256 a, uint256 b) 
        public pure returns (uint256);                    2
}

contract Calculator {

    SafeMathProxy safeMath;   
    
    constructor(address _libraryAddress) public           3

    {
        require(_libraryAddress != 0x0);
        safeMath = SafeMathProxy(_libraryAddress);        4
    }

    function calculateTheta(uint256 a, uint256 b) 
        public returns (uint256) {

        uint256 delta = safeMath.sub(a, b);               5
        uint256 beta = safeMath.add(delta, 
           1000000);                                      5
        uint256 theta = safeMath.mul(beta, b);            5
        
        uint256 result = safeMath.div(theta, a);          5
        
        return result;
    }
}

  • 1 This local abstract contract emulates the functionality offered by the library.
  • 2 These are the same function declarations present in the SafeMath library.
  • 3 SafeMath library address (copied from Remix, as show in figure 7.8)
  • 4 References the SafeMath library deployed at the specified address through an implicit constructor available for all contracts that takes the contract address as a parameter
  • 5 Calls to the deployed SafeMath library

Before trying this code, put the listing 7.6 code into a Remix code tab and instantiate the SafeMath library (by clicking Deploy, as for contracts). Copy the address by clicking the copy icon next to the contract instance panel, as shown in figure 7.7.

Figure 7.7. Get the SafeMath library address from Remix by clicking the copy icon.

Now enter the code from listing 7.7 into a new Remix code tab. Then paste the SafeMath library address you copied earlier into the Calculator constructor text box next to the Deploy button, as show in figure 7.8. (Remember to wrap it with double quotes.) Click Deploy, and the Calculator contract gets instantiated. Now you can call calculate-Theta() by entering a couple of values into its input parameters text box—for example, 200, 33—and clicking calculateTheta.

Figure 7.8. Copy the SafeMath library address into the Calculator constructor text box.

After you click calculateTheta, various calls to remote SafeMath functions are performed through the safeMath proxy instance: sub(), add(), mul(), and div() are executed in sequence and result is calculated. The output panel on the bottom left of the screen shows that calculateTheta()’s function completed successfully. You can then see the result by clicking the arrow next to the Debug button and checking the Decoded Output field, as shown in figure 7.9.

Figure 7.9. After the call to calculateTheta() completes, you can click Details and check the result in the Decoded Output field.

7.5.3. Library function execution

When a library function is called, its code is executed within the context of the calling contract. For example, if the code of a function library references msg, this isn’t the message sent by the contract to the library, but the message received by the contract from its caller. Also, during a library function invocation, only the calling contract, not the library itself, accesses storage directly. This means library functions manipulate the value of any reference type variables with storage data locations passed to the functions. As mentioned earlier, libraries don’t permanently hold any objects at all.

Note

A library function is executed in the context of the calling contract because it’s invoked through the DELEGATECALL opcode rather than the CALL opcode.

In the previous chapter, I introduced a small improvement to SimpleCoin by extracting the ownership functionality out to the Ownable contract. In this chapter, I’ll present a new enhancement related to token standardization.

7.6. Making SimpleCoin ERC20 compliant

Creating a custom cryptocurrency or token contract has become such a common requirement for most decentralized applications that a standard token interface has been proposed. Such an interface would allow any contract (such as your Simple-Crowdsale contract) to interact with a token contract in a predictable way. The standard Ethereum token contract is called ERC20. The following listing shows the standard token functionality every ERC20-compliant token is expected to provide, expressed as an abstract contract.

Listing 7.9. ERC20 abstract contract, defining the Ethereum standard token
pragma solidity ^0.4.24;
contract ERC20 {
    uint256 public totalSupply;
    function balanceOf(address _owner) 
       public view returns (uint256 balance);
    function transfer(address _to, uint256 _value) 
       public returns (bool success);
    function transferFrom(address _from, address _to, uint256 _value) 
       public returns (bool success);
    function approve(address _spender, uint256 _value) 
       public returns (bool success);
    function allowance(address _owner, address _spender) 
       public view returns (uint256 remaining);
    
    event Transfer(address indexed _from, 
       address indexed _to, uint256 _value);
    event Approval(address indexed _owner, 
       address indexed _spender, uint256 _value); 
}

If you compare this with your latest implementation of SimpleCoin, shown in appendix A, which includes the modifications around contract ownership I introduced in chapter 5, you’ll notice your token is almost ERC20-compliant. Table 7.1 summarizes the main differences between SimpleCoin and the ERC20 specification.

Table 7.1. Differences between the ERC20 specification and SimpleCoin

ERC20 specification

SimpleCoin equivalent

totalSupply Not available
balanceOf() coinBalance()
approve() authorize()
allowance() Not available (direct use of allowance state variable)
Approval Not available

You can use table 7.1 to refactor SimpleCoin into a fully compliant ERC20 token. The following listing shows what such an implementation would look like, also taking into account the standard parameter names of functions and events.

Listing 7.10. SimpleCoin refactored to an ERC20 token
pragma solidity ^0.4.24;

import "./Listing6_4_Ownable.sol";
import "./Listing7_9_ERC20.sol";

contract SimpleCoin is Ownable, ERC20 {
    
   mapping (address => uint256) 
      internal coinBalance;                                                1
   mapping (address => mapping 
      (address => uint256)) internal allowances;                           1
   mapping (address => bool) public frozenAccount;
    
   event Transfer(address indexed from, address indexed to, uint256 value);
   event Approval(address indexed authorizer, address indexed authorized, 
      uint256 value);                                                      2
   event FrozenAccount(address target, bool frozen);
       
   constructor(uint256 _initialSupply) public {
      owner = msg.sender;

      mint(owner, _initialSupply);
   }
    
   function balanceOf(address _account)                                    3
      public view returns (uint256 balance) {
      return coinBalance[_account];
   }

   function transfer(address _to, uint256 _amount) public returns (bool) {
      require(_to != 0x0); 
      require(coinBalance[msg.sender] > _amount);
      require(coinBalance[_to] + _amount >= coinBalance[_to]);
      coinBalance[msg.sender] -= _amount;  
      coinBalance[_to] += _amount;   
      emit Transfer(msg.sender, _to, _amount); 
      return true;
   }
    

   function approve(address _authorizedAccount, uint256 _allowance) 
      public returns (bool success) {
      allowances[msg.sender][_authorizedAccount] = _allowance; 
      emit Approval(msg.sender, 
         _authorizedAccount, _allowance);                                  4
      return true;
   }
    
   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 <= allowances[_from][msg.sender]);  
      coinBalance[_from] -= _amount; 
      coinBalance[_to] += _amount; 
      allowances[_from][msg.sender] -= _amount;
      emit Transfer(_from, _to, _amount);
      return true;
   }
    
   function allowance(address _authorizer, 
      address _authorizedAccount)                                          5
      public view returns (uint256) {
      return allowances[_authorizer][_authorizedAccount];
   }
    
   function mint(address _recipient, uint256  _mintedAmount) 
      onlyOwner public { 
            
      coinBalance[_recipient] += _mintedAmount; 
      emit Transfer(owner, _recipient, _mintedAmount); 
   }
    
   function freezeAccount(address target, bool freeze) 
      onlyOwner public { 

      frozenAccount[target] = freeze;  
      emit FrozenAccount(target, freeze);
   }
}

  • 1 These state variables have become internal. They’re now exposed externally through dedicated functions.
  • 2 New event associated with approving an allowance
  • 3 Allows you to check coinBalance externally
  • 4 Raises an event when a balance is approved
  • 5 Allows you to check allowances externally

Although the token constructor isn’t part of the interface and consequently isn’t included in the standard, ERC20 recommends initializing a token with the following useful information:

string public constant name = "Token Name";
string public constant symbol = "SYM";
uint8 public constant decimals = 18;         1

  • 1 Number of decimal digits to consider in fractional token amounts

The ERC20 wiki (http://mng.bz/5NaD) also shows a recommended implementation. Although this is similar to SimpleCoin, I still encourage you to review it. I suggest you also have a look at the OpenZeppelin section on tokens at http://mng.bz/6jQ6.

Summary

  • Abstract contracts generalize contract functionality and help minimize code duplication by providing default function implementations when appropriate.
  • An interface provides the declaration of the minimum set of functions a contract should provide.
  • You can seamlessly change the behavior of one element or aspect of a contract by modeling it with an interface. By doing so, you can instantiate a different implementation of the interface without having to alter the code of the contract using it.
  • You can organize general purpose Solidity functions in libraries, which you can share across different contracts or applications.
  • Creating a custom cryptocurrency or token contract is such a common requirement for most decentralized applications that a standard token interface called ERC20 has been proposed. Any decentralized application can use a token contract that follows the ERC20 standard in a predictable way.
..................Content has been hidden....................

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