Serverless Smart Contract Automation
Tags: serverless, solidity, ethereum
Published: 2020-07-08 02:47:57

---
title: Serverless Smart Contract Automation
published: true
description: ‘Smart contracts’ is a misnomer. Despite its name, smart contracts on Ethereum are not self-executing digital agreements. In this article, we’ll build a solution to this problem.
tags: serverless, solidity, ethereum
cover_image: https://yos.io/assets/serverless-ethers.png
---

> This article was originally published on [yos.io](https://yos.io/2020/07/04/serverless-smart-contract-automation/)

'Smart contracts' is a misnomer. Despite its name, **smart contracts on Ethereum are not self-executing** digital agreements. Smart contract code only run when triggered by an [external account](https://solidity.readthedocs.io/en/v0.6.10/introduction-to-smart-contracts.html#accounts). In other words, you need an external process to trigger the smart contract.

In this article, we'll build a solution to this problem. You'll learn:

- Why we need off-chain smart contract automation
- Use cases for smart contract automation
- How to deploy serverless functions with the Serverless framework

Finally, we'll go through **`serverless-ethers`**, a fully-functional smart contract automation service that you can run and deploy out-of-the box. You can use this project as a base for building custom smart contract automation that fit your needs.

> **The [`serverless-ethers`](https://github.com/yosriady/serverless-ethers) sample application is open source and available on Github.** Just clone and hit deploy!
>
> *Read on to learn why we need automation and how it works.*

<!--more-->

## The Problem: Smart contracts are not self-executing

> **Imagine that we want to implement a smart contract with a function that should be automatically executed every 1 hour.** ⏳⚙️
>
> How can you accomplish this?

You can't. This is *not possible* with plain Solidity smart contracts. Despite its name, 'smart' contracts in Ethereum are not self-executing. You need an external source (either human or machine) to call the smart contract and execute its code.

The most a contract can do is enforce a 1-hour interval between executions, for example:

```
function runMe() public {
  require(block.timestamp >= lastTriggeredAt + 1 hour);
  ...
}
```

The above `require()` statement ensures that there is at least an hour in between executions. Otherwise, the transaction reverts.

**However, somebody still needs to call the smart contract for the code to run in the first place.**

### An Aside on Self-Execution

> Technically, it is possible to use [function modifiers](https://solidity.readthedocs.io/en/v0.6.10/contracts.html#function-modifiers) to automatically execute certain operations. One example of this is [Compound Governance](https://medium.com/compound-finance/expanding-compound-governance-ce13fcd4fe36)'s COMP distribution. Once an address has earned 0.001 COMP, any Compound transaction (e.g. supplying an asset, or transferring a cToken) will automatically transfer COMP to their wallet.
>
> You can implement the above logic in a function modifier (a decorator), wrap the modifier around a function, and have the logic automatically executed whenever the function is called. The caller will pay the gas required for the additional logic.
>
> However, not all smart contract systems follow this approach. One reason is that it can lead to unpredictable gas usage, since these modifiers may only run under certain conditions. It also forces additional gas fees onto a random subset of users, who just happened to be the unlucky few selected to 'rebalance' the contract.
>
> Finally, *somebody* still needs to call the smart contract for the code to run.

## Common Use Cases for Smart Contract Automation

DeFi protocols already rely on some kind of off-chain smart contract automation. MakerDAO relies on third party [Keepers](https://blog.makerdao.com/how-to-run-your-own-auction-keeper-bot-in-mcd/) to monitor the collateralization ratios of debt positions and liquidate any undercollateralized position. Other DeFi protocols have similar needs.

There are two, often overlapping use cases around off-chain smart contract automation:

1. **Automated Triggers:** You want to execute a contract under a certain condition.
2. **State and Event Monitoring:** You want to know when a contract is in a certain condition.

### Use Case 1: Automated Triggers

You often want to execute a contract periodically or under certain conditions. For example:

- Rebalancing a pool periodically
- Closing out voting rounds in a DAO / governance process
- Poking oracles to refresh data
- Paying out pro-rated dividends for security tokens

### Use Case 2: State and Event Monitoring

You want to know if certain conditions are met. For example:

- You want to know if a value in a smart contract has changed
- You want to be notified of all Access Control changes
- You want to know when a specific smart contract Event was emitted


## The Solution: Serverless functions?

The above use cases sounds like [a good fit](https://www.serverless.com/learn/why/) for a serverless function. By going serverless, we can deploy code without provisioning anything beforehand, or managing anything afterward. It's easier than ever to make your idea live.

## Quickstart: Going Serverless with the Serverless Framework

The [Serverless Framework](https://www.serverless.com/) gives you everything you need to develop, deploy, monitor and secure serverless applications. We'll be using it to speed up our development and reduce mental overhead.

```
> npm install -g serverless
> serverless -v
x.x.x
```

Let's quickly go through how the Serverless Framework operates.

> You can [skip ahead](#introducing-serverless-ethers) if you're just interested in seeing things working. Read on to learn more about the Serverless framework.

### 0. serverless.yml

All of the Lambda functions and events in your Serverless service can be found in a configuration file called the [`serverless.yml`](https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml/). It defines a service with Functions and Events.

```
service: serverless-ethers
provider:
  name: aws
  runtime: nodejs12.x
  environment:
    CHAIN_ID: 3
    DEFAULT_GAS_PRICE: 60000000000

functions:
  myFunc:
    handler: functions/myFunc.handler
    events:
      - schedule: rate(2 hours)
```

Under the `functions` property, you define your serverless functions. In the above example:

- We have a Function called `myFunc`
- The `handler` property points to the file and module containing the code you want to run in your function.
- The `events` property specifies Event triggers for the function to be executed.

You can have multiple functions in a single service.

### 1. Functions

A Function is an AWS Lambda function. It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job.

```
// functions/myFunc.js
exports.handler = async function(event, context) {
  // Do anything
};
```

Functions are just normal JS functions. They can take an `event` object as payload.

### 2. Events

Events are the things that trigger your functions to run. Events belong to each Function and can be found in the `events` property in `serverless.yml`.

You can use the [Scheduled Events](https://www.serverless.com/framework/docs/providers/aws/events/schedule/) trigger to automatically execute functions periodically. For example, to run the `myFunc` function every 2 hours we specify:

```
# serverless.yml

functions:
  myFunc:
    handler: functions/myFunc.handler
    events:
      - schedule: rate(2 hours)
```

You can also specify the schedule using [cron schedule expressions](https://crontab.guru/):

```
# serverless.yml

events:
  - schedule: cron(0 12 * * ? *) # 12PM UTC
```

If you are using AWS as your provider, [all events](https://www.serverless.com/framework/docs/providers/aws/events/) in the service are anything in AWS that can trigger an AWS Lambda function, like:

- An AWS API Gateway HTTP endpoint request (e.g., for a REST API)
- An AWS S3 bucket upload (e.g., for an image)
- A CloudWatch timer (e.g., run every 5 minutes)
- An AWS SNS topic (e.g., a message)
- And more...

That's all you need to know for now.

> To learn more about the Serverless framework, check out [the docs](https://www.serverless.com/framework/docs/).

With the Serverless Framework basics out of the way, let's jump into the [`serverless-ethers`](https://github.com/yosriady/serverless-ethers) service.

# Introducing serverless-ethers

[`serverless-ethers`](https://github.com/yosriady/serverless-ethers) is a fully-functional Serverless service that you can deploy and run out-of-the box.

```
git clone git@github.com:yosriady/serverless-ethers.git
cd serverless-ethers
nvm use
npm install
```

You can use this project as a base for building custom smart contract automation. It comes preconfigured for AWS, but can be modified to work with other cloud providers such as [GCP](https://www.serverless.com/framework/docs/providers/google/guide/intro/), Azure, and many others.

The `serverless-ethers` project is structured as follows:

```
├── contracts/
│   ├── abis/
│   ├── abis.js
│   └── addresses.js
├── functions/
│   └── exec.js
└── serverless.yml
```

1. `contracts/` contain smart contract ABIs and addresses.
2. `functions/` contain JS functions that implmenet the business logic.
3. `serverless.yml` describe the service's configuration.

Let's look at each section in detail.

### Aside: Sample Smart Contracts

I've written and deployed a sample smart contract for testing purposes:

```
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.10;

contract DummyStorage {
    event Write(address indexed source, uint256 value);

    uint internal _currentValue;

    function get() public view returns (uint) {
        return _currentValue;
    }

    function put(uint value) public {
        emit Write(msg.sender, value);
        _currentValue = value;
    }
}
```

The `DummyStorage` smart contract has the following functions:

- `get` is a read-only function that returns the contract's current value.
- `put` is a write function that updates the contract's current value.

> The sample contract is verified and [live on Ropsten](https://ropsten.etherscan.io/address/0xfb01bf6abde080a278a7bbc62f3ba568380eafed#code). Feel free to use it to test your functions!

### 1. Smart Contract ABIs

The [`contracts`](https://github.com/yosriady/serverless-ethers/tree/master/contracts) directory contains the [ABIs](https://solidity.readthedocs.io/en/v0.6.10/abi-spec.html) of contracts the functions interact with. In the sample project, it contains the ABI for the `DummyStorage` contract.

```
├── contracts/
│   ├── abis/
│   │     └── DummyStorage.json
│   ├── abis.js
│   └── addresses.js
```

You can think of an ABI as a smart contract's public API specification, kind of like an OpenAPI specification. You need the ABI to call a contract's functions.

The `contracts/` directory structure lets us import both the contract ABI and address like so:

```
// functions/exec.js

const { abis, addresses } = require('../contracts');

const DummyStorageABI = abis.DummyStorage;
const DummyStorageAddress = addresses.DummyStorage;
```

We'll need these in our function.

### 2. Functions

The [`exec`](https://github.com/yosriady/serverless-ethers/blob/master/functions/exec.js) function uses [Ethers](https://docs.ethers.io/v5/) to load contract ABIs and call a smart contract:

```
// Initialize contract
const contract = new ethers.Contract(
  DummyStorageAddress,
  DummyStorageABI,
  wallet,
)

// Call smart contract function `put(uint)`
const RANDOM_INTEGER = Math.floor(Math.random() * 100); // returns a random integer from 0 to 99
const tx = await contract.put(RANDOM_INTEGER)
```

Loading the contract ABI and address gives us an `ethers.Contract` abstraction with all the functions of our smart contract, including `get()` and `put()`.

In the sample `exec` function, we call `contract.put()` with a random integer.

### 3. serverless.yml

**Before you can run the `exec` function, you'll need to specify some environment variables in your `serverless.yml`:**

```
# serverless.yml

service: serverless-ethers
provider:
  name: aws
  runtime: nodejs12.x
  region: ap-southeast-1
  timeout: 30
  environment:
    DEFAULT_GAS_PRICE: 60000000000
    MNEMONIC: ...
    SLACK_HOOK_URL: ...
```

`serverless-ethers` uses the following environment variables:

- `DEFAULT_GAS_PRICE`: Default gas price used when making write transactions.
- `MNEMONIC`: 12-word mnemonic used to derive an Ethereum address. Make sure it's [funded with Ether](https://faucet.metamask.io/) if you intend to write data to Ethereum!
- `SLACK_HOOK_URL`: The example sends messages to Slack using [Incoming Webhooks](https://api.slack.com/messaging/webhooks). You can get this URL from your Slack dashboard. (Optional)

You can change your deployed function's environment variables on the fly from the AWS Lambda console.

> **Important Note:** make sure you do not store keys in plaintext in production. Use a secure parameter store such as [AWS Secrets Manager](https://www.serverless.com/blog/aws-secrets-management/) when storing credentials such as mnemonics and API keys. Since every project has its own security requirements and setup, we leave it up to readers to decide how they want to approach storing secrets.

### Running locally

You can use the [serverless CLI](https://www.serverless.com/framework/docs/providers/aws/cli-reference/) command `invoke local` to run your functions locally. This is great for testing!

```
> serverless invoke local -f exec
Starting...
Contract ABIs loaded
Ethers wallet loaded
Contract loaded
Sending transaction...
:white_check_mark: Transaction sent https://ropsten.etherscan.io/tx/0x72204f07911a319b4e5f7eb54ad15ed666cfc1403b53def40c9d60188b176383
Completed
true
```

### Deploying to AWS

Deploying is as easy as `serverless deploy`:

```
> serverless deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service serverless-ethers.zip file to S3 (2.95 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.....................
Serverless: Stack update finished...
Service Information
service: serverless-ethers
stage: dev
region: ap-southeast-1
stack: serverless-ethers-dev
resources: 8
api keys:
  None
endpoints:
  None
functions:
  exec: serverless-ethers-dev-exec
layers:
  None
```

That's it! You now have a live serverless function that you can use to automate and monitor your smart contracts. You can use this project as a base for building your own smart contract automation.

## In Closing

Congratulations! You learned about:

- Why we need off-chain smart contract automation
- Use cases for smart contract automation
- The Serverless framework
- How the `serverless-ethers` sample application works

Feel free to let me know if you found this useful, or if you have any questions! I'd be interested to know what your automation use cases are.

> **The [`serverless-ethers`](https://github.com/yosriady/serverless-ethers) sample application is open source and available on Github.** Star the repo if you found it useful!

### Extra: ChatOps support with Slack

Included with `serverless-ethers` is a [`postToSlack`](https://github.com/yosriady/serverless-ethers/blob/master/functions/exec.js#L49-L54) function to help you integrate with Slack.

```
const successMessage = `:white_check_mark: Transaction sent https://ropsten.etherscan.io/tx/${tx.hash}`;
await postToSlack(successMessage);
```

![Slack incoming webhooks](https://bluescreenofjeff.com/assets/slackbot/webhook-diagram.png)

The `postToSlack` function makes use of a `SLACK_HOOK_URL` environment variable that you can get from your [Slack console](https://api.slack.com/messaging/webhooks). Once set up, you'll be able to notify Slack whenever a transaction was sent successfully:

![Slackbot](https://yos.io/assets/slackbot.png)

It's a nice and simple way to monitor your functions.

### Extra: Monitoring Smart Contract Events

So far, we've only implemented the 'Automated Trigger' use case. What about monitoring smart contract state and events?

You can use the Ethers v5 [Events API](https://github.com/ethers-io/ethers.js/issues/463#issuecomment-628374076) to periodically monitor for certain events. In your function, you can do the following:

```
// Given the following Event:
// event Transfer(bytes32 indexed node, address owner)

// Get the filter (the second null could be omitted)
const filter = contract.filters.Transfer(userAccount, null);

// Query the filter
const logs = contract.queryFilter(filter, 0, "latest"); // from block 0 to latest block

// Print out all the values:
logs.forEach((log) => {
  console.log(log.args._to, log.args._value);
});
```

Assuming that you want to have this function execute periodically (e.g. every 5 minutes), you'll also need to store a flag that keeps track of the last block the function has seen since the last execution.

Event monitoring is especially useful if you have an Access Control whitelist that you need to keep an eye on. With an event monitoring function, you can notify a Slack channel whenever new addresses are whitelisted for admin roles. Very handy!

> Find more interesting articles at [yos.io](https://yos.io/)