Smart Contract is similar to a class in object oriented programming. A smart contract can contain the following parts:
- Version Pragma
- Comments
- State Variables
- Functions
- Function Modifiers
- Events
- Structs Types
- Enums Types
Below is the brief definition of these components. In the subsequent blogs I will go in depth into each of these components:
Version Pragma
This pragma directive tells the compiler to use the correction version to compile the contract and to reject compiling with an incompatible compiler. This annotation ensures that our code is always compiled correctly as we intended.
The version pragma is used as follows:
pragma solidity ^0.4.14;
This directive is optional, but is highly recommended to annotate every source file with this version pragma. The version pragma uses semantic versioning and is denoted by [major, minor, patch] tuple. As shown above 0.4.14.
In the above version directive we used caret range to specify the supported compilers. The caret range allows compiler greater than the specified version and does not allow compiler greater than the left most non-zero digit. In our definition the left most non-zero digit is 4 (0.4). So the caret range allows compiler greater than the specified version (0.4.14) and does not allow greater than the left most non-zero digit (0.5.0). In other words, only compiler with version >= 0.4.14 and < 0.5.0 be allowed to compile to compile our contract code. This version pragma directive can also include prerelease tags such as alpha, beta. Below are examples of caret ranges:
- ^0.4.14 := >=0.4.14 and <0.5.0
- ^1.2.3 := >=1.2.3 and <2.0.0
- ^0.2.3 := >=0.2.3 <0.3.0
- ^0.0.3 := >=0.0.3 <0.0.4
The solidity compiler can use complex rules for identifying the correct compiler version. But this may be rarely used. Unless you want to target a specific version range, you don’t need to go that complex.
State Variables
State Variables are values which are permanently stored in contract storage. These are similar to the class variables.
pragma solidity ^0.4.14;
contract Service {
address client; // State variable to store client who requests service
address contractor; // State variable to store contractor for the service
address platform; // State variable to store 0x address
}
In the above contract we defined three state variables called client, contractor and platform which are of type address. We use these variable to store the address of the client who requested service, contractor who has hired to perform the service and 0x platform who facilitates the interaction between client and contractor. I will revisit State Variables once we cover more details.
Functions
Functions are the executable units of code within a contract. Again these are similar to the functions in the object oriented world.
A contract can have the following types of functions
- Member functions
- Constructor
- Fallback function
- Constant functions
Member functions
These are the functions which modify the state variables of the contract and performs transactions that are stored in the block chain. These functions typically don’t have return values.
// hiring the contractor to perform the work
function hire (address _contractor) {
contractor = _contractor;
}
In the above code the client of the service requestor finalized the contractor and is hiring the contractor to perform the service. As you see this function is updating the contractor state variable.
Constructor
A constructor is a special type of member function. The constructor is called only once for the life of the contract and during the initialization of the contract. Hence you can have all your initialization logic in this constructor. As show below I am storing the client address, one who creates the contract (msg.sender is the account which initializes the contract) and the 0x platform address which can be used to arbitrage between client and contractor.
// creates the service for the client
// 0x will be platform provider
function Service(address_platform) {
client = msg.sender;
platform = _platform;
}
Fallback function
Fallback function is a function which does not have a function name. This function is invoked whenever a contact is called with a function name that does not exist. For example if user calls a function called “close” and this close function does not exist in the contract, then the fallback function will be called. In our code we don’t want users to call functions that don’t exist, hence reverting the call.
//default function
function() {
revert();
}
A contract can have exactly one unnamed function. This function cannot have arguments and cannot return anything.
Constant function
A constant function is used in scenarios where you want to read the state variables of the contract and don’t want to update their state. Constant functions prevent updating the state variables of the contract. In our 0x contract we will have a validate function which checks the terms and conditions of our smart contract. For this POC the actual validation is out of scope. So let’s assume that the validation is always successful and return true as shown below:
// validate the terms of the contract
function validate() constant returns (bool) {
// for POC we are doing additional validations
return true;
}
Function Modifiers
Function Modifiers are used to amend the semantics of a function in a declarative way. These can be used to automatically check a condition prior and/or after the execution of a function. Here is an example on how Function Modifiers are used in our 0x service contract
// modifier to ensure client can only execute a function
modifier onlyClient() {
require(msg.sender == client);
_;
}
function hire (address _contractor) onlyClient { // using function modifier
contractor = _contractor;
}
In the above code we defined a function modifier, onlyClient, in which we are checking whether the caller of the contract function is a client. msg.sender contains the address of the caller. Require checks and validates the condition is true before proceeding. If the condition fails, requires throws an exception and exits the function.
The special symbol underscore “_” is very important in the modifier. The body of the actual function will be inserted and executed at this point. This enables us to use function modifiers before and/or after the execution of the function.
A function can have multiple function modifiers. These modifiers should be specified in a whitespace separated list and these modifiers are evaluated in the order present.
Structs Type
Structs are the custom defined types in Solidity. These are used to group several variables. In our 0x service we will be defining a custom type, struct, Bidder as shown below:
// struct type to hold bidder information
struct Bidder {
address contractor;
uint bidAmount;
}
mapping(address => Bidder) bids;
Struct types are typically used inside mappings and arrays. Mapping type in the above code is similar to the dictionary in C#. Structs can contain other structs and other complex types.
Enum Types
Enums are also user defined types in solidity (similar to structs). Enum Types will have a finite set of values. In our 0x Service contract we defined an enum to hold the current state of the contract as shown below:
enum State { Bid, Award, Work, Paid }
State contractState;
In the above code the State enum contains the possible states of the contract, i.e., open for bid, contract awarded, work started and finally payment for the service. contractState variable is used to store the current state of the contract.