Our tutorial on writing a smart contract will help you start building on Flow. Specifically, you’ll learn Cadence, the smart contract language created by the Flow blockchain. We learn about resources and their ownership through the example of a “Car” resource.
Video
Follow along in a video format.
Getting Started
The easiest way to start coding a smart contract on Flow without any installations required is to load up the Flow playground.
Deploying a Contract
You can start by deploying your own contract that does nothing.
pub contract Race {
// run only once: when the contract is deployed
init() {
// will get logged to the console when you deploy
log("Let's get started for a race!")
}
}
Resources
In contract to many other blockchains like Ethereum where storage is maintained in the contract in the form of key-value stores, it is most often in Cadence that an account stores a resource in their own storage. We will get to that in a bit.
Resources are characterized by having the following properties:
- They cannot be copied
- They cannot be lost (they must be explicity moved, stored, or destroyed)
- They have a unique identifier (
uuid
)
pub contract Race {
pub resource Car {
pub var speed: UFix64
pub var acceleration: UFix64
pub var handling: UFix64
// run only once: when the resource is created
init() {
self.speed = 0.0
self.acceleration = 0.0
self.handling = 0.0
}
}
init() {
log("Let's get started for a race!")
}
}
As stated earlier, resources must either be moved, stored, or destroyed. Resources themselves are really just objects, so they can stored:
- In other resources
- In an account’s storage
- In dictionaries/arrays
Functions
Several visibility modifiers limit or gate access to Cadence functions. pub
(or sometimes seen as access(all)
) functions can be called by anything. access(contract)
functions can be called within the contract they are defined. access(account)
functions can be called within the account that their contract is deployed in. Contracts are deployed to accounts, so an account can have multiple contracts. access(self)
(or sometimes seen as priv
) functions can only be called within the context they are defined.
A special function called init
is found in a contract (executing when it’s deployed), or in a resource or struct (executing when it’s created).
Similarly, destroy
is found in a resource, and only called when the resource is destroyed.
pub contract Race {
pub resource Car {
pub var speed: UFix64
pub var acceleration: UFix64
pub var handling: UFix64
pub var color: String
// gave our car a color to showcase
// you can also add functions
// to resources
pub fun changeColor(newColor: String) {
self.color = newColor
}
init(color: String) {
self.speed = 0.0
self.acceleration = 0.0
self.handling = 0.0
self.color = color
}
}
// resources cannot be created outside the contract
//
// so we must define a public function to create a car
// so anyone can call this in a transaction
pub fun createCar(color: String): @Car {
return <- create Car(color: color)
}
init() {
log("Let's get started for a race!")
}
}
Transactions and Scripts
Transactions (for writing data) and scripts (for reading data), like contracts, are implemented in Cadence. But they are seperate files.
Let’s add a variable in the contract that keeps track of the total amount of cars created.
// Race.cdc
pub contract Race {
pub var totalCars: UInt64
pub resource Car {
pub var speed: UFix64
pub var acceleration: UFix64
pub var handling: UFix64
pub var color: String
pub fun changeColor(newColor: String) {
self.color = newColor
}
init(color: String) {
self.speed = 0.0
self.acceleration = 0.0
self.handling = 0.0
self.color = color
// incremnet the total cars every time a resource is created
Race.totalCars = Race.totalCars + 1
}
destroy() {
Race.totalCars = Race.totalCars - 1
}
}
pub fun createCar(color: String): @Car {
return <- create Car(color: color)
}
init() {
log("Let's get started for a race!")
}
}
Now let’s run a transaction to mint a car and destroy it.
// create_car.cdc
import Race from 0x01
transaction() {
prepare(signer: AuthAccount) {
}
execute {
log(Race.totalCars) // 0
let car: @Race.Car <- Race.createCar()
log(Race.totalCars) // 1
destroy car
log(Race.totalCars) // 0
}
}
We can also write a script to view the total amount of cars at any point.
// get_total_cars.cdc
import Race from 0x01
pub fun main(): UInt64 {
return Race.totalCars
}
Account Storage
As mentioned earlier, unlike languages like Solidity where user data is stored in a centralized contract, in Cadence we store user data directly in their accounts. Let’s store a Car in a user’s account.
// create_car.cdc
import Race from 0x01
transaction() {
// prepare is used when needing to
// manipulate a user's account storage
// since it requires the `AuthAccount` type
prepare(signer: AuthAccount) {
let car: @Race.Car <- Race.createCar()
signer.save(<- car, to: /storage/Car)
}
execute {
}
}
Resources are stored in account paths. Each resource must have its own path. You cannot overwrite existing data.
We can read data about our car in a script. Without the use of Capabilities (a concept that we will not cover here, but you can read about here), we need the AuthAccount
type to borrow a reference to this car. Because AuthAccount
is a very dangerous type (it gives you full control of everything stored in an account), we would only get access to an AuthAccount
type in a tranaction if that account signs it.
However, because this is just a script and thus modified data will revert after its execution, scripts allow us to use the getAuthAccount
function to get the type.
// get_car_info.cdc
import Race from 0x01
pub fun main(carOwner: Address): Result {
let authAccount: AuthAccount = getAuthAccount(carOwner)
let carReference: &Race.Car = authAccount.borrow<&Race.Car>(from: /storage/Car) ?? panic("A Car is not stored here!")
return Result(
s: carReference.speed,
a: carReference.acceleration,
h: carReference.handling,
c: carReference.color
)
}
pub struct Result {
pub let speed: UFix64
pub let acceleration: UFix64
pub let handling: UFix64
pub let color: String
init(s: UFix64, a: UFix64, h: UFix64, c: String) {
self.speed = s
self.acceleration = a
self.handling = h
self.color = c
}
}
Next Steps
Take the Beginner Cadence Course next if you’d like to master Cadence development.
Till next time ~ Jacob Tucker