massa_sc_runtime/
abi_impl.rs

1///! *abi_impl.rs* contains all the implementation (and some tools as
2///! abi_bail!) of the massa abi.
3///!
4///! The ABIs are the imported function / object declared in the webassembly
5///! module. You can look at the other side of the mirror in `massa.ts` and the
6///! rust side in `execution_impl.rs`.
7///!
8///! ```
9use crate::env::{
10    get_remaining_points_for_env, sub_remaining_gas, sub_remaining_gas_with_mult, Env,
11};
12use crate::settings;
13use crate::types::Response;
14use as_ffi_bindings::{Read as ASRead, StringPtr, Write as ASWrite};
15use wasmer::Memory;
16
17pub type ABIResult<T, E = wasmer::RuntimeError> = core::result::Result<T, E>;
18macro_rules! abi_bail {
19    ($err:expr) => {
20        return Err(wasmer::RuntimeError::new($err.to_string()))
21    };
22}
23macro_rules! get_memory {
24    ($env:ident) => {
25        match $env.wasm_env.memory.get_ref() {
26            Some(mem) => mem,
27            _ => abi_bail!("uninitialized memory"),
28        }
29    };
30}
31pub(crate) use abi_bail;
32pub(crate) use get_memory;
33
34/// `Call` ABI called by the webassembly VM
35///
36/// Call an exported function in a WASM module at a given address
37///
38/// It take in argument the environment defined in env.rs
39/// this environment is automatically filled by the wasmer library
40/// And two pointers of string. (look at the readme in the wasm folder)
41fn call_module(
42    env: &Env,
43    address: &str,
44    function: &str,
45    param: &str,
46    raw_coins: i64,
47) -> ABIResult<Response> {
48    let raw_coins: u64 = match raw_coins.try_into() {
49        Ok(v) => v,
50        Err(_) => abi_bail!("negative amount of coins in Call"),
51    };
52    let module = &match env.interface.init_call(address, raw_coins) {
53        Ok(module) => module,
54        Err(err) => abi_bail!(err),
55    };
56    match crate::execution_impl::exec(
57        get_remaining_points_for_env(env)?,
58        None,
59        module,
60        function,
61        param,
62        &*env.interface,
63    ) {
64        Ok(resp) => match env.interface.finish_call() {
65            Ok(_) => Ok(resp),
66            Err(err) => abi_bail!(err),
67        },
68        Err(err) => abi_bail!(err),
69    }
70}
71
72/// Get the coins that have been made available for a specific purpose for the current call.
73pub(crate) fn assembly_script_get_call_coins(env: &Env) -> ABIResult<i64> {
74    sub_remaining_gas(env, settings::metering_get_call_coins())?;
75    match env.interface.get_call_coins() {
76        Ok(res) => Ok(res as i64),
77        Err(err) => abi_bail!(err),
78    }
79}
80
81/// Transfer an amount from the address on the current call stack to a target address.
82pub(crate) fn assembly_script_transfer_coins(
83    env: &Env,
84    to_address: i32,
85    raw_amount: i64,
86) -> ABIResult<()> {
87    sub_remaining_gas(env, settings::metering_transfer())?;
88    if raw_amount.is_negative() {
89        abi_bail!("Negative raw amount.");
90    }
91    let memory = get_memory!(env);
92    let to_address = &get_string(memory, to_address)?;
93    match env.interface.transfer_coins(to_address, raw_amount as u64) {
94        Ok(res) => Ok(res),
95        Err(err) => abi_bail!(err),
96    }
97}
98
99/// Transfer an amount from the specified address to a target address.
100pub(crate) fn assembly_script_transfer_coins_for(
101    env: &Env,
102    from_address: i32,
103    to_address: i32,
104    raw_amount: i64,
105) -> ABIResult<()> {
106    sub_remaining_gas(env, settings::metering_transfer())?;
107    if raw_amount.is_negative() {
108        abi_bail!("Negative raw amount.");
109    }
110    let memory = get_memory!(env);
111    let from_address = &get_string(memory, from_address)?;
112    let to_address = &get_string(memory, to_address)?;
113    match env
114        .interface
115        .transfer_coins_for(from_address, to_address, raw_amount as u64)
116    {
117        Ok(res) => Ok(res),
118        Err(err) => abi_bail!(err),
119    }
120}
121
122pub(crate) fn assembly_script_get_balance(env: &Env) -> ABIResult<i64> {
123    sub_remaining_gas(env, settings::metering_get_balance())?;
124    match env.interface.get_balance() {
125        Ok(res) => Ok(res as i64),
126        Err(err) => abi_bail!(err),
127    }
128}
129
130pub(crate) fn assembly_script_get_balance_for(env: &Env, address: i32) -> ABIResult<i64> {
131    sub_remaining_gas(env, settings::metering_get_balance())?;
132    let memory = get_memory!(env);
133    let address = &get_string(memory, address)?;
134    match env.interface.get_balance_for(address) {
135        Ok(res) => Ok(res as i64),
136        Err(err) => abi_bail!(err),
137    }
138}
139
140fn create_sc(env: &Env, bytecode: &[u8]) -> ABIResult<String> {
141    match env.interface.create_module(bytecode) {
142        Ok(address) => Ok(address),
143        Err(err) => abi_bail!(err),
144    }
145}
146
147/// Raw call that have the right type signature to be able to be call a module
148/// directly form AssemblyScript:
149#[doc = include_str!("../wasm/README.md")]
150pub(crate) fn assembly_script_call_module(
151    env: &Env,
152    address: i32,
153    function: i32,
154    param: i32,
155    call_coins: i64,
156) -> ABIResult<i32> {
157    sub_remaining_gas(env, settings::metering_call())?;
158    let memory = get_memory!(env);
159    let address = &get_string(memory, address)?;
160    let function = &get_string(memory, function)?;
161    let param = &get_string(memory, param)?;
162    let response = call_module(env, address, function, param, call_coins)?;
163    match StringPtr::alloc(&response.ret, &env.wasm_env) {
164        Ok(ret) => Ok(ret.offset() as i32),
165        _ => abi_bail!(format!(
166            "Cannot allocate response in call {}::{}",
167            address, function
168        )),
169    }
170}
171
172pub(crate) fn assembly_script_get_remaining_gas(env: &Env) -> ABIResult<i64> {
173    sub_remaining_gas(env, settings::metering_remaining_gas())?;
174    Ok(get_remaining_points_for_env(env)? as i64)
175}
176
177/// Create an instance of VM from a module with a
178/// given intefrace, an operation number limit and a webassembly module
179///
180/// An utility print function to write on stdout directly from AssemblyScript:
181pub(crate) fn assembly_script_print(env: &Env, arg: i32) -> ABIResult<()> {
182    sub_remaining_gas(env, settings::metering_print())?;
183    let memory = get_memory!(env);
184    if let Err(err) = env.interface.print(&get_string(memory, arg)?) {
185        abi_bail!(err);
186    }
187    Ok(())
188}
189
190/// Read a bytecode string, representing the webassembly module binary encoded
191/// with in base64.
192pub(crate) fn assembly_script_create_sc(env: &Env, bytecode: i32) -> ABIResult<i32> {
193    let memory = get_memory!(env);
194    // Base64 to Binary
195    let bytecode = match base64::decode(read_string_and_sub_gas(
196        env,
197        memory,
198        bytecode,
199        settings::metering_create_sc_mult(),
200    )?) {
201        Ok(bytecode) => bytecode,
202        Err(err) => abi_bail!(err),
203    };
204    let address = match create_sc(env, &bytecode) {
205        Ok(address) => address,
206        Err(err) => abi_bail!(err),
207    };
208    match StringPtr::alloc(&address, &env.wasm_env) {
209        Ok(ptr) => Ok(ptr.offset() as i32),
210        Err(err) => abi_bail!(err),
211    }
212}
213
214/// performs a hash on a string and returns the bs58check encoded hash
215pub(crate) fn assembly_script_hash(env: &Env, value: i32) -> ABIResult<i32> {
216    sub_remaining_gas(env, settings::metering_hash_const())?;
217    let memory = get_memory!(env);
218    let value = read_string_and_sub_gas(env, memory, value, settings::metering_hash_per_byte())?;
219    match env.interface.hash(value.as_bytes()) {
220        Ok(h) => Ok(pointer_from_string(env, &h)?.offset() as i32),
221        Err(err) => abi_bail!(err),
222    }
223}
224
225/// sets a key-indexed data entry in the datastore, overwriting existing values if any
226pub(crate) fn assembly_script_set_data(env: &Env, key: i32, value: i32) -> ABIResult<()> {
227    sub_remaining_gas(env, settings::metering_set_data_const())?;
228    let memory = get_memory!(env);
229    let key = read_string_and_sub_gas(env, memory, key, settings::metering_set_data_key_mult())?;
230    let value =
231        read_string_and_sub_gas(env, memory, value, settings::metering_set_data_value_mult())?;
232    if let Err(err) = env.interface.raw_set_data(&key, value.as_bytes()) {
233        abi_bail!(err)
234    }
235    Ok(())
236}
237
238/// gets a key-indexed data entry in the datastore, failing if non-existant
239pub(crate) fn assembly_script_get_data(env: &Env, key: i32) -> ABIResult<i32> {
240    sub_remaining_gas(env, settings::metering_get_data_const())?;
241    let memory = get_memory!(env);
242    let key = read_string_and_sub_gas(env, memory, key, settings::metering_get_data_key_mult())?;
243    match env.interface.raw_get_data(&key) {
244        Ok(data) => {
245            sub_remaining_gas_with_mult(env, data.len(), settings::metering_get_data_value_mult())?;
246            Ok(pointer_from_utf8(env, &data)?.offset() as i32)
247        }
248        Err(err) => abi_bail!(err),
249    }
250}
251
252/// checks if a key-indexed data entry exists in the datastore
253pub(crate) fn assembly_script_has_data(env: &Env, key: i32) -> ABIResult<i32> {
254    sub_remaining_gas(env, settings::metering_has_data_const())?;
255    let memory = get_memory!(env);
256    let key = read_string_and_sub_gas(env, memory, key, settings::metering_has_data_key_mult())?;
257    match env.interface.has_data(&key) {
258        Ok(true) => Ok(1),
259        Ok(false) => Ok(0),
260        Err(err) => abi_bail!(err),
261    }
262}
263
264pub(crate) fn assembly_script_set_data_for(
265    env: &Env,
266    address: i32,
267    key: i32,
268    value: i32,
269) -> ABIResult<()> {
270    sub_remaining_gas(env, settings::metering_set_data_const())?;
271    let memory = get_memory!(env);
272    let key = read_string_and_sub_gas(env, memory, key, settings::metering_set_data_key_mult())?;
273    let value =
274        read_string_and_sub_gas(env, memory, value, settings::metering_set_data_value_mult())?;
275    let address = get_string(memory, address)?;
276    if let Err(err) = env
277        .interface
278        .raw_set_data_for(&address, &key, value.as_bytes())
279    {
280        abi_bail!(err)
281    }
282    Ok(())
283}
284
285pub(crate) fn assembly_script_get_data_for(env: &Env, address: i32, key: i32) -> ABIResult<i32> {
286    sub_remaining_gas(env, settings::metering_get_data_const())?;
287    let memory = get_memory!(env);
288    let address = get_string(memory, address)?;
289    let key = read_string_and_sub_gas(env, memory, key, settings::metering_get_data_key_mult())?;
290    match env.interface.raw_get_data_for(&address, &key) {
291        Ok(data) => {
292            sub_remaining_gas_with_mult(env, data.len(), settings::metering_get_data_value_mult())?;
293            Ok(pointer_from_utf8(env, &data)?.offset() as i32)
294        }
295        Err(err) => abi_bail!(err),
296    }
297}
298
299pub(crate) fn assembly_script_has_data_for(env: &Env, address: i32, key: i32) -> ABIResult<i32> {
300    sub_remaining_gas(env, settings::metering_has_data_const())?;
301    let memory = get_memory!(env);
302    let address = get_string(memory, address)?;
303    let key = read_string_and_sub_gas(env, memory, key, settings::metering_has_data_key_mult())?;
304    match env.interface.has_data_for(&address, &key) {
305        Ok(true) => Ok(1),
306        Ok(false) => Ok(0),
307        Err(err) => abi_bail!(err),
308    }
309}
310
311pub(crate) fn assembly_script_get_owned_addresses_raw(env: &Env) -> ABIResult<i32> {
312    sub_remaining_gas(env, settings::metering_get_owned_addrs())?;
313    let data = match env.interface.get_owned_addresses() {
314        Ok(data) => data,
315        Err(err) => abi_bail!(err),
316    };
317    match StringPtr::alloc(&data.join(";"), &env.wasm_env) {
318        Ok(ptr) => Ok(ptr.offset() as i32),
319        Err(err) => abi_bail!(err),
320    }
321}
322
323pub(crate) fn assembly_script_get_call_stack_raw(env: &Env) -> ABIResult<i32> {
324    sub_remaining_gas(env, settings::metering_get_call_stack())?;
325    let data = match env.interface.get_call_stack() {
326        Ok(data) => data,
327        Err(err) => abi_bail!(err),
328    };
329    match StringPtr::alloc(&data.join(";"), &env.wasm_env) {
330        Ok(ptr) => Ok(ptr.offset() as i32),
331        Err(err) => abi_bail!(err),
332    }
333}
334
335pub(crate) fn assembly_script_get_owned_addresses(env: &Env) -> ABIResult<i32> {
336    sub_remaining_gas(env, settings::metering_get_owned_addrs())?;
337    match env.interface.get_owned_addresses() {
338        Ok(data) => alloc_string_array(env, &data),
339        Err(err) => abi_bail!(err),
340    }
341}
342
343pub(crate) fn assembly_script_get_call_stack(env: &Env) -> ABIResult<i32> {
344    sub_remaining_gas(env, settings::metering_get_call_stack())?;
345    match env.interface.get_call_stack() {
346        Ok(data) => alloc_string_array(env, &data),
347        Err(err) => abi_bail!(err),
348    }
349}
350
351pub(crate) fn assembly_script_generate_event(env: &Env, event: i32) -> ABIResult<()> {
352    sub_remaining_gas(env, settings::metering_generate_event())?;
353    let memory = get_memory!(env);
354    let event = get_string(memory, event)?;
355    if let Err(err) = env.interface.generate_event(event) {
356        abi_bail!(err)
357    }
358    Ok(())
359}
360
361/// verify a signature of data given a public key. Returns Ok(1) if correctly verified, otherwise Ok(0)
362pub(crate) fn assembly_script_signature_verify(
363    env: &Env,
364    data: i32,
365    signature: i32,
366    public_key: i32,
367) -> ABIResult<i32> {
368    sub_remaining_gas(env, settings::metering_signature_verify_const())?;
369    let memory = get_memory!(env);
370    let data = read_string_and_sub_gas(
371        env,
372        memory,
373        data,
374        settings::metering_signature_verify_data_mult(),
375    )?;
376    let signature = get_string(memory, signature)?;
377    let public_key = get_string(memory, public_key)?;
378    match env
379        .interface
380        .signature_verify(data.as_bytes(), &signature, &public_key)
381    {
382        Err(err) => abi_bail!(err),
383        Ok(false) => Ok(0),
384        Ok(true) => Ok(1),
385    }
386}
387
388/// converts a public key to an address
389pub(crate) fn assembly_script_address_from_public_key(
390    env: &Env,
391    public_key: i32,
392) -> ABIResult<i32> {
393    sub_remaining_gas(env, settings::metering_address_from_public_key())?;
394    let memory = get_memory!(env);
395    let public_key = get_string(memory, public_key)?;
396    match env.interface.address_from_public_key(&public_key) {
397        Err(err) => abi_bail!(err),
398        Ok(addr) => Ok(pointer_from_string(env, &addr)?.offset() as i32),
399    }
400}
401
402/// generates an unsafe random number
403pub(crate) fn assembly_script_unsafe_random(env: &Env) -> ABIResult<i64> {
404    sub_remaining_gas(env, settings::metering_unsafe_random())?;
405    match env.interface.unsafe_random() {
406        Err(err) => abi_bail!(err),
407        Ok(rnd) => Ok(rnd),
408    }
409}
410
411/// gets the current unix timestamp in milliseconds
412pub(crate) fn assembly_script_get_time(env: &Env) -> ABIResult<i64> {
413    sub_remaining_gas(env, settings::metering_get_time())?;
414    match env.interface.get_time() {
415        Err(err) => abi_bail!(err),
416        Ok(t) => Ok(t as i64),
417    }
418}
419
420/// sends an async message
421#[allow(clippy::too_many_arguments)]
422pub(crate) fn assembly_script_send_message(
423    env: &Env,
424    target_address: i32,
425    target_handler: i32,
426    validity_start_period: i64,
427    validity_start_thread: i32,
428    validity_end_period: i64,
429    validity_end_thread: i32,
430    max_gas: i64,
431    gas_price: i64,
432    raw_coins: i64,
433    data: i32,
434) -> ABIResult<()> {
435    sub_remaining_gas(env, settings::metering_send_message())?;
436    let validity_start: (u64, u8) = match (
437        validity_start_period.try_into(),
438        validity_start_thread.try_into(),
439    ) {
440        (Ok(p), Ok(t)) => (p, t),
441        (Err(_), _) => abi_bail!("negative validity start period"),
442        (_, Err(_)) => abi_bail!("invalid validity start thread"),
443    };
444    let validity_end: (u64, u8) = match (
445        validity_end_period.try_into(),
446        validity_end_thread.try_into(),
447    ) {
448        (Ok(p), Ok(t)) => (p, t),
449        (Err(_), _) => abi_bail!("negative validity end period"),
450        (_, Err(_)) => abi_bail!("invalid validity end thread"),
451    };
452    if max_gas.is_negative() {
453        abi_bail!("negative max gas");
454    }
455    if gas_price.is_negative() {
456        abi_bail!("negative gas price");
457    }
458    if raw_coins.is_negative() {
459        abi_bail!("negative coins")
460    }
461    let memory = get_memory!(env);
462    match env.interface.send_message(
463        &get_string(memory, target_address)?,
464        &get_string(memory, target_handler)?,
465        validity_start,
466        validity_end,
467        max_gas as u64,
468        gas_price as u64,
469        raw_coins as u64,
470        get_string(memory, data)?.as_bytes(),
471    ) {
472        Err(err) => abi_bail!(err),
473        Ok(_) => Ok(()),
474    }
475}
476
477/// gets the period of the current execution slot
478pub(crate) fn assembly_script_get_current_period(env: &Env) -> ABIResult<i64> {
479    sub_remaining_gas(env, settings::metering_get_current_period())?;
480    match env.interface.get_current_period() {
481        Err(err) => abi_bail!(err),
482        Ok(v) => Ok(v as i64),
483    }
484}
485
486/// gets the thread of the current execution slot
487pub(crate) fn assembly_script_get_current_thread(env: &Env) -> ABIResult<i32> {
488    sub_remaining_gas(env, settings::metering_get_current_thread())?;
489    match env.interface.get_current_thread() {
490        Err(err) => abi_bail!(err),
491        Ok(v) => Ok(v as i32),
492    }
493}
494
495/// Tooling, return a StringPtr allocated from a String
496fn pointer_from_string(env: &Env, value: &str) -> ABIResult<StringPtr> {
497    match StringPtr::alloc(&value.into(), &env.wasm_env) {
498        Ok(ptr) => Ok(*ptr),
499        Err(err) => abi_bail!(err),
500    }
501}
502
503/// Tooling, return a StringPtr allocated from bytes with utf8 parsing
504fn pointer_from_utf8(env: &Env, value: &[u8]) -> ABIResult<StringPtr> {
505    match std::str::from_utf8(value) {
506        Ok(data) => match StringPtr::alloc(&data.to_string(), &env.wasm_env) {
507            Ok(ptr) => Ok(*ptr),
508            Err(err) => abi_bail!(err),
509        },
510        Err(err) => abi_bail!(err),
511    }
512}
513
514/// Tooling that take read a String in memory and substract remaining gas
515/// with a multiplicator (String.len * mult).
516///
517/// Sub funtion of `assembly_script_set_data_for`, `assembly_script_set_data`
518/// and `assembly_script_create_sc`
519///
520/// Return the string value in the StringPtr
521fn read_string_and_sub_gas(
522    env: &Env,
523    memory: &Memory,
524    offset: i32,
525    mult: usize,
526) -> ABIResult<String> {
527    match StringPtr::new(offset as u32).read(memory) {
528        Ok(value) => {
529            sub_remaining_gas_with_mult(env, value.len(), mult)?;
530            Ok(value)
531        }
532        Err(err) => abi_bail!(err),
533    }
534}
535
536/// Tooling, return a string from a given offset
537fn get_string(memory: &Memory, ptr: i32) -> ABIResult<String> {
538    match StringPtr::new(ptr as u32).read(memory) {
539        Ok(str) => Ok(str),
540        Err(err) => abi_bail!(err),
541    }
542}
543
544/// Tooling, return a pointer offset of a serialized list in json
545fn alloc_string_array(env: &Env, vec: &[String]) -> ABIResult<i32> {
546    let addresses = match serde_json::to_string(vec) {
547        Ok(list) => list,
548        Err(err) => abi_bail!(err),
549    };
550    match StringPtr::alloc(&addresses, &env.wasm_env) {
551        Ok(ptr) => Ok(ptr.offset() as i32),
552        Err(err) => abi_bail!(err),
553    }
554}