Skip to content

Commit 4bc567b

Browse files
26.02.26 Release
26.02.26 Release b923324d63bc0fdefeff0007c11ca3b7047abc49
2 parents 0040758 + 839b324 commit 4bc567b

File tree

109 files changed

+3886
-2137
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+3886
-2137
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ commands:
173173
jobs:
174174
build:
175175
docker:
176-
- image: &img public.ecr.aws/certora/cvt-image:2025.05.12-4993-b9e8bfc
176+
- image: &img public.ecr.aws/certora/cvt-image:2026.02.23-5696-87a3342
177177
working_directory: ~/repo
178178
environment:
179179
# configure sccache usage

Public/TestEVM/Clz/C.conf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"files": [
3+
"C.sol"
4+
],
5+
"solc": "solc8.31",
6+
"verify": "C:C.spec"
7+
}

Public/TestEVM/Clz/C.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
contract C {
2+
function clz(uint256 x) pure public returns (uint256 r) {
3+
assembly {
4+
r := clz(x)
5+
}
6+
}
7+
}

Public/TestEVM/Clz/C.spec

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
methods {
2+
function clz(uint256) external returns (uint256) envfree;
3+
}
4+
5+
rule clz() {
6+
uint256 x = 127;
7+
uint256 r = clz(x);
8+
assert r <= 256;
9+
mathint bound = 2^(256 - r);
10+
assert x < bound;
11+
assert x >= bound / 2;
12+
}
13+
14+
rule clz_tests {
15+
// https://eips.ethereum.org/EIPS/eip-7939#test-cases
16+
assert clz(0x0000000000000000000000000000000000000000000000000000000000000000) == 0x0000000000000000000000000000000000000000000000000000000000000100;
17+
assert clz(0x8000000000000000000000000000000000000000000000000000000000000000) == 0x0000000000000000000000000000000000000000000000000000000000000000;
18+
assert clz(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) == 0x0000000000000000000000000000000000000000000000000000000000000000;
19+
assert clz(0x4000000000000000000000000000000000000000000000000000000000000000) == 0x0000000000000000000000000000000000000000000000000000000000000001;
20+
assert clz(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) == 0x0000000000000000000000000000000000000000000000000000000000000001;
21+
assert clz(0x0000000000000000000000000000000000000000000000000000000000000001) == 0x00000000000000000000000000000000000000000000000000000000000000ff;
22+
}

Public/TestEVM/Clz/C_bv.conf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"files": [
3+
"C.sol"
4+
],
5+
"solc": "solc8.31",
6+
"verify": "C:C.spec",
7+
"smt_use_bv": true
8+
}

Public/TestEVM/Clz/expectedC.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"rules": {
3+
"clz": "SUCCESS",
4+
"clz_tests": "SUCCESS",
5+
"envfreeFuncsStaticCheck": {
6+
"SUCCESS": [
7+
"clz(uint256)"
8+
]
9+
}
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"rules": {
3+
"clz": "SUCCESS",
4+
"clz_tests": "SUCCESS",
5+
"envfreeFuncsStaticCheck": {
6+
"SUCCESS": [
7+
"clz(uint256)"
8+
]
9+
}
10+
}
11+
}

lib/GeneralUtils/src/main/kotlin/algorithms/Reachability.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,43 @@ inline fun <V> traverseBFS(start: Iterable<V>, nexts: (V) -> Iterable<V>?): Unit
8181
inline fun <V> traverseBFS1(start: V, nexts: (V) -> Iterable<V>?): Unit {
8282
traverseBFS(listOf(start), nexts)
8383
}
84+
85+
/**
86+
* Compacts a graph to only include edges between target nodes.
87+
* Intermediate (non-target) nodes are collapsed, creating direct edges between target nodes.
88+
*
89+
* For each target node, finds all other target nodes reachable from it through paths
90+
* that may traverse non-target intermediate nodes.
91+
*
92+
*
93+
* @param targetNodes Nodes to keep in the compacted graph
94+
* @param nexts Function returning successors for each node
95+
* @return MultiMap representing the compacted graph's adjacency list
96+
*/
97+
inline fun <V> compactGraph(targetNodes: Set<V>, crossinline nexts: (V) -> Iterable<V>?): MultiMap<V, V> {
98+
val result = mutableMapOf<V, Set<V>>()
99+
100+
for (startNode in targetNodes) {
101+
// Get immediate successors of this target node
102+
val immediateSuccessors = nexts(startNode) ?: emptyList()
103+
104+
// Find all target nodes reachable through non-target intermediate nodes
105+
val reachableTargets = getReachable(immediateSuccessors) { node ->
106+
if (node in targetNodes) {
107+
// Reached a target node - include it but don't traverse further
108+
null
109+
} else {
110+
// Non-target node - traverse through it
111+
nexts(node)
112+
}
113+
}
114+
115+
// Store all reachable target nodes as successors
116+
val targets = reachableTargets.filter { it in targetNodes }.toSet()
117+
if (targets.isNotEmpty()) {
118+
result[startNode] = targets
119+
}
120+
}
121+
122+
return result
123+
}

