Introduction
Now that we’ve demystified smart contracts and better understand what they are and how they work, let’s build our own contract on top of the Ethereum blockchain. Because smart contracts are computer programs at their core, many of the concepts that we’ll use to build a basic contract will seem familiar. Major advancements in developer tooling and infrastructure in the Ethereum ecosystem allow us to effectively develop a smart contract completely in-browser, so let’s get started.
Remixing Solidity
The Ethereum Virtual Machine is a combination of a lightweight operating system and a state machine, purpose-built to understand and to execute Ethereum smart contracts. This virtual machine, commonly known as the “EVM”, is shipped with and embedded into every Ethereum node that makes up its network. An Ethereum smart contract program is a series of machine-level instructions that are understood by the EVM.
Solidity is a high-level, formal programming language that’s used to author Ethereum smart contracts. The EVM does not organically understand Solidity code; rather, Solidity ships with its own compiler that translates human-readable Solidity source code into low-level EVM-compatible bytecode. Solidity is a Turing-complete language that was inspired in both syntax and semantics by the likes of C++, Python, and JavaScript. It is object-oriented and exposes an expressive static typing system, and it holds true to foundational object-oriented concepts like encapsulation, inheritance, and polymorphism. The fact that Solidity supports inheritance has given rise to a rich ecosystem of contract utility libraries, base contracts designed for extension, and other modular functionality to aid contract creation.
Remix is a web-based development environment for creating smart contracts. It offers the ability to quickly prototype Solidity-based smart contracts without the need to set up any local tooling, and it mitigates the need to use real ETH to deploy and test smart contract functionality. To get started, follow the steps below to open Remix and to create a new contract file.
- Visit remix.ethereum.org.
- Choose the Solidity environment.
- Create a new file named
CellSubscription.sol
.
A tutorial: A cell phone contract
The semantics and intricacies of decentralized programming languages become much clearer in practice, so let’s use Solidity to build a basic Ethereum smart contract. Our contract will be very rudimentary and will codify the protocol of a very simple cell phone contract between a cell company and a subscriber.
1. Create a contract class
The first step in codifying our theorized cell phone contract is to define the contract itself.
pragma solidity >=0.4.22 <0.7.0;
contract CellSubscription {
}
In Solidity, a contract
is a first-class language citizen that’s a similar construct to a traditional class: it contains a collection of functions and a notion of inner state. In the code above, we also specify that the source code to follow is compatible with a Solidity version greater than 0.5.10
.
2. Set up internal state
Now that we’ve defined the structure of the contract itself, let’s start by adding a constructor
function. Similar to traditional programs, Solidity contract constructors are only called immediately after contract deployment and cannot be called again thereafter.
pragma solidity >=0.4.22 <0.7.0;
contract CellSubscription {
uint256 monthlyCost;
constructor(uint256 cost) public {
monthlyCost = cost;
}
}
A lot of new syntax was just introduced very quickly, so let’s break down the code above statement-by-statement. First, we declare a monthlyCost
member variable on the contract itself with a uint256
type, meaning this is an unsigned integer up to 256 bits in size. We’ll use this monthly cost variable later when determining if the cell subscriber has paid their contract in full. Next, we define a constructor function and set its visibility to public
using a function visibility modifier. Functions and member variables can assume four different visibilities in Solidity: public
, private
, internal
, and external
. Both public and private members behave as their name implies; internal members are similar to protected
members in TypeScript and can only be called from the current inheritance tree, and external
is its converse. Lastly, we initialize the monthlyCost
member variable with the monthly cost
argument passed into the constructor.
3. Add subscriber functionality
So far, we’ve defined an initial contract structure and initialized internal contract state. Let’s consider the actual subscription logic that we’re attempting to build. For the sake of simplicity, the functionality of this “cell phone subscription” will be straight forward: the subscriber pays a monthly fee in ether to the contract, and the cell phone company can check if the account is paid in full. Let’s codify the subscriber logic pahs now, i.e. the ability to make a monthly payment to the contract.
pragma solidity >=0.4.22 <0.7.0;
contract CellSubscription {
uint256 monthlyCost;
constructor(uint256 cost) public {
monthlyCost = cost;
}
function makePayment() payable public {
}
}
Ethereum smart contracts act as wallets by default, meaning they can send, receive, and store ether just like a regular wallet address. This built-in notion of contracts holding value makes them particularly useful for a monetary-based agreement like this one. In the code above, we define a new public makePayment
function that allows the subscriber to make a payment towards their account. The payable
function modifier tells the contract to automatically store any ETH internally that’s sent when calling the makePayment
function, which we’ll use later to determine if the subscriber has paid enough ether as of a given date. Notice how the function doesn’t have a body at all; the storing of sent ether is automatic when the payable
modifier is present. If desired, complex validation logic could be added to the function body, e.g. to reject partial payment.
4. Add company functionality
At this point, we have a contract that can be deployed with an established monthly cost and accept ether bill payments from a subscriber. Next, we need to add functionality that allows the cell phone company to check the status of the account on a given date.
pragma solidity >=0.4.22 <0.7.0;
contract CellSubscription {
uint256 monthlyCost;
constructor(uint256 cost) public {
monthlyCost = cost;
}
function makePayment() payable public {
}
function isBalanceCurrent(uint256 monthsElapsed) public view returns (bool) {
return monthlyCost * monthsElapsed >= address(this).balance;
}
}
We define another public function called isBalanceCurrent
that accepts a monthsElapsed
parameter typed as an unsigned integer. The cell phone company can call this simplified function and pass a given number of elapsed months to determine the status of the subscriber’s account at a point in time. The view
function modifier is used to indicate that this function does not modify internal state and is read only. The returns (bool)
clause does what it implies and indicates that a truthy or falsey value will be returned. Within the function body itself, we do simple arithmetic logic to determine the total amount that should’ve been paid, and we compare that to the internal ether balance of the contract using address(this).balance
. Just like in other languages, this
refers to the current contract instance being executed. The address(...)
statement is a global function that accepts a contract instance and returns an address instance, so address(this)
is shorthand for accessing the instance of the current address. Lastly, all contract addresses instances in Solidity also expose a balance
instance variable that refers to the amount of ether currently stored internally. This means that in the function body above, if the account has enough ether based on the number of months that have elapsed, it will return true
, otherwise it will return false
.
We’re still missing one crucial piece of company-related functionality: the ability to withdraw funds from the contract so they can be transferred to the company’s own accounts. Thankfully, Solidity exposes more convenience semantics for withdrawing value from a contract account.
pragma solidity >=0.4.22 <0.7.0;
contract CellSubscription {
uint256 monthlyCost;
constructor(uint256 cost) public {
monthlyCost = cost;
}
function makePayment() payable public {
}
function withdrawBalance() public {
msg.sender.transfer(address(this).balance);
}
function isBalanceCurrent(uint256 monthsElapsed) public view returns (bool) {
return monthlyCost * monthsElapsed == address(this).balance;
}
}
We add a final public function called withdrawBalance
that allows an account to be emptied. The function body uses the global msg
object that refers to the last incoming transaction payload. In this insecure and basic example, the entire account balance is transferred to the caller of withdrawBalance
using msg.sender.transfer
function that sends ether to the sender’s account. We again use address(this).balance
to access the internal ether stored, using this as the amount we wish to withdraw.
5. Compile and run
Now that we have a smart contract that can be deployed to the Ethereum blockchain, let’s use Solidity’s built-in virtual machine to simulate running this contract on the main Ethereum network.
- Save your contract using
<strong>⌘ </strong>+ S
orCtrl + S
depending on your operating system. - Switch to the deployment tab.
3. Define a monthly cost to be passed to the constructor and click to deploy.
4. Configure a simulated ETH value of 1000
wei, and call the makePayment
function. This simulates a subscriber making their payment for a given month.
5. Finally, call the isBalanceCurrent
function with a 1 as the monthsElapsed
parameter, meaning we’re checking the balance is current after one month has passed. You should see 0: bool: true
, indicating that the account balance is in fact current.
That’s it! While it may not seem like much, you’ve now written a rudimentary Ethereum smart contract that can be initialized with state, that holds, receives, and distributes monetary value, and that exposes the ability to determine financial account standing. While none of these concepts were implemented with security or completeness necessary for production use, they demonstrate key functionality that give Ethereum smart contracts their power.
Real-world considerations
The tutorial above is useful for introducing Solidity concepts and showing them in action, but developing and deploying real-world smart contracts meant to be used in high-value business exchanges requires careful planning and consideration.
The basic cell phone subscription contract we codified is inherently insecure and could be greatly improved. For example, funds withdrawal should be restricted to a set of pre-approved sender
addresses to prevent arbitrary users from emptying an account. As it stands now, anyone could call withdrawBalance
and receive all ETH stored in the contract. Further, the makePayment
function could be augmented to validate the received payment to prevent overpayment or underpayment, and the sender could be verified to be from a known account, if desired.
While the Remix development environment is great for prototyping Smart contracts and testing Solidity correctness, most real-world smart contracts would be developed locally using more complex build tooling such as Truffle. Perhaps most importantly, it costs real money in the form of ether to interact with smart contracts deployed on the main Ethereum network. This is by design, and forms the cornerstone of Ethereum’s economic self-sufficiency: it costs money to do anything on the live Ethereum network. Thankfully, Remix bypasses this initial complication by spinning up an in-browser, JavaScript-based EVM that mimics the behavior of the real network EVM. In practice, smart contracts would be deployed using Node.js-powered CLI tooling through a framework like Truffle, and both deploying contracts and calling functions on them would require an Ethereum account funded with real ether.
Conclusion
The practice of authoring smart contracts requires both programmatic knowledge of Solidity semantics and knowledge of the tooling surrounding both Solidity and Ethereum in general. The level to which concepts are abstracted and the overall Ethereum developer experience have both improved in recent years, and it continues to do so at a rapid pace. Ethereum has established itself as a mature smart contract platform with an impressive feature set and strong community backing. The question of whether businesses have both an appetite and a true need for such automated contracts remains to be seen.