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.
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.
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.
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; } }
This implementation might feel simple and satisfactory at first, but at closer look it has a couple of issues:
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 } ... }
Following this change, CappedCrowdsale no longer needs to override isValid-Investment(), but only isFullInvestmentWithinLimit(), as shown in the following listing.
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; } }
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.
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.
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.
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; } }
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.
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
contract FundingLimitStrategy{ function isFullInvestmentWithinLimit(uint256 _investment, uint256 _fullInvestmentReceived) public view returns (bool); 1 }
You can imagine this is a base contract for all possible funding limit strategies. Here are two possible funding limit strategies:
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; } }
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 } }
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.
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.
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.
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.
This is the main change taking place in SimpleCrowdsale:
contract SimpleCrowdsale { function calculateNumberOfTokens(uint256 investment) internal returns (uint256) ; 1 }
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 } }
Meanwhile, TranchePricingCrowdsale is unaltered from the previous implementation.
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 } //... }
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:
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.
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 } }
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.
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; } } ... }
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; }
You can define a contract that implements this interface by inheriting from it. Here’s an example:
contract ReleasableSimpleCoin is ReleasableToken { 1 ... }
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.
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 } ... }
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 } }
You can see UnlimitedFixedPricingCrowdsaleWithComplexCoin’s contract diagram in figure 7.6.
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:
interface ReleasableToken { function mint(address _beneficiary, uint256 _numberOfTokens) external; function release() external; function transfer(address _to, uint256 _amount) external; }
contract ReleasableSimpleCoin is ReleasableToken { ... }
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.
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:
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.
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.
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.
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.
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 }
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) { }
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 ... } }
When a function is called on a contract, its most overridden implementation, at the bottom of the inheritance hierarchy, will get executed.
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 } }
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 } ... }
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 }
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.
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 } }
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.
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.
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.
Copyright © 2016 Smart Contract Solutions, Inc., http://mng.bz/oNPv, under The MIT License (MIT).
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; } }
A library has the following limitations, compared to a contract:
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.
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; } }
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.
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; } }
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.
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.
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.
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.
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.
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.
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.
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.
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); } }
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
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.