From e80684f58973e07fa920157eb68f138baa4ec49f Mon Sep 17 00:00:00 2001 From: Gabriel Rondon Date: Wed, 27 May 2026 14:57:07 +0100 Subject: [PATCH] fix(sema): reject address.transfer / address.send on Soroban target Soroban contracts do not have a native value-transfer model: balances move through the Stellar Asset Contract (SAC) or the token interface, not the contract runtime. Compiling `.transfer(...)` or `.send(...)` for `--target soroban` was reaching codegen and panicking at `src/emit/instructions.rs:989` ("Found IntValue") while lowering `Instr::ValueTransfer`. Mirror the existing Solana guard one block above and reject both methods at sema time with a Soroban-specific diagnostic pointing the user toward SAC. Adds regression tests asserting the diagnostic is emitted for both methods. Fixes #1909 Signed-off-by: Gabriel Rondon --- src/sema/expression/function_call.rs | 14 ++++ tests/soroban_testcases/mod.rs | 1 + tests/soroban_testcases/value_transfer.rs | 97 +++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 tests/soroban_testcases/value_transfer.rs diff --git a/src/sema/expression/function_call.rs b/src/sema/expression/function_call.rs index 756c0d8e6..6c0dd43b2 100644 --- a/src/sema/expression/function_call.rs +++ b/src/sema/expression/function_call.rs @@ -1305,6 +1305,20 @@ fn try_type_method( return Err(()); } + if ns.target == Target::Soroban { + diagnostics.push(Diagnostic::error( + *loc, + format!( + "method '{}' is not available on Soroban. Soroban contracts \ + do not have a native value-transfer model; move assets through \ + the Stellar Asset Contract (SAC) or the token interface instead.", + func.name + ), + )); + + return Err(()); + } + if !is_payable { diagnostics.push(Diagnostic::error( *loc, diff --git a/tests/soroban_testcases/mod.rs b/tests/soroban_testcases/mod.rs index e1df43ec2..15389f98d 100644 --- a/tests/soroban_testcases/mod.rs +++ b/tests/soroban_testcases/mod.rs @@ -22,3 +22,4 @@ mod timestamp; mod token; mod ttl; mod unsupported_parameters; +mod value_transfer; diff --git a/tests/soroban_testcases/value_transfer.rs b/tests/soroban_testcases/value_transfer.rs new file mode 100644 index 000000000..410ac5da4 --- /dev/null +++ b/tests/soroban_testcases/value_transfer.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 + +use solang::codegen::Options; +use solang::file_resolver::FileResolver; +use solang::sema::ast::Namespace; +use solang::{compile, Target}; +use solang_parser::diagnostics::Level; +use std::ffi::OsStr; + +fn compile_soroban(src: &str) -> Namespace { + let tmp_file = OsStr::new("test.sol"); + let mut cache = FileResolver::default(); + cache.set_file_contents(tmp_file.to_str().unwrap(), src.to_string()); + let opt = inkwell::OptimizationLevel::Default; + + let (_, ns) = compile( + tmp_file, + &mut cache, + Target::Soroban, + &Options { + opt_level: opt.into(), + log_runtime_errors: true, + log_prints: true, + #[cfg(feature = "wasm_opt")] + wasm_opt: Some(contract_build::OptimizationPasses::Z), + soroban_version: None, + ..Default::default() + }, + std::vec!["unknown".to_string()], + "0.0.1", + ); + + ns +} + +// Regression test for https://github.com/hyperledger-solang/solang/issues/1909 +// Previously this would panic in codegen (`Found IntValue` at +// `src/emit/instructions.rs:989`) while lowering `Instr::ValueTransfer`. Soroban +// has no native value-transfer model, so the call should be rejected at sema +// time with an actionable diagnostic. +#[test] +fn address_transfer_is_rejected_on_soroban() { + let ns = compile_soroban( + r#" + contract C { + function run() external payable { + payable(address(this)).transfer(uint256(0)); + } + } + "#, + ); + + let errors = ns + .diagnostics + .iter() + .filter(|diagnostic| diagnostic.level == Level::Error) + .collect::>(); + + assert!( + errors + .iter() + .any(|diagnostic| diagnostic.message + == "method 'transfer' is not available on Soroban. Soroban contracts \ + do not have a native value-transfer model; move assets through \ + the Stellar Asset Contract (SAC) or the token interface instead."), + "expected a Soroban-specific rejection of `transfer`, got: {errors:?}", + ); +} + +#[test] +fn address_send_is_rejected_on_soroban() { + let ns = compile_soroban( + r#" + contract C { + function run() external payable { + payable(address(this)).send(uint256(0)); + } + } + "#, + ); + + let errors = ns + .diagnostics + .iter() + .filter(|diagnostic| diagnostic.level == Level::Error) + .collect::>(); + + assert!( + errors + .iter() + .any(|diagnostic| diagnostic.message + == "method 'send' is not available on Soroban. Soroban contracts \ + do not have a native value-transfer model; move assets through \ + the Stellar Asset Contract (SAC) or the token interface instead."), + "expected a Soroban-specific rejection of `send`, got: {errors:?}", + ); +}