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

[Certora] Dispatch tokens and verify safe transfer lib #321

Merged
merged 10 commits into from
Aug 17, 2023
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;
}