Skip to content

Commit

Permalink
Merge pull request #321 from morpho-labs/certora/dispatch-tokens
Browse files Browse the repository at this point in the history
[Certora] Dispatch tokens and verify safe transfer lib
  • Loading branch information
QGarchery authored Aug 17, 2023
2 parents 87eff1c + e0a9b50 commit 12e2261
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/certora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ jobs:
steps:
- uses: actions/checkout@v3

- name: Install submodules
run: git submodule update --init --recursive

- name: Install python
uses: actions/setup-python@v4
with:
Expand Down Expand Up @@ -44,4 +47,5 @@ jobs:
- verifyBlue.sh
- verifyBlueRatioMathSummary.sh
- verifyBlueExitLiquidity.sh
- verifyBlueTransfer.sh
- verifyReentrancy.sh
12 changes: 12 additions & 0 deletions certora/dispatch/ERC20Bad.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol";

contract ERC20Bad is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
_mint(to, amount);
}
}
8 changes: 8 additions & 0 deletions certora/dispatch/ERC20Good.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol";

contract ERC20Good is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
}
55 changes: 55 additions & 0 deletions certora/dispatch/ERC20NoRevert.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract ERC20NoRevert {
string public name;
string public symbol;
uint256 public decimals;
address owner;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowed;

constructor(string memory _name, string memory _symbol, uint256 _decimals) {
name = _name;
symbol = _symbol;
decimals = _decimals;
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

function _transfer(address _from, address _to, uint256 _amount) internal returns (bool) {
if (balanceOf[_from] < _amount) {
return false;
}
balanceOf[_from] -= _amount;
balanceOf[_to] += _amount;
return true;
}

function transfer(address _to, uint256 _amount) public returns (bool) {
return _transfer(msg.sender, _to, _amount);
}

function transferFrom(address _from, address _to, uint256 _amount) public returns (bool) {
allowed[_from][msg.sender] -= _amount;
return _transfer(_from, _to, _amount);
}

function approve(address _spender, uint256 _amount) public {
require(!((_amount != 0) && (allowed[msg.sender][_spender] != 0)));

allowed[msg.sender][_spender] = _amount;
}

function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
return allowed[_owner][_spender];
}

function mint(address _receiver, uint256 _amount) public onlyOwner {
balanceOf[_receiver] += _amount;
}
}
55 changes: 55 additions & 0 deletions certora/dispatch/ERC20USDT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract ERC20USDT {
uint256 public constant MAX_UINT = 2 ** 256 - 1;

string public name;
string public symbol;
uint256 public decimals;
address owner;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowed;

constructor(string memory _name, string memory _symbol, uint256 _decimals) {
name = _name;
symbol = _symbol;
decimals = _decimals;
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

function _transfer(address _from, address _to, uint256 _amount) public {
balanceOf[_from] -= _amount;
balanceOf[_to] += _amount;
}

function transfer(address _to, uint256 _amount) public {
_transfer(msg.sender, _to, _amount);
}

function transferFrom(address _from, address _to, uint256 _amount) public {
if (allowed[_from][msg.sender] < MAX_UINT) {
allowed[_from][msg.sender] -= _amount;
}
_transfer(_from, _to, _amount);
}

function approve(address _spender, uint256 _amount) public {
require(!((_amount != 0) && (allowed[msg.sender][_spender] != 0)));

allowed[msg.sender][_spender] = _amount;
}

function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
return allowed[_owner][_spender];
}

function mint(address _receiver, uint256 _amount) public onlyOwner {
balanceOf[_receiver] += _amount;
}
}
26 changes: 26 additions & 0 deletions certora/harness/TransferHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "../../src/libraries/SafeTransferLib.sol";
import "../../src/interfaces/IERC20.sol";

interface IERC20Extended is IERC20 {
function balanceOf(address) external view returns (uint256);
function totalSupply() external view returns (uint256);
}

contract TransferHarness {
using SafeTransferLib for IERC20;

function doTransfer(address token, address from, address to, uint256 value) public {
IERC20(token).safeTransferFrom(from, to, value);
}

function getBalance(address token, address user) public view returns (uint256) {
return IERC20Extended(token).balanceOf(user);
}

function getTotalSupply(address token) public view returns (uint256) {
return IERC20Extended(token).totalSupply();
}
}
15 changes: 15 additions & 0 deletions certora/scripts/verifyBlueTransfer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

set -euxo pipefail

certoraRun \
certora/harness/TransferHarness.sol \
certora/dispatch/ERC20Good.sol \
certora/dispatch/ERC20USDT.sol \
certora/dispatch/ERC20NoRevert.sol \
--verify TransferHarness:certora/specs/BlueTransfer.spec \
--packages openzeppelin-contracts=lib/openzeppelin-contracts/contracts \
--loop_iter 3 \
--optimistic_loop \
--msg "Morpho Transfer Summary" \
"$@"
37 changes: 37 additions & 0 deletions certora/specs/BlueTransfer.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
methods {
function doTransfer(address, address, address, uint256) external envfree;
function getBalance(address, address) external returns (uint256) envfree;
function getTotalSupply(address) external returns (uint256) envfree;

function _.transferFrom(address, address, uint256) external => DISPATCHER(true);
function _.balanceOf(address) external => DISPATCHER(true);
function _.totalSupply() external => DISPATCHER(true);
}

ghost mapping(address => mathint) myBalances
{
init_state axiom (forall address token. myBalances[token] == 0);
}

function summarySafeTransferFrom(address token, address from, address to, uint256 amount) {
if (from == currentContract) {
myBalances[token] = require_uint256(myBalances[token] - amount);
}
if (to == currentContract) {
myBalances[token] = require_uint256(myBalances[token] + amount);
}
}

rule checkTransfer(address token, address from, address to, uint256 amount) {
require from == currentContract || to == currentContract;

require from != to => getBalance(token, from) + getBalance(token, to) <= to_mathint(getTotalSupply(token));

mathint initialBalance = getBalance(token, currentContract);
doTransfer(token, from, to, amount);
mathint finalBalance = getBalance(token, currentContract);

require myBalances[token] == initialBalance;
summarySafeTransferFrom(token, from, to, amount);
assert myBalances[token] == finalBalance;
}

0 comments on commit 12e2261

Please sign in to comment.