certora-cli-beta-mirror 7.28.0__py3-none-any.whl → 8.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +10 -3
- certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +51 -16
- certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +3 -0
- certora_cli/CertoraProver/castingInstrumenter.py +192 -0
- certora_cli/CertoraProver/certoraApp.py +52 -0
- certora_cli/CertoraProver/certoraBuild.py +694 -207
- certora_cli/CertoraProver/certoraBuildCacheManager.py +21 -17
- certora_cli/CertoraProver/certoraBuildDataClasses.py +8 -2
- certora_cli/CertoraProver/certoraBuildRust.py +88 -54
- certora_cli/CertoraProver/certoraBuildSui.py +112 -0
- certora_cli/CertoraProver/certoraCloudIO.py +97 -96
- certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +230 -84
- certora_cli/CertoraProver/certoraCollectRunMetadata.py +52 -6
- certora_cli/CertoraProver/certoraCompilerParameters.py +11 -0
- certora_cli/CertoraProver/certoraConfigIO.py +43 -35
- certora_cli/CertoraProver/certoraContext.py +128 -54
- certora_cli/CertoraProver/certoraContextAttributes.py +415 -234
- certora_cli/CertoraProver/certoraContextValidator.py +152 -105
- certora_cli/CertoraProver/certoraContractFuncs.py +34 -1
- certora_cli/CertoraProver/certoraParseBuildScript.py +8 -10
- certora_cli/CertoraProver/certoraType.py +10 -1
- certora_cli/CertoraProver/certoraVerifyGenerator.py +22 -4
- certora_cli/CertoraProver/erc7201.py +45 -0
- certora_cli/CertoraProver/splitRules.py +23 -18
- certora_cli/CertoraProver/storageExtension.py +351 -0
- certora_cli/EquivalenceCheck/Eq_default.conf +0 -1
- certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -1
- certora_cli/EquivalenceCheck/equivCheck.py +2 -1
- certora_cli/Mutate/mutateApp.py +41 -22
- certora_cli/Mutate/mutateAttributes.py +11 -0
- certora_cli/Mutate/mutateValidate.py +42 -2
- certora_cli/Shared/certoraAttrUtil.py +21 -5
- certora_cli/Shared/certoraUtils.py +180 -60
- certora_cli/Shared/certoraValidateFuncs.py +68 -26
- certora_cli/Shared/proverCommon.py +308 -0
- certora_cli/certoraCVLFormatter.py +76 -0
- certora_cli/certoraConcord.py +39 -0
- certora_cli/certoraEVMProver.py +4 -3
- certora_cli/certoraRanger.py +39 -0
- certora_cli/certoraRun.py +83 -223
- certora_cli/certoraSolanaProver.py +40 -128
- certora_cli/certoraSorobanProver.py +59 -4
- certora_cli/certoraSuiProver.py +93 -0
- certora_cli_beta_mirror-8.5.0.dist-info/LICENSE +15 -0
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/METADATA +21 -5
- certora_cli_beta_mirror-8.5.0.dist-info/RECORD +81 -0
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/WHEEL +1 -1
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/entry_points.txt +3 -0
- certora_jars/ASTExtraction.jar +0 -0
- certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
- certora_jars/Typechecker.jar +0 -0
- certora_cli_beta_mirror-7.28.0.dist-info/LICENSE +0 -22
- certora_cli_beta_mirror-7.28.0.dist-info/RECORD +0 -70
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/top_level.txt +0 -0
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
#
|
|
13
13
|
# You should have received a copy of the GNU General Public License
|
|
14
14
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
-
|
|
15
|
+
import dataclasses
|
|
16
16
|
import json
|
|
17
17
|
from enum import Enum
|
|
18
|
-
from typing import Optional,
|
|
18
|
+
from typing import Optional, Any
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
import sys
|
|
21
21
|
|
|
@@ -25,27 +25,46 @@ sys.path.insert(0, str(scripts_dir_path))
|
|
|
25
25
|
import CertoraProver.certoraContextAttributes as Attrs
|
|
26
26
|
from CertoraProver.certoraCollectRunMetadata import RunMetaData, MetadataEncoder
|
|
27
27
|
import Shared.certoraUtils as Utils
|
|
28
|
+
from typing import List
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class MainSection(Enum):
|
|
31
32
|
GENERAL = "GENERAL"
|
|
33
|
+
OPTIONS = "OPTIONS"
|
|
32
34
|
SOLIDITY_COMPILER = "SOLIDITY_COMPILER"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
NEW_SECTION = "NEW_SECTION"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ContentType(Enum):
|
|
39
|
+
SIMPLE = "SIMPLE"
|
|
40
|
+
COMPLEX = "COMPLEX"
|
|
41
|
+
FLAG = "FLAG"
|
|
42
|
+
|
|
38
43
|
|
|
44
|
+
@dataclasses.dataclass
|
|
45
|
+
class InnerContent:
|
|
46
|
+
inner_title: str
|
|
47
|
+
content_type: str
|
|
48
|
+
content: Any
|
|
49
|
+
doc_link: str = ''
|
|
50
|
+
tooltip: str = ''
|
|
51
|
+
unsound: bool = False
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
53
|
+
def __post_init__(self) -> None:
|
|
54
|
+
if isinstance(self.content, bool):
|
|
55
|
+
self.content = 'true' if self.content else 'false'
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclasses.dataclass
|
|
59
|
+
class CardContent:
|
|
60
|
+
card_title: str
|
|
61
|
+
content_type: str
|
|
62
|
+
content: Any
|
|
44
63
|
|
|
45
64
|
|
|
46
65
|
DOC_LINK_PREFIX = 'https://docs.certora.com/en/latest/docs/'
|
|
47
66
|
GIT_ATTRIBUTES = ['origin', 'revision', 'branch', 'dirty']
|
|
48
|
-
|
|
67
|
+
ARG_LIST_ATTRIBUTES = ['prover_args', 'java_args']
|
|
49
68
|
|
|
50
69
|
|
|
51
70
|
class AttributeJobConfigData:
|
|
@@ -54,13 +73,13 @@ class AttributeJobConfigData:
|
|
|
54
73
|
This should be added to the AttributeDefinition and configured for every new attribute
|
|
55
74
|
presented in the Rule report.
|
|
56
75
|
|
|
57
|
-
Note: Attributes
|
|
76
|
+
Note: Attributes that do not contain specific information will be presented in the OPTIONS main section!
|
|
58
77
|
|
|
59
78
|
arguments:
|
|
60
79
|
- main_section : MainSection -- the main section inside the config tab
|
|
61
|
-
default: MainSection.
|
|
62
|
-
- subsection : str -- the subsection within the main_section
|
|
63
|
-
default:
|
|
80
|
+
default: MainSection.OPTIONS
|
|
81
|
+
- subsection : str -- the subsection within the main_section
|
|
82
|
+
default: None - they will be presented inside the OPTIONS card
|
|
64
83
|
- doc_link : Optional[str] -- a link to the Documentation page of this attribute (if exists)
|
|
65
84
|
default: 'https://docs.certora.com/en/latest/docs/' + Solana/EVM path + #<attribute_name>
|
|
66
85
|
- tooltip : Optional[str] -- a description of this attribute to present in the config tab
|
|
@@ -69,7 +88,7 @@ class AttributeJobConfigData:
|
|
|
69
88
|
default: False
|
|
70
89
|
"""
|
|
71
90
|
|
|
72
|
-
def __init__(self, main_section: MainSection = MainSection.
|
|
91
|
+
def __init__(self, main_section: MainSection = MainSection.OPTIONS, subsection: str = '',
|
|
73
92
|
doc_link: Optional[str] = '', tooltip: Optional[str] = '', unsound: bool = False):
|
|
74
93
|
self.main_section = main_section
|
|
75
94
|
self.subsection = subsection
|
|
@@ -87,18 +106,25 @@ class RunConfigurationLayout:
|
|
|
87
106
|
configuration_layout : Dict -- An aggregated configuration for a specific run, nested by main section, subsection.
|
|
88
107
|
Each leaf contains data about attribute value, type, documentation link and UI data.
|
|
89
108
|
"""
|
|
90
|
-
|
|
109
|
+
|
|
110
|
+
configuration_layout: list[Any]
|
|
111
|
+
|
|
112
|
+
def __init__(self, configuration_layout: list[Any]):
|
|
91
113
|
# Dynamically allocate class attributes from dict
|
|
92
|
-
|
|
93
|
-
setattr(self, key, value)
|
|
114
|
+
self.configuration_layout = configuration_layout
|
|
94
115
|
|
|
95
116
|
def __repr__(self) -> str:
|
|
96
|
-
|
|
117
|
+
try:
|
|
118
|
+
return json.dumps(self.configuration_layout, indent=2, sort_keys=True)
|
|
119
|
+
except TypeError:
|
|
120
|
+
# Fallback if something isn't serializable
|
|
121
|
+
return str(self.configuration_layout)
|
|
97
122
|
|
|
98
123
|
@classmethod
|
|
99
|
-
def dump_file(cls, data:
|
|
124
|
+
def dump_file(cls, data: list) -> None:
|
|
125
|
+
sorted_data = sort_configuration_layout(data)
|
|
100
126
|
with Utils.get_configuration_layout_data_file().open("w+") as f:
|
|
101
|
-
json.dump(
|
|
127
|
+
json.dump(sorted_data, f, indent=4, cls=MetadataEncoder)
|
|
102
128
|
|
|
103
129
|
@classmethod
|
|
104
130
|
def load_file(cls) -> dict:
|
|
@@ -110,12 +136,11 @@ class RunConfigurationLayout:
|
|
|
110
136
|
raise
|
|
111
137
|
|
|
112
138
|
def dump(self) -> None:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
raise
|
|
139
|
+
try:
|
|
140
|
+
self.dump_file(self.configuration_layout)
|
|
141
|
+
except Exception as e:
|
|
142
|
+
print(f"Failed to write configuration layout file: {Utils.get_configuration_layout_data_file()}\n{e}")
|
|
143
|
+
raise
|
|
119
144
|
|
|
120
145
|
|
|
121
146
|
def collect_configuration_layout() -> RunConfigurationLayout:
|
|
@@ -127,7 +152,7 @@ def collect_configuration_layout() -> RunConfigurationLayout:
|
|
|
127
152
|
metadata = RunMetaData.load_file()
|
|
128
153
|
except Exception as e:
|
|
129
154
|
print(f"failed to load job metadata! cannot create a configuration layout file without metadata!\n{e}")
|
|
130
|
-
return RunConfigurationLayout(configuration_layout=
|
|
155
|
+
return RunConfigurationLayout(configuration_layout=[])
|
|
131
156
|
|
|
132
157
|
attributes_configs = collect_attribute_configs(metadata)
|
|
133
158
|
configuration_layout = collect_run_config_from_metadata(attributes_configs, metadata)
|
|
@@ -154,89 +179,210 @@ def get_doc_link(attr) -> str: # type: ignore
|
|
|
154
179
|
return doc_link
|
|
155
180
|
|
|
156
181
|
|
|
157
|
-
def
|
|
182
|
+
def create_or_get_card_content(output: list[CardContent], name: str) -> CardContent:
|
|
183
|
+
"""
|
|
184
|
+
Returns an existing CardContent by name or creates and appends a new one if it doesn't exist.
|
|
185
|
+
Card content type will always be complex in this case.
|
|
186
|
+
Args:
|
|
187
|
+
output (list[CardContent]): List of CardContent objects.
|
|
188
|
+
name (str): Title of the card to find or create.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
CardContent: The found or newly created CardContent.
|
|
192
|
+
"""
|
|
193
|
+
main_section = next((section for section in output if section.card_title == name), None)
|
|
194
|
+
if main_section is None:
|
|
195
|
+
main_section = CardContent(
|
|
196
|
+
card_title=name,
|
|
197
|
+
content_type=ContentType.COMPLEX.value,
|
|
198
|
+
content=[]
|
|
199
|
+
)
|
|
200
|
+
output.append(main_section)
|
|
201
|
+
return main_section
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def split_and_sort_arg_list_value(args_list: List[str]) -> List[str]:
|
|
205
|
+
"""
|
|
206
|
+
Splits a unified CLI argument list of strings into a sorted list of flag+value groups.
|
|
207
|
+
This is useful mainly for --prover_args and --java_args.
|
|
208
|
+
|
|
209
|
+
For example:
|
|
210
|
+
"-depth 15 -adaptiveSolverConfig false" → ["-adaptiveSolverConfig false", "-depth 15"]
|
|
211
|
+
|
|
212
|
+
Assumes each flag starts with '-' and its value follows immediately, if exists.
|
|
213
|
+
Lines are sorted alphabetically.
|
|
214
|
+
"""
|
|
215
|
+
unified_args = ' '.join(str(arg) for arg in args_list)
|
|
216
|
+
|
|
217
|
+
if not unified_args.strip():
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
lines: List[str] = []
|
|
221
|
+
tokens = unified_args.split()
|
|
222
|
+
curr_line = ""
|
|
223
|
+
|
|
224
|
+
for token in tokens:
|
|
225
|
+
if token.startswith('-'):
|
|
226
|
+
if curr_line:
|
|
227
|
+
lines.append(curr_line)
|
|
228
|
+
curr_line = token
|
|
229
|
+
else:
|
|
230
|
+
curr_line += f" {token}"
|
|
231
|
+
|
|
232
|
+
if curr_line:
|
|
233
|
+
lines.append(curr_line)
|
|
234
|
+
|
|
235
|
+
return sorted(lines)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def create_inner_content(name: str, content_type: ContentType, value: Any, doc_link: str,
|
|
239
|
+
config_data: AttributeJobConfigData) -> InnerContent:
|
|
240
|
+
return InnerContent(
|
|
241
|
+
inner_title=name,
|
|
242
|
+
content_type=content_type.value,
|
|
243
|
+
content=value,
|
|
244
|
+
doc_link=doc_link,
|
|
245
|
+
tooltip=config_data.tooltip or '',
|
|
246
|
+
unsound=config_data.unsound
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def collect_attribute_configs(metadata: dict) -> list[CardContent]:
|
|
251
|
+
"""
|
|
252
|
+
Collects and organizes attribute configurations into a structured list of CardContent objects.
|
|
253
|
+
|
|
254
|
+
This function iterates through all available attributes defined, checks if relevant metadata is provided
|
|
255
|
+
for each attribute, and organizes the data into sections and subsections based on configuration rules.
|
|
256
|
+
|
|
257
|
+
Attributes are grouped under their respective main sections, with special handling for:
|
|
258
|
+
- Simple value attributes
|
|
259
|
+
- List and dictionary attributes
|
|
260
|
+
- Attributes requiring new sections (e.g., Files, Links, Packages)
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
metadata (dict): Metadata dictionary containing attribute values.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
list: A list of CardContent objects representing the structured configuration view,
|
|
267
|
+
ready for rendering or further processing.
|
|
268
|
+
"""
|
|
158
269
|
attr_list = Attrs.get_attribute_class().attribute_list()
|
|
159
|
-
output:
|
|
270
|
+
output: list[CardContent] = []
|
|
160
271
|
|
|
161
272
|
for attr in attr_list:
|
|
162
273
|
attr_name = attr.name.lower()
|
|
163
274
|
if attr.config_data is None:
|
|
164
275
|
continue
|
|
165
276
|
|
|
166
|
-
|
|
277
|
+
attr_value = metadata.get(attr_name) or metadata.get('conf', {}).get(attr_name)
|
|
278
|
+
if attr_value is None:
|
|
167
279
|
continue
|
|
168
280
|
|
|
169
|
-
attr_value = metadata.get(attr_name) or metadata.get('conf', {}).get(attr_name)
|
|
170
281
|
config_data: AttributeJobConfigData = attr.config_data
|
|
171
282
|
doc_link = config_data.doc_link or get_doc_link(attr)
|
|
172
283
|
|
|
173
|
-
#
|
|
284
|
+
# Find or create the main section
|
|
174
285
|
main_section_key = config_data.main_section.value.lower()
|
|
175
|
-
main_section = output
|
|
286
|
+
main_section = create_or_get_card_content(output, main_section_key)
|
|
287
|
+
|
|
288
|
+
# Files, Links and Packages are special cases where the main section is the attribute itself
|
|
289
|
+
if main_section_key == MainSection.NEW_SECTION.value.lower():
|
|
290
|
+
main_section.card_title = attr_name
|
|
291
|
+
main_section.content_type = ContentType.SIMPLE.value
|
|
292
|
+
main_section.content.append(
|
|
293
|
+
create_inner_content(attr_name, ContentType.SIMPLE, attr_value, doc_link, config_data)
|
|
294
|
+
)
|
|
295
|
+
continue
|
|
176
296
|
|
|
177
|
-
#
|
|
297
|
+
# Find or create the subsection (if it doesn't exist)
|
|
178
298
|
if isinstance(attr_value, list):
|
|
179
|
-
|
|
180
|
-
if
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
else:
|
|
184
|
-
current_section = main_section
|
|
185
|
-
|
|
186
|
-
flag_type = FlagType.LIST_FLAG
|
|
299
|
+
content_type = ContentType.SIMPLE
|
|
300
|
+
if attr_name in ARG_LIST_ATTRIBUTES:
|
|
301
|
+
attr_value = split_and_sort_arg_list_value(attr_value)
|
|
302
|
+
|
|
187
303
|
elif isinstance(attr_value, dict):
|
|
188
|
-
|
|
189
|
-
|
|
304
|
+
content_type = ContentType.COMPLEX
|
|
305
|
+
attr_value = [
|
|
306
|
+
create_inner_content(key, ContentType.FLAG, value, doc_link, config_data)
|
|
307
|
+
for key, value in attr_value.items()
|
|
308
|
+
]
|
|
190
309
|
else:
|
|
191
|
-
|
|
192
|
-
current_section = main_section.setdefault(subsection_key, {})
|
|
193
|
-
flag_type = FlagType.VALUE_FLAG
|
|
310
|
+
content_type = ContentType.FLAG
|
|
194
311
|
|
|
195
312
|
# Update the current section with attribute details
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
'doc_link': doc_link,
|
|
200
|
-
'tooltip': config_data.tooltip,
|
|
201
|
-
'unsound': config_data.unsound
|
|
202
|
-
}
|
|
313
|
+
main_section.content.append(
|
|
314
|
+
create_inner_content(attr_name, content_type, attr_value, doc_link, config_data)
|
|
315
|
+
)
|
|
203
316
|
|
|
204
317
|
return output
|
|
205
318
|
|
|
206
319
|
|
|
207
|
-
def collect_run_config_from_metadata(attributes_configs:
|
|
320
|
+
def collect_run_config_from_metadata(attributes_configs: list[CardContent], metadata: dict) -> list[CardContent]:
|
|
208
321
|
"""
|
|
209
322
|
Adding CLI and Git configuration from metadata
|
|
210
323
|
"""
|
|
211
|
-
metadata_section = attributes_configs.setdefault(MainSection.METADATA.value.lower(), {})
|
|
212
324
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
325
|
+
general_section = create_or_get_card_content(attributes_configs, MainSection.GENERAL.value.lower())
|
|
326
|
+
|
|
327
|
+
if cli_version := metadata.get('CLI_version'):
|
|
328
|
+
general_section.content.append(InnerContent(
|
|
329
|
+
inner_title='CLI Version',
|
|
330
|
+
content_type=ContentType.FLAG.value,
|
|
331
|
+
content=cli_version,
|
|
332
|
+
))
|
|
220
333
|
|
|
221
|
-
# Add metadata attributes dynamically if they exist
|
|
222
|
-
for key, value in metadata_mappings.items():
|
|
223
|
-
if value:
|
|
224
|
-
metadata_section[key] = {
|
|
225
|
-
'value': value,
|
|
226
|
-
'flag_type': FlagType.VALUE_FLAG.value,
|
|
227
|
-
'doc_link': '',
|
|
228
|
-
'tooltip': '',
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
# Adding GIT configuration from metadata
|
|
232
|
-
git_section = attributes_configs.setdefault(MainSection.GIT.value.lower(), {})
|
|
233
334
|
for attr in GIT_ATTRIBUTES:
|
|
234
335
|
if attr_value := metadata.get(attr):
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
336
|
+
general_section.content.append(InnerContent(
|
|
337
|
+
inner_title=attr,
|
|
338
|
+
content_type=ContentType.FLAG.value,
|
|
339
|
+
content=attr_value,
|
|
340
|
+
))
|
|
241
341
|
|
|
242
342
|
return attributes_configs
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def sort_configuration_layout(data: list[CardContent]) -> list[CardContent]:
|
|
346
|
+
"""
|
|
347
|
+
Sorts a configuration layout:
|
|
348
|
+
- Top-level sorted by 'card_title'
|
|
349
|
+
- Nested content sorted by 'inner_title', with 'verify' first
|
|
350
|
+
"""
|
|
351
|
+
priority = {
|
|
352
|
+
# Priorities for top-level cards
|
|
353
|
+
"general": 0,
|
|
354
|
+
"files": 1,
|
|
355
|
+
"options": 2,
|
|
356
|
+
# Top level items inside their respective cards
|
|
357
|
+
"verify": 0,
|
|
358
|
+
"solc": 0,
|
|
359
|
+
"CLI Version": 0
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
def inner_sort_key(item: Any) -> Any:
|
|
363
|
+
if isinstance(item, CardContent):
|
|
364
|
+
title = item.card_title
|
|
365
|
+
return priority.get(title, 3), title.lower()
|
|
366
|
+
elif isinstance(item, InnerContent):
|
|
367
|
+
title = item.inner_title
|
|
368
|
+
return priority.get(title, 3), title.lower()
|
|
369
|
+
else:
|
|
370
|
+
return item
|
|
371
|
+
|
|
372
|
+
def sort_content(content: list[InnerContent]) -> list[InnerContent]:
|
|
373
|
+
sorted_content = []
|
|
374
|
+
for item in content:
|
|
375
|
+
if isinstance(item.content, list):
|
|
376
|
+
# Recurse into nested 'content'
|
|
377
|
+
item.content = sorted(item.content, key=inner_sort_key)
|
|
378
|
+
sorted_content.append(item)
|
|
379
|
+
return sorted(sorted_content, key=inner_sort_key)
|
|
380
|
+
|
|
381
|
+
# Sort top-level entries by 'card_title'
|
|
382
|
+
sorted_data = sorted(data, key=inner_sort_key)
|
|
383
|
+
|
|
384
|
+
# Sort nested 'content'
|
|
385
|
+
for section in sorted_data:
|
|
386
|
+
section.content = sort_content(section.content)
|
|
387
|
+
|
|
388
|
+
return sorted_data
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
#
|
|
13
13
|
# You should have received a copy of the GNU General Public License
|
|
14
14
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
-
|
|
15
|
+
import dataclasses
|
|
16
16
|
import json
|
|
17
|
+
import re
|
|
17
18
|
from typing import Any, Dict, List, Optional
|
|
18
19
|
import subprocess
|
|
19
20
|
from datetime import datetime, timezone
|
|
@@ -31,6 +32,7 @@ import CertoraProver.certoraContextAttributes as Attrs
|
|
|
31
32
|
|
|
32
33
|
metadata_logger = logging.getLogger("metadata")
|
|
33
34
|
|
|
35
|
+
|
|
34
36
|
def collect_args_with_jar_flags() -> List[str]:
|
|
35
37
|
return_array = []
|
|
36
38
|
for attr in Attrs.get_attribute_class().attribute_list():
|
|
@@ -47,6 +49,8 @@ class MetadataEncoder(json.JSONEncoder):
|
|
|
47
49
|
def default(self, obj: Any) -> Any:
|
|
48
50
|
if isinstance(obj, set):
|
|
49
51
|
return list(obj)
|
|
52
|
+
if dataclasses.is_dataclass(obj):
|
|
53
|
+
return dataclasses.asdict(obj)
|
|
50
54
|
return json.JSONEncoder.default(self, obj)
|
|
51
55
|
|
|
52
56
|
|
|
@@ -68,10 +72,19 @@ class RunMetaData:
|
|
|
68
72
|
dirty -- true iff the git repository has changes (git diff is not empty)
|
|
69
73
|
main_spec -- the relative path to the main spec file that should be displayed by default at the web report
|
|
70
74
|
conf_path -- the relative path form the cwd_relative to the configuration file
|
|
75
|
+
group_id -- optional identifier for grouping this run
|
|
76
|
+
java_version -- version of Java used during the run, if available
|
|
77
|
+
default_solc_version -- version of default solc version on current machine, if available
|
|
78
|
+
python_version -- version of Python running the process
|
|
79
|
+
certora_ci_client -- name of the CI client if available, derived from environment
|
|
80
|
+
timestamp -- UTC timestamp when the run metadata was generated
|
|
81
|
+
CLI_package_name -- name of the CLI package
|
|
82
|
+
CLI_version -- version of the CLI
|
|
83
|
+
jar_flag_info -- CLI attributes which are jarFlags
|
|
71
84
|
"""
|
|
72
85
|
def __init__(self, raw_args: List[str], conf: Dict[str, Any], origin: str, revision: str,
|
|
73
86
|
branch: str, cwd_relative: Path, dirty: bool, main_spec: Optional[str],
|
|
74
|
-
conf_path: Optional[Path], group_id: Optional[str]):
|
|
87
|
+
conf_path: Optional[Path], group_id: Optional[str], java_version: str):
|
|
75
88
|
self.raw_args = raw_args
|
|
76
89
|
self.conf = conf
|
|
77
90
|
self.origin = origin
|
|
@@ -83,6 +96,8 @@ class RunMetaData:
|
|
|
83
96
|
self.conf_path = conf_path
|
|
84
97
|
self.group_id = group_id
|
|
85
98
|
self.python_version = ".".join(str(x) for x in sys.version_info[:3])
|
|
99
|
+
self.java_version = java_version
|
|
100
|
+
self.default_solc_version = get_solc_version(self.conf)
|
|
86
101
|
self.certora_ci_client = Utils.get_certora_ci_name()
|
|
87
102
|
self.timestamp = str(datetime.now(timezone.utc).timestamp())
|
|
88
103
|
_, self.CLI_package_name, self.CLI_version = Utils.get_package_and_version()
|
|
@@ -103,6 +118,8 @@ class RunMetaData:
|
|
|
103
118
|
f" conf_path: {self.conf_path}\n"
|
|
104
119
|
f" group_id: {self.group_id}\n"
|
|
105
120
|
f" python_version: {self.python_version}\n"
|
|
121
|
+
f" java_version: {self.java_version}\n"
|
|
122
|
+
f" default_solc_version: {self.default_solc_version}\n"
|
|
106
123
|
f" CertoraCI client: {self.certora_ci_client}\n"
|
|
107
124
|
f" jar_flag_info: {self.jar_flag_info}\n"
|
|
108
125
|
)
|
|
@@ -180,7 +197,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
180
197
|
dirty=True,
|
|
181
198
|
main_spec=None,
|
|
182
199
|
conf_path=None,
|
|
183
|
-
group_id=None
|
|
200
|
+
group_id=None,
|
|
201
|
+
java_version=context.java_version)
|
|
184
202
|
|
|
185
203
|
# collect information about current git snapshot
|
|
186
204
|
cwd_abs = wd.absolute()
|
|
@@ -208,7 +226,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
208
226
|
dirty=True,
|
|
209
227
|
main_spec=get_main_spec(context),
|
|
210
228
|
conf_path=conf_path,
|
|
211
|
-
group_id=context.group_id
|
|
229
|
+
group_id=context.group_id,
|
|
230
|
+
java_version=context.java_version)
|
|
212
231
|
|
|
213
232
|
try:
|
|
214
233
|
sha_out = subprocess.run(['git', 'rev-parse', 'HEAD'], cwd=wd,
|
|
@@ -243,7 +262,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
243
262
|
dirty=dirty,
|
|
244
263
|
main_spec=get_main_spec(context),
|
|
245
264
|
conf_path=conf_path,
|
|
246
|
-
group_id=context.group_id
|
|
265
|
+
group_id=context.group_id,
|
|
266
|
+
java_version=context.java_version)
|
|
247
267
|
|
|
248
268
|
metadata_logger.debug(f' collected data:\n{str(data)}')
|
|
249
269
|
|
|
@@ -261,4 +281,30 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
261
281
|
dirty=True,
|
|
262
282
|
main_spec=get_main_spec(context),
|
|
263
283
|
conf_path=conf_path,
|
|
264
|
-
group_id=context.group_id
|
|
284
|
+
group_id=context.group_id,
|
|
285
|
+
java_version=context.java_version)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def get_solc_version(conf: Dict[str, Any]) -> Optional[str]:
|
|
289
|
+
if conf.get('solc') or conf.get('solc_map') or conf.get('compiler_map'):
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
result = subprocess.run(
|
|
294
|
+
["solc", "--version"],
|
|
295
|
+
capture_output=True,
|
|
296
|
+
text=True,
|
|
297
|
+
check=True
|
|
298
|
+
)
|
|
299
|
+
for line in result.stdout.splitlines():
|
|
300
|
+
if line.startswith("Version:"):
|
|
301
|
+
version_matches = re.findall(r'^Version: (\d+)\.(\d+)\.(\d+)', line, re.MULTILINE)
|
|
302
|
+
if len(version_matches) != 1:
|
|
303
|
+
return None
|
|
304
|
+
match = version_matches[0]
|
|
305
|
+
return f'solc{int(match[1])}.{int(match[2])}'
|
|
306
|
+
|
|
307
|
+
except Exception as e:
|
|
308
|
+
metadata_logger.debug(f'Error while trying to fetch default solc version: {e}')
|
|
309
|
+
return None
|
|
310
|
+
return None
|
|
@@ -35,3 +35,14 @@ class SolcParameters(CompilerParameters):
|
|
|
35
35
|
as_dict.update({"optimizerOn": self.optimizer_on, "optimizerRuns": self.optimizer_runs, "viaIR": self.via_ir,
|
|
36
36
|
"type": "SolcParameters"})
|
|
37
37
|
return as_dict
|
|
38
|
+
|
|
39
|
+
class VyperParameters(CompilerParameters):
|
|
40
|
+
|
|
41
|
+
def __init__(self, is_venom: bool):
|
|
42
|
+
self.is_venom = is_venom
|
|
43
|
+
CompilerParameters.__init__(self)
|
|
44
|
+
|
|
45
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
46
|
+
as_dict = CompilerParameters.as_dict(self)
|
|
47
|
+
as_dict.update({"is_venom": self.is_venom, "type": "VyperParameters"})
|
|
48
|
+
return as_dict
|