1use evm_runtime::{ExitReason, Handler};
2use fe_common::diagnostics::print_diagnostics;
3use fe_common::utils::keccak;
4use fe_driver as driver;
5use primitive_types::{H160, U256};
6use std::cell::RefCell;
7use std::collections::BTreeMap;
8use std::fmt::{Display, Formatter};
9use std::str::FromStr;
10use yultsur::*;
11
12#[macro_export]
13macro_rules! assert_harness_gas_report {
14 ($harness: expr) => {
15 assert_snapshot!(format!("{}", $harness.gas_reporter));
16 };
17
18 ($harness: expr, $($expr:expr),*) => {
19 let mut settings = insta::Settings::clone_current();
20 let suffix = format!("{:?}", $($expr,)*).replace("\"", "");
21 settings.set_snapshot_suffix(suffix);
22 let _guard = settings.bind_to_scope();
23 assert_snapshot!(format!("{}", $harness.gas_reporter));
24 }
25}
26
27#[derive(Default, Debug)]
28pub struct GasReporter {
29 records: RefCell<Vec<GasRecord>>,
30}
31
32impl GasReporter {
33 pub fn add_record(&self, description: &str, gas_used: u64) {
34 self.records.borrow_mut().push(GasRecord {
35 description: description.to_string(),
36 gas_used,
37 })
38 }
39
40 pub fn add_func_call_record(&self, function: &str, input: &[ethabi::Token], gas_used: u64) {
41 let description = format!("{function}({input:?})");
42 self.add_record(&description, gas_used)
43 }
44}
45
46impl Display for GasReporter {
47 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
48 for record in self.records.borrow().iter() {
49 writeln!(f, "{} used {} gas", record.description, record.gas_used)?;
50 }
51
52 Ok(())
53 }
54}
55
56#[derive(Debug)]
57pub struct GasRecord {
58 pub description: String,
59 pub gas_used: u64,
60}
61
62pub trait ToBeBytes {
63 fn to_be_bytes(&self) -> [u8; 32];
64}
65
66impl ToBeBytes for U256 {
67 fn to_be_bytes(&self) -> [u8; 32] {
68 let mut input_bytes: [u8; 32] = [0; 32];
69 self.to_big_endian(&mut input_bytes);
70 input_bytes
71 }
72}
73
74#[allow(dead_code)]
75pub type Backend<'a> = evm::backend::MemoryBackend<'a>;
76
77#[allow(dead_code)]
78pub type StackState<'a> = evm::executor::stack::MemoryStackState<'a, 'a, Backend<'a>>;
79
80#[allow(dead_code)]
81pub type Executor<'a, 'b> = evm::executor::stack::StackExecutor<'a, 'b, StackState<'a>, ()>;
82
83#[allow(dead_code)]
84pub const DEFAULT_CALLER: &str = "1000000000000000000000000000000000000001";
85
86#[allow(dead_code)]
87pub struct ContractHarness {
88 pub gas_reporter: GasReporter,
89 pub address: H160,
90 pub abi: ethabi::Contract,
91 pub caller: H160,
92 pub value: U256,
93}
94
95#[allow(dead_code)]
96impl ContractHarness {
97 fn new(contract_address: H160, abi: ethabi::Contract) -> Self {
98 let caller = address(DEFAULT_CALLER);
99
100 ContractHarness {
101 gas_reporter: GasReporter::default(),
102 address: contract_address,
103 abi,
104 caller,
105 value: U256::zero(),
106 }
107 }
108
109 pub fn capture_call(
110 &self,
111 executor: &mut Executor,
112 name: &str,
113 input: &[ethabi::Token],
114 ) -> evm::Capture<(evm::ExitReason, Vec<u8>), std::convert::Infallible> {
115 let input = self.build_calldata(name, input);
116 self.capture_call_raw_bytes(executor, input)
117 }
118
119 pub fn build_calldata(&self, name: &str, input: &[ethabi::Token]) -> Vec<u8> {
120 let function = &self.abi.functions[name][0];
121 function
122 .encode_input(input)
123 .unwrap_or_else(|reason| panic!("Unable to encode input for {name}: {reason:?}"))
124 }
125
126 pub fn capture_call_raw_bytes(
127 &self,
128 executor: &mut Executor,
129 input: Vec<u8>,
130 ) -> evm::Capture<(evm::ExitReason, Vec<u8>), std::convert::Infallible> {
131 let context = evm::Context {
132 address: self.address,
133 caller: self.caller,
134 apparent_value: self.value,
135 };
136
137 executor.call(self.address, None, input, None, false, context)
138 }
139
140 pub fn test_function(
141 &self,
142 executor: &mut Executor,
143 name: &str,
144 input: &[ethabi::Token],
145 output: Option<ðabi::Token>,
146 ) {
147 let actual_output = self.call_function(executor, name, input);
148
149 assert_eq!(
150 output.map(ToOwned::to_owned),
151 actual_output,
152 "unexpected output from `fn {name}`"
153 )
154 }
155
156 pub fn call_function(
157 &self,
158 executor: &mut Executor,
159 name: &str,
160 input: &[ethabi::Token],
161 ) -> Option<ethabi::Token> {
162 let function = &self.abi.functions[name][0];
163 let start_gas = executor.used_gas();
164 let capture = self.capture_call(executor, name, input);
165 let gas_used = executor.used_gas() - start_gas;
166 self.gas_reporter
167 .add_func_call_record(name, input, gas_used);
168
169 match capture {
170 evm::Capture::Exit((ExitReason::Succeed(_), output)) => function
171 .decode_output(&output)
172 .unwrap_or_else(|_| panic!("unable to decode output of {}: {:?}", name, &output))
173 .pop(),
174 evm::Capture::Exit((reason, _)) => panic!("failed to run \"{name}\": {reason:?}"),
175 evm::Capture::Trap(_) => panic!("trap"),
176 }
177 }
178
179 pub fn test_function_reverts(
180 &self,
181 executor: &mut Executor,
182 name: &str,
183 input: &[ethabi::Token],
184 revert_data: &[u8],
185 ) {
186 validate_revert(self.capture_call(executor, name, input), revert_data)
187 }
188
189 pub fn test_call_reverts(&self, executor: &mut Executor, input: Vec<u8>, revert_data: &[u8]) {
190 validate_revert(self.capture_call_raw_bytes(executor, input), revert_data)
191 }
192
193 pub fn test_function_returns(
194 &self,
195 executor: &mut Executor,
196 name: &str,
197 input: &[ethabi::Token],
198 return_data: &[u8],
199 ) {
200 validate_return(self.capture_call(executor, name, input), return_data)
201 }
202
203 pub fn test_call_returns(&self, executor: &mut Executor, input: Vec<u8>, return_data: &[u8]) {
204 validate_return(self.capture_call_raw_bytes(executor, input), return_data)
205 }
206
207 pub fn events_emitted(&self, executor: Executor, events: &[(&str, &[ethabi::Token])]) {
209 let raw_logs = executor
210 .into_state()
211 .deconstruct()
212 .1
213 .into_iter()
214 .map(|log| ethabi::RawLog::from((log.topics, log.data)))
215 .collect::<Vec<ethabi::RawLog>>();
216
217 for (name, expected_output) in events {
218 let event = self
219 .abi
220 .events()
221 .find(|event| event.name.eq(name))
222 .expect("unable to find event for name");
223
224 let outputs_for_event = raw_logs
225 .iter()
226 .filter_map(|raw_log| event.parse_log(raw_log.clone()).ok())
227 .map(|event_log| {
228 event_log
229 .params
230 .into_iter()
231 .map(|param| param.value)
232 .collect::<Vec<_>>()
233 })
234 .collect::<Vec<_>>();
235
236 if !outputs_for_event.iter().any(|v| v == expected_output) {
237 println!("raw logs dump: {raw_logs:?}");
238 panic!(
239 "no \"{name}\" logs matching: {expected_output:?}\nfound: {outputs_for_event:?}"
240 )
241 }
242 }
243 }
244
245 pub fn set_caller(&mut self, caller: H160) {
246 self.caller = caller;
247 }
248}
249
250#[allow(dead_code)]
251pub fn with_executor(test: &dyn Fn(Executor)) {
252 let vicinity = evm::backend::MemoryVicinity {
253 gas_price: U256::zero(),
254 origin: H160::zero(),
255 chain_id: U256::zero(),
256 block_hashes: Vec::new(),
257 block_number: U256::zero(),
258 block_coinbase: H160::zero(),
259 block_timestamp: U256::zero(),
260 block_difficulty: U256::zero(),
261 block_gas_limit: primitive_types::U256::MAX,
262 block_base_fee_per_gas: U256::zero(),
263 };
264 let state: BTreeMap<primitive_types::H160, evm::backend::MemoryAccount> = BTreeMap::new();
265 let backend = evm::backend::MemoryBackend::new(&vicinity, state);
266
267 with_executor_backend(backend, test)
268}
269
270#[allow(dead_code)]
271pub fn with_executor_backend(backend: Backend, test: &dyn Fn(Executor)) {
272 let config = evm::Config::london();
273 let stack_state = StackState::new(
274 evm::executor::stack::StackSubstateMetadata::new(u64::MAX, &config),
275 &backend,
276 );
277 let executor = Executor::new_with_precompiles(stack_state, &config, &());
278
279 test(executor)
280}
281
282pub fn validate_revert(
283 capture: evm::Capture<(evm::ExitReason, Vec<u8>), std::convert::Infallible>,
284 expected_data: &[u8],
285) {
286 if let evm::Capture::Exit((evm::ExitReason::Revert(_), output)) = capture {
287 assert_eq!(
288 format!("0x{}", hex::encode(output)),
289 format!("0x{}", hex::encode(expected_data))
290 );
291 } else {
292 panic!("Method was expected to revert but didn't")
293 };
294}
295
296pub fn validate_return(
297 capture: evm::Capture<(evm::ExitReason, Vec<u8>), std::convert::Infallible>,
298 expected_data: &[u8],
299) {
300 if let evm::Capture::Exit((evm::ExitReason::Succeed(_), output)) = capture {
301 assert_eq!(
302 format!("0x{}", hex::encode(output)),
303 format!("0x{}", hex::encode(expected_data))
304 );
305 } else {
306 panic!("Method was expected to return but didn't")
307 };
308}
309
310pub fn encoded_panic_assert() -> Vec<u8> {
311 encode_revert("Panic(uint256)", &[uint_token(0x01)])
312}
313
314pub fn encoded_over_or_underflow() -> Vec<u8> {
315 encode_revert("Panic(uint256)", &[uint_token(0x11)])
316}
317
318pub fn encoded_panic_out_of_bounds() -> Vec<u8> {
319 encode_revert("Panic(uint256)", &[uint_token(0x32)])
320}
321
322pub fn encoded_div_or_mod_by_zero() -> Vec<u8> {
323 encode_revert("Panic(uint256)", &[uint_token(0x12)])
324}
325
326pub fn encoded_invalid_abi_data() -> Vec<u8> {
327 encode_revert("Error(uint256)", &[uint_token(0x103)])
328}
329
330#[allow(dead_code)]
331#[cfg(feature = "solc-backend")]
332pub fn deploy_contract(
333 executor: &mut Executor,
334 fixture: &str,
335 contract_name: &str,
336 init_params: &[ethabi::Token],
337) -> ContractHarness {
338 let mut db = driver::Db::default();
339 let compiled_module = match driver::compile_single_file(
340 &mut db,
341 fixture,
342 test_files::fixture(fixture),
343 true,
344 false,
345 true,
346 ) {
347 Ok(module) => module,
348 Err(error) => {
349 fe_common::diagnostics::print_diagnostics(&db, &error.0);
350 panic!("failed to compile module: {fixture}")
351 }
352 };
353
354 let compiled_contract = compiled_module
355 .contracts
356 .get(contract_name)
357 .expect("could not find contract in fixture");
358
359 _deploy_contract(
360 executor,
361 &compiled_contract.bytecode,
362 &compiled_contract.json_abi,
363 init_params,
364 )
365}
366
367#[allow(dead_code)]
368#[cfg(feature = "solc-backend")]
369pub fn deploy_contract_from_ingot(
370 executor: &mut Executor,
371 path: &str,
372 contract_name: &str,
373 init_params: &[ethabi::Token],
374) -> ContractHarness {
375 use fe_common::utils::files::BuildFiles;
376
377 let files = test_files::fixture_dir_files("ingots");
378 let build_files = BuildFiles::load_static(files, path).expect("failed to load build files");
379 let mut db = driver::Db::default();
380 let compiled_module = match driver::compile_ingot(&mut db, &build_files, true, false, true) {
381 Ok(module) => module,
382 Err(error) => {
383 fe_common::diagnostics::print_diagnostics(&db, &error.0);
384 panic!("failed to compile ingot: {path}")
385 }
386 };
387
388 let compiled_contract = compiled_module
389 .contracts
390 .get(contract_name)
391 .expect("could not find contract in fixture");
392
393 _deploy_contract(
394 executor,
395 &compiled_contract.bytecode,
396 &compiled_contract.json_abi,
397 init_params,
398 )
399}
400
401#[allow(dead_code)]
402#[cfg(feature = "solc-backend")]
403pub fn deploy_solidity_contract(
404 executor: &mut Executor,
405 fixture: &str,
406 contract_name: &str,
407 init_params: &[ethabi::Token],
408 optimized: bool,
409) -> ContractHarness {
410 let src = test_files::fixture(fixture)
411 .replace('\n', "")
412 .replace('"', "\\\"");
413
414 let (bytecode, abi) = compile_solidity_contract(contract_name, &src, optimized)
415 .expect("Could not compile contract");
416
417 _deploy_contract(executor, &bytecode, &abi, init_params)
418}
419
420#[allow(dead_code)]
421pub fn encode_error_reason(reason: &str) -> Vec<u8> {
422 encode_revert("Error(string)", &[string_token(reason)])
423}
424
425#[allow(dead_code)]
426pub fn encode_revert(selector: &str, input: &[ethabi::Token]) -> Vec<u8> {
427 let mut data = String::new();
428 for param in input {
429 let encoded = match param {
430 ethabi::Token::Uint(val) | ethabi::Token::Int(val) => {
431 format!("{:0>64}", format!("{val:x}"))
432 }
433 ethabi::Token::Bool(val) => format!("{:0>64x}", *val as i32),
434 ethabi::Token::String(val) => {
435 const DATA_OFFSET: &str =
436 "0000000000000000000000000000000000000000000000000000000000000020";
437
438 let string_len = format!("{:0>64x}", val.len());
440
441 let mut string_bytes = val.as_bytes().to_vec();
442 while string_bytes.len() % 32 != 0 {
443 string_bytes.push(0)
444 }
445 let string_bytes = hex::encode(&string_bytes);
448
449 format!("{DATA_OFFSET}{string_len}{string_bytes}")
450 }
451 _ => todo!("Other ABI types not supported yet"),
452 };
453 data.push_str(&encoded);
454 }
455
456 let all = format!("{}{}", get_function_selector(selector), data);
457 hex::decode(&all).unwrap_or_else(|_| panic!("No valid hex: {}", &all))
458}
459
460fn get_function_selector(signature: &str) -> String {
461 hex::encode(&keccak::full_as_bytes(signature.as_bytes())[..4])
463}
464
465fn _deploy_contract(
466 executor: &mut Executor,
467 bytecode: &str,
468 abi: &str,
469 init_params: &[ethabi::Token],
470) -> ContractHarness {
471 let abi = ethabi::Contract::load(abi.as_bytes()).expect("unable to load the ABI");
472
473 let mut bytecode = hex::decode(bytecode).expect("failed to decode bytecode");
474
475 if let Some(constructor) = &abi.constructor {
476 bytecode = constructor.encode_input(bytecode, init_params).unwrap()
477 }
478
479 if let evm::Capture::Exit(exit) = executor.create(
480 address(DEFAULT_CALLER),
481 evm_runtime::CreateScheme::Legacy {
482 caller: address(DEFAULT_CALLER),
483 },
484 U256::zero(),
485 bytecode,
486 None,
487 ) {
488 return ContractHarness::new(
489 exit.1
490 .unwrap_or_else(|| panic!("Unable to retrieve contract address: {:?}", exit.0)),
491 abi,
492 );
493 }
494
495 panic!("Failed to create contract")
496}
497
498#[derive(Debug)]
499pub struct SolidityCompileError(Vec<serde_json::Value>);
500
501impl std::fmt::Display for SolidityCompileError {
502 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
503 write!(f, "{:?}", &self.0[..])
504 }
505}
506
507impl std::error::Error for SolidityCompileError {}
508
509#[cfg(feature = "solc-backend")]
510pub fn compile_solidity_contract(
511 name: &str,
512 solidity_src: &str,
513 optimized: bool,
514) -> Result<(String, String), SolidityCompileError> {
515 let solc_config = r#"
516 {
517 "language": "Solidity",
518 "sources": { "input.sol": { "content": "{src}" } },
519 "settings": {
520 "optimizer": { "enabled": {optimizer_enabled} },
521 "outputSelection": { "*": { "*": ["*"], "": [ "*" ] } }
522 }
523 }
524 "#;
525 let solc_config = solc_config
526 .replace("{src}", solidity_src)
527 .replace("{optimizer_enabled}", &optimized.to_string());
528
529 let raw_output = solc::compile(&solc_config);
530
531 let output: serde_json::Value =
532 serde_json::from_str(&raw_output).expect("Unable to compile contract");
533
534 if output["errors"].is_array() {
535 let severity: serde_json::Value =
536 serde_json::to_value("error").expect("Unable to convert into serde value type");
537 let errors: serde_json::Value = output["errors"]
538 .as_array()
539 .unwrap()
540 .iter()
541 .cloned()
542 .filter_map(|err| {
543 if err["severity"] == severity {
544 Some(err["formattedMessage"].clone())
545 } else {
546 None
547 }
548 })
549 .collect();
550
551 let errors_list = errors
552 .as_array()
553 .unwrap_or_else(|| panic!("Unable to parse error properly"));
554 if !errors_list.is_empty() {
555 return Err(SolidityCompileError(errors_list.clone()));
556 }
557 }
558
559 let bytecode = output["contracts"]["input.sol"][name]["evm"]["bytecode"]["object"]
560 .to_string()
561 .replace('"', "");
562
563 let abi = if let serde_json::Value::Array(data) = &output["contracts"]["input.sol"][name]["abi"]
564 {
565 data.iter()
566 .filter(|val| {
567 val["type"] != "error"
570 })
571 .cloned()
572 .collect::<Vec<_>>()
573 } else {
574 vec![]
575 };
576
577 let abi = serde_json::Value::Array(abi).to_string();
578
579 if [&bytecode, &abi].iter().any(|val| val == &"null") {
580 return Err(SolidityCompileError(vec![serde_json::Value::String(
581 String::from("Bytecode not found"),
582 )]));
583 }
584
585 Ok((bytecode, abi))
586}
587
588#[allow(dead_code)]
589pub fn load_contract(address: H160, fixture: &str, contract_name: &str) -> ContractHarness {
590 let mut db = driver::Db::default();
591 let compiled_module = driver::compile_single_file(
592 &mut db,
593 fixture,
594 test_files::fixture(fixture),
595 true,
596 false,
597 true,
598 )
599 .unwrap_or_else(|err| {
600 print_diagnostics(&db, &err.0);
601 panic!("failed to compile fixture: {fixture}");
602 });
603 let compiled_contract = compiled_module
604 .contracts
605 .get(contract_name)
606 .expect("could not find contract in fixture");
607 let abi = ethabi::Contract::load(compiled_contract.json_abi.as_bytes())
608 .expect("unable to load the ABI");
609
610 ContractHarness::new(address, abi)
611}
612pub struct Runtime {
613 functions: Vec<yul::Statement>,
614 test_statements: Vec<yul::Statement>,
615 data: Vec<yul::Data>,
616}
617
618impl Default for Runtime {
619 fn default() -> Self {
620 Self::new()
621 }
622}
623
624pub struct ExecutionOutput {
625 exit_reason: ExitReason,
626 data: Vec<u8>,
627}
628
629#[allow(dead_code)]
630impl Runtime {
631 pub fn new() -> Runtime {
633 Runtime {
634 functions: vec![],
635 test_statements: vec![],
636 data: vec![],
637 }
638 }
639
640 pub fn with_functions(self, fns: Vec<yul::Statement>) -> Runtime {
642 Runtime {
643 functions: fns,
644 ..self
645 }
646 }
647
648 pub fn with_test_statements(self, statements: Vec<yul::Statement>) -> Runtime {
650 Runtime {
651 test_statements: statements,
652 ..self
653 }
654 }
655
656 pub fn with_data(self, data: Vec<yul::Data>) -> Runtime {
658 Runtime { data, ..self }
659 }
660
661 pub fn to_yul(&self) -> yul::Object {
663 let all_statements = [self.functions.clone(), self.test_statements.clone()].concat();
664 yul::Object {
665 name: identifier! { Contract },
666 code: code! { [all_statements...] },
667 objects: vec![],
668 data: self.data.clone(),
669 }
670 }
671
672 #[cfg(feature = "solc-backend")]
673 pub fn execute(&self, executor: &mut Executor) -> ExecutionOutput {
674 let (exit_reason, data) = execute_runtime_functions(executor, self);
675 ExecutionOutput::new(exit_reason, data)
676 }
677}
678
679#[allow(dead_code)]
680impl ExecutionOutput {
681 pub fn new(exit_reason: ExitReason, data: Vec<u8>) -> ExecutionOutput {
683 ExecutionOutput { exit_reason, data }
684 }
685
686 pub fn expect_success(self) -> ExecutionOutput {
688 if let ExecutionOutput {
689 exit_reason: ExitReason::Succeed(_),
690 ..
691 } = &self
692 {
693 self
694 } else {
695 panic!("Execution did not succeed: {:?}", &self.exit_reason)
696 }
697 }
698
699 pub fn expect_revert(self) -> ExecutionOutput {
701 if let ExecutionOutput {
702 exit_reason: ExitReason::Revert(_),
703 ..
704 } = &self
705 {
706 self
707 } else {
708 panic!("Execution did not revert: {:?}", &self.exit_reason)
709 }
710 }
711
712 pub fn expect_revert_reason(self, reason: &str) -> ExecutionOutput {
714 assert_eq!(self.data, encode_error_reason(reason));
715 self
716 }
717}
718
719#[cfg(feature = "solc-backend")]
720fn execute_runtime_functions(executor: &mut Executor, runtime: &Runtime) -> (ExitReason, Vec<u8>) {
721 let yul_code = runtime.to_yul().to_string().replace('"', "\\\"");
722 let contract_bytecode = fe_yulc::compile_single_contract("Contract", &yul_code, false, false)
723 .expect("failed to compile Yul");
724 let bytecode = hex::decode(contract_bytecode.bytecode).expect("failed to decode bytecode");
725
726 if let evm::Capture::Exit((reason, _, output)) = executor.create(
727 address(DEFAULT_CALLER),
728 evm_runtime::CreateScheme::Legacy {
729 caller: address(DEFAULT_CALLER),
730 },
731 U256::zero(),
732 bytecode,
733 None,
734 ) {
735 (reason, output)
736 } else {
737 panic!("EVM trap during test")
738 }
739}
740
741#[allow(dead_code)]
742pub fn uint_token(n: u64) -> ethabi::Token {
743 ethabi::Token::Uint(U256::from(n))
744}
745
746#[allow(dead_code)]
747pub fn uint_token_from_dec_str(val: &str) -> ethabi::Token {
748 ethabi::Token::Uint(U256::from_dec_str(val).expect("Not a valid dec string"))
749}
750
751#[allow(dead_code)]
752pub fn int_token(val: i64) -> ethabi::Token {
753 ethabi::Token::Int(to_2s_complement(val))
754}
755
756#[allow(dead_code)]
757pub fn string_token(s: &str) -> ethabi::Token {
758 ethabi::Token::String(s.to_string())
759}
760
761#[allow(dead_code)]
762pub fn address(s: &str) -> H160 {
763 H160::from_str(s).unwrap_or_else(|_| panic!("couldn't create address from: {s}"))
764}
765
766#[allow(dead_code)]
767pub fn address_token(s: &str) -> ethabi::Token {
768 ethabi::Token::Address(address(&format!("{s:0>40}")))
770}
771
772#[allow(dead_code)]
773pub fn bool_token(val: bool) -> ethabi::Token {
774 ethabi::Token::Bool(val)
775}
776
777#[allow(dead_code)]
778pub fn bytes_token(s: &str) -> ethabi::Token {
779 ethabi::Token::Bytes(ethabi::Bytes::from(s))
780}
781
782#[allow(dead_code)]
783pub fn uint_array_token(v: &[u64]) -> ethabi::Token {
784 ethabi::Token::FixedArray(v.iter().map(|n| uint_token(*n)).collect())
785}
786
787#[allow(dead_code)]
788pub fn int_array_token(v: &[i64]) -> ethabi::Token {
789 ethabi::Token::FixedArray(v.iter().map(|n| int_token(*n)).collect())
790}
791
792#[allow(dead_code)]
793pub fn address_array_token(v: &[&str]) -> ethabi::Token {
794 ethabi::Token::FixedArray(v.iter().map(|s| address_token(s)).collect())
795}
796
797#[allow(dead_code)]
798pub fn tuple_token(tokens: &[ethabi::Token]) -> ethabi::Token {
799 ethabi::Token::Tuple(tokens.to_owned())
800}
801
802#[allow(dead_code)]
803pub fn to_2s_complement(val: i64) -> U256 {
804 if val >= 0 {
812 U256::from(val)
813 } else {
814 let positive_val = -val;
815 get_2s_complement_for_negative(U256::from(positive_val))
816 }
817}
818
819#[allow(dead_code)]
822pub fn get_2s_complement_for_negative(assume_negative: U256) -> U256 {
823 assume_negative.overflowing_neg().0
824}
825
826#[allow(dead_code)]
827pub struct NumericAbiTokenBounds {
828 pub size: u64,
829 pub u_min: ethabi::Token,
830 pub i_min: ethabi::Token,
831 pub u_max: ethabi::Token,
832 pub i_max: ethabi::Token,
833}
834
835impl NumericAbiTokenBounds {
836 #[allow(dead_code)]
837 pub fn get_all() -> [NumericAbiTokenBounds; 6] {
838 let zero = uint_token(0);
839 let u64_max = ethabi::Token::Uint(U256::from(2).pow(U256::from(64)) - 1);
840 let i64_min = ethabi::Token::Int(get_2s_complement_for_negative(
841 U256::from(2).pow(U256::from(63)),
842 ));
843
844 let u128_max = ethabi::Token::Uint(U256::from(2).pow(U256::from(128)) - 1);
845 let i128_max = ethabi::Token::Int(U256::from(2).pow(U256::from(127)) - 1);
846 let i128_min = ethabi::Token::Int(get_2s_complement_for_negative(
847 U256::from(2).pow(U256::from(127)),
848 ));
849
850 let u256_max = ethabi::Token::Uint(U256::MAX);
851 let i256_max = ethabi::Token::Int(U256::from(2).pow(U256::from(255)) - 1);
852 let i256_min = ethabi::Token::Int(get_2s_complement_for_negative(
853 U256::from(2).pow(U256::from(255)),
854 ));
855
856 [
857 NumericAbiTokenBounds {
858 size: 8,
859 u_min: zero.clone(),
860 i_min: int_token(-128),
861 u_max: uint_token(255),
862 i_max: int_token(127),
863 },
864 NumericAbiTokenBounds {
865 size: 16,
866 u_min: zero.clone(),
867 i_min: int_token(-32768),
868 u_max: uint_token(65535),
869 i_max: int_token(32767),
870 },
871 NumericAbiTokenBounds {
872 size: 32,
873 u_min: zero.clone(),
874 i_min: int_token(-2147483648),
875 u_max: uint_token(4294967295),
876 i_max: int_token(2147483647),
877 },
878 NumericAbiTokenBounds {
879 size: 64,
880 u_min: zero.clone(),
881 i_min: i64_min,
882 u_max: u64_max,
883 i_max: int_token(9223372036854775807),
884 },
885 NumericAbiTokenBounds {
886 size: 128,
887 u_min: zero.clone(),
888 i_min: i128_min,
889 u_max: u128_max,
890 i_max: i128_max,
891 },
892 NumericAbiTokenBounds {
893 size: 256,
894 u_min: zero,
895 i_min: i256_min,
896 u_max: u256_max,
897 i_max: i256_max,
898 },
899 ]
900 }
901}