Skip to content
This repository was archived by the owner on Sep 27, 2023. It is now read-only.
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,11 @@ cache/
.certora*
.last_confs
certora_*
.zip-output-url.txt

# mac
.DS_Store

# vim
.*.swp
.*.swo
1 change: 0 additions & 1 deletion 01.Lesson_GettingStarted/ERC20Lesson1/.zip-output-url.txt

This file was deleted.

96 changes: 52 additions & 44 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20.spec
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
/***
/**
* # ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is an example specification for a generic ERC20 contract. It contains several
* simple rules verifying the integrity of the transfer function.
* To run, execute the following command in terminal:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* One of the rules here is badly phrased, and results in an erroneous fail.
* Understand the counter example provided by the Prover and then run the fixed
* spec:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
// Operations on mathints can never overflow nor underflow
assert balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

Expand All@@ -46,33 +48,39 @@ rule transferSpec {
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
99 changes: 52 additions & 47 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20Fixed.spec
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,86 @@
/***
* # ERC20 Example
/**
* # Fixed ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is the fixed version of ERC20.spec. Note the changes in rule `transferSpec`.
* Run using:
*
*certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* There should be no errors.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint256 amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
assert recip != sender => balance_sender_after == balance_sender_before - amount,
address sender = e.msg.sender; // A convenient alias

// Operations on mathints can never overflow or underflow.
assert recipient != sender => balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

assert recip != sender => balance_recip_after == balance_recip_before + amount,
assert recipient != sender => balance_recip_after == balance_recip_before + amount,
"transfer must increase recipient's balance by amount";

assert recip == sender => balance_sender_after == balance_sender_before,
assert recipient == sender => balance_sender_after == balance_sender_before,
"transfer must not change sender's balancer when transferring to self";
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
60 changes: 36 additions & 24 deletions 01.Lesson_GettingStarted/ERC20Lesson1/Parametric.spec
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,53 @@
/***
* # ERC20 Example
/**
* # ERC20 Parametric Example
*
* This is an example specification for a generic ERC20 contract.
*
* To simulate the execution of all functions in the main contract,
* you can define a method argument in the rule and use it in a statement.
* Run:
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "parametric rule"
* Another example specification for an ERC20 contract. This one using a parametric rule,
* which is a rule that encompasses all the methods in the current contract. It is called
* parametric since one of the rule's parameters is the current contract method.
* To run enter:
*
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "Parametric rule"
*
* The `onlyHolderCanChangeAllowance` fails for one of the methods. Look at the Prover
* results and understand the counter example - which discovers a weakness in the
* current contract.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}


//// ## Part 2: parametric rules ///////////////////////////////////////////////

/// If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance {
address holder; address spender;
/// @title If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance(address holder, address spender, method f) {

// The allowance before the method was called
mathint allowance_before = allowance(holder, spender);

method f; env e; calldataarg args;
env e;
calldataarg args; // Arguments for the method f
f(e, args);

// The allowance after the method was called
mathint allowance_after = allowance(holder, spender);

assert allowance_after > allowance_before => e.msg.sender == holder,
"approve must only change the sender's allowance";

assert allowance_after > allowance_before =>
(f.selector == approve(address,uint).selector || f.selector == increaseAllowance(address,uint).selector),
"only approve and increaseAllowance can increase allowances";
"only the sender can change its own allowance";

// Assert that if the allowance changed then `approve` or `increaseAllowance` was called.
assert (
allowance_after > allowance_before =>
(
f.selector == sig:approve(address, uint).selector ||
f.selector == sig:increaseAllowance(address, uint).selector
)
),
"only approve and increaseAllowance can increase allowances";
}

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Update Lesson 1 to CVL2 and config files by shoham-certora · Pull Request #33 · Certora/Tutorials · GitHub
Skip to content
This repository was archived by the owner on Sep 27, 2023. It is now read-only.
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,11 @@ cache/
.certora*
.last_confs
certora_*
.zip-output-url.txt

# mac
.DS_Store

# vim
.*.swp
.*.swo
1 change: 0 additions & 1 deletion 01.Lesson_GettingStarted/ERC20Lesson1/.zip-output-url.txt

This file was deleted.

96 changes: 52 additions & 44 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20.spec
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
/***
/**
* # ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is an example specification for a generic ERC20 contract. It contains several
* simple rules verifying the integrity of the transfer function.
* To run, execute the following command in terminal:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* One of the rules here is badly phrased, and results in an erroneous fail.
* Understand the counter example provided by the Prover and then run the fixed
* spec:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
// Operations on mathints can never overflow nor underflow
assert balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

Expand All@@ -46,33 +48,39 @@ rule transferSpec {
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
99 changes: 52 additions & 47 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20Fixed.spec
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,86 @@
/***
* # ERC20 Example
/**
* # Fixed ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is the fixed version of ERC20.spec. Note the changes in rule `transferSpec`.
* Run using:
*
*certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* There should be no errors.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint256 amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
assert recip != sender => balance_sender_after == balance_sender_before - amount,
address sender = e.msg.sender; // A convenient alias

// Operations on mathints can never overflow or underflow.
assert recipient != sender => balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

assert recip != sender => balance_recip_after == balance_recip_before + amount,
assert recipient != sender => balance_recip_after == balance_recip_before + amount,
"transfer must increase recipient's balance by amount";

assert recip == sender => balance_sender_after == balance_sender_before,
assert recipient == sender => balance_sender_after == balance_sender_before,
"transfer must not change sender's balancer when transferring to self";
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
60 changes: 36 additions & 24 deletions 01.Lesson_GettingStarted/ERC20Lesson1/Parametric.spec
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,53 @@
/***
* # ERC20 Example
/**
* # ERC20 Parametric Example
*
* This is an example specification for a generic ERC20 contract.
*
* To simulate the execution of all functions in the main contract,
* you can define a method argument in the rule and use it in a statement.
* Run:
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "parametric rule"
* Another example specification for an ERC20 contract. This one using a parametric rule,
* which is a rule that encompasses all the methods in the current contract. It is called
* parametric since one of the rule's parameters is the current contract method.
* To run enter:
*
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "Parametric rule"
*
* The `onlyHolderCanChangeAllowance` fails for one of the methods. Look at the Prover
* results and understand the counter example - which discovers a weakness in the
* current contract.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}


//// ## Part 2: parametric rules ///////////////////////////////////////////////

/// If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance {
address holder; address spender;
/// @title If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance(address holder, address spender, method f) {

// The allowance before the method was called
mathint allowance_before = allowance(holder, spender);

method f; env e; calldataarg args;
env e;
calldataarg args; // Arguments for the method f
f(e, args);

// The allowance after the method was called
mathint allowance_after = allowance(holder, spender);

assert allowance_after > allowance_before => e.msg.sender == holder,
"approve must only change the sender's allowance";

assert allowance_after > allowance_before =>
(f.selector == approve(address,uint).selector || f.selector == increaseAllowance(address,uint).selector),
"only approve and increaseAllowance can increase allowances";
"only the sender can change its own allowance";

// Assert that if the allowance changed then `approve` or `increaseAllowance` was called.
assert (
allowance_after > allowance_before =>
(
f.selector == sig:approve(address, uint).selector ||
f.selector == sig:increaseAllowance(address, uint).selector
)
),
"only approve and increaseAllowance can increase allowances";
}

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Update Lesson 1 to CVL2 and config files by shoham-certora · Pull Request #33 · Certora/Tutorials · GitHub
Skip to content
This repository was archived by the owner on Sep 27, 2023. It is now read-only.
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,11 @@ cache/
.certora*
.last_confs
certora_*
.zip-output-url.txt

# mac
.DS_Store

# vim
.*.swp
.*.swo
1 change: 0 additions & 1 deletion 01.Lesson_GettingStarted/ERC20Lesson1/.zip-output-url.txt

This file was deleted.

96 changes: 52 additions & 44 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20.spec
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
/***
/**
* # ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is an example specification for a generic ERC20 contract. It contains several
* simple rules verifying the integrity of the transfer function.
* To run, execute the following command in terminal:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* One of the rules here is badly phrased, and results in an erroneous fail.
* Understand the counter example provided by the Prover and then run the fixed
* spec:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
// Operations on mathints can never overflow nor underflow
assert balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

Expand All@@ -46,33 +48,39 @@ rule transferSpec {
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
99 changes: 52 additions & 47 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20Fixed.spec
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,86 @@
/***
* # ERC20 Example
/**
* # Fixed ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is the fixed version of ERC20.spec. Note the changes in rule `transferSpec`.
* Run using:
*
*certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* There should be no errors.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint256 amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
assert recip != sender => balance_sender_after == balance_sender_before - amount,
address sender = e.msg.sender; // A convenient alias

// Operations on mathints can never overflow or underflow.
assert recipient != sender => balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

assert recip != sender => balance_recip_after == balance_recip_before + amount,
assert recipient != sender => balance_recip_after == balance_recip_before + amount,
"transfer must increase recipient's balance by amount";

assert recip == sender => balance_sender_after == balance_sender_before,
assert recipient == sender => balance_sender_after == balance_sender_before,
"transfer must not change sender's balancer when transferring to self";
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
60 changes: 36 additions & 24 deletions 01.Lesson_GettingStarted/ERC20Lesson1/Parametric.spec
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,53 @@
/***
* # ERC20 Example
/**
* # ERC20 Parametric Example
*
* This is an example specification for a generic ERC20 contract.
*
* To simulate the execution of all functions in the main contract,
* you can define a method argument in the rule and use it in a statement.
* Run:
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "parametric rule"
* Another example specification for an ERC20 contract. This one using a parametric rule,
* which is a rule that encompasses all the methods in the current contract. It is called
* parametric since one of the rule's parameters is the current contract method.
* To run enter:
*
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "Parametric rule"
*
* The `onlyHolderCanChangeAllowance` fails for one of the methods. Look at the Prover
* results and understand the counter example - which discovers a weakness in the
* current contract.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}


//// ## Part 2: parametric rules ///////////////////////////////////////////////

/// If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance {
address holder; address spender;
/// @title If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance(address holder, address spender, method f) {

// The allowance before the method was called
mathint allowance_before = allowance(holder, spender);

method f; env e; calldataarg args;
env e;
calldataarg args; // Arguments for the method f
f(e, args);

// The allowance after the method was called
mathint allowance_after = allowance(holder, spender);

assert allowance_after > allowance_before => e.msg.sender == holder,
"approve must only change the sender's allowance";

assert allowance_after > allowance_before =>
(f.selector == approve(address,uint).selector || f.selector == increaseAllowance(address,uint).selector),
"only approve and increaseAllowance can increase allowances";
"only the sender can change its own allowance";

// Assert that if the allowance changed then `approve` or `increaseAllowance` was called.
assert (
allowance_after > allowance_before =>
(
f.selector == sig:approve(address, uint).selector ||
f.selector == sig:increaseAllowance(address, uint).selector
)
),
"only approve and increaseAllowance can increase allowances";
}

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Update Lesson 1 to CVL2 and config files by shoham-certora · Pull Request #33 · Certora/Tutorials · GitHub
Skip to content
This repository was archived by the owner on Sep 27, 2023. It is now read-only.
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,11 @@ cache/
.certora*
.last_confs
certora_*
.zip-output-url.txt

# mac
.DS_Store

# vim
.*.swp
.*.swo
1 change: 0 additions & 1 deletion 01.Lesson_GettingStarted/ERC20Lesson1/.zip-output-url.txt

This file was deleted.

96 changes: 52 additions & 44 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20.spec
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
/***
/**
* # ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is an example specification for a generic ERC20 contract. It contains several
* simple rules verifying the integrity of the transfer function.
* To run, execute the following command in terminal:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* One of the rules here is badly phrased, and results in an erroneous fail.
* Understand the counter example provided by the Prover and then run the fixed
* spec:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
// Operations on mathints can never overflow nor underflow
assert balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

Expand All@@ -46,33 +48,39 @@ rule transferSpec {
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
99 changes: 52 additions & 47 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20Fixed.spec
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,86 @@
/***
* # ERC20 Example
/**
* # Fixed ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is the fixed version of ERC20.spec. Note the changes in rule `transferSpec`.
* Run using:
*
*certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* There should be no errors.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint256 amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
assert recip != sender => balance_sender_after == balance_sender_before - amount,
address sender = e.msg.sender; // A convenient alias

// Operations on mathints can never overflow or underflow.
assert recipient != sender => balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

assert recip != sender => balance_recip_after == balance_recip_before + amount,
assert recipient != sender => balance_recip_after == balance_recip_before + amount,
"transfer must increase recipient's balance by amount";

assert recip == sender => balance_sender_after == balance_sender_before,
assert recipient == sender => balance_sender_after == balance_sender_before,
"transfer must not change sender's balancer when transferring to self";
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
60 changes: 36 additions & 24 deletions 01.Lesson_GettingStarted/ERC20Lesson1/Parametric.spec
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,53 @@
/***
* # ERC20 Example
/**
* # ERC20 Parametric Example
*
* This is an example specification for a generic ERC20 contract.
*
* To simulate the execution of all functions in the main contract,
* you can define a method argument in the rule and use it in a statement.
* Run:
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "parametric rule"
* Another example specification for an ERC20 contract. This one using a parametric rule,
* which is a rule that encompasses all the methods in the current contract. It is called
* parametric since one of the rule's parameters is the current contract method.
* To run enter:
*
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "Parametric rule"
*
* The `onlyHolderCanChangeAllowance` fails for one of the methods. Look at the Prover
* results and understand the counter example - which discovers a weakness in the
* current contract.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}


//// ## Part 2: parametric rules ///////////////////////////////////////////////

/// If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance {
address holder; address spender;
/// @title If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance(address holder, address spender, method f) {

// The allowance before the method was called
mathint allowance_before = allowance(holder, spender);

method f; env e; calldataarg args;
env e;
calldataarg args; // Arguments for the method f
f(e, args);

// The allowance after the method was called
mathint allowance_after = allowance(holder, spender);

assert allowance_after > allowance_before => e.msg.sender == holder,
"approve must only change the sender's allowance";

assert allowance_after > allowance_before =>
(f.selector == approve(address,uint).selector || f.selector == increaseAllowance(address,uint).selector),
"only approve and increaseAllowance can increase allowances";
"only the sender can change its own allowance";

// Assert that if the allowance changed then `approve` or `increaseAllowance` was called.
assert (
allowance_after > allowance_before =>
(
f.selector == sig:approve(address, uint).selector ||
f.selector == sig:increaseAllowance(address, uint).selector
)
),
"only approve and increaseAllowance can increase allowances";
}

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Update Lesson 1 to CVL2 and config files by shoham-certora · Pull Request #33 · Certora/Tutorials · GitHub
Skip to content
This repository was archived by the owner on Sep 27, 2023. It is now read-only.
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,11 @@ cache/
.certora*
.last_confs
certora_*
.zip-output-url.txt

# mac
.DS_Store

# vim
.*.swp
.*.swo
1 change: 0 additions & 1 deletion 01.Lesson_GettingStarted/ERC20Lesson1/.zip-output-url.txt

This file was deleted.

96 changes: 52 additions & 44 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20.spec
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
/***
/**
* # ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is an example specification for a generic ERC20 contract. It contains several
* simple rules verifying the integrity of the transfer function.
* To run, execute the following command in terminal:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* One of the rules here is badly phrased, and results in an erroneous fail.
* Understand the counter example provided by the Prover and then run the fixed
* spec:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
// Operations on mathints can never overflow nor underflow
assert balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

Expand All@@ -46,33 +48,39 @@ rule transferSpec {
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
99 changes: 52 additions & 47 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20Fixed.spec
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,86 @@
/***
* # ERC20 Example
/**
* # Fixed ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is the fixed version of ERC20.spec. Note the changes in rule `transferSpec`.
* Run using:
*
*certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* There should be no errors.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint256 amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
assert recip != sender => balance_sender_after == balance_sender_before - amount,
address sender = e.msg.sender; // A convenient alias

// Operations on mathints can never overflow or underflow.
assert recipient != sender => balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

assert recip != sender => balance_recip_after == balance_recip_before + amount,
assert recipient != sender => balance_recip_after == balance_recip_before + amount,
"transfer must increase recipient's balance by amount";

assert recip == sender => balance_sender_after == balance_sender_before,
assert recipient == sender => balance_sender_after == balance_sender_before,
"transfer must not change sender's balancer when transferring to self";
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
60 changes: 36 additions & 24 deletions 01.Lesson_GettingStarted/ERC20Lesson1/Parametric.spec
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,53 @@
/***
* # ERC20 Example
/**
* # ERC20 Parametric Example
*
* This is an example specification for a generic ERC20 contract.
*
* To simulate the execution of all functions in the main contract,
* you can define a method argument in the rule and use it in a statement.
* Run:
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "parametric rule"
* Another example specification for an ERC20 contract. This one using a parametric rule,
* which is a rule that encompasses all the methods in the current contract. It is called
* parametric since one of the rule's parameters is the current contract method.
* To run enter:
*
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "Parametric rule"
*
* The `onlyHolderCanChangeAllowance` fails for one of the methods. Look at the Prover
* results and understand the counter example - which discovers a weakness in the
* current contract.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}


//// ## Part 2: parametric rules ///////////////////////////////////////////////

/// If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance {
address holder; address spender;
/// @title If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance(address holder, address spender, method f) {

// The allowance before the method was called
mathint allowance_before = allowance(holder, spender);

method f; env e; calldataarg args;
env e;
calldataarg args; // Arguments for the method f
f(e, args);

// The allowance after the method was called
mathint allowance_after = allowance(holder, spender);

assert allowance_after > allowance_before => e.msg.sender == holder,
"approve must only change the sender's allowance";

assert allowance_after > allowance_before =>
(f.selector == approve(address,uint).selector || f.selector == increaseAllowance(address,uint).selector),
"only approve and increaseAllowance can increase allowances";
"only the sender can change its own allowance";

// Assert that if the allowance changed then `approve` or `increaseAllowance` was called.
assert (
allowance_after > allowance_before =>
(
f.selector == sig:approve(address, uint).selector ||
f.selector == sig:increaseAllowance(address, uint).selector
)
),
"only approve and increaseAllowance can increase allowances";
}

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Update Lesson 1 to CVL2 and config files by shoham-certora · Pull Request #33 · Certora/Tutorials · GitHub
Skip to content
This repository was archived by the owner on Sep 27, 2023. It is now read-only.
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,11 @@ cache/
.certora*
.last_confs
certora_*
.zip-output-url.txt

# mac
.DS_Store

# vim
.*.swp
.*.swo
1 change: 0 additions & 1 deletion 01.Lesson_GettingStarted/ERC20Lesson1/.zip-output-url.txt

This file was deleted.

96 changes: 52 additions & 44 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20.spec
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
/***
/**
* # ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is an example specification for a generic ERC20 contract. It contains several
* simple rules verifying the integrity of the transfer function.
* To run, execute the following command in terminal:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* One of the rules here is badly phrased, and results in an erroneous fail.
* Understand the counter example provided by the Prover and then run the fixed
* spec:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
// Operations on mathints can never overflow nor underflow
assert balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

Expand All@@ -46,33 +48,39 @@ rule transferSpec {
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
99 changes: 52 additions & 47 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20Fixed.spec
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,86 @@
/***
* # ERC20 Example
/**
* # Fixed ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is the fixed version of ERC20.spec. Note the changes in rule `transferSpec`.
* Run using:
*
*certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* There should be no errors.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint256 amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
assert recip != sender => balance_sender_after == balance_sender_before - amount,
address sender = e.msg.sender; // A convenient alias

// Operations on mathints can never overflow or underflow.
assert recipient != sender => balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

assert recip != sender => balance_recip_after == balance_recip_before + amount,
assert recipient != sender => balance_recip_after == balance_recip_before + amount,
"transfer must increase recipient's balance by amount";

assert recip == sender => balance_sender_after == balance_sender_before,
assert recipient == sender => balance_sender_after == balance_sender_before,
"transfer must not change sender's balancer when transferring to self";
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
60 changes: 36 additions & 24 deletions 01.Lesson_GettingStarted/ERC20Lesson1/Parametric.spec
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,53 @@
/***
* # ERC20 Example
/**
* # ERC20 Parametric Example
*
* This is an example specification for a generic ERC20 contract.
*
* To simulate the execution of all functions in the main contract,
* you can define a method argument in the rule and use it in a statement.
* Run:
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "parametric rule"
* Another example specification for an ERC20 contract. This one using a parametric rule,
* which is a rule that encompasses all the methods in the current contract. It is called
* parametric since one of the rule's parameters is the current contract method.
* To run enter:
*
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "Parametric rule"
*
* The `onlyHolderCanChangeAllowance` fails for one of the methods. Look at the Prover
* results and understand the counter example - which discovers a weakness in the
* current contract.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}


//// ## Part 2: parametric rules ///////////////////////////////////////////////

/// If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance {
address holder; address spender;
/// @title If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance(address holder, address spender, method f) {

// The allowance before the method was called
mathint allowance_before = allowance(holder, spender);

method f; env e; calldataarg args;
env e;
calldataarg args; // Arguments for the method f
f(e, args);

// The allowance after the method was called
mathint allowance_after = allowance(holder, spender);

assert allowance_after > allowance_before => e.msg.sender == holder,
"approve must only change the sender's allowance";

assert allowance_after > allowance_before =>
(f.selector == approve(address,uint).selector || f.selector == increaseAllowance(address,uint).selector),
"only approve and increaseAllowance can increase allowances";
"only the sender can change its own allowance";

// Assert that if the allowance changed then `approve` or `increaseAllowance` was called.
assert (
allowance_after > allowance_before =>
(
f.selector == sig:approve(address, uint).selector ||
f.selector == sig:increaseAllowance(address, uint).selector
)
),
"only approve and increaseAllowance can increase allowances";
}

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Update Lesson 1 to CVL2 and config files by shoham-certora · Pull Request #33 · Certora/Tutorials · GitHub
Skip to content
This repository was archived by the owner on Sep 27, 2023. It is now read-only.
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,11 @@ cache/
.certora*
.last_confs
certora_*
.zip-output-url.txt

# mac
.DS_Store

# vim
.*.swp
.*.swo
1 change: 0 additions & 1 deletion 01.Lesson_GettingStarted/ERC20Lesson1/.zip-output-url.txt

This file was deleted.

96 changes: 52 additions & 44 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20.spec
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
/***
/**
* # ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is an example specification for a generic ERC20 contract. It contains several
* simple rules verifying the integrity of the transfer function.
* To run, execute the following command in terminal:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* One of the rules here is badly phrased, and results in an erroneous fail.
* Understand the counter example provided by the Prover and then run the fixed
* spec:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
// Operations on mathints can never overflow nor underflow
assert balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

Expand All@@ -46,33 +48,39 @@ rule transferSpec {
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
99 changes: 52 additions & 47 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20Fixed.spec
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,86 @@
/***
* # ERC20 Example
/**
* # Fixed ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is the fixed version of ERC20.spec. Note the changes in rule `transferSpec`.
* Run using:
*
*certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* There should be no errors.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint256 amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
assert recip != sender => balance_sender_after == balance_sender_before - amount,
address sender = e.msg.sender; // A convenient alias

// Operations on mathints can never overflow or underflow.
assert recipient != sender => balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

assert recip != sender => balance_recip_after == balance_recip_before + amount,
assert recipient != sender => balance_recip_after == balance_recip_before + amount,
"transfer must increase recipient's balance by amount";

assert recip == sender => balance_sender_after == balance_sender_before,
assert recipient == sender => balance_sender_after == balance_sender_before,
"transfer must not change sender's balancer when transferring to self";
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
60 changes: 36 additions & 24 deletions 01.Lesson_GettingStarted/ERC20Lesson1/Parametric.spec
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,53 @@
/***
* # ERC20 Example
/**
* # ERC20 Parametric Example
*
* This is an example specification for a generic ERC20 contract.
*
* To simulate the execution of all functions in the main contract,
* you can define a method argument in the rule and use it in a statement.
* Run:
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "parametric rule"
* Another example specification for an ERC20 contract. This one using a parametric rule,
* which is a rule that encompasses all the methods in the current contract. It is called
* parametric since one of the rule's parameters is the current contract method.
* To run enter:
*
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "Parametric rule"
*
* The `onlyHolderCanChangeAllowance` fails for one of the methods. Look at the Prover
* results and understand the counter example - which discovers a weakness in the
* current contract.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}


//// ## Part 2: parametric rules ///////////////////////////////////////////////

/// If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance {
address holder; address spender;
/// @title If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance(address holder, address spender, method f) {

// The allowance before the method was called
mathint allowance_before = allowance(holder, spender);

method f; env e; calldataarg args;
env e;
calldataarg args; // Arguments for the method f
f(e, args);

// The allowance after the method was called
mathint allowance_after = allowance(holder, spender);

assert allowance_after > allowance_before => e.msg.sender == holder,
"approve must only change the sender's allowance";

assert allowance_after > allowance_before =>
(f.selector == approve(address,uint).selector || f.selector == increaseAllowance(address,uint).selector),
"only approve and increaseAllowance can increase allowances";
"only the sender can change its own allowance";

// Assert that if the allowance changed then `approve` or `increaseAllowance` was called.
assert (
allowance_after > allowance_before =>
(
f.selector == sig:approve(address, uint).selector ||
f.selector == sig:increaseAllowance(address, uint).selector
)
),
"only approve and increaseAllowance can increase allowances";
}

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Update Lesson 1 to CVL2 and config files by shoham-certora · Pull Request #33 · Certora/Tutorials · GitHub
Skip to content
This repository was archived by the owner on Sep 27, 2023. It is now read-only.
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,11 @@ cache/
.certora*
.last_confs
certora_*
.zip-output-url.txt

# mac
.DS_Store

# vim
.*.swp
.*.swo
1 change: 0 additions & 1 deletion 01.Lesson_GettingStarted/ERC20Lesson1/.zip-output-url.txt

This file was deleted.

96 changes: 52 additions & 44 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20.spec
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
/***
/**
* # ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is an example specification for a generic ERC20 contract. It contains several
* simple rules verifying the integrity of the transfer function.
* To run, execute the following command in terminal:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* One of the rules here is badly phrased, and results in an erroneous fail.
* Understand the counter example provided by the Prover and then run the fixed
* spec:
*
* certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
// Operations on mathints can never overflow nor underflow
assert balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

Expand All@@ -46,33 +48,39 @@ rule transferSpec {
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
99 changes: 52 additions & 47 deletions 01.Lesson_GettingStarted/ERC20Lesson1/ERC20Fixed.spec
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,86 @@
/***
* # ERC20 Example
/**
* # Fixed ERC20 Example
*
* This is an example specification for a generic ERC20 contract.
* To run, execute the following command in terminal/cmd:
* This is the fixed version of ERC20.spec. Note the changes in rule `transferSpec`.
* Run using:
*
*certoraRun ERC20.sol --verify ERC20:ERC20.spec --solc solc8.0
*certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
*
* A simple rule that checks the integrity of the transfer function.
*
* Understand the counter example and then rerun:
*
* certoraRun ERC20.sol: --verify ERC20:ERC20Fixed.spec --solc solc8.0
* There should be no errors.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}

//// ## Part 1: Basic rules ////////////////////////////////////////////////////

/// Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec {
address recip; uint256 amount;
/// @title Transfer must move `amount` tokens from the caller's account to `recipient`
rule transferSpec(address recipient, uint amount) {

env e;
address sender = e.msg.sender;
// mathinttype that represents an integer of any size;
mathint balance_sender_before = balanceOf(sender);
mathint balance_recip_before = balanceOf(recip);

// `mathint` is a type that represents an integer of any size
mathint balance_sender_before = balanceOf(e.msg.sender);
mathint balance_recip_before = balanceOf(recipient);

transfer(e, recip, amount);
transfer(e, recipient, amount);

mathint balance_sender_after = balanceOf(sender);
mathint balance_recip_after = balanceOf(recip);
mathint balance_sender_after = balanceOf(e.msg.sender);
mathint balance_recip_after = balanceOf(recipient);

// operations on mathints can never overflow or underflow.
assert recip != sender => balance_sender_after == balance_sender_before - amount,
address sender = e.msg.sender; // A convenient alias

// Operations on mathints can never overflow or underflow.
assert recipient != sender => balance_sender_after == balance_sender_before - amount,
"transfer must decrease sender's balance by amount";

assert recip != sender => balance_recip_after == balance_recip_before + amount,
assert recipient != sender => balance_recip_after == balance_recip_before + amount,
"transfer must increase recipient's balance by amount";

assert recip == sender => balance_sender_after == balance_sender_before,
assert recipient == sender => balance_sender_after == balance_sender_before,
"transfer must not change sender's balancer when transferring to self";
}


/// Transfer must revert if the sender's balance is too small
rule transferReverts {
env e; address recip; uint amount;
/// @title Transfer must revert if the sender's balance is too small
rule transferReverts(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) < amount;

transfer@withrevert(e, recip, amount);
transfer@withrevert(e, recipient, amount);

assert lastReverted,
"transfer(recip,amount) must revert if sender's balance is less than `amount`";
"transfer(recipient,amount) must revert if sender's balance is less than `amount`";
}


/// Transfer must not revert unless
/// the sender doesn't have enough funds,
/// or the message value is nonzero,
/// or the recipient's balance would overflow,
/// or the message sender is 0,
/// or the recipient is 0
///
/// @title Transfer doesn't revert
rule transferDoesntRevert {
env e; address recipient; uint amount;
/** @title Transfer must not revert unless
* - the sender doesn't have enough funds,
* - or the message value is nonzero,
* - or the recipient's balance would overflow,
* - or the message sender is 0,
* - or the recipient is 0
*/
rule transferDoesntRevert(address recipient, uint amount) {
env e;

require balanceOf(e.msg.sender) > amount;
require e.msg.value == 0;
require balanceOf(recipient) + amount < max_uint;
require e.msg.value == 0; // No payment

// This requirement prevents overflow of recipient's balance.
// We convert `max_uint` to type `mathint` since:
// 1. a sum always returns type `mathint`, hence the left hand side is `mathint`,
// 2. `mathint` can only be compared to another `mathint`
require balanceOf(recipient) + amount < to_mathint(max_uint);

// Recall that `address(0)` is a special address that in general should not be used
require e.msg.sender != 0;
require recipient != 0;

Expand Down
60 changes: 36 additions & 24 deletions 01.Lesson_GettingStarted/ERC20Lesson1/Parametric.spec
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,53 @@
/***
* # ERC20 Example
/**
* # ERC20 Parametric Example
*
* This is an example specification for a generic ERC20 contract.
*
* To simulate the execution of all functions in the main contract,
* you can define a method argument in the rule and use it in a statement.
* Run:
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "parametric rule"
* Another example specification for an ERC20 contract. This one using a parametric rule,
* which is a rule that encompasses all the methods in the current contract. It is called
* parametric since one of the rule's parameters is the current contract method.
* To run enter:
*
* certoraRun ERC20.sol --verify ERC20:Parametric.spec --solc solc8.0 --msg "Parametric rule"
*
* The `onlyHolderCanChangeAllowance` fails for one of the methods. Look at the Prover
* results and understand the counter example - which discovers a weakness in the
* current contract.
*/

methods {
// When a function is not using the environment (e.g., msg.sender), it can be declared as envfree
balanceOf(address) returns(uint) envfree
allowance(address,address) returns(uint) envfree
totalSupply() returns(uint) envfree
// The methods block below gives various declarations regarding solidity methods.
methods
{
// When a function is not using the environment (e.g., `msg.sender`), it can be
// declared as `envfree`
function balanceOf(address) external returns (uint) envfree;
function allowance(address,address) external returns(uint) envfree;
function totalSupply() external returns (uint) envfree;
}


//// ## Part 2: parametric rules ///////////////////////////////////////////////

/// If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance {
address holder; address spender;
/// @title If `approve` changes a holder's allowance, then it was called by the holder
rule onlyHolderCanChangeAllowance(address holder, address spender, method f) {

// The allowance before the method was called
mathint allowance_before = allowance(holder, spender);

method f; env e; calldataarg args;
env e;
calldataarg args; // Arguments for the method f
f(e, args);

// The allowance after the method was called
mathint allowance_after = allowance(holder, spender);

assert allowance_after > allowance_before => e.msg.sender == holder,
"approve must only change the sender's allowance";

assert allowance_after > allowance_before =>
(f.selector == approve(address,uint).selector || f.selector == increaseAllowance(address,uint).selector),
"only approve and increaseAllowance can increase allowances";
"only the sender can change its own allowance";

// Assert that if the allowance changed then `approve` or `increaseAllowance` was called.
assert (
allowance_after > allowance_before =>
(
f.selector == sig:approve(address, uint).selector ||
f.selector == sig:increaseAllowance(address, uint).selector
)
),
"only approve and increaseAllowance can increase allowances";
}

Loading