ink_engine/
ext.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Provides the same interface as Substrate's FRAME `revive` module.
16//!
17//! See [the documentation for the `revive` module](https://docs.rs/crate/pallet-revive)
18//! for more information.
19
20use crate::{
21    database::Database,
22    exec_context::ExecContext,
23    test_api::{
24        DebugInfo,
25        EmittedEvent,
26    },
27    types::BlockTimestamp,
28};
29use hex_literal::hex;
30use ink_primitives::{
31    Address,
32    U256,
33};
34pub use pallet_revive_uapi::ReturnErrorCode as Error;
35use std::panic::panic_any;
36
37/// The off-chain engine.
38pub struct Engine {
39    /// The environment database.
40    pub database: Database,
41    /// The current execution context.
42    pub exec_context: ExecContext,
43    /// Recorder for relevant interactions with the engine.
44    /// This is specifically about debug info. This info is
45    /// not available in the `contracts` pallet.
46    pub(crate) debug_info: DebugInfo,
47    /// The chain specification.
48    pub chain_spec: ChainSpec,
49}
50
51/// The chain specification.
52pub struct ChainSpec {
53    /// The current gas price.
54    pub gas_price: U256,
55    /// The minimum value an account of the chain must have
56    /// (i.e. the chain's existential deposit).
57    pub minimum_balance: U256,
58    /// The targeted block time.
59    pub block_time: BlockTimestamp,
60}
61
62/// The default values for the chain specification are:
63///
64///   * `gas_price`: 100
65///   * `minimum_balance`: 42
66///   * `block_time`: 6
67///
68/// There is no particular reason behind choosing them this way.
69impl Default for ChainSpec {
70    fn default() -> Self {
71        Self {
72            gas_price: 100.into(),
73            minimum_balance: 42.into(),
74            block_time: 6,
75        }
76    }
77}
78
79impl Engine {
80    // Creates a new `Engine` instance.
81    pub fn new() -> Self {
82        Self {
83            database: Database::new(),
84            exec_context: ExecContext::new(),
85            debug_info: DebugInfo::new(),
86            chain_spec: ChainSpec::default(),
87        }
88    }
89}
90
91impl Default for Engine {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl Engine {
98    /// Transfers value from the contract to the destination account.
99    #[allow(clippy::arithmetic_side_effects)] // todo
100    pub fn transfer(&mut self, dest: Address, mut value: &[u8]) -> Result<(), Error> {
101        // Note that a transfer of `0` is allowed here
102        let increment = <u128 as scale::Decode>::decode(&mut value)
103            .map_err(|_| Error::TransferFailed)?;
104
105        // Note that the destination account does not have to exist
106        let dest_old_balance = self.get_balance(dest).unwrap_or_default();
107
108        let contract = self.get_callee();
109        let contract_old_balance = self
110            .get_balance(contract)
111            .map_err(|_| Error::TransferFailed)?;
112
113        self.database
114            .set_balance(&contract, contract_old_balance - increment);
115        self.database
116            .set_balance(&dest, dest_old_balance + increment);
117        Ok(())
118    }
119
120    /// Deposits an event identified by the supplied topics and data.
121    pub fn deposit_event(&mut self, topics: &[[u8; 32]], data: &[u8]) {
122        self.debug_info.record_event(EmittedEvent {
123            topics: topics.to_vec(),
124            data: data.to_vec(),
125        });
126    }
127
128    /// Writes the encoded value into the storage at the given key.
129    /// Returns the size of the previously stored value at the key if any.
130    pub fn set_storage(&mut self, key: &[u8], encoded_value: &[u8]) -> Option<u32> {
131        let callee = self.get_callee();
132
133        self.debug_info.inc_writes(callee);
134        self.debug_info
135            .record_cell_for_account(callee, key.to_vec());
136
137        self.database
138            .insert_into_contract_storage(&callee, key, encoded_value.to_vec())
139            .map(|v| <u32>::try_from(v.len()).expect("usize to u32 conversion failed"))
140    }
141
142    /// Returns the contract storage bytes at the key if any.
143    pub fn get_storage(&mut self, key: &[u8]) -> Result<&[u8], Error> {
144        let callee = self.get_callee();
145
146        self.debug_info.inc_reads(callee);
147        match self.database.get_from_contract_storage(&callee, key) {
148            Some(val) => Ok(val),
149            None => Err(Error::KeyNotFound),
150        }
151    }
152
153    /// Removes the storage entries at the given key,
154    /// returning previously stored value at the key if any.
155    pub fn take_storage(&mut self, key: &[u8]) -> Result<Vec<u8>, Error> {
156        let callee = self.get_callee();
157
158        self.debug_info.inc_writes(callee);
159        match self.database.remove_contract_storage(&callee, key) {
160            Some(val) => Ok(val),
161            None => Err(Error::KeyNotFound),
162        }
163    }
164
165    /// Returns the size of the value stored in the contract storage at the key if any.
166    pub fn contains_storage(&mut self, key: &[u8]) -> Option<u32> {
167        let callee = self.get_callee();
168
169        self.debug_info.inc_reads(callee);
170        self.database
171            .get_from_contract_storage(&callee, key)
172            .map(|val| val.len() as u32)
173    }
174
175    /// Removes the storage entries at the given key.
176    /// Returns the size of the previously stored value at the key if any.
177    pub fn clear_storage(&mut self, key: &[u8]) -> Option<u32> {
178        let callee = self.get_callee();
179        self.debug_info.inc_writes(callee);
180        let _ = self
181            .debug_info
182            .remove_cell_for_account(callee, key.to_vec());
183        self.database
184            .remove_contract_storage(&callee, key)
185            .map(|val| val.len() as u32)
186    }
187
188    /// Remove the calling account and transfer remaining balance.
189    ///
190    /// todo is the following comment still correct?
191    /// This function never returns. Either the termination was successful and the
192    /// execution of the destroyed contract is halted. Or it failed during the
193    /// termination which is considered fatal.
194    pub fn terminate(&mut self, beneficiary: Address) -> ! {
195        // Send the remaining balance to the beneficiary
196        let contract = self.get_callee();
197        let all = self
198            .get_balance(contract)
199            .unwrap_or_else(|err| panic!("could not get balance: {err:?}"));
200        let value = &scale::Encode::encode(&all)[..];
201        self.transfer(beneficiary, value)
202            .unwrap_or_else(|err| panic!("transfer did not work: {err:?}"));
203
204        // Encode the result of the termination and panic with it.
205        // This enables testing for the proper result and makes sure this
206        // method returns `Never`.
207        let res = (all, beneficiary);
208        panic_any(scale::Encode::encode(&res));
209    }
210
211    /// Returns the address of the caller.
212    pub fn caller(&self, output: &mut &mut [u8]) {
213        let caller = self.exec_context.caller;
214        let caller = scale::Encode::encode(&caller);
215        set_output(output, &caller[..])
216    }
217
218    /// Returns the balance of the executed contract.
219    pub fn balance(&self, output: &mut &mut [u8]) {
220        let contract = self
221            .exec_context
222            .callee
223            .as_ref()
224            .expect("no callee has been set");
225
226        let balance_in_storage = self
227            .database
228            .get_balance(contract)
229            .expect("currently executing contract must exist");
230        let balance = scale::Encode::encode(&balance_in_storage);
231        set_output(output, &balance[..])
232    }
233
234    /// Returns the transferred value for the called contract.
235    pub fn value_transferred(&self, output: &mut &mut [u8]) {
236        let value_transferred: Vec<u8> =
237            scale::Encode::encode(&self.exec_context.value_transferred);
238        set_output(output, &value_transferred[..])
239    }
240
241    /// Returns the address of the executed contract.
242    pub fn address(&self, output: &mut &mut [u8]) {
243        let callee = self
244            .exec_context
245            .callee
246            .as_ref()
247            .expect("no callee has been set")
248            .as_bytes();
249        set_output(output, callee)
250    }
251
252    pub fn account_id(&self, output: &mut &mut [u8]) {
253        let callee = self
254            .exec_context
255            .callee
256            .as_ref()
257            .expect("no callee has been set");
258        let account_id = self.database.to_account_id(callee);
259        set_output(output, account_id.as_slice())
260    }
261
262    /// Retrieves the account id for a specified address.
263    pub fn to_account_id(&self, input: &[u8], output: &mut &mut [u8]) {
264        let addr =
265            scale::Decode::decode(&mut &input[..]).expect("unable to decode Address");
266        let account_id = self.database.to_account_id(&addr);
267        set_output(output, account_id.as_slice())
268    }
269
270    /// Conduct the BLAKE-2 256-bit hash and place the result into `output`.
271    pub fn hash_blake2_256(input: &[u8], output: &mut [u8; 32]) {
272        super::hashing::blake2b_256(input, output);
273    }
274
275    /// Conduct the BLAKE-2 128-bit hash and place the result into `output`.
276    pub fn hash_blake2_128(input: &[u8], output: &mut [u8; 16]) {
277        super::hashing::blake2b_128(input, output);
278    }
279
280    /// Conduct the SHA-2 256-bit hash and place the result into `output`.
281    pub fn hash_sha2_256(input: &[u8], output: &mut [u8; 32]) {
282        super::hashing::sha2_256(input, output);
283    }
284
285    /// Conduct the KECCAK 256-bit hash and place the result into `output`.
286    pub fn hash_keccak_256(input: &[u8], output: &mut [u8; 32]) {
287        super::hashing::keccak_256(input, output);
288    }
289
290    /// Returns the current block number.
291    pub fn block_number(&self, output: &mut &mut [u8]) {
292        let block_number: Vec<u8> =
293            scale::Encode::encode(&self.exec_context.block_number);
294        set_output(output, &block_number[..])
295    }
296
297    /// Returns the timestamp of the current block.
298    pub fn block_timestamp(&self, output: &mut &mut [u8]) {
299        let block_timestamp: Vec<u8> =
300            scale::Encode::encode(&self.exec_context.block_timestamp);
301        set_output(output, &block_timestamp[..])
302    }
303
304    /// Returns the minimum balance that is required for creating an account
305    /// (i.e. the chain's existential deposit).
306    pub fn minimum_balance(&self, output: &mut &mut [u8]) {
307        let minimum_balance: Vec<u8> =
308            scale::Encode::encode(&self.chain_spec.minimum_balance);
309        set_output(output, &minimum_balance[..])
310    }
311
312    #[allow(clippy::too_many_arguments)]
313    pub fn instantiate(
314        &mut self,
315        _code_hash: &[u8],
316        _gas_limit: u64,
317        _endowment: &[u8],
318        _input: &[u8],
319        _out_address: &mut &mut [u8],
320        _out_return_value: &mut &mut [u8],
321        _salt: &[u8],
322    ) -> Result<(), Error> {
323        unimplemented!("off-chain environment does not yet support `instantiate`");
324    }
325
326    pub fn call(
327        &mut self,
328        callee: &[u8],
329        _gas_limit: u64,
330        _value: &[u8],
331        input: &[u8],
332        output: &mut &mut [u8],
333    ) -> Result<(), Error> {
334        const ECRECOVER: [u8; 20] = hex!("0000000000000000000000000000000000000001");
335        if callee == ECRECOVER {
336            let mut signature = [0u8; 65];
337            signature.copy_from_slice(&input[..65]);
338            let mut message_hash = [0u8; 32];
339            message_hash.copy_from_slice(&input[65..65 + 32]);
340
341            let out: &mut [u8; 33] = output
342                .as_mut()
343                .try_into()
344                .expect("Slice must be exactly 33 bytes long");
345            let _ = self.ecdsa_recover(&signature, &message_hash, out);
346        }
347        unimplemented!(
348            "off-chain environment does not yet support `call` for non-precompiles"
349        );
350    }
351
352    /// Emulates gas price calculation.
353    pub fn weight_to_fee(&self, gas: u64, output: &mut &mut [u8]) {
354        let fee = self.chain_spec.gas_price.saturating_mul(gas.into());
355        let fee: Vec<u8> = scale::Encode::encode(&fee);
356        set_output(output, &fee[..])
357    }
358}
359
360impl Engine {
361    /// Recovers the compressed ECDSA public key for given `signature` and `message_hash`,
362    /// and stores the result in `output`.
363    #[allow(clippy::arithmetic_side_effects)] // todo
364    pub fn ecdsa_recover(
365        &mut self,
366        signature: &[u8; 65],
367        message_hash: &[u8; 32],
368        output: &mut [u8; 33],
369    ) -> Result<(), Error> {
370        use secp256k1::{
371            Message,
372            SECP256K1,
373            ecdsa::{
374                RecoverableSignature,
375                RecoveryId,
376            },
377        };
378
379        // In most implementations, the v is just 0 or 1 internally, but 27 was added
380        // as an arbitrary number for signing Bitcoin messages and Ethereum adopted that
381        // as well.
382        let recovery_byte = if signature[64] > 26 {
383            signature[64] - 27
384        } else {
385            signature[64]
386        };
387
388        let recovery_id = RecoveryId::try_from(recovery_byte as i32)
389            .unwrap_or_else(|error| panic!("Unable to parse the recovery id: {error}"));
390
391        let message = Message::from_digest_slice(message_hash).unwrap_or_else(|error| {
392            panic!("Unable to create the message from hash: {error}")
393        });
394        let signature =
395            RecoverableSignature::from_compact(&signature[0..64], recovery_id)
396                .unwrap_or_else(|error| panic!("Unable to parse the signature: {error}"));
397
398        let pub_key = SECP256K1.recover_ecdsa(&message, &signature);
399        match pub_key {
400            Ok(pub_key) => {
401                *output = pub_key.serialize();
402                Ok(())
403            }
404            Err(_) => Err(Error::EcdsaRecoveryFailed),
405        }
406    }
407}
408
409/// Copies the `slice` into `output`.
410///
411/// Panics if the slice is too large and does not fit.
412fn set_output(output: &mut &mut [u8], slice: &[u8]) {
413    assert!(
414        slice.len() <= output.len(),
415        "the output buffer is too small! the decoded storage is of size {} bytes, \
416        but the output buffer has only room for {}.",
417        slice.len(),
418        output.len(),
419    );
420    output[..slice.len()].copy_from_slice(slice);
421}