diff --git a/docs/book/src/reference/compiler_intrinsics.md b/docs/book/src/reference/compiler_intrinsics.md index 1d21d29bace..d516dea4250 100644 --- a/docs/book/src/reference/compiler_intrinsics.md +++ b/docs/book/src/reference/compiler_intrinsics.md @@ -183,3 +183,13 @@ __ptr_sub(ptr: raw_ptr, offset: u64) **Constraints:** None. ___ + +```sway +__smo(recipient: b256, data: T, output_index: u64, coins: u64) +``` + +**Description:** Sends a message `data` of arbitrary type `T` and `coins` amount of the base asset to address `recipient`. This intrinsic assumes that an OutputMessage is available at index + +**Constraints:** None. + +___ diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index e530b5d586b..62708c324fc 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -407,6 +407,10 @@ pub(crate) fn compile_ast_to_ir_to_asm( errors.extend(e); } + if build_config.print_ir { + tracing::info!("{}", ir); + } + // Now we're working with all functions in the module. let all_functions = ir .module_iter() diff --git a/sway-core/src/semantic_analysis/ast_node/expression/intrinsic_function.rs b/sway-core/src/semantic_analysis/ast_node/expression/intrinsic_function.rs index 8d8b15a899b..d56a46d88fd 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/intrinsic_function.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/intrinsic_function.rs @@ -1051,7 +1051,6 @@ fn type_check_ptr_ops( /// to address `recipient`. This intrinsic assumes that an OutputMessage is available at index /// `output_index`. /// Constraints: None. -/// fn type_check_smo( mut ctx: TypeCheckContext, kind: sway_ast::Intrinsic, @@ -1082,17 +1081,7 @@ fn type_check_smo( return err(warnings, errors); } - // Type check the first argument which is the recipient address, so it has to be a `b256`. - let mut ctx = ctx - .by_ref() - .with_type_annotation(type_engine.insert_type(TypeInfo::B256)); - let recipient = check!( - ty::TyExpression::type_check(ctx.by_ref(), arguments[0].clone()), - return err(warnings, errors), - warnings, - errors - ); - + // Type check the type argument let type_argument = type_arguments.get(0).map(|targ| { let mut ctx = ctx .by_ref() @@ -1127,7 +1116,19 @@ fn type_check_smo( } }); - // Type check the second argument which is the data, which can be anything. + // Type check the first argument which is the recipient address, so it has to be a `b256`. + let mut ctx = ctx + .by_ref() + .with_type_annotation(type_engine.insert_type(TypeInfo::B256)); + let recipient = check!( + ty::TyExpression::type_check(ctx.by_ref(), arguments[0].clone()), + return err(warnings, errors), + warnings, + errors + ); + + // Type check the second argument which is the data, which can be anything. If a type + // argument is provided, make sure that it matches the type of the data. let mut ctx = ctx.by_ref().with_type_annotation( type_argument .clone() diff --git a/sway-ir/src/error.rs b/sway-ir/src/error.rs index 414e3337b80..be44afc3b37 100644 --- a/sway-ir/src/error.rs +++ b/sway-ir/src/error.rs @@ -59,6 +59,8 @@ pub enum IrError { VerifyLogId, VerifyMismatchedLoggedTypes, VerifyRevertCodeBadType, + VerifySmoRecipientBadType, + VerifySmoBadRecipientAndMessageType, VerifySmoMessageSize, VerifySmoCoins, VerifySmoOutputIndex, @@ -324,6 +326,19 @@ impl fmt::Display for IrError { "Verification failed: error code for revert must be a u64." ) } + IrError::VerifySmoRecipientBadType => { + write!( + f, + "Verification failed: the struct `recipient_and_message` of `smo` must have a `b256` \ + as its first field." + ) + } + IrError::VerifySmoBadRecipientAndMessageType => { + write!( + f, + "Verification failed: `recipient_and_message` of `smo` must have a struct" + ) + } IrError::VerifySmoMessageSize => { write!( f, diff --git a/sway-ir/src/instruction.rs b/sway-ir/src/instruction.rs index c3d975a52e8..c7a9321415b 100644 --- a/sway-ir/src/instruction.rs +++ b/sway-ir/src/instruction.rs @@ -124,12 +124,11 @@ pub enum Instruction { Ret(Value, Type), /// Revert VM execution. Revert(Value), - /// Sends `message_data` of type `message_ty` and a `coins` amount of the base asset to - /// `recipient`. - /// - Assume the existence of an `OutputMessage` at `output_index` - /// - Also accepts a unique ID describing the `smo` instance. - /// - `recipient` must be of type `b256` - /// - `output_index`, `coins`, and `message_id` must be of type `U64`. + /// - Sends a message to an output via the `smo` FuelVM instruction. The first operand must be + /// a struct with the first field being a `B256` representing the recipient. The rest of the + /// struct is the message data being sent. + /// - Assumes the existence of an `OutputMessage` at `output_index` + /// - `message_size`, `output_index`, and `coins` must be of type `U64`. Smo { recipient_and_message: Value, message_size: Value, diff --git a/sway-ir/src/parser.rs b/sway-ir/src/parser.rs index 70eb5539e61..6799e2edd52 100644 --- a/sway-ir/src/parser.rs +++ b/sway-ir/src/parser.rs @@ -170,6 +170,7 @@ mod ir_builder { / op_read_register() / op_ret() / op_revert() + / op_smo() / op_state_load_quad_word() / op_state_load_word() / op_state_store_quad_word() @@ -314,6 +315,12 @@ mod ir_builder { IrAstOperation::Revert(vn) } + rule op_smo() -> IrAstOperation + = "smo" _ + recipient_and_message:id() comma() message_size:id() comma() output_index:id() comma() coins:id() _ { + IrAstOperation::Smo(recipient_and_message, message_size, output_index, coins) + } + rule op_state_load_quad_word() -> IrAstOperation = "state_load_quad_word" _ ptr() dst:id() comma() "key" _ ptr() _ key:id() { IrAstOperation::StateLoadQuadWord(dst, key) @@ -668,6 +675,7 @@ mod ir_builder { ReadRegister(String), Ret(IrAstTy, String), Revert(String), + Smo(String, String, String, String), StateLoadQuadWord(String, String), StateLoadWord(String), StateStoreQuadWord(String, String), @@ -1223,6 +1231,20 @@ mod ir_builder { .ins(context) .revert(*val_map.get(&ret_val_name).unwrap()) .add_metadatum(context, opt_metadata), + IrAstOperation::Smo( + recipient_and_message, + message_size, + output_index, + coins, + ) => block + .ins(context) + .smo( + *val_map.get(&recipient_and_message).unwrap(), + *val_map.get(&message_size).unwrap(), + *val_map.get(&output_index).unwrap(), + *val_map.get(&coins).unwrap(), + ) + .add_metadatum(context, opt_metadata), IrAstOperation::StateLoadQuadWord(dst, key) => block .ins(context) .state_load_quad_word( diff --git a/sway-ir/src/verify.rs b/sway-ir/src/verify.rs index 7a4446cac32..8933cea07fd 100644 --- a/sway-ir/src/verify.rs +++ b/sway-ir/src/verify.rs @@ -711,19 +711,33 @@ impl<'a> InstructionVerifier<'a> { fn verify_smo( &self, - _recipient_and_message: &Value, + recipient_and_message: &Value, message_size: &Value, output_index: &Value, coins: &Value, ) -> Result<(), IrError> { + // Check that the first operand is a struct with the first field being a `b256` + // representing the recipient address + if let Some(Type::Struct(agg)) = recipient_and_message.get_stripped_ptr_type(self.context) { + let fields = self.context.aggregates[agg.0].field_types(); + if fields.is_empty() || !fields[0].eq(self.context, &Type::B256) { + return Err(IrError::VerifySmoRecipientBadType); + } + } else { + return Err(IrError::VerifySmoBadRecipientAndMessageType); + } + + // Check that the second operand is a `u64` representing the message size. if !matches!(message_size.get_type(self.context), Some(Type::Uint(64))) { return Err(IrError::VerifySmoMessageSize); } + // Check that the third operand is a `u64` representing the output index. if !matches!(output_index.get_type(self.context), Some(Type::Uint(64))) { return Err(IrError::VerifySmoOutputIndex); } + // Check that the fourth operand is a `u64` representing the amount of coins being sent. if !matches!(coins.get_type(self.context), Some(Type::Uint(64))) { return Err(IrError::VerifySmoCoins); } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/Forc.lock new file mode 100644 index 00000000000..d598601c686 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = 'smo' +source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/Forc.toml new file mode 100644 index 00000000000..bb0bc129139 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +implicit-std = false +license = "Apache-2.0" +name = "smo" + +[dependencies] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/json_abi_oracle.json new file mode 100644 index 00000000000..45fb7bce495 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/json_abi_oracle.json @@ -0,0 +1,259 @@ +{ + "functions": [ + { + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 3, + "typeArguments": null + } + } + ], + "loggedTypes": [ + { + "logId": 0, + "loggedType": { + "name": "", + "type": 7, + "typeArguments": null + } + }, + { + "logId": 1, + "loggedType": { + "name": "", + "type": 1, + "typeArguments": null + } + } + ], + "messagesTypes": [ + { + "messageId": 0, + "messageType": { + "name": "", + "type": 60, + "typeArguments": null + } + }, + { + "messageId": 1, + "messageType": { + "name": "", + "type": 67, + "typeArguments": null + } + }, + { + "messageId": 2, + "messageType": { + "name": "", + "type": 75, + "typeArguments": null + } + }, + { + "messageId": 3, + "messageType": { + "name": "", + "type": 83, + "typeArguments": null + } + }, + { + "messageId": 4, + "messageType": { + "name": "", + "type": 91, + "typeArguments": null + } + }, + { + "messageId": 5, + "messageType": { + "name": "", + "type": 41, + "typeArguments": null + } + }, + { + "messageId": 6, + "messageType": { + "name": "", + "type": 103, + "typeArguments": null + } + }, + { + "messageId": 7, + "messageType": { + "name": "", + "type": 52, + "typeArguments": [ + { + "name": "", + "type": 51, + "typeArguments": null + } + ] + } + }, + { + "messageId": 8, + "messageType": { + "name": "", + "type": 58, + "typeArguments": [] + } + }, + { + "messageId": 9, + "messageType": { + "name": "", + "type": 131, + "typeArguments": [ + { + "name": "", + "type": 120, + "typeArguments": [ + { + "name": "", + "type": 124, + "typeArguments": null + } + ] + } + ] + } + } + ], + "types": [ + { + "components": [], + "type": "()", + "typeId": 0, + "typeParameters": null + }, + { + "components": [ + { + "name": "__array_element", + "type": 12, + "typeArguments": null + } + ], + "type": "[_; 3]", + "typeId": 1, + "typeParameters": null + }, + { + "components": null, + "type": "b256", + "typeId": 2, + "typeParameters": null + }, + { + "components": null, + "type": "bool", + "typeId": 3, + "typeParameters": null + }, + { + "components": [ + { + "name": "None", + "type": 0, + "typeArguments": null + }, + { + "name": "Some", + "type": 6, + "typeArguments": null + } + ], + "type": "enum Option", + "typeId": 4, + "typeParameters": [ + 6 + ] + }, + { + "components": [ + { + "name": "VariantOne", + "type": 0, + "typeArguments": null + }, + { + "name": "VariantTwo", + "type": 0, + "typeArguments": null + } + ], + "type": "enum TestEnum", + "typeId": 5, + "typeParameters": null + }, + { + "components": null, + "type": "generic T", + "typeId": 6, + "typeParameters": null + }, + { + "components": null, + "type": "str[4]", + "typeId": 7, + "typeParameters": null + }, + { + "components": [ + { + "name": "field_1", + "type": 3, + "typeArguments": null + }, + { + "name": "field_2", + "type": 6, + "typeArguments": null + }, + { + "name": "field_3", + "type": 11, + "typeArguments": null + } + ], + "type": "struct TestStruct", + "typeId": 8, + "typeParameters": [ + 6 + ] + }, + { + "components": null, + "type": "u16", + "typeId": 9, + "typeParameters": null + }, + { + "components": null, + "type": "u32", + "typeId": 10, + "typeParameters": null + }, + { + "components": null, + "type": "u64", + "typeId": 11, + "typeParameters": null + }, + { + "components": null, + "type": "u8", + "typeId": 12, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/src/main.sw new file mode 100644 index 00000000000..57b3e695b7f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/src/main.sw @@ -0,0 +1,59 @@ +script; + +fn smo(recipient: b256, value: T, output_index: u64, coins: u64) { + __smo::(recipient, value, output_index, coins); +} + +struct TestStruct { + field_1: bool, + field_2: T, + field_3: u64, +} + +enum TestEnum { + VariantOne: (), + VariantTwo: (), +} + +pub enum Option { + None: (), + Some: T, +} + +fn main() -> bool { + let recipient = 0x0101010101010101010101010101010101010101010101010101010101010101; + let output_index = 3; + let coins = 24; + + // Check various data types as message data in `__smo` + let k: b256 = 0xef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a; + let a: str[4] = "Fuel"; + let b: [u8; 3] = [1u8, 2u8, 3u8]; + let test_struct = TestStruct { + field_1: true, + field_2: k, + field_3: 11, + }; + + let test_enum = TestEnum::VariantTwo; + smo(recipient, k, output_index, coins); + smo(recipient, 42, output_index, coins); + smo(recipient, 42u32, output_index, coins); + smo(recipient, 42u16, output_index, coins); + smo(recipient, 42u8, output_index, coins); + __smo(recipient, a, output_index, coins); + __smo(recipient, b, output_index, coins); + __smo(recipient, test_struct, output_index, coins); + __smo(recipient, test_enum, output_index, coins); + __smo::>>(recipient, Option::Some(TestStruct { + field_1: true, + field_2: 42, + field_3: 42, + }), output_index, coins); + + // Make sure that logs don't clobber messages in the JSON ABI + __log(a); + __log(b); + + true +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/test.toml new file mode 100644 index 00000000000..6691150d9c8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/smo/test.toml @@ -0,0 +1,2 @@ +category = "compile" +validate_abi = true diff --git a/test/src/ir_generation/tests/smo.sw b/test/src/ir_generation/tests/smo.sw new file mode 100644 index 00000000000..39cfb5e4ddd --- /dev/null +++ b/test/src/ir_generation/tests/smo.sw @@ -0,0 +1,28 @@ +script; + +fn main() { + let recipient = 0x0000000000000000000000000000000000000000000000000000000000000000; + let data = 5; + let output_index = 4; + let coins = 8; + __smo(recipient, data, output_index, coins); +} + +// ::check-ir:: + +// check: $(v10=$VAL) = get_ptr ptr { b256, u64, u64 } +// check: $(v13=$VAL) = insert_value $v10, { b256, u64, u64 }, $VAL, 0 +// check: $(v15=$VAL) = insert_value $v13, { b256, u64, u64 }, $VAL, 1 +// check: $(v16=$VAL) = insert_value $v15, { b256, u64, u64 }, $VAL, 2 +// check: $(v17=$VAL) = get_ptr ptr u64 +// check: $(v18=$VAL) = load ptr $v17 +// check: $(v19=$VAL) = get_ptr ptr u64 +// check: $(v20=$VAL) = load ptr $v19 +// check: $(v21=$VAL) = const u64 16 +// check: smo $v16, $v21, $v18, $v20 + +// ::check-asm:: + +// regex: REG=\$r\d+ + +// check: smo $REG $REG $REG $REG