lib/Shared/src/main/kotlin/compiler/SourceContext.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
package compiler
1919

2020
import com.certora.collect.*
21+
import config.Config
2122
import log.Logger
2223
import log.LoggerTypes
2324
import report.TreeViewLocation
2425
import utils.Range
2526
import utils.*
2627
import java.io.File
2728
import java.io.Serializable
29+
import kotlin.io.path.Path
2830

2931
private val logger = Logger(LoggerTypes.COMMON)
3032

@@ -130,6 +132,8 @@ data class SourceSegment(
130132
)
131133
}
132134

135+
136+
133137
fun resolveFromContext(context: SourceContext, identifier: SourceIdentifier): SourceSegment? {
134138
val sourceFile = identifier.resolve(context) ?: return null
135139
val relativeFile = identifier.relativeSourceFile(context)?.toString() ?: return null
@@ -138,6 +142,44 @@ data class SourceSegment(
138142
}
139143
}
140144

145+
/**
146+
* Takes a [range] and approximates the [compiler.SourceSegment] for it.
147+
*
148+
* Note, that this function is used in Solana where a Range doesn't have a precise end
149+
* and the returned [compiler.SourceSegment]'s content will just start at
150+
* [range]'s start and contain the remaining content of the start line.
151+
*/
152+
fun Range.Range?.toHeuristicSourceSegment(): SourceSegment? {
153+
val sourceFile = this?.file ?: return null
154+
val (allBytes, lineStarts) = try {
155+
val sourceFilePath = Path(sourceFile)
156+
val s = if(sourceFilePath.isAbsolute){
157+
CertoraFileCache.tryResolvePathInCargoHome(sourceFile)?.toString() ?: sourceFile
158+
} else{
159+
Config.prependSourcesDir(sourceFile)
160+
}
161+
CertoraFileCache.byteContent(s) to CertoraFileCache.lineStarts(s)
162+
} catch (e: CertoraFileCacheException) {
163+
logger.warn(e.cause) { "Had error when reading from source file $sourceFile" }
164+
return null
165+
}
166+
val startIndex =
167+
lineStarts.lineNumberStartOffset(start.line.toInt())?.let { it + start.charByteOffset.toInt() }
168+
?: return null
169+
// This is a heuristic, we don't have the full range, but only the start. As end range we define the offset
170+
// at the end of the start line, or if not existent, the remaining content of the file. The result
171+
// is that content of the returned SourceSegment will be the start line until the end of this line.
172+
val endIndex = lineStarts.lineNumberStartOffset(start.line.toInt() + 1) ?: allBytes.size
173+
174+
if (startIndex >= allBytes.size) {
175+
logger.warn { "Trying to source contents starting at ($startIndex) which is out of the bounds of the file content of ($sourceFile)" }
176+
return null
177+
}
178+
return SourceSegment(
179+
this,
180+
content = String(allBytes, startIndex, endIndex - startIndex).removeNonPrintableChars()
181+
)
182+
}
141183

142184
@Suppress("ForbiddenMethodCall") // for Regex
143185
private fun String.removeNonPrintableChars() = replace(Regex( "[^\\P{C}\\s]"), "").let {

lib/Shared/src/main/kotlin/disassembler/EVMInstruction.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ enum class EVMInstruction(
7373
SHL(0x1bu, 2, 1),
7474
SHR(0x1cu, 2, 1),
7575
SAR(0x1du, 2, 1),
76+
CLZ(0x1eu, 1, 1),
7677
KECCAK256(0x20u, 2, 1),
7778
ADDRESS(0x30u, 0, 1),
7879
BALANCE(0x31u, 1, 1),

0 commit comments

Comments
 (0)