Skip to content

Commit ba2b68f

Browse files
committed
Add missing validations and fix infinite loop in type topological sort
- Fix potential infinite loop in TypeDeclMulti::to_rescript_schema when type declarations have cyclic dependencies. The while loop now detects lack of progress and returns a clear error instead of hanging forever. - Add validation in TypeDeclMulti::new: reject empty declarations and duplicate type names. - Add validation in TypeDecl::new: reject duplicate type parameters. - Add event name uniqueness validation in Contract::new to catch duplicate event names within a contract early. - Add __proto__ field name check in schema validation to prevent prototype pollution issues in the JavaScript runtime. - All new validations include corresponding unit tests. https://claude.ai/code/session_01BwrGigSvKsk4MR43QhkSrx
1 parent 36cd252 commit ba2b68f

5 files changed

Lines changed: 258 additions & 43 deletions

File tree

codegenerator/cli/src/config_parsing/entity_parsing.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,38 @@ impl Schema {
155155
]
156156
.concat();
157157

158-
// TODO: It'd be nice to check field names not having __proto__ name
159-
// I don't think any other field names should be restricted
160158
match check_names_from_schema_for_reserved_words(all_names) {
161-
reserved_enum_types_used if reserved_enum_types_used.is_empty() => Ok(self),
162-
reserved_enum_types_used => Err(anyhow!(
163-
"Schema contains the following reserved keywords: {}",
164-
reserved_enum_types_used.join(", ")
165-
)),
159+
reserved_enum_types_used if reserved_enum_types_used.is_empty() => {}
160+
reserved_enum_types_used => {
161+
return Err(anyhow!(
162+
"Schema contains the following reserved keywords: {}",
163+
reserved_enum_types_used.join(", ")
164+
))
165+
}
166166
}
167+
168+
self.check_field_names_for_proto()
169+
}
170+
171+
/// Check that no entity field uses the __proto__ name, which can cause
172+
/// prototype pollution issues in JavaScript runtimes.
173+
fn check_field_names_for_proto(self) -> anyhow::Result<Self> {
174+
let mut violations: Vec<String> = vec![];
175+
for entity in self.entities.values() {
176+
for field in &entity.fields {
177+
if field.name == "__proto__" {
178+
violations.push(format!("{}.{}", entity.name, field.name));
179+
}
180+
}
181+
}
182+
if !violations.is_empty() {
183+
return Err(anyhow!(
184+
"Schema contains fields named '__proto__' which can cause prototype \
185+
pollution issues: {}. Please rename these fields.",
186+
violations.join(", ")
187+
));
188+
}
189+
Ok(self)
167190
}
168191

169192
fn check_duplicate_naming_between_enums_and_entities(self) -> anyhow::Result<Self> {

codegenerator/cli/src/config_parsing/system_config.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,11 +1259,25 @@ impl Contract {
12591259
events: Vec<Event>,
12601260
abi: Abi,
12611261
) -> Result<Self> {
1262-
// TODO: Validatate that all event names are unique
1263-
validate_names_valid_rescript(
1264-
&events.iter().map(|e| e.name.clone()).collect(),
1265-
"event".to_string(),
1266-
)?;
1262+
let event_names: Vec<String> = events.iter().map(|e| e.name.clone()).collect();
1263+
1264+
// Validate that all event names are unique
1265+
let mut seen_event_names: HashSet<String> = HashSet::new();
1266+
let duplicate_event_names: Vec<String> = event_names
1267+
.iter()
1268+
.filter(|n| !seen_event_names.insert(n.to_string()))
1269+
.cloned()
1270+
.collect();
1271+
if !duplicate_event_names.is_empty() {
1272+
return Err(anyhow!(
1273+
"Contract '{}' has duplicate event names: {}. All event names within a \
1274+
contract must be unique.",
1275+
name,
1276+
duplicate_event_names.join(", ")
1277+
));
1278+
}
1279+
1280+
validate_names_valid_rescript(&event_names, "event".to_string())?;
12671281

12681282
Ok(Self {
12691283
name,

codegenerator/cli/src/fuel/abi.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ impl FuelAbi {
320320
Ok(Some(FuelType {
321321
id: abi_type_decl.type_id,
322322
abi_type_field: abi_type_decl.type_field.clone(),
323-
type_decl: TypeDecl::new(name, type_expr?, type_params),
323+
type_decl: TypeDecl::new(name, type_expr?, type_params)?,
324324
}))
325325
})
326326
.collect::<Result<Vec<Option<FuelType>>>>()
@@ -477,6 +477,6 @@ impl FuelAbi {
477477
.map(|t| t.type_decl.clone())
478478
.collect();
479479

480-
Ok(TypeDeclMulti::new(type_declerations))
480+
TypeDeclMulti::new(type_declerations)
481481
}
482482
}

codegenerator/cli/src/hbs_templating/codegen_templates.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -997,14 +997,21 @@ let eventSignatures = [{}]
997997
contract.name
998998
))?;
999999

1000+
let abi_type_schema = all_abi_type_declarations
1001+
.to_rescript_schema(&SchemaMode::ForDb)
1002+
.context(format!(
1003+
"Failed generating schema for the '{}' contract ABI types",
1004+
contract.name
1005+
))?;
1006+
10001007
format!(
10011008
"let abi = FuelSDK.transpileAbi((await Utils.importPathWithJson(`../${{Path.\
10021009
relativePathToRootFromGenerated}}/{}`))[\"default\"])\n{}\n{}\n{chain_id_type_code}",
10031010
// If we decide to inline the abi, instead of using require
10041011
// we need to remember that abi might contain ` and we should escape it
10051012
abi.path_buf.to_string_lossy(),
10061013
all_abi_type_declarations,
1007-
all_abi_type_declarations.to_rescript_schema(&SchemaMode::ForDb)
1014+
abi_type_schema
10081015
)
10091016
}
10101017
};

0 commit comments

Comments
 (0)