-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Formal Verification #5
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
861141d
ERC20 mock
ElliotFriedman e36aa1c
repayBadDebt -> repayBadDebtWithCash
ElliotFriedman c17a223
remove extraneous code, simplify
ElliotFriedman 7aff94a
repayBadDebt -> repayBadDebtWithCash
ElliotFriedman 06be734
certora mock to fetch principal and borrow index
ElliotFriedman d6c8ce9
start of certora specs
ElliotFriedman 8b0810c
.certora_internal added to gitignore
ElliotFriedman be620a3
remove rule sanity
ElliotFriedman 5f3127c
remove 0xB3E6420941AcC44C2996666b4B5C998C1545fc19 from mFrax liquidat…
ElliotFriedman 53819b0
remove 0xA5D20094Cf1Bc45B6FF1eB3B5A828865C1E8d583 from frax proposal,…
ElliotFriedman 6a9bb83
remove 0xcD111815A4Acb5355E137B2aae6Ec3E043D57ce9 from mxcDot propose…
ElliotFriedman 204005a
two more v's to log out information
ElliotFriedman decfe0e
remove 0x57e421c8a16bf0a609ef87e296cb931113a5e3ed from mxcDot propose…
ElliotFriedman 68c4d4b
remove checks on mxcDOT users
ElliotFriedman 33e285d
remove two v's to trim log output
ElliotFriedman 872d460
summary and dispatcher, ghosts, additional rules and invariants
ElliotFriedman 1dd5d24
link well address to underlying to prevent havoc
ElliotFriedman 83c11ec
getUserBorrowInterestIndex, getInitialExchangeRateMantissa
ElliotFriedman 272aea8
bump mETH cash 2269023468465447134524 -> 2269023504004122147416
ElliotFriedman 4caf882
summarize borrowBalanceStored
ElliotFriedman 6196964
mock all comptroller behavior, add cannotChangeBadDebt
ElliotFriedman 8ebfbb6
share price specification
ElliotFriedman 679b284
remove todo
ElliotFriedman a0d5f7e
testFixUserFailsNoUserBorrows, testFixUserFailsUserEqLiquidator
ElliotFriedman 45c3fe4
rule sanity advanced -> basic
ElliotFriedman bc2e736
getUserBorrowSnapshot gets interest index
ElliotFriedman File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,3 +12,5 @@ docs/ | |
|
||
# Dotenv file | ||
.env | ||
|
||
.certora_internal/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"files": [ | ||
"test/mock/MockMErc20DelegateFixer.sol", | ||
"src/Comptroller.sol", | ||
"src/JumpRateModel.sol", | ||
"test/mock/MockERC20.sol" | ||
], | ||
"verify": "MockMErc20DelegateFixer:certora/specs/MErc20DelegateFixer.spec", | ||
"send_only": true, | ||
"optimistic_loop": true, | ||
"solc": "solc", | ||
"msg": "Verification of MErc20DelegateFixer", | ||
"rule_sanity": "basic", | ||
"packages": [], | ||
"link": [ | ||
"MockMErc20DelegateFixer:underlying=MockERC20", | ||
"MockMErc20DelegateFixer:comptroller=Comptroller", | ||
"MockMErc20DelegateFixer:interestRateModel=JumpRateModel", | ||
"Comptroller:wellAddress=MockERC20" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"files": [ | ||
"test/mock/MockMErc20DelegateFixer.sol", | ||
"test/mock/MockERC20.sol" | ||
], | ||
"verify": "MockMErc20DelegateFixer:certora/specs/SharePrice.spec", | ||
"send_only": true, | ||
"optimistic_loop": true, | ||
"solc": "solc", | ||
"msg": "Verification of MErc20DelegateFixer", | ||
"rule_sanity": "basic", | ||
"packages": [], | ||
"link": [ | ||
"MockMErc20DelegateFixer:underlying=MockERC20" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
using MockERC20 as token; | ||
using MockMErc20DelegateFixer as fixer; | ||
using Comptroller as comptroller; | ||
using JumpRateModel as jrm; | ||
|
||
methods { | ||
function badDebt() external returns (uint256) envfree; | ||
function fixUser(address liquidator, address user) external; | ||
function borrowIndex() external returns (uint256) envfree; | ||
function totalBorrows() external returns (uint256) envfree; | ||
function totalReserves() external returns (uint256) envfree; | ||
function token.balanceOf(address) external returns (uint256) envfree; | ||
function balanceOf(address) external returns (uint256) envfree; | ||
function repayBadDebtWithCash(uint256) external; | ||
function accrueInterest() external returns (uint256); | ||
function exchangeRateCurrent() external returns (uint256); | ||
function borrowBalanceCurrent(address account) external returns (uint256); | ||
function repayBadDebtWithReserves() external envfree; | ||
function getUserBorrowSnapshot(address user) external returns (uint256, uint256) envfree; | ||
function getUserBorrowInterestIndex(address user) external returns (uint256) envfree; | ||
function getInitialExchangeRateMantissa() external returns (uint256) envfree; | ||
|
||
/// summarize these calls to prevent prover havoc | ||
function _.isComptroller() external => DISPATCHER(true); | ||
function _.isInterestRateModel() external => DISPATCHER(true); | ||
function _.admin() external => DISPATCHER(true); | ||
function _.borrowIndex() external => DISPATCHER(true); | ||
function _._acceptImplementation() external => CONSTANT; | ||
function comptroller._ external => NONDET; | ||
function jrm._ external => NONDET; | ||
} | ||
|
||
ghost uint256 borrowIndex { | ||
init_state axiom borrowIndex == 0; | ||
} | ||
|
||
ghost uint256 totalBorrows { | ||
init_state axiom totalBorrows == 0; | ||
} | ||
|
||
ghost uint256 initialExchangeRateMantissa { | ||
init_state axiom initialExchangeRateMantissa == 0; | ||
} | ||
|
||
hook Sstore borrowIndex uint256 newBorrowIndex (uint256 oldBorrowIndex) STORAGE { | ||
borrowIndex = newBorrowIndex; | ||
} | ||
|
||
hook Sstore totalBorrows uint256 newTotalBorrows (uint256 oldTotalBorrows) STORAGE { | ||
totalBorrows = newTotalBorrows; | ||
} | ||
|
||
hook Sstore initialExchangeRateMantissa uint256 newInitialExchangeRateMantissa (uint256 oldInitialExchangeRateMantissa) STORAGE { | ||
initialExchangeRateMantissa = newInitialExchangeRateMantissa; | ||
} | ||
|
||
function one() returns uint256 { | ||
return 1000000000000000000; | ||
} | ||
|
||
function uintMax() returns uint256 { | ||
return 2 ^ 256 - 1; | ||
} | ||
|
||
/// market initialization check | ||
|
||
invariant ghostInitialExchangeRateMantissaMirrorsStorage() | ||
initialExchangeRateMantissa == getInitialExchangeRateMantissa(); | ||
|
||
invariant ghostBorrowIndexMirrorsStorage() | ||
borrowIndex == borrowIndex(); | ||
|
||
invariant ghostTotalBorrowsMirrorsStorage() | ||
totalBorrows == totalBorrows(); | ||
|
||
invariant initialBorrowIndexGteOne() | ||
borrowIndex() != 0 => borrowIndex() >= one(); | ||
|
||
// invariant initialExchangeRateMantissaGteOne() | ||
// borrowIndex() != 0 => initialExchangeRateMantissa() >= one(); | ||
|
||
invariant exchangeRateGteOne(env e) | ||
borrowIndex() >= one() => exchangeRateCurrent(e) >= one() { | ||
preserved { | ||
requireInvariant initialBorrowIndexGteOne(); | ||
// requireInvariant initialExchangeRateMantissaGteOne(); | ||
} | ||
} | ||
|
||
invariant userBorrowIndexLteExchangeRateCurrent(env e, address user) | ||
getUserBorrowInterestIndex(user) != 0 => | ||
(getUserBorrowInterestIndex(user) <= exchangeRateCurrent(e) && getUserBorrowInterestIndex(user) >= one()) { | ||
preserved { | ||
requireInvariant ghostBorrowIndexMirrorsStorage(); | ||
} | ||
} | ||
|
||
rule fixUserIncreasesBadDebt(env e) { | ||
address user; | ||
address liquidator; | ||
uint256 principle; | ||
uint256 interestIndex; | ||
|
||
principle, interestIndex = getUserBorrowSnapshot(user); | ||
|
||
uint256 badDebt = badDebt(); | ||
uint256 liquidatorBalance = balanceOf(liquidator); | ||
uint256 userBalance = balanceOf(user); | ||
uint256 borrowBalance = borrowBalanceCurrent(e, user); | ||
|
||
fixUser(e, liquidator, user); | ||
|
||
uint256 badDebtAfter = badDebt(); | ||
|
||
assert badDebtAfter >= badDebt, "bad debt decreased from fixing a user"; | ||
assert balanceOf(user) == 0, "user balance not zero"; | ||
assert (liquidatorBalance + userBalance == to_mathint(balanceOf(liquidator))), "liquidator balance not increased by user balance"; | ||
assert borrowBalanceCurrent(e, user) == 0, "user borrow balance not zero"; | ||
assert interestIndex >= one() => (badDebt + borrowBalance == to_mathint(badDebtAfter)), "bad debt not increased by user borrow amt"; | ||
} | ||
|
||
rule fixingUserDoesNotChangeSharePrice(env e) { | ||
address user; | ||
address liquidator; | ||
|
||
uint256 startingSharePrice = exchangeRateCurrent(e); | ||
|
||
fixUser(e, liquidator, user); | ||
|
||
assert exchangeRateCurrent(e) == startingSharePrice, "share price should not change fixing user"; | ||
} | ||
|
||
rule repayBadDebtDecreasesBadDebt(env e, uint256 repayAmount) { | ||
require e.msg.sender != fixer; | ||
|
||
uint256 badDebt = badDebt(); | ||
uint256 userBalance = token.balanceOf(e.msg.sender); | ||
uint256 mTokenBalance = token.balanceOf(fixer); | ||
uint256 startingSharePrice = exchangeRateCurrent(e); | ||
|
||
repayBadDebtWithCash(e, repayAmount); | ||
|
||
uint256 badDebtAfter = badDebt(); | ||
|
||
assert repayAmount != 0 => badDebtAfter < badDebt, "bad debt did not decrease from repaying"; | ||
assert badDebtAfter <= badDebt, "bad debt increased from repaying"; | ||
assert to_mathint(token.balanceOf(fixer)) == mTokenBalance + repayAmount, "underlying balance did not increase"; | ||
assert badDebt - repayAmount == to_mathint(badDebt()), "bad debt not decreased by repay amt"; | ||
assert exchangeRateCurrent(e) == startingSharePrice, "share price should not change repaying bad debt"; | ||
} | ||
|
||
rule badDebtRules(method f, env e, calldataarg args) | ||
filtered { | ||
f -> | ||
f.selector == sig:fixUser(address,address).selector || | ||
f.selector == sig:repayBadDebtWithCash(uint256).selector || | ||
f.selector == sig:repayBadDebtWithReserves().selector | ||
} { | ||
require e.msg.sender != fixer; | ||
|
||
uint256 startingSharePrice = exchangeRateCurrent(e); | ||
uint256 startingBadDebt = badDebt(); | ||
uint256 startingCash = token.balanceOf(fixer); | ||
|
||
f(e, args); | ||
|
||
uint256 endingBadDebt = badDebt(); | ||
|
||
assert startingCash + startingBadDebt <= to_mathint(uintMax()) => | ||
exchangeRateCurrent(e) == startingSharePrice, | ||
"share price should not change repaying bad debt"; | ||
|
||
assert (endingBadDebt > startingBadDebt) => | ||
(f.selector == sig:fixUser(address,address).selector), | ||
"bad debt should only increase when fixing users"; | ||
|
||
assert (startingBadDebt >= endingBadDebt) => | ||
(f.selector == sig:repayBadDebtWithCash(uint256).selector || | ||
f.selector == sig:repayBadDebtWithReserves().selector), | ||
"bad debt should only increase when fixing users"; | ||
} | ||
|
||
rule cannotChangeBadDebt(method f, env e, calldataarg args) | ||
filtered { | ||
f -> | ||
!f.isView && | ||
f.selector != sig:fixUser(address,address).selector && | ||
f.selector != sig:repayBadDebtWithCash(uint256).selector && | ||
f.selector != sig:repayBadDebtWithReserves().selector | ||
} { | ||
require e.msg.sender != fixer; | ||
|
||
uint256 startingBadDebt = badDebt(); | ||
|
||
f(e, args); | ||
|
||
uint256 endingBadDebt = badDebt(); | ||
|
||
assert endingBadDebt == startingBadDebt, | ||
"bad debt should not change"; | ||
} | ||
|
||
rule repayBadDebtWithReserves(env e) { | ||
uint256 startingReserves = totalReserves(); | ||
uint256 startingBadDebt = badDebt(); | ||
|
||
repayBadDebtWithReserves(); | ||
|
||
uint256 endingReserves = totalReserves(); | ||
uint256 endingBadDebt = badDebt(); | ||
|
||
assert (startingReserves >= startingBadDebt) => | ||
(endingBadDebt == 0), | ||
"bad debt not fully paid off"; | ||
|
||
assert (startingReserves < startingBadDebt) => | ||
(to_mathint(endingBadDebt) == startingBadDebt - startingReserves), | ||
"bad debt not paid off by reserve amount"; | ||
} | ||
|
||
rule repayBadDebtWithReservesDoesNotChangeSharePrice(env e) { | ||
require e.msg.sender != fixer; | ||
|
||
uint256 startingSharePrice = exchangeRateCurrent(e); | ||
|
||
repayBadDebtWithReserves(); | ||
|
||
uint256 endingSharePrice = exchangeRateCurrent(e); | ||
|
||
assert endingSharePrice == startingSharePrice, "share price should remain unchanged"; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this commented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because it isn't an actual invariant. initializer doesn't ensure exchange rate is gte one on initialization