casper_contract_sdk/
casper.rs

1#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
2pub mod native;
3
4use crate::{
5    abi::{CasperABI, EnumVariant},
6    prelude::{
7        ffi::c_void,
8        marker::PhantomData,
9        mem::MaybeUninit,
10        ptr::{self, NonNull},
11    },
12    reserve_vec_space,
13    serializers::borsh::{BorshDeserialize, BorshSerialize},
14    types::{Address, CallError},
15    Message, ToCallData,
16};
17
18use casper_contract_sdk_sys::casper_env_info;
19use casper_executor_wasm_common::{
20    env_info::EnvInfo,
21    error::{result_from_code, CommonResult, HOST_ERROR_SUCCESS},
22    flags::ReturnFlags,
23    keyspace::{Keyspace, KeyspaceTag},
24};
25
26/// Print a message.
27#[inline]
28pub fn print(msg: &str) {
29    unsafe { casper_contract_sdk_sys::casper_print(msg.as_ptr(), msg.len()) };
30}
31
32pub enum Alloc<F: FnOnce(usize) -> Option<ptr::NonNull<u8>>> {
33    Callback(F),
34    Static(ptr::NonNull<u8>),
35}
36
37extern "C" fn alloc_callback<F: FnOnce(usize) -> Option<ptr::NonNull<u8>>>(
38    len: usize,
39    ctx: *mut c_void,
40) -> *mut u8 {
41    let opt_closure = ctx.cast::<Option<F>>();
42    let allocated_ptr = unsafe { (*opt_closure).take().unwrap()(len) };
43    match allocated_ptr {
44        Some(ptr) => ptr.as_ptr(),
45        None => ptr::null_mut(),
46    }
47}
48
49/// Provided callback should ensure that it can provide a pointer that can store `size` bytes.
50/// Function returns last pointer after writing data, or None otherwise.
51pub fn copy_input_into<F: FnOnce(usize) -> Option<ptr::NonNull<u8>>>(
52    alloc: Option<F>,
53) -> Option<NonNull<u8>> {
54    let ret = unsafe {
55        casper_contract_sdk_sys::casper_copy_input(
56            alloc_callback::<F>,
57            &alloc as *const _ as *mut c_void,
58        )
59    };
60    NonNull::<u8>::new(ret)
61}
62
63/// Copy input data into a vector.
64pub fn copy_input() -> Vec<u8> {
65    let mut vec = Vec::new();
66    let last_ptr = copy_input_into(Some(|size| reserve_vec_space(&mut vec, size)));
67    match last_ptr {
68        Some(_last_ptr) => vec,
69        None => {
70            // TODO: size of input was 0, we could properly deal with this case by not calling alloc
71            // cb if size==0
72            Vec::new()
73        }
74    }
75}
76
77/// Provided callback should ensure that it can provide a pointer that can store `size` bytes.
78pub fn copy_input_to(dest: &mut [u8]) -> Option<&[u8]> {
79    let last_ptr = copy_input_into(Some(|size| {
80        if size > dest.len() {
81            None
82        } else {
83            // SAFETY: `dest` is guaranteed to be non-null and large enough to hold `size`
84            // bytes.
85            Some(unsafe { ptr::NonNull::new_unchecked(dest.as_mut_ptr()) })
86        }
87    }));
88
89    let end_ptr = last_ptr?;
90    let length = unsafe { end_ptr.as_ptr().offset_from(dest.as_mut_ptr()) };
91    let length: usize = length.try_into().unwrap();
92    Some(&dest[..length])
93}
94
95/// Return from the contract.
96pub fn ret(flags: ReturnFlags, data: Option<&[u8]>) {
97    let (data_ptr, data_len) = match data {
98        Some(data) => (data.as_ptr(), data.len()),
99        None => (ptr::null(), 0),
100    };
101    unsafe { casper_contract_sdk_sys::casper_return(flags.bits(), data_ptr, data_len) };
102    #[cfg(target_arch = "wasm32")]
103    unreachable!()
104}
105
106/// Read from the global state.
107pub fn read<F: FnOnce(usize) -> Option<ptr::NonNull<u8>>>(
108    key: Keyspace,
109    f: F,
110) -> Result<Option<()>, CommonResult> {
111    let (key_space, key_bytes) = match key {
112        Keyspace::State => (KeyspaceTag::State as u64, &[][..]),
113        Keyspace::Context(key_bytes) => (KeyspaceTag::Context as u64, key_bytes),
114        Keyspace::NamedKey(key_bytes) => (KeyspaceTag::NamedKey as u64, key_bytes.as_bytes()),
115        Keyspace::PaymentInfo(payload) => (KeyspaceTag::PaymentInfo as u64, payload.as_bytes()),
116    };
117
118    let mut info = casper_contract_sdk_sys::ReadInfo {
119        data: ptr::null(),
120        size: 0,
121    };
122
123    extern "C" fn alloc_cb<F: FnOnce(usize) -> Option<ptr::NonNull<u8>>>(
124        len: usize,
125        ctx: *mut c_void,
126    ) -> *mut u8 {
127        let opt_closure = ctx as *mut Option<F>;
128        let allocated_ptr = unsafe { (*opt_closure).take().unwrap()(len) };
129        match allocated_ptr {
130            Some(mut ptr) => unsafe { ptr.as_mut() },
131            None => ptr::null_mut(),
132        }
133    }
134
135    let ctx = &Some(f) as *const _ as *mut _;
136
137    let ret = unsafe {
138        casper_contract_sdk_sys::casper_read(
139            key_space,
140            key_bytes.as_ptr(),
141            key_bytes.len(),
142            &mut info as *mut casper_contract_sdk_sys::ReadInfo,
143            alloc_cb::<F>,
144            ctx,
145        )
146    };
147
148    match result_from_code(ret) {
149        Ok(()) => Ok(Some(())),
150        Err(CommonResult::NotFound) => Ok(None),
151        Err(err) => Err(err),
152    }
153}
154
155/// Write to the global state.
156pub fn write(key: Keyspace, value: &[u8]) -> Result<(), CommonResult> {
157    let (key_space, key_bytes) = match key {
158        Keyspace::State => (KeyspaceTag::State as u64, &[][..]),
159        Keyspace::Context(key_bytes) => (KeyspaceTag::Context as u64, key_bytes),
160        Keyspace::NamedKey(key_bytes) => (KeyspaceTag::NamedKey as u64, key_bytes.as_bytes()),
161        Keyspace::PaymentInfo(payload) => (KeyspaceTag::PaymentInfo as u64, payload.as_bytes()),
162    };
163    let ret = unsafe {
164        casper_contract_sdk_sys::casper_write(
165            key_space,
166            key_bytes.as_ptr(),
167            key_bytes.len(),
168            value.as_ptr(),
169            value.len(),
170        )
171    };
172    result_from_code(ret)
173}
174
175/// Remove from the global state.
176pub fn remove(key: Keyspace) -> Result<(), CommonResult> {
177    let (key_space, key_bytes) = match key {
178        Keyspace::State => (KeyspaceTag::State as u64, &[][..]),
179        Keyspace::Context(key_bytes) => (KeyspaceTag::Context as u64, key_bytes),
180        Keyspace::NamedKey(key_bytes) => (KeyspaceTag::NamedKey as u64, key_bytes.as_bytes()),
181        Keyspace::PaymentInfo(payload) => (KeyspaceTag::PaymentInfo as u64, payload.as_bytes()),
182    };
183    let ret = unsafe {
184        casper_contract_sdk_sys::casper_remove(key_space, key_bytes.as_ptr(), key_bytes.len())
185    };
186    result_from_code(ret)
187}
188
189/// Create a new contract instance.
190pub fn create(
191    code: Option<&[u8]>,
192    transferred_value: u64,
193    constructor: Option<&str>,
194    input_data: Option<&[u8]>,
195    seed: Option<&[u8; 32]>,
196) -> Result<casper_contract_sdk_sys::CreateResult, CallError> {
197    let (code_ptr, code_size): (*const u8, usize) = match code {
198        Some(code) => (code.as_ptr(), code.len()),
199        None => (ptr::null(), 0),
200    };
201
202    let mut result = MaybeUninit::uninit();
203
204    let call_error = unsafe {
205        casper_contract_sdk_sys::casper_create(
206            code_ptr,
207            code_size,
208            transferred_value,
209            constructor.map(|s| s.as_ptr()).unwrap_or(ptr::null()),
210            constructor.map(|s| s.len()).unwrap_or(0),
211            input_data.map(|s| s.as_ptr()).unwrap_or(ptr::null()),
212            input_data.map(|s| s.len()).unwrap_or(0),
213            seed.map(|s| s.as_ptr()).unwrap_or(ptr::null()),
214            seed.map(|s| s.len()).unwrap_or(0),
215            result.as_mut_ptr(),
216        )
217    };
218
219    if call_error == 0 {
220        let result = unsafe { result.assume_init() };
221        Ok(result)
222    } else {
223        Err(CallError::try_from(call_error).expect("Unexpected error code"))
224    }
225}
226
227pub(crate) fn call_into<F: FnOnce(usize) -> Option<ptr::NonNull<u8>>>(
228    address: &Address,
229    transferred_value: u64,
230    entry_point: &str,
231    input_data: &[u8],
232    alloc: Option<F>,
233) -> Result<(), CallError> {
234    let result_code = unsafe {
235        casper_contract_sdk_sys::casper_call(
236            address.as_ptr(),
237            address.len(),
238            transferred_value,
239            entry_point.as_ptr(),
240            entry_point.len(),
241            input_data.as_ptr(),
242            input_data.len(),
243            alloc_callback::<F>,
244            &alloc as *const _ as *mut _,
245        )
246    };
247    call_result_from_code(result_code)
248}
249
250fn call_result_from_code(result_code: u32) -> Result<(), CallError> {
251    if result_code == HOST_ERROR_SUCCESS {
252        Ok(())
253    } else {
254        Err(CallError::try_from(result_code).expect("Unexpected error code"))
255    }
256}
257
258/// Call a contract.
259pub fn casper_call(
260    address: &Address,
261    transferred_value: u64,
262    entry_point: &str,
263    input_data: &[u8],
264) -> (Option<Vec<u8>>, Result<(), CallError>) {
265    let mut output = None;
266    let result_code = call_into(
267        address,
268        transferred_value,
269        entry_point,
270        input_data,
271        Some(|size| {
272            let mut vec = Vec::new();
273            reserve_vec_space(&mut vec, size);
274            let result = Some(unsafe { ptr::NonNull::new_unchecked(vec.as_mut_ptr()) });
275            output = Some(vec);
276            result
277        }),
278    );
279    (output, result_code)
280}
281
282/// Upgrade the contract.
283pub fn upgrade(
284    code: &[u8],
285    entry_point: Option<&str>,
286    input_data: Option<&[u8]>,
287) -> Result<(), CallError> {
288    let code_ptr = code.as_ptr();
289    let code_size = code.len();
290    let entry_point_ptr = entry_point.map(str::as_ptr).unwrap_or(ptr::null());
291    let entry_point_size = entry_point.map(str::len).unwrap_or(0);
292    let input_ptr = input_data.map(|s| s.as_ptr()).unwrap_or(ptr::null());
293    let input_size = input_data.map(|s| s.len()).unwrap_or(0);
294
295    let result_code = unsafe {
296        casper_contract_sdk_sys::casper_upgrade(
297            code_ptr,
298            code_size,
299            entry_point_ptr,
300            entry_point_size,
301            input_ptr,
302            input_size,
303        )
304    };
305    match call_result_from_code(result_code) {
306        Ok(()) => Ok(()),
307        Err(err) => Err(err),
308    }
309}
310
311/// Read from the global state into a vector.
312pub fn read_into_vec(key: Keyspace) -> Result<Option<Vec<u8>>, CommonResult> {
313    let mut vec = Vec::new();
314    let out = read(key, |size| reserve_vec_space(&mut vec, size))?.map(|()| vec);
315    Ok(out)
316}
317
318/// Read from the global state into a vector.
319pub fn has_state() -> Result<bool, CommonResult> {
320    // TODO: Host side optimized `casper_exists` to check if given entry exists in the global state.
321    let mut vec = Vec::new();
322    let read_info = read(Keyspace::State, |size| reserve_vec_space(&mut vec, size))?;
323    match read_info {
324        Some(()) => Ok(true),
325        None => Ok(false),
326    }
327}
328
329/// Read state from the global state.
330pub fn read_state<T: Default + BorshDeserialize>() -> Result<T, CommonResult> {
331    let mut vec = Vec::new();
332    let read_info = read(Keyspace::State, |size| reserve_vec_space(&mut vec, size))?;
333    match read_info {
334        Some(()) => Ok(borsh::from_slice(&vec).unwrap()),
335        None => Ok(T::default()),
336    }
337}
338
339/// Write state to the global state.
340pub fn write_state<T: BorshSerialize>(state: &T) -> Result<(), CommonResult> {
341    let new_state = borsh::to_vec(state).unwrap();
342    write(Keyspace::State, &new_state)?;
343    Ok(())
344}
345
346#[derive(Debug)]
347pub struct CallResult<T: ToCallData> {
348    pub data: Option<Vec<u8>>,
349    pub result: Result<(), CallError>,
350    pub marker: PhantomData<T>,
351}
352
353impl<T: ToCallData> CallResult<T> {
354    pub fn into_result<'a>(self) -> Result<T::Return<'a>, CallError>
355    where
356        <T as ToCallData>::Return<'a>: BorshDeserialize,
357    {
358        match self.result {
359            Ok(()) | Err(CallError::CalleeReverted) => {
360                let data = self.data.unwrap_or_default();
361                Ok(borsh::from_slice(&data).unwrap())
362            }
363            Err(call_error) => Err(call_error),
364        }
365    }
366
367    pub fn did_revert(&self) -> bool {
368        self.result == Err(CallError::CalleeReverted)
369    }
370}
371
372/// Call a contract.
373pub fn call<T: ToCallData>(
374    contract_address: &Address,
375    transferred_value: u64,
376    call_data: T,
377) -> Result<CallResult<T>, CallError> {
378    let input_data = call_data.input_data().unwrap_or_default();
379
380    let (maybe_data, result_code) = casper_call(
381        contract_address,
382        transferred_value,
383        call_data.entry_point(),
384        &input_data,
385    );
386    match result_code {
387        Ok(()) | Err(CallError::CalleeReverted) => Ok(CallResult::<T> {
388            data: maybe_data,
389            result: result_code,
390            marker: PhantomData,
391        }),
392        Err(error) => Err(error),
393    }
394}
395
396/// Get the environment info.
397pub fn get_env_info() -> EnvInfo {
398    let ret = {
399        let mut info = MaybeUninit::<EnvInfo>::uninit();
400
401        let ret = unsafe { casper_env_info(info.as_mut_ptr().cast(), size_of::<EnvInfo>() as u32) };
402        result_from_code(ret).map(|()| {
403            // SAFETY: The size of `EnvInfo` is known and the pointer is valid.
404            unsafe { info.assume_init() }
405        })
406    };
407
408    match ret {
409        Ok(info) => info,
410        Err(err) => panic!("Failed to get environment info: {:?}", err),
411    }
412}
413
414/// Get the caller.
415#[must_use]
416pub fn get_caller() -> Entity {
417    let info = get_env_info();
418    Entity::from_parts(info.caller_kind, info.caller_addr).expect("Invalid caller kind")
419}
420
421#[must_use]
422pub fn get_callee() -> Entity {
423    let info = get_env_info();
424    Entity::from_parts(info.callee_kind, info.callee_addr).expect("Invalid callee kind")
425}
426
427/// Enum representing either an account or a contract.
428#[derive(
429    BorshSerialize, BorshDeserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord,
430)]
431pub enum Entity {
432    Account([u8; 32]),
433    Contract([u8; 32]),
434}
435
436impl Entity {
437    /// Get the tag of the entity.
438    #[must_use]
439    pub fn tag(&self) -> u32 {
440        match self {
441            Entity::Account(_) => 0,
442            Entity::Contract(_) => 1,
443        }
444    }
445
446    #[must_use]
447    pub fn from_parts(tag: u32, address: [u8; 32]) -> Option<Self> {
448        match tag {
449            0 => Some(Self::Account(address)),
450            1 => Some(Self::Contract(address)),
451            _ => None,
452        }
453    }
454
455    #[must_use]
456    pub fn address(&self) -> &Address {
457        match self {
458            Entity::Account(addr) | Entity::Contract(addr) => addr,
459        }
460    }
461}
462
463impl CasperABI for Entity {
464    fn populate_definitions(definitions: &mut crate::abi::Definitions) {
465        definitions.populate_one::<[u8; 32]>();
466    }
467
468    fn declaration() -> crate::abi::Declaration {
469        "Entity".into()
470    }
471
472    fn definition() -> crate::abi::Definition {
473        crate::abi::Definition::Enum {
474            items: vec![
475                EnumVariant {
476                    name: "Account".into(),
477                    discriminant: 0,
478                    decl: <[u8; 32] as CasperABI>::declaration(),
479                },
480                EnumVariant {
481                    name: "Contract".into(),
482                    discriminant: 1,
483                    decl: <[u8; 32] as CasperABI>::declaration(),
484                },
485            ],
486        }
487    }
488}
489
490/// Get the balance of an account or contract.
491#[must_use]
492pub fn get_balance_of(entity_kind: &Entity) -> u64 {
493    let (kind, addr) = match entity_kind {
494        Entity::Account(addr) => (0, addr),
495        Entity::Contract(addr) => (1, addr),
496    };
497    let mut output: MaybeUninit<u64> = MaybeUninit::uninit();
498    let ret = unsafe {
499        casper_contract_sdk_sys::casper_env_balance(
500            kind,
501            addr.as_ptr(),
502            addr.len(),
503            output.as_mut_ptr().cast(),
504        )
505    };
506    if ret == 1 {
507        unsafe { output.assume_init() }
508    } else {
509        0
510    }
511}
512
513/// Get the transferred token value passed to the contract.
514#[must_use]
515pub fn transferred_value() -> u64 {
516    let info = get_env_info();
517    info.transferred_value
518}
519
520/// Transfer tokens from the current contract to another account or contract.
521pub fn transfer(target_account: &Address, amount: u64) -> Result<(), CallError> {
522    let amount: *const c_void = &amount as *const _ as *const c_void;
523    let result_code = unsafe {
524        casper_contract_sdk_sys::casper_transfer(
525            target_account.as_ptr(),
526            target_account.len(),
527            amount,
528        )
529    };
530    call_result_from_code(result_code)
531}
532
533/// Get the current block time.
534#[inline]
535pub fn get_block_time() -> u64 {
536    let info = get_env_info();
537    info.block_time
538}
539
540#[doc(hidden)]
541pub fn emit_raw(topic: &str, payload: &[u8]) -> Result<(), CommonResult> {
542    let ret = unsafe {
543        casper_contract_sdk_sys::casper_emit(
544            topic.as_ptr(),
545            topic.len(),
546            payload.as_ptr(),
547            payload.len(),
548        )
549    };
550    result_from_code(ret)
551}
552
553/// Emit a message.
554pub fn emit<M>(message: M) -> Result<(), CommonResult>
555where
556    M: Message,
557{
558    let topic = M::TOPIC;
559    let payload = message.payload();
560    emit_raw(topic, &payload)
561}