Skip to content

Niv/fix sanity failure #183

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

Open
wants to merge 5 commits into
base: cli-beta
Choose a base branch
from
Open

Niv/fix sanity failure #183

wants to merge 5 commits into from

Conversation

nivcertora
Copy link
Contributor

No description provided.

@nivcertora nivcertora requested a review from liav-certora July 6, 2025 09:49
@nivcertora nivcertora self-assigned this Jul 6, 2025
@nivcertora nivcertora changed the base branch from master to cli-beta July 6, 2025 09:49
@nivcertora nivcertora requested review from nd-certora and yoav-el-certora and removed request for liav-certora July 6, 2025 10:44
@yoav-el-certora yoav-el-certora requested a review from johspaeth July 7, 2025 08:32
Copy link
Contributor

@johspaeth johspaeth left a comment

Choose a reason for hiding this comment

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

Minor nits and questions - looks ok for me.

Comment on lines 23 to 25
// invariant version is getting SANITY_FAILURE using the below rule instead
// invariant lockStatusDontChange()
// !contract_lock_status;
Copy link
Contributor

Choose a reason for hiding this comment

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

Which sanity failure did you receive on this invariant?

I couldn't find a link to a failing job here:
https://certora.atlassian.net/browse/CERT-9252

Copy link
Contributor

Choose a reason for hiding this comment

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

would also like to know what was wrong with the invariant, it looks ok and we still want such invariants. do you have a link to the original spec with the sanity failures

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

I reverted the change by Niv and make this invariant working again.

The issue here is / was that there is a hook on tload/tstore within the constructor and the hooks call out to external solidity function of the same contract which isn't allowed within the constructor (the contract hasn't been created yet). In my fix, I removed all calls that are applied in the hook and changed the accesses to constants that are declared in CVL. This is the only solution I could find here.

@@ -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
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
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.

Comment on lines 23 to 25
// invariant version is getting SANITY_FAILURE using the below rule instead
// invariant lockStatusDontChange()
// !contract_lock_status;
Copy link
Contributor

Choose a reason for hiding this comment

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

would also like to know what was wrong with the invariant, it looks ok and we still want such invariants. do you have a link to the original spec with the sanity failures

@@ -1,14 +1,11 @@
invariant isUnlocked(env e)
getLock(e) == 0;

Copy link
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
Contributor

Choose a reason for hiding this comment

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

Copy link
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.

Copy link
Contributor

@johspaeth johspaeth left a comment

Choose a reason for hiding this comment

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

@nd-certora @yoav-el-certora please re-review.

  1. The issue with lockStatusDontChange was that a hook in the constructor called an external method on the same contract - this causes vacuity as the contract doesn't exist yet. This is also not allowed in solidity itself.
  2. I added a new function changeLock which shows that the invariant fails for lockStatusDontChange. This is expected, @yoav-el-certora. Please update output.json expectedly.
  3. For isUnlocked a filter for a few function was needed.

@@ -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
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.

@@ -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
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
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
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
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
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.

@@ -1,14 +1,11 @@
invariant isUnlocked(env e)
getLock(e) == 0;

Copy link
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.

Comment on lines +20 to +24
uint256 key = CONTRACT_LOCK;
assembly {
value := tload(key)
}
return value == Mutex.Locked;
Copy link
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.

@johspaeth johspaeth requested a review from nd-certora July 18, 2025 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants