1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
extern crate alloc;

use alloc::vec::Vec;
use fuel_indexer_lib::{
    graphql::MAX_FOREIGN_KEY_LIST_FIELDS,
    utils::{deserialize, serialize},
    WasmIndexerError,
};
use fuel_indexer_schema::{
    join::{JoinMetadata, RawQuery},
    FtColumn,
};
use fuel_indexer_types::{ffi::*, scalar::UID};

pub use bincode;
pub use hex::FromHex;
pub use sha2::{Digest, Sha256};
pub use std::collections::{HashMap, HashSet};

pub use crate::find::{Field, Filter, ManyFilter, OptionField, SingleFilter};

// These are instantiated with functions which return
// `Result<T, WasmIndexerError>`. `wasmer` unwraps the `Result` and uses the
// `Err` variant for early exit.
extern "C" {
    fn ff_get_object(type_id: i64, ptr: *const u8, len: *mut u8) -> *mut u8;
    fn ff_find_many(type_id: i64, ptr: *const u8, len: *mut u8) -> *mut u8;
    fn ff_log_data(ptr: *const u8, len: u32, log_level: u32);
    fn ff_put_object(type_id: i64, ptr: *const u8, len: u32);
    fn ff_put_many_to_many_record(ptr: *const u8, len: u32);
    fn ff_early_exit(err_code: u32);
}

// TODO: more to do here, hook up to 'impl log::Log for Logger'
pub struct Logger;

impl Logger {
    pub fn error(log: &str) {
        unsafe { ff_log_data(log.as_ptr(), log.len() as u32, LOG_LEVEL_ERROR) }
    }

    pub fn warn(log: &str) {
        unsafe { ff_log_data(log.as_ptr(), log.len() as u32, LOG_LEVEL_WARN) }
    }

    pub fn info(log: &str) {
        unsafe { ff_log_data(log.as_ptr(), log.len() as u32, LOG_LEVEL_INFO) }
    }

    pub fn debug(log: &str) {
        unsafe { ff_log_data(log.as_ptr(), log.len() as u32, LOG_LEVEL_DEBUG) }
    }

    pub fn trace(log: &str) {
        unsafe { ff_log_data(log.as_ptr(), log.len() as u32, LOG_LEVEL_TRACE) }
    }
}

/// Trait for a type entity.
///
/// Any entity type that will be processed through a WASM indexer is required to implement this trait.
pub trait Entity<'a>: Sized + PartialEq + Eq + std::fmt::Debug {
    /// Unique identifier for a type.
    const TYPE_ID: i64;

    /// Necessary metadata for saving an entity's list type fields.
    const JOIN_METADATA: Option<[Option<JoinMetadata<'a>>; MAX_FOREIGN_KEY_LIST_FIELDS]>;

    /// Convert database row representation into an instance of an entity.
    fn from_row(vec: Vec<FtColumn>) -> Self;

    /// Convert an instance of an entity into a row representation for use in a database.
    fn to_row(&self) -> Vec<FtColumn>;

    /// Returns an entity's internal type ID.
    fn type_id(&self) -> i64 {
        Self::TYPE_ID
    }

    /// Saves a record that contains a list of multiple elements.
    fn save_many_to_many(&self) {
        if let Some(meta) = Self::JOIN_METADATA {
            let items = meta.iter().filter_map(|x| x.clone()).collect::<Vec<_>>();
            let row = self.to_row();
            let queries = items
                .iter()
                .map(|item| RawQuery::from_metadata(item, &row))
                .filter(|query| !query.is_empty())
                .collect::<Vec<_>>();
            let bytes = serialize(&queries);
            unsafe {
                ff_put_many_to_many_record(bytes.as_ptr(), bytes.len() as u32);
            }
        }
    }

    /// Loads a record given a UID.
    fn load(id: UID) -> Option<Self> {
        Self::load_unsafe(id)
    }

    /// Loads a record through the FFI with the WASM runtime and checks for errors.
    fn load_unsafe(id: UID) -> Option<Self> {
        unsafe {
            let buff = if let Ok(bytes) = bincode::serialize(&id.to_string()) {
                bytes
            } else {
                early_exit(WasmIndexerError::SerializationError);
            };

            let mut bufflen = (buff.len() as u32).to_le_bytes();

            let ptr = ff_get_object(Self::TYPE_ID, buff.as_ptr(), bufflen.as_mut_ptr());

            if !ptr.is_null() {
                let len = u32::from_le_bytes(bufflen) as usize;
                let bytes = Vec::from_raw_parts(ptr, len, len);
                match deserialize(&bytes) {
                    Ok(vec) => Some(Self::from_row(vec)),
                    Err(_) => {
                        early_exit(WasmIndexerError::DeserializationError);
                    }
                };
            }

            None
        }
    }

    /// Finds the first entity that satisfies the given constraints.
    fn find(filter: impl Into<SingleFilter<Self>>) -> Option<Self> {
        let result = Self::find_many(filter.into());
        result.into_iter().next()
    }

    /// Finds the entities that satisfy the given constraints.
    fn find_many(filter: impl Into<ManyFilter<Self>>) -> Vec<Self> {
        unsafe {
            let filter: ManyFilter<Self> = filter.into();
            let buff = bincode::serialize(&filter.to_string())
                .expect("Failed to serialize query");
            let mut bufflen = (buff.len() as u32).to_le_bytes();

            let ptr = ff_find_many(Self::TYPE_ID, buff.as_ptr(), bufflen.as_mut_ptr());

            if !ptr.is_null() {
                let len = u32::from_le_bytes(bufflen) as usize;
                let bytes = Vec::from_raw_parts(ptr, len, len);
                let data: Vec<Vec<u8>> =
                    deserialize(&bytes).expect("Failed to deserialize data");
                data.iter()
                    .map(|x| {
                        Self::from_row(
                            deserialize(x).expect("Failed to deserialize data"),
                        )
                    })
                    .collect()
            } else {
                vec![]
            }
        }
    }

    /// Saves a record.
    fn save(&self) {
        self.save_unsafe()
    }

    /// Saves a record through the FFI with the WASM runtime and checks for errors.
    fn save_unsafe(&self) {
        unsafe {
            let buf = serialize(&self.to_row());
            ff_put_object(Self::TYPE_ID, buf.as_ptr(), buf.len() as u32);
        }

        self.save_many_to_many()
    }
}

#[no_mangle]
/// Allocation function to be called by an executor in a WASM runtime.
fn alloc_fn(size: u32) -> *const u8 {
    let vec = Vec::with_capacity(size as usize);
    let ptr = vec.as_ptr();

    core::mem::forget(vec);

    ptr
}

#[no_mangle]
/// Deallocation function to be called by an executor in a WASM runtime.
fn dealloc_fn(ptr: *mut u8, len: usize) {
    let _vec = unsafe { Vec::from_raw_parts(ptr, len, len) };
}

#[no_mangle]
/// Immediately terminate WASM execution with the specified error code.
pub fn early_exit(err_code: WasmIndexerError) -> ! {
    unsafe { ff_early_exit(err_code as u32) }
    unreachable!("Expected termination of WASM exetution after a call to ff_early_exit.")
}