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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
use crate::{Config, Determinism, Origin, Schedule};
use codec::{Decode, Encode, MaxEncodedLen};

use frame_support::{dispatch::RawOrigin, pallet_prelude::Weight};

use pallet_contracts_primitives::{
    ContractExecResult, ExecReturnValue, ReturnFlags, StorageDeposit,
};
use scale_info::TypeInfo;

use sp_runtime::{
    traits::{Saturating, Zero},
    DispatchError, RuntimeDebug,
};

use sp_std::vec::Vec;
use t3rn_primitives::{
    threevm::{
        GetState, ModuleOperations, Precompile, PrecompileArgs, PrecompileInvocation, ThreeVm,
    },
    SpeedMode,
};
use t3rn_sdk_primitives::{
    signal::ExecutionSignal, state::SideEffects, GET_STATE_FUNCTION_CODE,
    POST_SIGNAL_FUNCTION_CODE, SUBMIT_FUNCTION_CODE,
};

const CONTRACTS_LOG_TARGET: &str = "runtime::contracts::chain_extension";
const GET_STATE_LOG_TARGET: &str = "runtime::contracts::get_state";
const SIGNAL_LOG_TARGET: &str = "runtime::contracts::signal";

#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct ComposableExecReturnValue {
    /// Flags passed along by `seal_return`. Empty when `seal_return` was never called.
    pub flags: ReturnFlags,
    /// Buffer passed along by `seal_return`. Empty when `seal_return` was never called.
    pub data: Vec<u8>,
    /// Side effects returned from the call
    pub side_effects: Vec<Vec<u8>>,
}

impl ComposableExecReturnValue {
    /// The contract did revert all storage changes.
    pub fn did_revert(&self) -> bool {
        self.flags.contains(ReturnFlags::REVERT)
    }
}

pub trait Contracts<AccountId, Balance, EventRecord> {
    type Outcome;
    fn call(
        origin: AccountId,
        dest: AccountId,
        value: Balance,
        gas_limit: Weight,
        storage_deposit_limit: Option<Balance>,
        data: Vec<u8>,
        debug: bool,
    ) -> Self::Outcome;
}

// /// Result type of a `bare_call` call as well as `ContractsApi::call`.
// pub type ContractExecResult<Balance, EventRecord> =
// ContractResult<Result<ExecReturnValue, DispatchError>, Balance, EventRecord>;
impl<AccountId, Balance: Default, EventRecord> Contracts<AccountId, Balance, EventRecord> for () {
    type Outcome = ContractExecResult<Balance, EventRecord>;

    fn call(
        _origin: AccountId,
        _dest: AccountId,
        _value: Balance,
        _gas_limit: Weight,
        _storage_deposit_limit: Option<Balance>,
        _data: Vec<u8>,
        _debug: bool,
    ) -> Self::Outcome {
        ContractExecResult::<Balance, EventRecord> {
            gas_consumed: Weight::zero(),
            gas_required: Weight::zero(),
            debug_message: Vec::new(),
            storage_deposit: StorageDeposit::Refund(Default::default()),
            result: Ok(ExecReturnValue {
                flags: ReturnFlags::empty(),
                data: Vec::default(),
            }),
            events: None,
        }
    }
}

// Chain extensions
use crate::BalanceOf;

pub struct ThreeVmExtension;
use crate::{
    chain_extension::{
        BufInBufOutState, ChainExtension, Environment, Ext, InitState, RegisteredChainExtension,
        RetVal,
    },
    wasm::WasmBlob,
};

impl<C: Config> ChainExtension<C> for ThreeVmExtension {
    fn call<E>(&mut self, env: Environment<E, InitState>) -> Result<RetVal, DispatchError>
    where
        E: Ext<T = C>,
    {
        let func_id = env.func_id() as u32;
        log::trace!(
            target: CONTRACTS_LOG_TARGET,
            "[ChainExtension]|call|func_id:{:}",
            func_id
        );
        match func_id {
            GET_STATE_FUNCTION_CODE => {
                let mut env = env.buf_in_buf_out();

                // For some reason the parameter is passed through as a default, not an option
                let execution_id: C::Hash = env.read_as()?;
                log::debug!(
                    target: GET_STATE_LOG_TARGET,
                    "reading state for execution_id: {:?}",
                    execution_id
                );
                let default: C::Hash = Default::default();
                let execution_id = if execution_id == default {
                    None
                } else {
                    // TODO: allow a modifiable multiplier constant in the config
                    env.charge_weight(size_to_weight(&execution_id))?;
                    Some(execution_id)
                };

                let raw_origin: RawOrigin<C::AccountId> = match env.ext().caller() {
                    Origin::Signed(acc) => RawOrigin::Signed(acc.clone()),
                    Origin::Root => RawOrigin::Root,
                };

                let invocation = <C as Config>::ThreeVm::invoke(PrecompileArgs::GetState(
                    C::RuntimeOrigin::from(raw_origin),
                    GetState {
                        xtx_id: execution_id,
                    },
                ))?;
                let state = invocation.get_state().ok_or("NoStateReturned")?;

                let xtx_id = state.xtx_id;
                let bytes = state.encode();
                log::debug!(
                    target: GET_STATE_LOG_TARGET,
                    "loaded local state id: {:?}, state: {:?}",
                    xtx_id,
                    bytes,
                );

                env.write(&bytes[..], false, None)?;

                Ok(RetVal::Converging(0))
            },
            SUBMIT_FUNCTION_CODE => {
                let mut env = env.buf_in_buf_out();

                let arg: (SideEffects<C::AccountId, BalanceOf<C>, C::Hash>, SpeedMode) =
                    read_from_environment(&mut env)?;

                let raw_origin: RawOrigin<C::AccountId> = match env.ext().caller() {
                    Origin::Signed(acc) => RawOrigin::Signed(acc.clone()),
                    Origin::Root => RawOrigin::Root,
                };

                <C as Config>::ThreeVm::invoke(PrecompileArgs::SubmitSideEffects(
                    C::RuntimeOrigin::from(raw_origin),
                    arg.0,
                    arg.1,
                ))?;
                Ok(RetVal::Converging(0))
            },
            POST_SIGNAL_FUNCTION_CODE => {
                let mut env = env.buf_in_buf_out();

                let signal: ExecutionSignal<C::Hash> = read_from_environment(&mut env)?;
                log::debug!(target: SIGNAL_LOG_TARGET, "submitting signal {:?}", signal);

                let raw_origin: RawOrigin<C::AccountId> = match env.ext().caller() {
                    Origin::Signed(acc) => RawOrigin::Signed(acc.clone()),
                    Origin::Root => RawOrigin::Root,
                };
                C::ThreeVm::invoke(PrecompileArgs::Signal(
                    C::RuntimeOrigin::from(raw_origin),
                    signal,
                ))?;
                Ok(RetVal::Converging(0))
            },
            n => {
                log::error!(
                    target: CONTRACTS_LOG_TARGET,
                    "Called an unregistered `func_id`: {:}",
                    func_id
                );
                Ok(RetVal::Converging(n))
            },
        }
    }
}

impl<C: Config> RegisteredChainExtension<C> for () {
    const ID: u16 = 3330;
}

fn read_from_environment<C, T, E>(
    env: &mut Environment<E, BufInBufOutState>,
) -> Result<T, DispatchError>
where
    C: Config,
    T: Decode + MaxEncodedLen,
    E: Ext<T = C>,
{
    let bytes = env.read(<T as MaxEncodedLen>::max_encoded_len() as u32)?;

    Decode::decode(&mut &bytes[..])
        .map_err(|e| {
            log::error!(target: CONTRACTS_LOG_TARGET, "decoding type failed {:?}", e);
            "read_from_environment::DecodingFailed".into()
        })
        .and_then(|t: T| env.charge_weight(size_to_weight(&t)).map(|_| t))
}

fn size_to_weight<T: Encode>(encodable: &T) -> Weight {
    Weight::from_parts(encodable.encoded_size() as u64, Zero::zero())
}

// Used in src/lib.rs
pub fn try_instantiate_from_contracts_registry<T: Config>(
    origin: &T::AccountId,
    hash: &T::Hash,
    schedule: &Schedule<T>,
) -> Result<(WasmBlob<T>, BalanceOf<T>), DispatchError> {
    // Use ThreeVm to try to retrieve a module from the registry.
    // If found, attempt to construct a WasmBlob from it.
    let module = T::ThreeVm::from_registry::<WasmBlob<T>, _>(hash, |bytes| {
        WasmBlob::from_code(bytes, schedule, origin.clone(), Determinism::Relaxed)
            .unwrap_or(WasmBlob::<T>::new_empty())
    })?;

    if module.is_empty() {
        return Err("Could not instantiate from contracts registry".into())
    }

    T::ThreeVm::instantiate_check(module.get_type())?;

    // Retrieve the fee for using the module, or use a default if not specified
    let fee = module
        .get_author()
        .as_ref()
        .and_then(|author| author.fees_per_single_use)
        .unwrap_or_default();

    // Return the fee and the module itself
    Ok((module, fee))
}

pub fn try_submit_side_effects<T: Config>(
    caller: &T::AccountId,
    mut input_data: &[u8],
) -> Result<PrecompileInvocation<T, BalanceOf<T>>, DispatchError> {
    // Try to decode the input data into the expected arguments
    let decoded_args = Decode::decode(&mut input_data);

    // Use match to deal with the Result, which is more idiomatic in Rust than if let
    match decoded_args {
        Ok((side_effects, speed_mode)) => {
            // If decoding succeeded, invoke the ThreeVm function with the decoded arguments
            T::ThreeVm::invoke(PrecompileArgs::SubmitSideEffects(
                RawOrigin::Signed(caller.clone()).into(),
                side_effects,
                speed_mode,
            ))
        },
        Err(_) => {
            // If decoding failed, return an error
            Err("Failed to decode side effects".into())
        },
    }
}