The first aspect of a smart contract is that it must make a set of programmatic promises. The reason we have chosen a token sale contract to look at is that it has a very simple promise to make: if you send the contract Ethereum, the contract will in turn automatically send your account a new token. Let's look at some basic code, which is explicitly not for production; this is simplified code to make certain concepts clearer. This code comes from the StandardToken contract, part of the OpenZeppelin (You'll find a link for the same in the References section) project on which this is based, which has full-featured and audited code to achieve the same effect but is more complicated to understand.
First, here is an interface contract for an ERC20 token, which we will save as a file called ERC20.sol:
pragma solidity ^0.4.23;
interface ERC20 {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
function allowance(address owner, address spender) public view returns (uint256);
function transferFrom(address from, address to, uint256 value) public returns (bool);
function approve(address spender, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
Next, we will reference that token interface in our crowdsale contract, which will send an ERC20 token in response to a payment in ether:
pragma solidity ^0.4.23;
import "./ERC20.sol";
contract Crowdsale {
// The token being sold, conforms to ERC20 standard.
ERC20 public token;
// 1 tokens per Eth, both have 18 decimals.
uint256 public rate = 1;
constructor(ERC20 _token) public {
token = _token;
}
function () external payable {
uint256 _tokenAmount = msg.value * rate;
token.transfer(msg.sender, _tokenAmount);
}
}
This is a very simplified contract, but again, it is not sufficient for a complete, real-world Crowdsale. However, it does illustrate the key concepts for a smart contract. Let's look at each piece. The constructor method requires a reference to an ERC20 token, which is the token that will be given to buyers who send in Ethereum, as shown in the following code:
constructor(ERC20 _token) public {
token = _token;
}
Because of the way Solidity works, this contract cannot function unless a token has been loaded. So this is the first promise implicitly made by this code: there must be an ERC20 token available for purchase. The second promise is the conversion rate, which is placed at the very simple 1. For each wei (the smallest unit of currency in Ethereum), a person buying this token will get 1 unit of the new token. Ethereum has 18 decimal places, and by convention so do most tokens, so it would be presumed that this would make the conversion of Ethereum to this token now 1:1. This brings us to item #4 in the necessary aspects of a smart contract: automatic fulfillment. The following code handles this:
function () external payable {
uint 256 _tokenAmount = msg.value * rate; //Calculate tokens purchased
token.transfer(msg.sender, _tokenAmount); //Execute send on token contract.
}
As this is code, the requirement that the smart contract should be in digital form is obvious. The automatic aspect here is also straightforward. In Ethereum, msg.value holds the value of the ether currency that is sent as part of the command. When the contract receives Ethereum, it calculates the number of tokens the purchaser should receive and sends them: no human interaction needed, and no trusted party necessary or possible. Similarly, no one can intervene, as once it is deployed to the network, the code in Ethereum is immutable. Therefore, a sender who is using this smart contract can be absolutely assured that they will receive their tokens.