Formal Reasoning about Decentralized Finance Protocols2021 Move fast and break nothing Formal...
Transcript of Formal Reasoning about Decentralized Finance Protocols2021 Move fast and break nothing Formal...
2021
Move fast and break nothing
Formal Reasoning about
Decentralized Finance Protocols
Mooly Sagiv
Certora and Tel Aviv University
Workshop
• Prerequisites
• Python3.5
• Java 11 and up
• Solidity compiler v0.8.4
• Add solc compb iler to PATH
• Install certora-cli
• pip install certora-cli
• Q&A on Discord
• Checkout CertoraProverSupplementarygithub.com/Certora/CertoraProverSupplementary
• Run Certora Prover
• cd CertoraProverSupplementary/Examples/Liquidate
• ./run.sh
• If you get a ‘permission denied’, use chmod +x run.sh, then run again
Team
Mooly SagivActing CEO & Founder
Shelly GrossmanCTO
Dr. James WilcoxChief of Technical Market
Development
Dr. Nurit DorHead of Product
Dr. John TomanVP R&D
Dr. Yoav RodehSecurity Researcher
Thomas BernardiSoftware Engineer
Or PistinerSoftware Engineer
Dr. Alexander NutzFormal Verification Expert
Anastasia FedotovSoftware Engineer
Uri KirsteinSoftware Engineer
Sameer AroraSoftware Engineer
Lior OppenheimSecurity Researcher
Vasu GuptaSoftware engineer
Noam YakovSecurity Researcher
Nathan TribbleSecurity Researcher
Gadi RechlisSecurity Researcher
Move fast and break nothing
2021
Zach Segal,
Head of Listings, Coinbase
“Certora's technology helps keep Coinbase safe. We
listed 100 new tokens, each with their own unique
risks. Certora lets us move quickly without sacrificing
on security.”
Move fast and break nothing
2020
“Certora’s technology helped to cover security on decentralized
Aave Protocol, essentially finding vulnerabilities that are usually
difficult to find in manual code reviews and audits. When
building mission critical software such as financial technology,
Certora is a must.”
Stani Kulechov ,
Founder and CEO, Aave
Pain point
● High risk in DeFi○ Irreversible transactions
● Code correctness an acute problem
● We reduce the risk in DeFi!
https://cryptobriefing.com/50-million-lost-the-top-19-defi-cryptocurrency-hacks-2020
96mm
8mm
DeFi hacks cost 2020 ($ Millions)
Code vulnerabilities Other
Problem:
Can you prove that your code does what you think it does?
Any loan can be fully repaid
And now?
Token supply must be bounded
Solvent
No front-running
Sushi
Prover (SaaS)
Invariantsessential properties
Product: Continuous Code Verification
Proofs forguaranteed correctness
Test cases with inputs showing actual invariant violations
Low LevelCustomer Code“what we do”
V1
V2
V3
Formal Code Verification
Prover
Invariant:
a: address balances[a]
Proof
a: address old.balances[a]
=
a : address new.balances[a]
Code:
transfer (address from, address to, uint256 amount) {
require (balances[from] amount);balances[from] := balances[from] - amount;balances[to] := balances[to] + amount;}
Formal Code Verification
Prover
Invariant:
a: address balances[a]
Test
from=“Alice”to=“Alice”amount = 18old.balances(Alice) = 20new.balances(Alice) = 38
transfer (address from, address to, uint256 amount) {
require (balances[from] amount);balancesFrom := balances[from] - amount;balancesTo := balances[to] + amount;balances[from] := balancesFrom;balances[to] := balancesTo;
}
Prover
Code
Certora Prover Architecture
VC
GeneratorDecompiler
Smart Contract
EVM
Intermediate
Representation Rules
(CVL)
Mathematical
Constraints
Constraint
Solver
Z3, CVC5, Yices, Vampire
Rules
that are guaranteed
to hold on all inputs
Test cases with inputs
showing actual violations
.
Static
analyzer
a: address balances[a]
transfer (address from, address to, uint256 amount) {
require (balances[from] amount);balancesFrom := balances[from] - amount;balancesTo := balances[to] + amount;balances[from] := balancesFrom;balances[to] := balancesTo;
}
Code:
balances@start(from) amountbalancesFrom = balances@start(from) – amountbalancesTo = balances@start(to) + amountbalances(from) = balancesFrom balances(to) = balancesTobalances(from) + balances(to)
balances@start(from) + balances@start(to)
SMT query
Verification by reduction to SMT
from=“Alice”to=“Alice”amount = 18balances@start(Alice) = 20balances(Alice) = 38
SatisfiableInvariant:
mathint amountuninterpreted balances:mathint mathintuninterpreted balances@start:mathint mathint
a: address balances[a]
transfer (from, to, amount) {require from ≠ to;require (balances[from] amount);balancesFrom := balances[from] - amount;balancesTo := balances[to] + amount;balances[from] := balancesFrom;balances[to] := balancesTo;
}
Code:
from ≠ tobalances@start(rom) amountbalancesFrom = balances@start(from) – amountbalancesTo = balances@start(to) + amountbalances(from) = balancesFrom balances(to) = balancesTobalances(from) + balances(to)
balances@start(from) + balances@start(to)
SMT query
Verification by reduction to SMT
UnsatisfiableInvariant:
SMT solving: Saturation x Model Finding
models
proofs
Model (Bug) FindingFind truth values which make the formula
satisfiable
SaturationLook for a proof tree which shows that the negation of the formula is
valid
The SushiSwap Protocol
• Sophisticated protocol (established 2020)
• Decentralized exchange
• Tricky code & thousands of locs
• TVL $2.71B
KashiPair simplified
Bob has assets to
cover the loan of
Alice
Collateral tokenBorrow token
Collateral token
Bob Borrow assets
reduced and
collateral asset
increased
Borrow tokenCollateral token
liquidate(Alice, Bob)
msg.sender = Bob
System receive
Borrow for
Collateral
Borrow token
Collateral tokenBorrow token
System’s assets Bob’s assets
Inside the system External to the system
Bob’s assetsSystem’s assets
User Userborrow
Usercollateral
Alice 100 120
…
User Userborrow
Usercollateral
Alice 0 0
…
Alice has a loan
Alice’s loan is closed
1 <=> 1.3
KashiPair simplified
function batchCalls(address[] callee, bytes[] calldata datas) {…callee[i].call(datas[i]);
}
/* Liquidation of a user that is in insolvent state.user - address to liquidateto - address to receive collateral */
function liquidate(address user, address to) {require (!_isSolvent(user));
borrow = userBorrowAmount[user];collateral = userCollateralAmount[user];
userBorrowAmount[user] = 0;userCollateralAmount[user] = 0;
borrowToken.transferFrom(msg.sender, address(this), borrow);collateralToken.transfer(to, collateral);
}
any function with any argumentsany contract list
Good Properties of Liquidation
● Collateral token balance and Borrow token balance are complementary
● If Borrow token balance increases then Collateral token balance decreases
● Weaker requirements in practice
Collateral=100Borrow=60
Collateral=80Borrow=80
Collateral=90Borrow=70
Collateral=90Borrow=60
Rule: Antimonotonicity of liquidation
{ b = BorrowT.balanceOf(System) ٿ c = CollateralT.balanceOf(System) }
liquidate(x, y)
{ BorrowT.balanceOf(System) > b CollateralT.balanceOf(System) < c }
https://www.certora.com/pubs/KashiLendingMar2021.pdf
Checking antimonotonicity
Results
function liquidate(address user, address to) {require (!_isSolvent(user));
borrow = userBorrowAmount[user];collateral = userCollateralAmount[user];
userBorrowAmount[user] = 0;userCollateralAmount[user] = 0;
borrowToken.transferFrom(msg.sender,address(this), borrow);
collateralToken.transfer(to, collateral);}
rule antimonotonicityOfLiquidation () {env e;address user;address to;
collateralBefore = collateralToken.balanceOf(this);borrowBefore = borrowToken.balanceOf(this);…
liquidate(e, user, to);
collateralAfter = collateralToken.balanceOf(this);borrowAfter = borrowToken.balanceOf(this);
assert(borrowBefore < borrowAfter <=>collateralBefore > collateralAfter);
}
KashiPair simplified
Mallory does not
need assets to cover
the loan
Collateral tokenBorrow token
Collateral token
Mallory received
assets
Borrow token
Collateral token
Borrow token
Collateral tokenBorrow token
System’s assets Mallory’s assets
Inside the system External to the system
Bob’s assetsSystem’s assets
User Userborrow
Usercollateral
Alice 100 120
…
User Userborrow
Usercollateral
Alice 0 0
…
Alice has a loan
Alice’s loan is closed
1 <=> 1.3
batchCalls([System],[liquidate(Mallory, Alice)] ) msg.sender = Mallory
liquidate(Alice, Mallory) msg.sender = this
System only
gives Collateral
System “pays”
itself for the
loan
Checking antimonotonicity of fixed code
function liquidate(address user, address to) {require (!_isSolvent(user));require (msg.sender != address(this));
borrow = userBorrowAmount[user];collateral = userCollateralAmount[user];
userBorrowAmount[user] = 0;userCollateralAmount[user] = 0;
borrowToken.transferFrom(msg.sender,address(this), borrow);
collateralToken.transfer(to, collateral);}
Results
rule antimonotonicityOfLiquidation () {env e;address user;address to;
collateralBefore = collateralToken.balanceOf(this);borrowBefore = borrowToken.balanceOf(this);…
liquidate(e, user, to);
collateralAfter = collateralToken.balanceOf(this);borrowAfter = borrowToken.balanceOf(this);
assert(borrowBefore < borrowAfter <=>collateralBefore > collateralAfter);
}
Formal Verification Techniques
Technique ToolsProgramming Language
Advantages
Interactive
Theorem Proving
Isabelle/HOL
Coq
Lean
K
Agnostic Completeness
(Sound) Static
Analysis
Chainsecurity
Astrée
Certora
Deductive
Verification
SMTchecker
Solc-Verifier
Verisol
Dafny
Ivy
Certora
Solidity
Solidity
Solidity
Specialized .net
Specialized C++
EVM
CI
Bug finding
Solidity
C
EVM
CI
Locality
Company
Team
Mooly SagivActing CEO & Founder
Shelly GrossmanCTO
Dr. James WilcoxChief of Technical Market
Development
Dr. Nurit DorHead of Product
Dr. John TomanVP R&D
Dr. Yoav RodehSecurity Researcher
Thomas BernardiSoftware Engineer
Or PistinerSoftware Engineer
Dr. Alexander NutzFormal Verification Expert
Anastasia FedotovSoftware Engineer
Uri KirsteinSoftware Engineer
Sameer AroraSoftware Engineer
Lior OppenheimSecurity Researcher
Vasu GuptaSoftware engineer
Noam YakovSecurity Researcher
Nathan TribbleSecurity Researcher
Gadi RechlisSecurity Researcher
FV is useful in the
development phase
Formal specification
to crystallize ideasCode is law Spec is law
Early detection of
subtle errors
Formal guarantee
for essential rules
Continuous CI/CD
Faster and Safer Development
Take Away