l1x_sdk/
lib.rs

1use borsh::BorshSerialize;
2pub use l1x_sdk_macros::contract;
3pub use l1x_sys as sys;
4use std::panic as std_panic;
5use types::{Address, Balance, BlockHash, BlockNumber, Gas, TimeStamp};
6
7pub mod contract_interaction;
8pub mod store;
9pub mod types;
10use contract_interaction::ContractCall;
11pub mod utils;
12pub(crate) use crate::utils::*;
13
14const EVICTED_REGISTER: u64 = std::u64::MAX - 1;
15const ATOMIC_OP_REGISTER: u64 = std::u64::MAX - 2;
16
17#[derive(Debug)]
18pub enum TransferError {
19    TransferFailed,
20    InsufficientFunds,
21}
22
23macro_rules! try_method_into_register {
24    ( $method:ident ) => {{
25        unsafe { l1x_sys::$method(ATOMIC_OP_REGISTER) };
26        read_register(ATOMIC_OP_REGISTER)
27    }};
28}
29
30macro_rules! method_into_register {
31    ( $method:ident ) => {{
32        expect_register(try_method_into_register!($method))
33    }};
34}
35
36/// Returns the size of the register. If register is not used returns `None`.
37fn register_len(register_id: u64) -> Option<u64> {
38    let len = unsafe { l1x_sys::register_len(register_id) };
39    if len == std::u64::MAX {
40        None
41    } else {
42        Some(len)
43    }
44}
45
46/// Reads the content of the `register_id`. If register is not used returns `None`.
47fn read_register(register_id: u64) -> Option<Vec<u8>> {
48    let len: usize = register_len(register_id)?
49        .try_into()
50        .unwrap_or_else(|_| abort());
51
52    let mut buffer = Vec::with_capacity(len);
53
54    unsafe {
55        l1x_sys::read_register(register_id, buffer.as_mut_ptr() as u64);
56
57        buffer.set_len(len);
58    }
59    Some(buffer)
60}
61
62fn expect_register<T>(option: Option<T>) -> T {
63    option.unwrap_or_else(|| abort())
64}
65
66/// Implements panic hook that converts `PanicInfo` into a string and provides it through the
67/// blockchain interface.
68fn panic_hook_impl(info: &std_panic::PanicInfo) {
69    panic(&info.to_string());
70}
71
72/// Setups panic hook to expose error info to the blockchain.
73pub fn setup_panic_hook() {
74    std_panic::set_hook(Box::new(panic_hook_impl));
75}
76
77/// Aborts the current contract execution without a custom message.
78/// To include a message, use [`crate::panic`].
79pub fn abort() -> ! {
80    #[cfg(test)]
81    std::panic!("Mocked panic function called!");
82    #[cfg(not(test))]
83    unsafe {
84        l1x_sys::panic()
85    }
86}
87
88/// Terminates the execution of the program with the message.
89pub fn panic(message: &str) -> ! {
90    msg(message);
91
92    #[cfg(test)]
93    std::panic!("Mocked panic function called!");
94    #[cfg(not(test))]
95    unsafe {
96        l1x_sys::panic_msg(message.as_ptr() as _, message.len() as _)
97    }
98}
99
100/// The input to the contract call serialized as bytes. If input is not provided returns `None`.
101pub fn input() -> Option<Vec<u8>> {
102    #[cfg(test)]
103    {
104        return tests::input();
105    }
106    #[cfg(not(test))]
107    try_method_into_register!(input)
108}
109
110/// Writes `data` to 'output' register
111pub fn output(data: &[u8]) {
112    #[cfg(test)]
113    {
114        return tests::output(data);
115    }
116    #[cfg(not(test))]
117    unsafe {
118        sys::output(data.as_ptr() as _, data.len() as _)
119    }
120}
121
122pub fn msg(message: &str) {
123    #[cfg(test)]
124    {
125        return tests::msg(message);
126    }
127    #[cfg(not(test))]
128    {
129        #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
130        eprintln!("{}", message);
131
132        unsafe { l1x_sys::msg(message.as_ptr() as _, message.len() as _) }
133    }
134}
135
136/// Writes key-value into storage.
137///
138/// If the the storage did not have this key present, `false` is returned.
139///
140/// If the map did have this key present, the value is updated, and `true` is returned.
141pub fn storage_write(key: &[u8], value: &[u8]) -> bool {
142    #[cfg(test)]
143    {
144        return tests::storage_write(key, value);
145    }
146    #[cfg(not(test))]
147    match unsafe {
148        sys::storage_write(
149            key.as_ptr() as _,
150            key.len() as _,
151            value.as_ptr() as _,
152            value.len() as _,
153            EVICTED_REGISTER,
154        )
155    } {
156        0 => false,
157        1 => true,
158        _ => abort(),
159    }
160}
161
162/// Removes the value stored under the given key.
163///
164/// If key-value existed returns `true`, otherwise `false`.
165pub fn storage_remove(key: &[u8]) -> bool {
166    #[cfg(test)]
167    {
168        return tests::storage_remove(key);
169    }
170
171    #[cfg(not(test))]
172    match unsafe { sys::storage_remove(key.as_ptr() as _, key.len() as _, EVICTED_REGISTER) } {
173        0 => false,
174        1 => true,
175        _ => abort(),
176    }
177}
178
179/// Reads the value stored under the given key.
180///
181/// If the storage doesn't have the key present, returns `None`
182pub fn storage_read(key: &[u8]) -> Option<Vec<u8>> {
183    #[cfg(test)]
184    {
185        return tests::storage_read(key);
186    }
187
188    #[cfg(not(test))]
189    match unsafe { sys::storage_read(key.as_ptr() as _, key.len() as _, ATOMIC_OP_REGISTER) } {
190        0 => None,
191        1 => Some(expect_register(read_register(ATOMIC_OP_REGISTER))),
192        _ => abort(),
193    }
194}
195
196/// Returns `true` if the contract has write permissions and `false` if it doesn't.
197pub fn storage_write_perm() -> bool {
198    match unsafe { sys::storage_write_perm() } {
199        0 => false,
200        1 => true,
201        _ => abort(),
202    }
203}
204
205/// Returns the address of the account that owns the current contract.
206pub fn contract_owner_address() -> Address {
207    #[cfg(test)]
208    {
209        return tests::contract_owner_address();
210    }
211    #[cfg(not(test))]
212    method_into_register!(contract_owner_address)
213        .try_into()
214        .unwrap_or_else(|_| abort())
215}
216
217/// Returns the address of the account or the contract that called the current contract.
218pub fn caller_address() -> Address {
219    #[cfg(test)]
220    {
221        return tests::caller_address();
222    }
223    #[cfg(not(test))]
224    method_into_register!(caller_address)
225        .try_into()
226        .unwrap_or_else(|_| abort())
227}
228
229/// Returns the address of the current contract's instance.
230pub fn contract_instance_address() -> Address {
231    #[cfg(test)]
232    {
233        return tests::contract_instance_address();
234    }
235    #[cfg(not(test))]
236    method_into_register!(contract_instance_address)
237        .try_into()
238        .unwrap_or_else(|_| abort())
239}
240
241/// Returns the address of the account that owns the given contract instance
242///
243/// # Panics
244///
245/// If the contract instance is not found by the given address
246pub fn contract_owner_address_of(instance_address: Address) -> Address {
247    let addr = instance_address.as_bytes();
248    unsafe {
249        l1x_sys::contract_owner_address_of(addr.as_ptr() as _, addr.len() as _, ATOMIC_OP_REGISTER);
250    }
251    let maybe_addr = expect_register(read_register(ATOMIC_OP_REGISTER));
252    Address::try_from(maybe_addr).expect("VM returned an incorrect address")
253}
254
255/// Returns the address of the account that owns the given contract code
256///
257/// # Panics
258///
259/// If the contract code is not found by the given address
260pub fn contract_code_owner_address_of(code_address: Address) -> Address {
261    let addr = code_address.as_bytes();
262    unsafe {
263        l1x_sys::contract_code_owner_address_of(
264            addr.as_ptr() as _,
265            addr.len() as _,
266            ATOMIC_OP_REGISTER,
267        );
268    }
269    let maybe_addr = expect_register(read_register(ATOMIC_OP_REGISTER));
270    Address::try_from(maybe_addr).expect("VM returned an incorrect address")
271}
272
273/// Returns the address of the contract code that is used for the given contract instance
274///
275/// # Panics
276///
277/// If the contract instance is not found by the given address
278pub fn contract_code_address_of(instance_address: Address) -> Address {
279    let addr = instance_address.as_bytes();
280    unsafe {
281        l1x_sys::contract_code_address_of(addr.as_ptr() as _, addr.len() as _, ATOMIC_OP_REGISTER);
282    }
283    let maybe_addr = expect_register(read_register(ATOMIC_OP_REGISTER));
284    Address::try_from(maybe_addr).expect("VM returned an incorrect address")
285}
286
287/// Returns `Balance` of the given `Address`
288///
289/// If `Address` not found, returns `0`
290pub fn address_balance(address: &Address) -> Balance {
291    let address_vec = address.to_vec();
292    unsafe {
293        l1x_sys::address_balance(
294            address_vec.as_ptr() as _,
295            address_vec.len() as _,
296            ATOMIC_OP_REGISTER,
297        )
298    };
299    let bytes = expect_register(read_register(ATOMIC_OP_REGISTER));
300
301    u128::from_le_bytes(bytes.try_into().unwrap_or_else(|_| abort()))
302}
303
304/// Transfers `amount` of L1X tokens from [`contract_instance_address`] to the specified address
305///
306/// # Panics
307///
308/// Panics if transfer failed
309pub fn transfer_to(to: &Address, amount: Balance) {
310    let to_address_vec = to.to_vec();
311    let amount = amount.to_le_bytes();
312    match unsafe {
313        l1x_sys::transfer_to(
314            to_address_vec.as_ptr() as _,
315            to_address_vec.len() as _,
316            amount.as_ptr() as _,
317            amount.len() as _,
318        )
319    } {
320        1 => (),
321        0 => crate::panic("Transfer tokens from the contract balance failed"),
322        _ => abort(),
323    };
324}
325
326/// Transfers `amount` of L1X tokens from [`caller_address`] to [`contract_instance_address`]
327///
328/// # Panics
329///
330/// Panics if transfer failed
331pub fn transfer_from_caller(amount: Balance) {
332    let amount = amount.to_le_bytes();
333    match unsafe { l1x_sys::transfer_from_caller(amount.as_ptr() as _, amount.len() as _) } {
334        1 => (),
335        0 => crate::panic("Transfer tokens from the caller balance failed"),
336        _ => abort(),
337    }
338}
339
340/// Returns the hash of the current block
341pub fn block_hash() -> BlockHash {
342    let mut buf = BlockHash::default();
343
344    unsafe { l1x_sys::block_hash(buf.as_mut_ptr() as _, buf.len() as _) };
345
346    buf
347}
348
349/// Returns the number of the current block
350pub fn block_number() -> BlockNumber {
351    let mut buf = [0u8; std::mem::size_of::<BlockNumber>()];
352
353    unsafe { l1x_sys::block_number(buf.as_mut_ptr() as _, buf.len() as _) };
354
355    BlockNumber::from_le_bytes(buf)
356}
357
358/// Returns the timestamp of the current block
359pub fn block_timestamp() -> TimeStamp {
360    let mut buf = [0u8; std::mem::size_of::<TimeStamp>()];
361
362    unsafe { l1x_sys::block_timestamp(buf.as_mut_ptr() as _, buf.len() as _) };
363
364    TimeStamp::from_le_bytes(buf)
365}
366
367/// Returns the total amount of `Gas` that is allowed the contract to burn out
368pub fn gas_limit() -> Gas {
369    unsafe { l1x_sys::gas_limit() }
370}
371
372/// Returns the amount of available `Gas`
373pub fn gas_left() -> Gas {
374    unsafe { l1x_sys::gas_left() }
375}
376
377/// Returns the deposit
378pub fn deposit() -> Balance {
379    let mut buf = [0u8; std::mem::size_of::<TimeStamp>()];
380
381    unsafe { l1x_sys::deposit(buf.as_mut_ptr() as _, buf.len() as _) };
382
383    Balance::from_le_bytes(buf)
384}
385
386/// Returns `Balance` of the current contract's instance.
387pub fn contract_instance_balance() -> Balance {
388    address_balance(&contract_instance_address())
389}
390
391/// Calls another contract
392///
393/// # Panics
394///
395/// - If deserialization of `call` failed
396/// - If `call.read_only` is `false` but `call_contract` is called from read-only context
397/// - If there is not enough `Gas` to satisfy `gas_limit`
398pub fn call_contract(call: &ContractCall) -> Result<Vec<u8>, String> {
399    let call = call
400        .try_to_vec()
401        .expect("Can't serialize the function arguments");
402    match unsafe { sys::call_contract2(call.as_ptr() as _, call.len() as _, ATOMIC_OP_REGISTER) } {
403        0 => Err(
404            String::from_utf8_lossy(&expect_register(read_register(ATOMIC_OP_REGISTER)))
405                .to_string(),
406        ),
407        1 => Ok(expect_register(read_register(ATOMIC_OP_REGISTER))),
408        _ => abort(),
409    }
410}
411
412/// Emits the event. This `event` is stored on chain.
413pub fn emit_event_experimental<T>(event: T)
414where
415    T: BorshSerialize,
416{
417    let event_data = event.try_to_vec().expect("Can't serialize the event");
418    match unsafe { sys::emit_event_experimental(event_data.as_ptr() as _, event_data.len() as _) } {
419        0 => abort(),
420        _ => (),
421    }
422}
423
424#[cfg(test)]
425mod tests {
426
427    use crate::types::Address;
428    use std::cell::RefCell;
429    use std::collections::HashMap;
430
431    thread_local! {
432        static MOCK_DATA: RefCell<MockData> = RefCell::new(MockData::new());
433    }
434
435    const CONTRACT_OWNER_ADDRESS: &[u8; 20] = b"mock_owner_address11";
436    const CONTRACT_INSTANCE_ADDRESS: &[u8; 20] = b"mock_instance_addres";
437    const CALLER_ADDRESS: &[u8; 20] = b"mock_caller_address1";
438
439    pub struct MockData {
440        storage: HashMap<Vec<u8>, Vec<u8>>,
441        input: Option<Vec<u8>>,
442        output: Vec<u8>,
443        messages: Vec<String>,
444        contract_owner_address: Address,
445        caller_address: Address,
446        contract_instance_address: Address,
447    }
448
449    impl MockData {
450        pub fn new() -> Self {
451            Self {
452                storage: HashMap::new(),
453                input: Some(Vec::new()),
454                output: Vec::new(),
455                messages: Vec::new(),
456                contract_owner_address: Address::test_create_address(
457                    &CONTRACT_OWNER_ADDRESS.to_vec(),
458                ),
459                caller_address: Address::test_create_address(&CALLER_ADDRESS.to_vec()),
460                contract_instance_address: Address::test_create_address(
461                    &CONTRACT_INSTANCE_ADDRESS.to_vec(),
462                ),
463            }
464        }
465    }
466
467    pub fn storage_write(key: &[u8], value: &[u8]) -> bool {
468        MOCK_DATA.with(|data| {
469            let mut mock_data = data.borrow_mut();
470            // Check if the key is already in the storage
471            let is_new_insertion = !mock_data.storage.contains_key(key);
472            mock_data.storage.insert(key.to_vec(), value.to_vec());
473            is_new_insertion
474        })
475    }
476
477    pub fn storage_read(key: &[u8]) -> Option<Vec<u8>> {
478        MOCK_DATA.with(|data| data.borrow().storage.get(key).cloned())
479    }
480
481    pub fn storage_remove(key: &[u8]) -> bool {
482        MOCK_DATA.with(|data| data.borrow_mut().storage.remove(key).is_some())
483    }
484
485    pub fn contract_owner_address() -> Address {
486        MOCK_DATA.with(|data| data.borrow().contract_owner_address.clone())
487    }
488
489    pub fn caller_address() -> Address {
490        MOCK_DATA.with(|data| data.borrow().caller_address.clone())
491    }
492
493    pub fn contract_instance_address() -> Address {
494        MOCK_DATA.with(|data| data.borrow().contract_instance_address.clone())
495    }
496
497    pub fn remove_from_mock_storage(key: &[u8]) -> bool {
498        MOCK_DATA.with(|data| data.borrow_mut().storage.remove(key).is_some())
499    }
500
501    pub fn input() -> Option<Vec<u8>> {
502        MOCK_DATA.with(|data| data.borrow().input.clone())
503    }
504
505    pub fn output(data: &[u8]) {
506        MOCK_DATA.with(|data_refcell| {
507            let mut data_inside = data_refcell.borrow_mut();
508            data_inside.output = data.to_vec();
509        })
510    }
511
512    pub fn msg(message: &str) {
513        MOCK_DATA.with(|data| data.borrow_mut().messages.push(message.to_owned()))
514    }
515
516    pub fn set_mock_input(data: Vec<u8>) {
517        MOCK_DATA.with(|data_refcell| {
518            let mut data_inside = data_refcell.borrow_mut();
519            data_inside.input = Some(data);
520        });
521    }
522
523    pub fn get_mock_output() -> Vec<u8> {
524        MOCK_DATA.with(|data| data.borrow().output.clone())
525    }
526
527    pub fn get_mock_msgs() -> Vec<String> {
528        MOCK_DATA.with(|data| data.borrow().messages.clone())
529    }
530
531    pub fn clear_mock_io() {
532        MOCK_DATA.with(|data| {
533            let mut data = data.borrow_mut();
534            data.input = None;
535            data.output = Vec::new();
536            data.messages = Vec::new();
537        })
538    }
539
540    pub fn set_mock_contract_owner_address(owner_address: Vec<u8>) {
541        MOCK_DATA.with(|data| {
542            data.borrow_mut().contract_owner_address = Address::test_create_address(&owner_address)
543        })
544    }
545
546    pub fn set_mock_caller_address(caller_address: Vec<u8>) {
547        MOCK_DATA.with(|data| {
548            data.borrow_mut().caller_address = Address::test_create_address(&caller_address)
549        })
550    }
551
552    pub fn set_mock_contract_instance_address(contract_instance_address: Vec<u8>) {
553        MOCK_DATA.with(|data| {
554            data.borrow_mut().contract_instance_address =
555                Address::test_create_address(&contract_instance_address)
556        })
557    }
558
559    ////////////////////////////////////////////// TESTS ////////////////////////////////////////////////////////////
560    #[test]
561    fn test_storage() {
562        // Prepare key-value
563        let key = b"key";
564        let value = b"value";
565
566        // Write to storage
567        assert!(storage_write(key, value));
568
569        // Read from storage
570        let stored_value = storage_read(key).unwrap();
571        assert_eq!(stored_value, value);
572
573        // Remove from storage
574        assert!(storage_remove(key));
575
576        // Try to read removed key
577        assert!(storage_read(key).is_none());
578    }
579
580    #[test]
581    fn test_msg() {
582        let message = "Test message";
583        msg(message);
584
585        let mock_messages = get_mock_msgs();
586        assert_eq!(mock_messages.len(), 1);
587        assert_eq!(mock_messages[0], message);
588    }
589
590    #[test]
591    fn test_input_output() {
592        let data = vec![1, 2, 3, 4];
593
594        set_mock_input(data.clone());
595
596        // Check input
597        let input_data = input().unwrap();
598        assert_eq!(input_data, data);
599
600        // Output
601        output(&data);
602
603        // Check output
604        let output_data = get_mock_output();
605        assert_eq!(output_data, data);
606
607        // Clear
608        clear_mock_io();
609
610        // Check input and output are cleared
611        assert!(input().is_none());
612        assert!(get_mock_output().is_empty());
613    }
614
615    #[test]
616    fn test_storage_write_and_read() {
617        let key = vec![1, 2, 3];
618        let value = vec![4, 5, 6];
619
620        // Write to storage
621        storage_write(&key, &value);
622
623        // Read from storage and check value
624        let stored_value = storage_read(&key).unwrap();
625        assert_eq!(stored_value, value);
626    }
627
628    #[test]
629    fn test_remove_from_mock_storage() {
630        let key = vec![1, 2, 3];
631        let value = vec![4, 5, 6];
632
633        // Write to storage and then remove
634        storage_write(&key, &value);
635        remove_from_mock_storage(&key);
636
637        // Check value is removed
638        let stored_value = storage_read(&key);
639        assert!(stored_value.is_none());
640    }
641
642    #[test]
643    fn test_contract_owner_address_and_caller_address() {
644        let mock_owner_address = b"current_address12345".to_vec();
645        let mock_caller_address = b"caller_address123456".to_vec();
646        let mock_instance_address = b"instance_address3456".to_vec();
647
648        // Set mock data
649        set_mock_contract_owner_address(mock_owner_address.clone());
650        set_mock_caller_address(mock_caller_address.clone());
651        set_mock_contract_instance_address(mock_instance_address.clone());
652
653        // Test contract_owner_address
654        assert_eq!(
655            contract_owner_address(),
656            Address::test_create_address(&mock_owner_address)
657        );
658
659        // Test caller_address
660        assert_eq!(
661            caller_address(),
662            Address::test_create_address(&mock_caller_address)
663        );
664
665        assert_eq!(
666            contract_instance_address(),
667            Address::test_create_address(&mock_instance_address)
668        );
669    }
670
671    #[test]
672    fn test_input_and_output() {
673        let data = vec![1, 2, 3];
674
675        // Set mock input and verify it
676        set_mock_input(data.clone());
677        assert_eq!(input().unwrap(), data);
678
679        // Write to output
680        output(&data);
681
682        // Verify output
683        assert_eq!(get_mock_output(), data);
684    }
685
686    #[test]
687    fn test_clear_mock_io() {
688        // Set some mock input/output data and a message
689        set_mock_input(vec![1, 2, 3]);
690        output(&vec![4, 5, 6]);
691        msg("Hello, world!");
692
693        // Clear the mock I/O data
694        clear_mock_io();
695
696        // Verify everything was cleared
697        assert!(input().is_none());
698        assert_eq!(get_mock_output(), vec![] as Vec<u8>);
699        assert_eq!(get_mock_msgs(), Vec::<String>::new());
700    }
701}