Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: add blog post on verification of an ERC-20 #31

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions blog/2024-08-08-coq-of-solidity-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
---
title: 🪨 Coq of Solidity – part 3
tags: [formal verification, Coq, Solidity, Yul]
authors: []
---

We present how we develop a formal verification framework for [Solidity](https://soliditylang.org/) smart contracts with the [Coq](https://coq.inria.fr/) proof assistant. We use the [coq-of-solidity](https://github.com/formal-land/solidity) tool to translate Solidity code to Coq that we presented in our previous blog post [🪨 Coq of Solidity – part 1](/blog/2024/06/28/coq-of-solidity-1).

We will take the example of an [ERC-20 smart contract](https://en.wikipedia.org/wiki/Ethereum#ERC20) implementation and show that it is equivalent to a purely functional definition in Coq acting as a specification.

<!-- truncate -->

:::success AlephZero

_We are happy to be working with [AlephZero](https://alephzero.org/) to develop tools to bring more security for the audit of Solidity smart contracts, thanks to the use of formal verification and the interactive theorem prover [Coq](https://coq.inria.fr/). We thank the Aleph Zero Foundation for their support._

:::

<figure>
![ERC-20 smart contract in forest](2024-08-08/erc20-jungle.webp)
</figure>

## Simplifying the code

We take the ERC-20 smart contract given in the tests of the Solidity compiler: [test/libsolidity/semanticTests/various/erc20.sol](https://github.com/ethereum/solidity/blob/develop/test/libsolidity/semanticTests/various/erc20.sol). It contains around **100 lines of Solidity**. We translate it with `coq-of-solidity` to the file [CoqOfSolidity/test/libsolidity/semanticTests/various/erc20/ERC20.v](https://github.com/formal-land/solidity/blob/guillaume-claret%40experiments-with-yul/CoqOfSolidity/test/libsolidity/semanticTests/various/erc20/ERC20.v) of **6,662 lines of Coq**. These lines are generated but this is still a large increase in terms of code size, more than sixty times. Looking at the Yul code of the contrat, the intermediate representation of the Solidity code that we use to make a translation to Coq, we get around **1,000 lines of Yul** when pretty-printed with the `--ir-optimized` option of the Solidity compiler. This is the actual size of the code that we need to analyze, once we remove the verbosity due to our Coq notations.

The beginning of the contract in Solidity looks like this:

```solidity
pragma solidity >=0.4.0 <0.9.0;

contract ERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);

mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
uint256 private _totalSupply;

constructor() {
_mint(msg.sender, 20);
}

// ...
```

Here is the constructor of the contract, calling the function `_mint` defined below. In Yul we get for the code of the constructor:

```go
object "ERC20_403" {
code {
{
/// @src 0:33:3484 "contract ERC20 {..."
mstore(64, memoryguard(0x80))
if callvalue()
{
revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb()
}
constructor_ERC20()
let _1 := allocate_unbounded()
codecopy(_1, dataoffset("ERC20_403_deployed"), datasize("ERC20_403_deployed"))
return(_1, datasize("ERC20_403_deployed"))
}
function allocate_unbounded() -> memPtr
{ memPtr := mload(64) }
function revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb()
{ revert(0, 0) }
function cleanup_rational_by(value) -> cleaned
{ cleaned := value }
function cleanup_uint256(value) -> cleaned
{ cleaned := value }
// ...
```

As we can see there is a lot of small intermediate functions that are introduced and for many of them they are very simple, or even doing nothing. The translation in Coq is the following:

```coq
(* Generated by coq-of-solidity *)
Require Import CoqOfSolidity.CoqOfSolidity.

Module ERC20.
Definition code : Code.t := {|
Code.name := "ERC20_403";
Code.hex_name := 0x45524332305f3430330000000000000000000000000000000000000000000000;
Code.functions :=
[
Code.Function.make (
"allocate_unbounded",
[],
["memPtr"],
M.scope (
do* ltac:(M.monadic (
M.assign (|
["memPtr"],
Some (M.call (|
"mload",
[
[Literal.number 64]
]
|))
|)
)) in
M.pure BlockUnit.Tt
)
);
Code.Function.make (
"revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb",
[],
[],
M.scope (
do* ltac:(M.monadic (
M.expr_stmt (|
M.call (|
"revert",
[
[Literal.number 0];
[Literal.number 0]
]
|)
|)
)) in
M.pure BlockUnit.Tt
)
);
Code.Function.make (
"cleanup_rational_by",
["value"],
["cleaned"],
M.scope (
do* ltac:(M.monadic (
M.assign (|
["cleaned"],
Some (M.get_var (| "value" |))
|)
)) in
M.pure BlockUnit.Tt
)
);
Code.Function.make (
"cleanup_uint256",
["value"],
["cleaned"],
M.scope (
do* ltac:(M.monadic (
M.assign (|
["cleaned"],
Some (M.get_var (| "value" |))
|)
)) in
M.pure BlockUnit.Tt
)
);
```

This version is far longer, but the main difference is that here the variables are represented as names that are strings instead of proper Coq variable names.

A first thing we want to do is to rewrite all of these functions in Coq:

- simplifying the obvious cases, like removing the calls to identity functions,
- using proper Coq variables and functions instead of strings.

:::warning For more

If you have smart contract projects that you want to formally verify, going further than a manual audit to find bugs, contact us at&nbsp;[&#099;&#111;&#110;&#116;&#097;&#099;&#116;&#064;formal&#046;&#108;&#097;&#110;&#100;](mailto:[email protected])! Formal verification has the unique advantage of covering all possible execution cases.

:::

## Conclusion

We have presented our ongoing development of a formal verification tool for Solidity using the Coq proof assistant. We have briefly shown how we translate Solidity code to Coq using the Yul intermediate language and how we define the semantics of Yul in Coq. We have tested our tool on the examples of the Solidity compiler's test suite to check that our formalization is correct.

Our next steps will be to:

1. Complete our definitions of the Ethereum's primitives, to have a 100% success on the Solidity test suite.
2. Formally specify and verify an example of contract, looking at the [erc20.sol](https://github.com/formal-land/solidity/blob/guillaume-claret%40experiments-with-yul/test/libsolidity/semanticTests/various/erc20.sol) example.
Binary file added blog/2024-08-08/erc20-jungle.webp
Binary file not shown.