neo3/neo_builder/script/
script_builder.rs

1use crate::{
2	builder::{BuilderError, CallFlags, InteropService},
3	codec::Encoder,
4	crypto::Secp256r1PublicKey,
5	neo_crypto::utils::{FromBase64String, FromHexString, ToHexString},
6	Bytes, ContractParameter, ContractParameterType, OpCode, ParameterValue, ScriptHashExtension,
7};
8use futures_util::future::ok;
9use getset::{Getters, Setters};
10use num_bigint::BigInt;
11use num_traits::{Signed, ToPrimitive};
12use primitive_types::H160;
13use serde::{Deserialize, Serialize};
14use std::{
15	cmp::PartialEq,
16	collections::HashMap,
17	convert::TryInto,
18	fmt::{Debug, Formatter},
19	str::FromStr,
20};
21use tokio::io::AsyncWriteExt;
22
23/// A builder for constructing Neo smart contract scripts.
24///
25/// The `ScriptBuilder` provides methods to create and manipulate scripts
26/// by adding opcodes, pushing data, and performing various operations
27/// required for Neo smart contract execution.
28///
29/// # Examples
30///
31/// ```rust
32/// use neo3::neo_builder::ScriptBuilder;
33/// use neo3::neo_types::OpCode;
34/// use num_bigint::BigInt;
35///
36/// let mut builder = ScriptBuilder::new();
37/// builder.push_integer(BigInt::from(42))
38///        .push_data("Hello, Neo!".as_bytes().to_vec())
39///        .op_code(&[OpCode::Add]);
40///
41/// let script = builder.to_bytes();
42/// ```
43#[derive(Debug, PartialEq, Eq, Hash, Getters, Setters)]
44pub struct ScriptBuilder {
45	#[getset(get = "pub")]
46	pub script: Encoder,
47}
48
49impl ScriptBuilder {
50	/// Creates a new `ScriptBuilder` instance.
51	///
52	/// # Examples
53	///
54	/// ```rust
55	/// use neo3::neo_builder::ScriptBuilder;
56	///
57	/// let builder = ScriptBuilder::new();
58	/// ```
59	pub fn new() -> Self {
60		Self { script: Encoder::new() }
61	}
62
63	/// Appends one or more opcodes to the script.
64	///
65	/// # Arguments
66	///
67	/// * `op_codes` - A slice of `OpCode` values to append to the script.
68	///
69	/// # Returns
70	///
71	/// A mutable reference to the `ScriptBuilder` for method chaining.
72	///
73	/// # Examples
74	///
75	/// ```rust
76	/// use neo3::neo_builder::ScriptBuilder;
77	/// use neo3::neo_types::OpCode;
78	///
79	/// let mut builder = ScriptBuilder::new();
80	/// builder.op_code(&[OpCode::Push1, OpCode::Push2, OpCode::Add]);
81	/// ```
82	pub fn op_code(&mut self, op_codes: &[OpCode]) -> &mut Self {
83		for opcode in op_codes {
84			self.script.write_u8(opcode.opcode());
85		}
86		self
87	}
88
89	/// Appends an opcode with an argument to the script.
90	///
91	/// # Arguments
92	///
93	/// * `opcode` - The `OpCode` to append.
94	/// * `argument` - The data argument for the opcode.
95	///
96	/// # Returns
97	///
98	/// A mutable reference to the `ScriptBuilder` for method chaining.
99	///
100	/// # Examples
101	///
102	/// ```rust
103	/// use neo3::neo_builder::ScriptBuilder;
104	/// use neo3::neo_types::OpCode;
105	///
106	/// let mut builder = ScriptBuilder::new();
107	/// builder.op_code_with_arg(OpCode::PushData1, vec![0x01, 0x02, 0x03]);
108	/// ```
109	pub fn op_code_with_arg(&mut self, opcode: OpCode, argument: Bytes) -> &mut Self {
110		self.script.write_u8(opcode.opcode());
111		let _ = self.script.write_bytes(&argument);
112		self
113	}
114
115	/// Appends a contract call operation to the script.
116	///
117	/// # Arguments
118	///
119	/// * `hash160` - The 160-bit hash of the contract to call.
120	/// * `method` - The name of the method to call.
121	/// * `params` - A slice of `ContractParameter` values to pass as arguments to the method.
122	/// * `call_flags` - An optional `CallFlags` value specifying the call flags.
123	///
124	/// # Returns
125	///
126	/// A `Result` containing a mutable reference to the `ScriptBuilder` for method chaining,
127	/// or a `BuilderError` if an error occurs.
128	///
129	/// # Examples
130	///
131	/// ```rust
132	/// use neo3::neo_builder::ScriptBuilder;
133	/// use primitive_types::H160;
134	/// use neo3::neo_types::ContractParameter;
135	/// use neo3::builder::CallFlags;
136	///
137	/// let mut builder = ScriptBuilder::new();
138	/// let contract_hash = H160::from_slice(&[0; 20]);
139	/// let result = builder.contract_call(
140	///     &contract_hash,
141	///     "transfer",
142	///     &[ContractParameter::from("NeoToken"), ContractParameter::from(1000)],
143	///     Some(CallFlags::All)
144	/// );
145	/// ```
146	pub fn contract_call(
147		&mut self,
148		hash160: &H160,
149		method: &str,
150		params: &[ContractParameter],
151		call_flags: Option<CallFlags>,
152	) -> Result<&mut Self, BuilderError> {
153		if params.is_empty() {
154			self.op_code(&[OpCode::NewArray0]);
155		} else {
156			self.push_params(params);
157		}
158
159		Ok(self
160			.push_integer(BigInt::from(match call_flags {
161				Some(flags) => flags.value(),
162				None => CallFlags::All.value(),
163			}))
164			.push_data(method.as_bytes().to_vec())
165			.push_data(hash160.to_vec())
166			.sys_call(InteropService::SystemContractCall))
167	}
168
169	/// Appends a system call operation to the script.
170	///
171	/// # Arguments
172	///
173	/// * `operation` - The `InteropService` to call.
174	///
175	/// # Returns
176	///
177	/// A mutable reference to the `ScriptBuilder` for method chaining.
178	///
179	/// # Examples
180	///
181	/// ```rust
182	/// use neo3::neo_builder::ScriptBuilder;
183	/// use neo3::builder::InteropService;
184	///
185	/// let mut builder = ScriptBuilder::new();
186	/// builder.sys_call(InteropService::SystemRuntimeCheckWitness);
187	/// ```
188	pub fn sys_call(&mut self, operation: InteropService) -> &mut Self {
189		self.push_opcode_bytes(
190			OpCode::Syscall,
191			operation
192				.hash()
193				.from_hex_string()
194				.map_err(|e| {
195					BuilderError::IllegalArgument(format!("Invalid operation hash: {}", e))
196				})
197				.expect("InteropService hash should always be valid hex"),
198		)
199	}
200
201	/// Pushes an array of contract parameters to the script.
202	///
203	/// # Arguments
204	///
205	/// * `params` - A slice of `ContractParameter` values to push to the script.
206	///
207	/// # Returns
208	///
209	/// A mutable reference to the `ScriptBuilder` for method chaining.
210	///
211	/// # Examples
212	///
213	/// ```rust
214	/// use neo3::neo_builder::ScriptBuilder;
215	/// use neo3::neo_types::ContractParameter;
216	///
217	/// let mut builder = ScriptBuilder::new();
218	/// builder.push_params(&[
219	///     ContractParameter::from("param1"),
220	///     ContractParameter::from(42),
221	///     ContractParameter::from(true)
222	/// ]);
223	/// ```
224	pub fn push_params(&mut self, params: &[ContractParameter]) -> Result<&mut Self, BuilderError> {
225		for param in params {
226			self.push_param(param).map_err(|e| {
227				BuilderError::IllegalArgument(format!("Failed to push parameter: {}", e))
228			})?;
229		}
230
231		Ok(self.push_integer(BigInt::from(params.len())).op_code(&[OpCode::Pack]))
232	}
233
234	/// Pushes a single contract parameter to the script.
235	///
236	/// # Arguments
237	///
238	/// * `param` - The `ContractParameter` value to push to the script.
239	///
240	/// # Returns
241	///
242	/// A `Result` containing a mutable reference to the `ScriptBuilder` for method chaining,
243	/// or a `BuilderError` if an error occurs.
244	///
245	/// # Examples
246	///
247	/// ```rust
248	/// use neo3::neo_builder::ScriptBuilder;
249	/// use neo3::neo_types::ContractParameter;
250	///
251	/// let mut builder = ScriptBuilder::new();
252	/// builder.push_param(&ContractParameter::from("Hello, Neo!")).unwrap();
253	/// ```
254	pub fn push_param(&mut self, param: &ContractParameter) -> Result<&mut Self, BuilderError> {
255		if param.get_type() == ContractParameterType::Any {
256			self.op_code(&[OpCode::PushNull]);
257			return Ok(self);
258		}
259		match &param
260			.value
261			.clone()
262			.ok_or_else(|| BuilderError::IllegalArgument("Parameter value is None".to_string()))?
263		{
264			ParameterValue::Boolean(b) => self.push_bool(*b),
265			ParameterValue::Integer(i) => self.push_integer(BigInt::from(i.clone())),
266			ParameterValue::ByteArray(b) => {
267				// Decode the base64-encoded string to get the actual bytes
268				let bytes = b.from_base64_string().map_err(|e| {
269					BuilderError::IllegalArgument(format!(
270						"Failed to decode base64 ByteArray: {}",
271						e
272					))
273				})?;
274				self.push_data(bytes)
275			},
276			ParameterValue::Signature(b) | ParameterValue::PublicKey(b) => {
277				self.push_data(b.as_bytes().to_vec())
278			},
279			ParameterValue::H160(h) => self.push_data(h.as_bytes().to_vec()),
280			ParameterValue::H256(h) => self.push_data(h.as_bytes().to_vec()),
281			ParameterValue::String(s) => self.push_data(s.as_bytes().to_vec()),
282			ParameterValue::Array(arr) => self.push_array(arr).map_err(|e| {
283				BuilderError::IllegalArgument(format!("Failed to push array: {}", e))
284			})?,
285			ParameterValue::Map(map) => self
286				.push_map(&map.0)
287				.map_err(|e| BuilderError::IllegalArgument(format!("Failed to push map: {}", e)))?,
288			_ => {
289				return Err(BuilderError::IllegalArgument("Unsupported parameter type".to_string()))
290			},
291		};
292
293		Ok(self)
294	}
295
296	/// Adds a push operation with the given integer to the script.
297	///
298	/// The integer is encoded in its two's complement representation and little-endian byte order.
299	///
300	/// The integer can be up to 32 bytes in length. Values larger than 32 bytes will return an error.
301	///
302	/// # Arguments
303	///
304	/// * `i` - The integer to push to the script
305	///
306	/// # Returns
307	///
308	/// A mutable reference to the `ScriptBuilder` for method chaining.
309	///
310	/// # Examples
311	///
312	/// ```rust
313	/// use neo3::neo_builder::ScriptBuilder;
314	/// use num_bigint::BigInt;
315	///
316	/// let mut builder = ScriptBuilder::new();
317	/// builder.push_integer(BigInt::from(42));
318	/// ```
319	pub fn push_integer(&mut self, i: BigInt) -> &mut Self {
320		if i >= BigInt::from(-1) && i <= BigInt::from(16) {
321			self.op_code(
322				vec![OpCode::try_from(i.to_i32().unwrap() as u8 + OpCode::Push0 as u8).unwrap()]
323					.as_slice(),
324			);
325		} else {
326			let mut bytes = i.to_signed_bytes_le();
327
328			// Remove unnecessary zero padding for positive numbers
329			// BigInt::to_signed_bytes_le() adds extra zero bytes for positive numbers
330			// to ensure they're not interpreted as negative
331			// For positive numbers, we can remove trailing zeros if the previous byte doesn't have the sign bit set
332			// OR if the number is positive and we have a trailing zero
333			while bytes.len() > 1 && bytes[bytes.len() - 1] == 0 && !i.is_negative() {
334				bytes.pop();
335			}
336
337			let len = bytes.len();
338
339			// bytes.reverse();
340
341			match len {
342				1 => self.push_opcode_bytes(OpCode::PushInt8, bytes),
343				2 => self.push_opcode_bytes(OpCode::PushInt16, bytes),
344				len if len <= 4 => self.push_opcode_bytes(
345					OpCode::PushInt32,
346					Self::pad_right(&bytes, 4, i.is_negative()),
347				),
348				len if len <= 8 => self.push_opcode_bytes(
349					OpCode::PushInt64,
350					Self::pad_right(&bytes, 8, i.is_negative()),
351				),
352				len if len <= 16 => self.push_opcode_bytes(
353					OpCode::PushInt128,
354					Self::pad_right(&bytes, 16, i.is_negative()),
355				),
356				len if len <= 32 => self.push_opcode_bytes(
357					OpCode::PushInt256,
358					Self::pad_right(&bytes, 32, i.is_negative()),
359				),
360				_ => {
361					// Instead of panicking, we'll truncate to 32 bytes and log a warning
362					// This is safer than crashing the application
363					eprintln!("Warning: Integer too large, truncating to 32 bytes");
364					self.push_opcode_bytes(
365						OpCode::PushInt256,
366						Self::pad_right(&bytes[..32.min(bytes.len())], 32, i.is_negative()),
367					)
368				},
369			};
370		}
371
372		self
373	}
374
375	/// Append opcodes to the script in the provided order.
376	///
377	/// # Arguments
378	///
379	/// * `opcode` - The opcode to append
380	/// * `argument` - The data argument for the opcode
381	///
382	/// # Returns
383	///
384	/// A mutable reference to the `ScriptBuilder` for method chaining.
385	///
386	/// # Examples
387	///
388	/// ```rust
389	/// use neo3::neo_builder::ScriptBuilder;
390	/// use neo3::neo_types::OpCode;
391	///
392	/// let mut builder = ScriptBuilder::new();
393	/// builder.push_opcode_bytes(OpCode::PushData1, vec![0x01, 0x02, 0x03]);
394	/// ```
395	pub fn push_opcode_bytes(&mut self, opcode: OpCode, argument: Vec<u8>) -> &mut ScriptBuilder {
396		self.script.write_u8(opcode as u8);
397		self.script.write_bytes(&argument);
398
399		self
400	}
401
402	fn pad_right(bytes: &[u8], size: usize, negative: bool) -> Vec<u8> {
403		let pad_value = if negative { 0xFF } else { 0 };
404
405		let mut padded = vec![0; size];
406		padded[0..bytes.len()].copy_from_slice(bytes);
407		padded[bytes.len()..].fill(pad_value);
408		padded
409	}
410
411	// Push data handling
412
413	/// Pushes data to the script.
414	///
415	/// # Arguments
416	///
417	/// * `data` - The data to push to the script.
418	///
419	/// # Returns
420	///
421	/// A mutable reference to the `ScriptBuilder` for method chaining.
422	///
423	/// # Examples
424	///
425	/// ```rust
426	/// use neo3::neo_builder::ScriptBuilder;
427	///
428	/// let mut builder = ScriptBuilder::new();
429	/// builder.push_data("Hello, Neo!".as_bytes().to_vec());
430	/// ```
431	pub fn push_data(&mut self, data: Vec<u8>) -> &mut Self {
432		match data.len() {
433			0..=0xff => {
434				self.op_code(&[OpCode::PushData1]);
435				self.script.write_u8(data.len() as u8);
436				let _ = self.script.write_bytes(&data);
437			},
438			0x100..=0xffff => {
439				self.op_code(&[OpCode::PushData2]);
440				self.script.write_u16(data.len() as u16);
441				let _ = self.script.write_bytes(&data);
442			},
443			_ => {
444				self.op_code(&[OpCode::PushData4]);
445				self.script.write_u32(data.len() as u32);
446				let _ = self.script.write_bytes(&data);
447			}, // _ => return Err(BuilderError::IllegalArgument("Data too long".to_string())),
448		}
449		self
450	}
451
452	/// Pushes a boolean value to the script.
453	///
454	/// # Arguments
455	///
456	/// * `b` - The boolean value to push to the script.
457	///
458	/// # Returns
459	///
460	/// A mutable reference to the `ScriptBuilder` for method chaining.
461	///
462	/// # Examples
463	///
464	/// ```rust
465	/// use neo3::neo_builder::ScriptBuilder;
466	///
467	/// let mut builder = ScriptBuilder::new();
468	/// builder.push_bool(true);
469	/// ```
470	pub fn push_bool(&mut self, b: bool) -> &mut Self {
471		if b {
472			self.op_code(&[OpCode::PushTrue])
473		} else {
474			self.op_code(&[OpCode::PushFalse])
475		};
476		self
477	}
478
479	/// Pushes an array of contract parameters to the script.
480	///
481	/// # Arguments
482	///
483	/// * `arr` - A slice of `ContractParameter` values to push to the script.
484	///
485	/// # Returns
486	///
487	/// A `Result` containing a mutable reference to the `ScriptBuilder` for method chaining,
488	/// or a `BuilderError` if an error occurs.
489	///
490	pub fn push_array(&mut self, arr: &[ContractParameter]) -> Result<&mut Self, BuilderError> {
491		if arr.is_empty() {
492			self.op_code(&[OpCode::NewArray0]);
493		} else {
494			self.push_params(arr);
495		};
496		Ok(self)
497	}
498
499	/// Pushes a map of contract parameters to the script.
500	///
501	/// # Arguments
502	///
503	/// * `map` - A reference to a `HashMap` mapping `ContractParameter` keys to `ContractParameter` values.
504	///
505	/// # Returns
506	///
507	/// A `Result` containing a mutable reference to the `ScriptBuilder` for method chaining,
508	/// or a `BuilderError` if an error occurs.
509	pub fn push_map(
510		&mut self,
511		map: &HashMap<ContractParameter, ContractParameter>,
512	) -> Result<&mut Self, BuilderError> {
513		for (k, v) in map {
514			let kk: ContractParameter = k.clone().into();
515			let vv: ContractParameter = v.clone().into();
516			self.push_param(&vv).unwrap();
517			self.push_param(&kk).unwrap();
518		}
519
520		Ok(self.push_integer(BigInt::from(map.len())).op_code(&[OpCode::PackMap]))
521	}
522
523	/// Appends the `Pack` opcode to the script.
524	///
525	/// # Returns
526	///
527	/// A mutable reference to the `ScriptBuilder` for method chaining.
528	pub fn pack(&mut self) -> &mut Self {
529		self.op_code(&[OpCode::Pack])
530	}
531
532	/// Returns the script as a `Bytes` object.
533	pub fn to_bytes(&self) -> Bytes {
534		self.script.to_bytes()
535	}
536
537	/// Builds a verification script for the given public key.
538	///
539	/// # Arguments
540	///
541	/// * `pub_key` - The public key to use for verification.
542	///
543	/// # Returns
544	///
545	/// A `Bytes` object containing the verification script.
546	pub fn build_verification_script(pub_key: &Secp256r1PublicKey) -> Bytes {
547		let mut sb = ScriptBuilder::new();
548		sb.push_data(pub_key.get_encoded(true))
549			.sys_call(InteropService::SystemCryptoCheckSig);
550		sb.to_bytes()
551	}
552
553	/// Builds a multi-signature script for the given public keys and threshold.
554	///
555	/// # Arguments
556	///
557	/// * `pubkeys` - A mutable slice of `Secp256r1PublicKey` values representing the public keys.
558	/// * `threshold` - The minimum number of signatures required to validate the script.
559	///
560	/// # Returns
561	///
562	/// A `Result` containing a `Bytes` object containing the multi-signature script,
563	/// or a `BuilderError` if an error occurs.
564	pub fn build_multi_sig_script(
565		pubkeys: &mut [Secp256r1PublicKey],
566		threshold: u8,
567	) -> Result<Bytes, BuilderError> {
568		let mut sb = ScriptBuilder::new();
569		sb.push_integer(BigInt::from(threshold));
570		pubkeys.sort_by(|a, b| a.get_encoded(true).cmp(&b.get_encoded(true)));
571		for pk in pubkeys.iter() {
572			sb.push_data(pk.get_encoded(true));
573		}
574		sb.push_integer(BigInt::from(pubkeys.len()));
575		sb.sys_call(InteropService::SystemCryptoCheckMultiSig);
576		Ok(sb.to_bytes())
577	}
578
579	/// Builds a contract script for the given sender, NEF checksum, and contract name.
580	///
581	/// This method creates a script for deploying a smart contract on the Neo N3 blockchain.
582	///
583	/// # Arguments
584	///
585	/// * `sender` - The 160-bit hash of the contract sender.
586	/// * `nef_checksum` - The checksum of the NEF (Neo Executable Format) file.
587	/// * `name` - The name of the contract.
588	///
589	/// # Returns
590	///
591	/// A `Result` containing a `Bytes` object with the contract deployment script,
592	/// or a `BuilderError` if an error occurs.
593	///
594	/// # Examples
595	///
596	/// ```rust
597	/// use neo3::neo_builder::ScriptBuilder;
598	/// use primitive_types::H160;
599	/// use std::str::FromStr;
600	///
601	/// let sender = H160::from_str("0xd2a4cff31913016155e38e474a2c06d08be276cf").unwrap();
602	/// let nef_checksum = 1234567890;
603	/// let name = "MyContract";
604	///
605	/// let script = ScriptBuilder::build_contract_script(&sender, nef_checksum, name).unwrap();
606	/// ```
607	/// * `nef_checksum` - The checksum of the NEF file.
608	/// * `name` - The name of the contract.
609	///
610	/// # Returns
611	///
612	/// A `Result` containing a `Bytes` object containing the contract script,
613	/// or a `BuilderError` if an error occurs.
614	pub fn build_contract_script(
615		sender: &H160,
616		nef_checksum: u32,
617		name: &str,
618	) -> Result<Bytes, BuilderError> {
619		let mut sb = ScriptBuilder::new();
620		sb.op_code(&[OpCode::Abort])
621			.push_data(sender.to_vec())
622			.push_integer(BigInt::from(nef_checksum))
623			.push_data(name.as_bytes().to_vec());
624		Ok(sb.to_bytes())
625	}
626
627	/// Builds a script that calls a contract method and unwraps the iterator result.
628	///
629	/// This method is particularly useful when calling contract methods that return iterators.
630	/// It automatically handles the iteration process and collects the results into an array.
631	///
632	/// # Arguments
633	///
634	/// * `contract_hash` - The 160-bit hash of the contract to call.
635	/// * `method` - The name of the method to call.
636	/// * `params` - A slice of `ContractParameter` values to pass as arguments to the method.
637	/// * `max_items` - The maximum number of items to retrieve from the iterator.
638	/// * `call_flags` - An optional `CallFlags` value specifying the call flags.
639	///
640	/// # Returns
641	///
642	/// A `Result` containing a `Bytes` object with the script that calls the contract method
643	/// and unwraps the iterator result into an array, or a `BuilderError` if an error occurs.
644	///
645	/// # Examples
646	///
647	/// ```rust
648	/// use neo3::neo_builder::ScriptBuilder;
649	/// use neo3::neo_types::ContractParameter;
650	/// use neo3::builder::CallFlags;
651	/// use primitive_types::H160;
652	/// use std::str::FromStr;
653	///
654	/// // Call a contract method that returns an iterator and collect up to 100 items
655	/// let contract_hash = H160::from_str("0xd2a4cff31913016155e38e474a2c06d08be276cf").unwrap();
656	/// let method = "getTokens";
657	/// let params = vec![ContractParameter::from("owner_address")];
658	/// let max_items = 100;
659	///
660	/// // Build the script
661	/// let script = ScriptBuilder::build_contract_call_and_unwrap_iterator(
662	///     &contract_hash,
663	///     method,
664	///     &params,
665	///     max_items,
666	///     Some(CallFlags::All)
667	/// ).unwrap();
668	///
669	/// // The resulting script will:
670	/// // 1. Call the contract method
671	/// // 2. Iterate through the returned iterator
672	/// // 3. Collect up to max_items into an array
673	/// // 4. Leave the array on the stack
674	/// ```
675	pub fn build_contract_call_and_unwrap_iterator(
676		contract_hash: &H160,
677		method: &str,
678		params: &[ContractParameter],
679		max_items: u32,
680		call_flags: Option<CallFlags>,
681	) -> Result<Bytes, BuilderError> {
682		let mut sb = Self::new();
683		sb.push_integer(BigInt::from(max_items));
684
685		sb.contract_call(contract_hash, method, params, call_flags).unwrap();
686
687		sb.op_code(&[OpCode::NewArray]);
688
689		let cycle_start = sb.len();
690		sb.op_code(&[OpCode::Over]);
691		sb.sys_call(InteropService::SystemIteratorNext);
692
693		let jmp_if_not = sb.len();
694		sb.op_code_with_arg(OpCode::JmpIf, vec![0]);
695
696		sb.op_code(&[OpCode::Dup, OpCode::Push2, OpCode::Pick])
697			.sys_call(InteropService::SystemIteratorValue)
698			.op_code(&[
699				OpCode::Append,
700				OpCode::Dup,
701				OpCode::Size,
702				OpCode::Push3,
703				OpCode::Pick,
704				OpCode::Ge,
705			]);
706
707		let jmp_if_max = sb.len();
708		sb.op_code_with_arg(OpCode::JmpIf, vec![0]);
709
710		let jmp_offset = sb.len();
711		// Calculate backward jump as a signed offset
712		let jmp_bytes = (cycle_start as i32 - jmp_offset as i32) as i8;
713		sb.op_code_with_arg(OpCode::Jmp, vec![jmp_bytes as u8]);
714
715		let load_result = sb.len();
716		sb.op_code(&[OpCode::Nip, OpCode::Nip]);
717
718		let mut script = sb.to_bytes();
719		let jmp_not_bytes = (load_result - jmp_if_not) as i8;
720		script[jmp_if_not + 1] = jmp_not_bytes as u8;
721
722		let jmp_max_bytes = (load_result - jmp_if_max) as i8;
723		script[jmp_if_max + 1] = jmp_max_bytes as u8;
724
725		Ok(script)
726	}
727
728	/// Returns the length of the script in bytes.
729	///
730	/// This method is useful for determining the current position in the script,
731	/// which is needed for calculating jump offsets in control flow operations.
732	///
733	/// # Returns
734	///
735	/// The length of the script in bytes.
736	///
737	/// # Examples
738	///
739	/// ```rust
740	/// use neo3::neo_builder::ScriptBuilder;
741	///
742	/// let mut builder = ScriptBuilder::new();
743	/// builder.push_data("Hello, Neo!".as_bytes().to_vec());
744	/// let script_length = builder.len();
745	/// println!("Script length: {} bytes", script_length);
746	/// ```
747	pub fn len(&self) -> usize {
748		self.script().size()
749	}
750}
751
752#[cfg(test)]
753mod tests {
754	use super::*;
755	use crate::{
756		neo_types::{contract::ContractParameterMap, ContractParameter, ContractParameterType},
757		prelude::Bytes,
758	};
759	use num_bigint::BigInt;
760	use std::collections::HashMap;
761
762	#[test]
763	fn test_push_empty_array() {
764		let mut builder = ScriptBuilder::new();
765		builder.push_array(&[]).unwrap();
766		assert_builder(&builder, &[OpCode::NewArray0 as u8]);
767	}
768
769	#[test]
770	fn test_push_byte_array() {
771		let mut builder = ScriptBuilder::new();
772		let data = vec![0x01, 0x02, 0x03];
773		builder.push_data(data.clone());
774
775		let mut expected = vec![OpCode::PushData1 as u8, data.len() as u8];
776		expected.extend(data);
777		assert_builder(&builder, &expected);
778	}
779
780	#[test]
781	fn test_push_string() {
782		let mut builder = ScriptBuilder::new();
783		let string_data = "Hello, Neo!";
784		builder.push_data(string_data.as_bytes().to_vec());
785
786		let mut expected = vec![OpCode::PushData1 as u8, string_data.len() as u8];
787		expected.extend(string_data.as_bytes());
788		assert_builder(&builder, &expected);
789	}
790
791	#[test]
792	fn test_push_integer() {
793		let mut builder = ScriptBuilder::new();
794
795		// Test small integers (-1 to 16)
796		builder.push_integer(BigInt::from(0));
797		assert_builder(&builder, &[OpCode::Push0 as u8]);
798
799		let mut builder = ScriptBuilder::new();
800		builder.push_integer(BigInt::from(16));
801		assert_builder(&builder, &[OpCode::Push16 as u8]);
802
803		// Test larger integers
804		let mut builder = ScriptBuilder::new();
805		builder.push_integer(BigInt::from(255));
806		assert_builder(&builder, &[OpCode::PushInt8 as u8, 0xff]);
807
808		let mut builder = ScriptBuilder::new();
809		builder.push_integer(BigInt::from(65535));
810		assert_builder(&builder, &[OpCode::PushInt16 as u8, 0xff, 0xff]);
811
812		// Test negative integers - update expectations to match our more efficient implementation
813		let mut builder = ScriptBuilder::new();
814		builder.push_integer(BigInt::from(-1000000000000000i64));
815		// Our implementation uses PushInt64 (more efficient) instead of PushInt128
816		// The actual bytes are: [0, 128, 57, 91, 129, 114, 252] padded to 8 bytes with 0xFF
817		let expected_bytes = vec![OpCode::PushInt64 as u8, 0, 128, 57, 91, 129, 114, 252, 255];
818		assert_builder(&builder, &expected_bytes);
819
820		let mut builder = ScriptBuilder::new();
821		builder.push_integer(BigInt::from(1000000000000000i64));
822		// Similarly, this should use PushInt64 instead of PushInt128
823		// The actual bytes for positive 1000000000000000 should be different
824		let pos_big_int = BigInt::from(1000000000000000i64);
825		let pos_bytes = pos_big_int.to_signed_bytes_le();
826		let mut expected_pos = vec![OpCode::PushInt64 as u8];
827		let padded_pos = ScriptBuilder::pad_right(&pos_bytes, 8, false);
828		expected_pos.extend(padded_pos);
829		assert_builder(&builder, &expected_pos);
830	}
831
832	#[test]
833	fn test_verification_script() {
834		let public_key = Secp256r1PublicKey::from_bytes(
835			&hex::decode("035fdb1d1f06759547020891ae97c729327853aeb1256b6fe0473bc2e9fa42ff50")
836				.unwrap(),
837		)
838		.unwrap();
839
840		let script = ScriptBuilder::build_verification_script(&public_key);
841
842		// The script should be: PushData1 (0x0c) + length (33/0x21) + public key (33 bytes) + Syscall (0x41) + SystemCryptoCheckSig hash
843		// Let's build the expected result dynamically to ensure it's correct
844		let mut expected = vec![0x0c, 0x21]; // PushData1 + length 33
845		expected.extend_from_slice(
846			&hex::decode("035fdb1d1f06759547020891ae97c729327853aeb1256b6fe0473bc2e9fa42ff50")
847				.unwrap(),
848		);
849		expected.push(0x41); // Syscall opcode
850		expected
851			.extend_from_slice(&hex::decode(&InteropService::SystemCryptoCheckSig.hash()).unwrap());
852
853		assert_eq!(script.to_vec(), expected);
854	}
855
856	#[test]
857	fn test_map() {
858		let mut map = HashMap::new();
859		map.insert(
860			ContractParameter::string("first".to_string()),
861			ContractParameter::byte_array(hex::decode("7365636f6e64").unwrap()),
862		);
863
864		let mut builder = ScriptBuilder::new();
865		builder.push_map(&map).unwrap();
866
867		let expected = builder.to_bytes().to_hex_string();
868
869		let mut builder2 = ScriptBuilder::new();
870		builder2
871			.push_data(hex::decode("7365636f6e64").unwrap())
872			.push_data("first".as_bytes().to_vec())
873			.push_integer(BigInt::from(1))
874			.op_code(&[OpCode::PackMap]);
875
876		let expected2 = builder2.to_bytes().to_hex_string();
877
878		let mut builder3 = ScriptBuilder::new().push_map(&map).unwrap().to_bytes().to_hex_string();
879
880		assert_eq!(expected, expected2);
881		assert_eq!(expected, builder3);
882	}
883
884	#[test]
885	fn test_map_nested() {
886		let mut inner = ContractParameterMap::new();
887		inner.0.insert(
888			ContractParameter::string("inner_key".to_string()),
889			ContractParameter::integer(42),
890		);
891
892		let mut outer = ContractParameterMap::new();
893		outer.0.insert(
894			ContractParameter::string("outer_key".to_string()),
895			ContractParameter::map(inner),
896		);
897
898		let expected = ScriptBuilder::new().push_map(&outer.0).unwrap().to_bytes().to_hex_string();
899
900		// Manually build the expected script
901		let mut manual_builder = ScriptBuilder::new();
902		manual_builder
903			.push_integer(BigInt::from(42))
904			.push_data("inner_key".as_bytes().to_vec())
905			.push_integer(BigInt::from(1))
906			.op_code(&[OpCode::PackMap])
907			.push_data("outer_key".as_bytes().to_vec())
908			.push_integer(BigInt::from(1))
909			.op_code(&[OpCode::PackMap]);
910
911		let manual_expected = manual_builder.to_bytes().to_hex_string();
912
913		assert_eq!(expected, manual_expected);
914	}
915
916	fn assert_builder(builder: &ScriptBuilder, expected: &[u8]) {
917		assert_eq!(builder.to_bytes().to_vec(), expected);
918	}
919
920	fn byte_array(size: usize) -> Vec<u8> {
921		(0..size).map(|i| (i % 256) as u8).collect()
922	}
923}