Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
**/emv*
**/collect.json

.vscode/
24 changes: 18 additions & 6 deletions CVLByExample/TransientStorage/Hooks/MockMutexer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,29 @@ contract MockMutexer is Mutexer {

constructor() contractLock{}

// getters
function lockValue() external returns (uint){
return uint(Mutex.Locked);
}
function getContractLock() public returns (uint256){
return CONTRACT_LOCK;
function isLocked() external returns (bool){
Mutex value;
uint256 key = CONTRACT_LOCK;
assembly {
value := tload(key)
}
return value == Mutex.Locked;
Comment on lines +20 to +24

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should actually be _tload() which is private in the parent contract and cannot be called here.

}


// for testing

function contractLevelAccess() external contractLock {
emit Accessed(Access.Contract);
}
function changeLock() external {
_mocktstore(CONTRACT_LOCK, Mutex.Locked);
}

function _mocktstore(uint256 key, Mutex value) private {
assembly {
tstore(key, value)
}
}

}
28 changes: 15 additions & 13 deletions CVLByExample/TransientStorage/Hooks/Mutexer.spec
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
methods {

function lockValue() external returns (uint256) envfree;
function getContractLock() external returns (uint256) envfree;
function isLocked() external returns (bool) envfree;
}

definition isLocked(uint256 status) returns bool = status == lockValue();

persistent ghost bool contract_lock_status;
definition isLockedCVL(uint256 status) returns bool = status == 1;
definition slot() returns uint = 0xa2e3618ded7ae709dc8e3747186ad9112d852b6c6eef8285dea55c33fdac431f;

persistent ghost bool contract_lock_status{
init_state axiom contract_lock_status == false;
}
hook ALL_TSTORE(uint loc, uint v) {
if (loc == getContractLock() && executingContract == currentContract) {
contract_lock_status = isLocked(v);
if(loc == slot() && executingContract == currentContract){
contract_lock_status = isLockedCVL(v);
} else {
havoc contract_lock_status;
}
}

hook ALL_TLOAD(uint loc) uint v {
if (loc == getContractLock() && executingContract == currentContract) {
require contract_lock_status == isLocked(v);
if(loc == slot() && executingContract == currentContract){
require contract_lock_status == isLockedCVL(v);
} else {
havoc contract_lock_status;
}
}

invariant lockStatusDontChange()
!contract_lock_status;

// if contract was locked function call always reverted
rule checkContractLockReverts(){
env e;
require contract_lock_status; // require contract is locked
require isLocked(); // require contract is locked

contractLevelAccess@withrevert(e);

Expand Down
3 changes: 2 additions & 1 deletion CVLByExample/TransientStorage/Hooks/runTransientStorage.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
],
"verify": "MockMutexer:./Mutexer.spec",
"solc": "solc8.24",
"solc_evm_version": "cancun"
"solc_evm_version": "cancun",
"prover_args": ["-enableStorageSplitting false"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was needed here as we got storage splitting failures otherwise. @christiane-certora do you know if this is expected here?

The contract defines a constant slot via keccak256("Mutexer.CONTRACT_LOCK") and then in spec uses alltload/alltstore.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the contract uses ALLTLOAD or ALLTSTORE, you should get an error that storage splitting must be disabled, these hook types are not compatible with it (same as the non-transient versions of them).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So should we instead rather use TLOAD and TSTORE here directly (now that we support them?)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we could, yeah, although the currently used way of course also still works.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But maybe it is not so bad to leave this example, since there are still a lot of patterns with transient storage where we will fail the storage analysis (when the slot accessed is just a parameter, typically), so in that case it could be useful to still see how it can be worked with without.

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
invariant isUnlocked(env e)
getLock(e) == 0;
invariant isUnlocked(env e)
getLock(e) == 0
filtered {
// The functions repay and borrow use the modifier onlyLocked therefore excluding them here, as it causes vacuity with the invariant expression.
f -> f.selector != sig:repay(int256).selector
&& f.selector != sig:borrow(int256).selector
// The function callback calls repay and borrow, thus same applied as above.
&& f.selector != sig:CallBacker.callback().selector
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what was the sanity failure? we prefer invariant as the requireinvariant is safer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue here was that a few functions (repay, borrow, callback) use a modifier onlyLocked that contract with the actual invariant and cause vacuity. I filtered them for this invariant.

invariant deltaZeroWhenUnlocked(env e)
getLock(e) == 0 => getDelta(e) == 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,6 @@ hook Sstore _customers[KEY address user].accounts.length uint256 newLength {
numOfAccounts[user] = newLength;
}

/**
An internal step check to verify that our ghost works as expected, it should mirror the number of accounts.
Once the sload is defined, this invariant becomes a tautology

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the sload is defined, this invariant becomes a tautology

Is sload now defined nowadays, so is this why we see the sanity failure for trivial post condition of the invariant?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reviewing, I suggest we indeed remove this invariant as it fails on SANITY due to the tautology check.

*/
invariant checkNumOfAccounts(address user)
numOfAccounts[user] == bank.getNumberOfAccounts(user);

/// This Sload is required in order to eliminate adding unintializaed account balance to sumBalances.
hook Sload uint256 length _customers[KEY address user].accounts.length {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ contract ConstantProductPool is ERC20 {
}

constructor(address _token0, address _token1) {
require(token0 != address(0));
require(token0 != token1);
require(_token0 != address(0));
require(_token0 != _token1);
token0 = _token0;
token1 = _token1;
locked = 1;
Expand Down
2 changes: 1 addition & 1 deletion DEFI/LiquidityPool/certora/specs/Full.spec
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ rule sharesRoundingTripFavoursContract(env e) {
prove bug fix
*/
invariant noClientHasSharesWithMoreValueThanDepositedAmount(address a)
sharesToAmount(balanceOf(a)) <= depositedAmount()
totalSupply() == 0 || sharesToAmount(balanceOf(a)) <= depositedAmount()
{
preserved with(env e) {
require balanceOf(a) + balanceOf(e.msg.sender) < totalSupply();
Expand Down