fe_compiler_test_utils/
lib.rs

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<&ethabi::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    // Executor must be passed by value to get emitted events.
208    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                // Length of the string padded to 32 bit hex
439                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                // The bytes of the string itself, right padded to consume a multiple of 32
446                // bytes
447                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    // Function selector (e.g first 4 bytes of keccak("Error(string)")
462    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                // ethabi doesn't yet support error types so we just filter them out for now
568                // https://github.com/rust-ethereum/ethabi/issues/225
569                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    /// Create a new `Runtime` instance.
632    pub fn new() -> Runtime {
633        Runtime {
634            functions: vec![],
635            test_statements: vec![],
636            data: vec![],
637        }
638    }
639
640    /// Add the given set of functions
641    pub fn with_functions(self, fns: Vec<yul::Statement>) -> Runtime {
642        Runtime {
643            functions: fns,
644            ..self
645        }
646    }
647
648    /// Add the given set of test statements
649    pub fn with_test_statements(self, statements: Vec<yul::Statement>) -> Runtime {
650        Runtime {
651            test_statements: statements,
652            ..self
653        }
654    }
655
656    // Add the given set of data
657    pub fn with_data(self, data: Vec<yul::Data>) -> Runtime {
658        Runtime { data, ..self }
659    }
660
661    /// Generate the top level YUL object
662    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    /// Create an `ExecutionOutput` instance
682    pub fn new(exit_reason: ExitReason, data: Vec<u8>) -> ExecutionOutput {
683        ExecutionOutput { exit_reason, data }
684    }
685
686    /// Panic if the execution did not succeed.
687    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    /// Panic if the execution did not revert.
700    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    /// Panic if the output is not an encoded error reason of the given string.
713    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    // left pads to 40 characters
769    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    // Since this API takes an `i64` we can be sure that the min and max values
805    // will never be above what fits the `I256` type which has the same capacity
806    // as U256 but splits it so that one half covers numbers above 0 and the
807    // other half covers the numbers below 0.
808
809    // Conversion to Two's Complement: https://www.cs.cornell.edu/~tomf/notes/cps104/twoscomp.html
810
811    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/// To get the 2s complement value for e.g. -128 call
820/// get_2s_complement_for_negative(128)
821#[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}