From 27fce0983814b67f28a69d4102866e89c1c32011 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Mon, 25 Mar 2024 17:46:54 +0000 Subject: [PATCH] feat(avm): add AvmContextInputs (https://github.com/AztecProtocol/aztec-packages/pull/5396) NOTE: I don't know why this triggered a change in the snapshot. --- This structure lets us easily pass things from the (TS) context to the constructor of the `AvmContext` in Noir, using `calldata` as a vehicle (effectively adding them as public inputs). The choice to add the structure to the function arguments as LAST and not first is because adding them first would break any non-noir-generated bytecode (since they would have to shift their expected calldata by a magic number `N = sizeof(AvmContextInputs)`). Putting it last, makes it transparent for them. A calldatacopy would still work. However, having this makes any external call always have `AvmContextInputs` in the calldata, bloating it (on chain) for non-noir-generated bytecode. This is not an issue now. For the moment, this is temporary, but might be useful long term. (Sean mentioned passing maybe env getters like this). --- Implemented `AvmContext.selector()` and `AvmContext.get_args_hash()` using the above. --- .aztec-sync-commit | 2 +- .github/pull_request_template.md | 2 +- aztec_macros/src/transforms/functions.rs | 126 +++++++++--------- .../src/ssa/ir/instruction/call.rs | 9 +- compiler/noirc_evaluator/src/ssa/ir/types.rs | 7 + .../src/hir/type_check/errors.rs | 5 +- .../noirc_frontend/src/hir/type_check/stmt.rs | 5 + docs/docs/how_to/how-to-oracles.md | 2 +- .../docs/noir/concepts/data_types/integers.md | 2 +- docs/docs/tutorials/noirjs_app.md | 4 +- .../version-v0.24.0/how_to/how-to-oracles.md | 2 +- .../version-v0.24.0/tutorials/noirjs_app.md | 4 +- .../version-v0.25.0/how_to/how-to-oracles.md | 2 +- .../version-v0.25.0/tutorials/noirjs_app.md | 4 +- noir_stdlib/src/collections/bounded_vec.nr | 9 ++ noir_stdlib/src/field.nr | 2 +- .../array_to_slice/src/main.nr | 23 ++++ 17 files changed, 132 insertions(+), 78 deletions(-) diff --git a/.aztec-sync-commit b/.aztec-sync-commit index ead8f3147c0..425b994546c 100644 --- a/.aztec-sync-commit +++ b/.aztec-sync-commit @@ -1 +1 @@ -208abbb63af4c9a3f25d723fe1c49e82aa461061 +12e2844f9af433beb1a586640b08ce284ad91095 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e81ede7199d..dfb141e29f7 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -17,7 +17,7 @@ Resolves Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. -- [ ] **[Exceptional Case]** Documentation to be submitted in a separate PR. +- [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* diff --git a/aztec_macros/src/transforms/functions.rs b/aztec_macros/src/transforms/functions.rs index c719651e10e..5e42dccdd78 100644 --- a/aztec_macros/src/transforms/functions.rs +++ b/aztec_macros/src/transforms/functions.rs @@ -116,6 +116,10 @@ pub fn transform_vm_function( let create_context = create_avm_context()?; func.def.body.0.insert(0, create_context); + // Add the inputs to the params (first!) + let input = create_inputs("AvmContextInputs"); + func.def.parameters.insert(0, input); + // We want the function to be seen as a public function func.def.is_unconstrained = true; @@ -232,62 +236,62 @@ fn create_assert_initializer() -> Statement { /// ```noir /// #[aztec(private)] /// fn foo(structInput: SomeStruct, arrayInput: [u8; 10], fieldInput: Field) -> Field { -/// // Create the hasher object -/// let mut hasher = Hasher::new(); +/// // Create the bounded vec object +/// let mut serialized_args = BoundedVec::new(); /// /// // struct inputs call serialize on them to add an array of fields -/// hasher.add_multiple(structInput.serialize()); +/// serialized_args.extend_from_array(structInput.serialize()); /// -/// // Array inputs are iterated over and each element is added to the hasher (as a field) +/// // Array inputs are iterated over and each element is added to the bounded vec (as a field) /// for i in 0..arrayInput.len() { -/// hasher.add(arrayInput[i] as Field); +/// serialized_args.push(arrayInput[i] as Field); /// } -/// // Field inputs are added to the hasher -/// hasher.add({ident}); +/// // Field inputs are added to the bounded vec +/// serialized_args.push({ident}); /// /// // Create the context /// // The inputs (injected by this `create_inputs`) and completed hash object are passed to the context -/// let mut context = PrivateContext::new(inputs, hasher.hash()); +/// let mut context = PrivateContext::new(inputs, hash_args(serialized_args)); /// } /// ``` fn create_context(ty: &str, params: &[Param]) -> Result, AztecMacroError> { let mut injected_expressions: Vec = vec![]; - // `let mut hasher = Hasher::new();` - let let_hasher = mutable_assignment( - "hasher", // Assigned to + // `let mut serialized_args = BoundedVec::new();` + let let_serialized_args = mutable_assignment( + "serialized_args", // Assigned to call( - variable_path(chained_dep!("aztec", "hasher", "Hasher", "new")), // Path - vec![], // args + variable_path(chained_dep!("std", "collections", "bounded_vec", "BoundedVec", "new")), // Path + vec![], // args ), ); - // Completes: `let mut hasher = Hasher::new();` - injected_expressions.push(let_hasher); + // Completes: `let mut serialized_args = BoundedVec::new();` + injected_expressions.push(let_serialized_args); - // Iterate over each of the function parameters, adding to them to the hasher + // Iterate over each of the function parameters, adding to them to the bounded vec for Param { pattern, typ, span, .. } in params { match pattern { Pattern::Identifier(identifier) => { // Match the type to determine the padding to do let unresolved_type = &typ.typ; let expression = match unresolved_type { - // `hasher.add_multiple({ident}.serialize())` - UnresolvedTypeData::Named(..) => add_struct_to_hasher(identifier), + // `serialized_args.extend_from_array({ident}.serialize())` + UnresolvedTypeData::Named(..) => add_struct_to_serialized_args(identifier), UnresolvedTypeData::Array(_, arr_type) => { - add_array_to_hasher(identifier, arr_type) + add_array_to_serialized_args(identifier, arr_type) } - // `hasher.add({ident})` - UnresolvedTypeData::FieldElement => add_field_to_hasher(identifier), - // Add the integer to the hasher, casted to a field - // `hasher.add({ident} as Field)` + // `serialized_args.push({ident})` + UnresolvedTypeData::FieldElement => add_field_to_serialized_args(identifier), + // Add the integer to the serialized args, casted to a field + // `serialized_args.push({ident} as Field)` UnresolvedTypeData::Integer(..) | UnresolvedTypeData::Bool => { - add_cast_to_hasher(identifier) + add_cast_to_serialized_args(identifier) } UnresolvedTypeData::String(..) => { let (var_bytes, id) = str_to_bytes(identifier); injected_expressions.push(var_bytes); - add_array_to_hasher( + add_array_to_serialized_args( &id, &UnresolvedType { typ: UnresolvedTypeData::Integer( @@ -313,11 +317,10 @@ fn create_context(ty: &str, params: &[Param]) -> Result, AztecMac // Create the inputs to the context let inputs_expression = variable("inputs"); - // `hasher.hash()` - let hash_call = method_call( - variable("hasher"), // variable - "hash", // method name - vec![], // args + // `hash_args(serialized_args)` + let hash_call = call( + variable_path(chained_dep!("aztec", "hash", "hash_args")), // variable + vec![variable("serialized_args")], // args ); let path_snippet = ty.to_case(Case::Snake); // e.g. private_context @@ -354,11 +357,14 @@ fn create_context(ty: &str, params: &[Param]) -> Result, AztecMac /// // ... /// } fn create_avm_context() -> Result { + // Create the inputs to the context + let inputs_expression = variable("inputs"); + let let_context = mutable_assignment( "context", // Assigned to call( variable_path(chained_dep!("aztec", "context", "AVMContext", "new")), // Path - vec![], // args + vec![inputs_expression], // args ), ); @@ -598,11 +604,11 @@ fn create_context_finish() -> Statement { } // -// Methods to create hasher inputs +// Methods to create hash_args inputs // -fn add_struct_to_hasher(identifier: &Ident) -> Statement { - // If this is a struct, we call serialize and add the array to the hasher +fn add_struct_to_serialized_args(identifier: &Ident) -> Statement { + // If this is a struct, we call serialize and add the array to the serialized args let serialized_call = method_call( variable_path(path(identifier.clone())), // variable "serialize", // method name @@ -610,9 +616,9 @@ fn add_struct_to_hasher(identifier: &Ident) -> Statement { ); make_statement(StatementKind::Semi(method_call( - variable("hasher"), // variable - "add_multiple", // method name - vec![serialized_call], // args + variable("serialized_args"), // variable + "extend_from_array", // method name + vec![serialized_call], // args ))) } @@ -632,7 +638,7 @@ fn str_to_bytes(identifier: &Ident) -> (Statement, Ident) { } fn create_loop_over(var: Expression, loop_body: Vec) -> Statement { - // If this is an array of primitive types (integers / fields) we can add them each to the hasher + // If this is an array of primitive types (integers / fields) we can add them each to the serialized args // casted to a field let span = var.span; @@ -644,7 +650,7 @@ fn create_loop_over(var: Expression, loop_body: Vec) -> Statement { ); // What will be looped over - // - `hasher.add({ident}[i] as Field)` + // - `serialized_args.push({ident}[i] as Field)` let for_loop_block = expression(ExpressionKind::Block(BlockExpression(loop_body))); // `for i in 0..{ident}.len()` @@ -662,66 +668,66 @@ fn create_loop_over(var: Expression, loop_body: Vec) -> Statement { })) } -fn add_array_to_hasher(identifier: &Ident, arr_type: &UnresolvedType) -> Statement { - // If this is an array of primitive types (integers / fields) we can add them each to the hasher +fn add_array_to_serialized_args(identifier: &Ident, arr_type: &UnresolvedType) -> Statement { + // If this is an array of primitive types (integers / fields) we can add them each to the serialized_args // casted to a field // Wrap in the semi thing - does that mean ended with semi colon? - // `hasher.add({ident}[i] as Field)` + // `serialized_args.push({ident}[i] as Field)` let arr_index = index_array(identifier.clone(), "i"); - let (add_expression, hasher_method_name) = match arr_type.typ { + let (add_expression, vec_method_name) = match arr_type.typ { UnresolvedTypeData::Named(..) => { - let hasher_method_name = "add_multiple".to_owned(); + let vec_method_name = "extend_from_array".to_owned(); let call = method_call( // All serialize on each element arr_index, // variable "serialize", // method name vec![], // args ); - (call, hasher_method_name) + (call, vec_method_name) } _ => { - let hasher_method_name = "add".to_owned(); + let vec_method_name = "push".to_owned(); let call = cast( arr_index, // lhs - `ident[i]` UnresolvedTypeData::FieldElement, // cast to - `as Field` ); - (call, hasher_method_name) + (call, vec_method_name) } }; let block_statement = make_statement(StatementKind::Semi(method_call( - variable("hasher"), // variable - &hasher_method_name, // method name + variable("serialized_args"), // variable + &vec_method_name, // method name vec![add_expression], ))); create_loop_over(variable_ident(identifier.clone()), vec![block_statement]) } -fn add_field_to_hasher(identifier: &Ident) -> Statement { - // `hasher.add({ident})` +fn add_field_to_serialized_args(identifier: &Ident) -> Statement { + // `serialized_args.push({ident})` let ident = variable_path(path(identifier.clone())); make_statement(StatementKind::Semi(method_call( - variable("hasher"), // variable - "add", // method name - vec![ident], // args + variable("serialized_args"), // variable + "push", // method name + vec![ident], // args ))) } -fn add_cast_to_hasher(identifier: &Ident) -> Statement { - // `hasher.add({ident} as Field)` +fn add_cast_to_serialized_args(identifier: &Ident) -> Statement { + // `serialized_args.push({ident} as Field)` // `{ident} as Field` let cast_operation = cast( variable_path(path(identifier.clone())), // lhs UnresolvedTypeData::FieldElement, // rhs ); - // `hasher.add({ident} as Field)` + // `serialized_args.push({ident} as Field)` make_statement(StatementKind::Semi(method_call( - variable("hasher"), // variable - "add", // method name - vec![cast_operation], // args + variable("serialized_args"), // variable + "push", // method name + vec![cast_operation], // args ))) } diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 8b800e0db54..5b268de239d 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -85,10 +85,11 @@ pub(super) fn simplify_call( } } Intrinsic::AsSlice => { - let slice = dfg.get_array_constant(arguments[0]); - if let Some((slice, element_type)) = slice { - let slice_length = dfg.make_constant(slice.len().into(), Type::length_type()); - let new_slice = dfg.make_array(slice, element_type); + let array = dfg.get_array_constant(arguments[0]); + if let Some((array, array_type)) = array { + let slice_length = dfg.make_constant(array.len().into(), Type::length_type()); + let inner_element_types = array_type.element_types(); + let new_slice = dfg.make_array(array, Type::Slice(inner_element_types)); SimplifyResult::SimplifiedToMultiple(vec![slice_length, new_slice]) } else { SimplifyResult::None diff --git a/compiler/noirc_evaluator/src/ssa/ir/types.rs b/compiler/noirc_evaluator/src/ssa/ir/types.rs index ea3f5393245..48036580d29 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/types.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/types.rs @@ -159,6 +159,13 @@ impl Type { Type::Reference(element) => element.contains_an_array(), } } + + pub(crate) fn element_types(self) -> Rc> { + match self { + Type::Array(element_types, _) | Type::Slice(element_types) => element_types, + other => panic!("element_types: Expected array or slice, found {other}"), + } + } } /// Composite Types are essentially flattened struct or tuple types. diff --git a/compiler/noirc_frontend/src/hir/type_check/errors.rs b/compiler/noirc_frontend/src/hir/type_check/errors.rs index 642cebc83b0..6beb6929ce1 100644 --- a/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -140,6 +140,8 @@ pub enum TypeCheckError { method_name: String, span: Span, }, + #[error("Strings do not support indexed assignment")] + StringIndexAssign { span: Span }, } impl TypeCheckError { @@ -237,7 +239,8 @@ impl From for Diagnostic { | TypeCheckError::ConstrainedReferenceToUnconstrained { span } | TypeCheckError::UnconstrainedReferenceToConstrained { span } | TypeCheckError::UnconstrainedSliceReturnToConstrained { span } - | TypeCheckError::NonConstantSliceLength { span } => { + | TypeCheckError::NonConstantSliceLength { span } + | TypeCheckError::StringIndexAssign { span } => { Diagnostic::simple_error(error.to_string(), String::new(), span) } TypeCheckError::PublicReturnType { typ, span } => Diagnostic::simple_error( diff --git a/compiler/noirc_frontend/src/hir/type_check/stmt.rs b/compiler/noirc_frontend/src/hir/type_check/stmt.rs index 49ba3244dc9..69363d5f00a 100644 --- a/compiler/noirc_frontend/src/hir/type_check/stmt.rs +++ b/compiler/noirc_frontend/src/hir/type_check/stmt.rs @@ -258,6 +258,11 @@ impl<'interner> TypeChecker<'interner> { Type::Array(_, elem_type) => *elem_type, Type::Slice(elem_type) => *elem_type, Type::Error => Type::Error, + Type::String(_) => { + let (_lvalue_name, lvalue_span) = self.get_lvalue_name_and_span(&lvalue); + self.errors.push(TypeCheckError::StringIndexAssign { span: lvalue_span }); + Type::Error + } other => { // TODO: Need a better span here self.errors.push(TypeCheckError::TypeMismatch { diff --git a/docs/docs/how_to/how-to-oracles.md b/docs/docs/how_to/how-to-oracles.md index 0d84d992320..ab225b9421f 100644 --- a/docs/docs/how_to/how-to-oracles.md +++ b/docs/docs/how_to/how-to-oracles.md @@ -198,7 +198,7 @@ For example, if your Noir program expects the host machine to provide CPU pseudo ```js const foreignCallHandler = (name, inputs) => crypto.randomBytes(16) // etc -await noir.generateFinalProof(inputs, foreignCallHandler) +await noir.generateProof(inputs, foreignCallHandler) ``` As one can see, in NoirJS, the [`foreignCallHandler`](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) function simply means "a callback function that returns a value of type [`ForeignCallOutput`](../reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md). It doesn't have to be an RPC call like in the case for Nargo. diff --git a/docs/docs/noir/concepts/data_types/integers.md b/docs/docs/noir/concepts/data_types/integers.md index 4d58d96fed5..1c6b375db49 100644 --- a/docs/docs/noir/concepts/data_types/integers.md +++ b/docs/docs/noir/concepts/data_types/integers.md @@ -51,7 +51,7 @@ The built-in structure `U128` allows you to use 128-bit unsigned integers almost - You cannot cast between a native integer and `U128` - There is a higher performance cost when using `U128`, compared to a native type. -Conversion between unsigned integer types and U128 are done through the use of `from_integer` and `to_integer` functions. +Conversion between unsigned integer types and U128 are done through the use of `from_integer` and `to_integer` functions. `from_integer` also accepts the `Field` type as input. ```rust fn main() { diff --git a/docs/docs/tutorials/noirjs_app.md b/docs/docs/tutorials/noirjs_app.md index ad76dd255cc..12beb476994 100644 --- a/docs/docs/tutorials/noirjs_app.md +++ b/docs/docs/tutorials/noirjs_app.md @@ -243,7 +243,7 @@ Now we're ready to prove stuff! Let's feed some inputs to our circuit and calcul await setup(); // let's squeeze our wasm inits here display('logs', 'Generating proof... ⌛'); -const proof = await noir.generateFinalProof(input); +const proof = await noir.generateProof(input); display('logs', 'Generating proof... ✅'); display('results', proof.proof); ``` @@ -264,7 +264,7 @@ Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add th ```js display('logs', 'Verifying proof... ⌛'); -const verification = await noir.verifyFinalProof(proof); +const verification = await noir.verifyProof(proof); if (verification) display('logs', 'Verifying proof... ✅'); ``` diff --git a/docs/versioned_docs/version-v0.24.0/how_to/how-to-oracles.md b/docs/versioned_docs/version-v0.24.0/how_to/how-to-oracles.md index 0d84d992320..ab225b9421f 100644 --- a/docs/versioned_docs/version-v0.24.0/how_to/how-to-oracles.md +++ b/docs/versioned_docs/version-v0.24.0/how_to/how-to-oracles.md @@ -198,7 +198,7 @@ For example, if your Noir program expects the host machine to provide CPU pseudo ```js const foreignCallHandler = (name, inputs) => crypto.randomBytes(16) // etc -await noir.generateFinalProof(inputs, foreignCallHandler) +await noir.generateProof(inputs, foreignCallHandler) ``` As one can see, in NoirJS, the [`foreignCallHandler`](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) function simply means "a callback function that returns a value of type [`ForeignCallOutput`](../reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md). It doesn't have to be an RPC call like in the case for Nargo. diff --git a/docs/versioned_docs/version-v0.24.0/tutorials/noirjs_app.md b/docs/versioned_docs/version-v0.24.0/tutorials/noirjs_app.md index ad76dd255cc..12beb476994 100644 --- a/docs/versioned_docs/version-v0.24.0/tutorials/noirjs_app.md +++ b/docs/versioned_docs/version-v0.24.0/tutorials/noirjs_app.md @@ -243,7 +243,7 @@ Now we're ready to prove stuff! Let's feed some inputs to our circuit and calcul await setup(); // let's squeeze our wasm inits here display('logs', 'Generating proof... ⌛'); -const proof = await noir.generateFinalProof(input); +const proof = await noir.generateProof(input); display('logs', 'Generating proof... ✅'); display('results', proof.proof); ``` @@ -264,7 +264,7 @@ Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add th ```js display('logs', 'Verifying proof... ⌛'); -const verification = await noir.verifyFinalProof(proof); +const verification = await noir.verifyProof(proof); if (verification) display('logs', 'Verifying proof... ✅'); ``` diff --git a/docs/versioned_docs/version-v0.25.0/how_to/how-to-oracles.md b/docs/versioned_docs/version-v0.25.0/how_to/how-to-oracles.md index 0d84d992320..ab225b9421f 100644 --- a/docs/versioned_docs/version-v0.25.0/how_to/how-to-oracles.md +++ b/docs/versioned_docs/version-v0.25.0/how_to/how-to-oracles.md @@ -198,7 +198,7 @@ For example, if your Noir program expects the host machine to provide CPU pseudo ```js const foreignCallHandler = (name, inputs) => crypto.randomBytes(16) // etc -await noir.generateFinalProof(inputs, foreignCallHandler) +await noir.generateProof(inputs, foreignCallHandler) ``` As one can see, in NoirJS, the [`foreignCallHandler`](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) function simply means "a callback function that returns a value of type [`ForeignCallOutput`](../reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md). It doesn't have to be an RPC call like in the case for Nargo. diff --git a/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md b/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md index ad76dd255cc..12beb476994 100644 --- a/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md +++ b/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md @@ -243,7 +243,7 @@ Now we're ready to prove stuff! Let's feed some inputs to our circuit and calcul await setup(); // let's squeeze our wasm inits here display('logs', 'Generating proof... ⌛'); -const proof = await noir.generateFinalProof(input); +const proof = await noir.generateProof(input); display('logs', 'Generating proof... ✅'); display('results', proof.proof); ``` @@ -264,7 +264,7 @@ Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add th ```js display('logs', 'Verifying proof... ⌛'); -const verification = await noir.verifyFinalProof(proof); +const verification = await noir.verifyProof(proof); if (verification) display('logs', 'Verifying proof... ✅'); ``` diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr index 752b96d6591..6d5fbd44247 100644 --- a/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -48,6 +48,15 @@ impl BoundedVec { self.len = new_len; } + pub fn extend_from_slice(&mut self, slice: [T]) { + let new_len = self.len + slice.len(); + assert(new_len as u64 <= MaxLen as u64, "extend_from_slice out of bounds"); + for i in 0..slice.len() { + self.storage[self.len + i] = slice[i]; + } + self.len = new_len; + } + pub fn extend_from_bounded_vec(&mut self, vec: BoundedVec) { let append_len = vec.len(); let new_len = self.len + append_len; diff --git a/noir_stdlib/src/field.nr b/noir_stdlib/src/field.nr index 0f4c2caffdf..b876bcc967b 100644 --- a/noir_stdlib/src/field.nr +++ b/noir_stdlib/src/field.nr @@ -97,7 +97,7 @@ pub fn modulus_be_bytes() -> [u8] {} #[builtin(modulus_le_bytes)] pub fn modulus_le_bytes() -> [u8] {} -// Convert a 32 byte array to a field element +// Convert a 32 byte array to a field element by modding pub fn bytes32_to_field(bytes32: [u8; 32]) -> Field { // Convert it to a field element let mut v = 1; diff --git a/test_programs/execution_success/array_to_slice/src/main.nr b/test_programs/execution_success/array_to_slice/src/main.nr index b97f68fc280..0d0f9562d7b 100644 --- a/test_programs/execution_success/array_to_slice/src/main.nr +++ b/test_programs/execution_success/array_to_slice/src/main.nr @@ -7,6 +7,7 @@ fn as_slice_push(xs: [T; N]) -> [T] { slice } +// Expected that x == 0 and y == 1 fn main(x: Field, y: pub Field) { let xs: [Field; 0] = []; let ys: [Field; 1] = [1]; @@ -30,4 +31,26 @@ fn main(x: Field, y: pub Field) { assert(dynamic.as_slice()[2] == dynamic_expected[2]); assert(dynamic.as_slice()[3] == dynamic_expected[3]); assert(dynamic.as_slice().len() == 4); + + regression_4609_append_slices(x, y); + regression_4609_append_dynamic_slices(x, y); +} + +fn regression_4609_append_slices(x: Field, y: Field) { + let sl = [x, 1, 2, 3].as_slice(); + let sl2 = [y, 5, 6].as_slice(); + let sl3 = sl.append(sl2); + assert(sl3[0] == x); + assert(sl3[4] == y); +} + +fn regression_4609_append_dynamic_slices(x: Field, y: Field) { + let mut sl = [x, 1, 2, 3].as_slice(); + sl[x] = x + 10; + let mut sl2 = [y, 5, 6].as_slice(); + sl2[y] = y + 5; + let sl3 = sl.append(sl2); + assert(sl3[0] == 10); + assert(sl3[4] == y); + assert(sl3[5] == 6); }