dusk_vm/
execute.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7mod config;
8
9use blake2b_simd::Params;
10use dusk_core::abi::{ContractError, ContractId, Metadata, CONTRACT_ID_BYTES};
11use dusk_core::transfer::data::ContractBytecode;
12use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT};
13use piecrust::{CallReceipt, Error, Session};
14
15pub use config::Config;
16
17/// Executes a transaction in the provided session.
18///
19/// This function processes the transaction, invoking smart contracts or
20/// updating state.
21///
22/// During the execution the following steps are performed:
23///
24/// 1. Check if the transaction contains contract deployment data, and if so,
25///    verifies if gas limit is enough for deployment and if the gas price is
26///    sufficient for deployment. If either gas price or gas limit is not
27///    sufficient for deployment, transaction is discarded.
28///
29/// 2. Call the "spend_and_execute" function on the transfer contract with
30///    unlimited gas. If this fails, an error is returned. If an error is
31///    returned the transaction should be considered unspendable/invalid, but no
32///    re-execution of previous transactions is required.
33///
34/// 3. If the transaction contains contract deployment data, additional checks
35///    are performed and if they pass, deployment is executed. The following
36///    checks are performed:
37///    - gas limit should be is smaller than deploy charge plus gas used for
38///      spending funds
39///    - transaction's bytecode's bytes are consistent with bytecode's hash
40///    Deployment execution may fail for deployment-specific reasons, such as
41///    for example:
42///    - contract already deployed
43///    - corrupted bytecode
44///    If deployment execution fails, the entire gas limit is consumed and error
45///    is returned.
46///
47/// 4. Call the "refund" function on the transfer contract with unlimited gas.
48///    The amount charged depends on the gas spent by the transaction, and the
49///    optional contract call in steps 2 or 3.
50///
51/// Note that deployment transaction will never be re-executed for reasons
52/// related to deployment, as it is either discarded or it charges the
53/// full gas limit. It might be re-executed only if some other transaction
54/// failed to fit the block.
55///
56/// # Arguments
57/// * `session` - A mutable reference to the session executing the transaction.
58/// * `tx` - The transaction to execute.
59/// * `config` - The configuration for the execution of the transaction.
60///
61/// # Returns
62/// A result indicating success or failure.
63pub fn execute(
64    session: &mut Session,
65    tx: &Transaction,
66    config: &Config,
67) -> Result<CallReceipt<Result<Vec<u8>, ContractError>>, Error> {
68    // Transaction will be discarded if it is a deployment transaction
69    // with gas limit smaller than deploy charge.
70    deploy_check(tx, config)?;
71
72    if config.with_public_sender {
73        let _ = session
74            .set_meta(Metadata::PUBLIC_SENDER, tx.moonlight_sender().copied());
75    }
76
77    // Spend the inputs and execute the call. If this errors the transaction is
78    // unspendable.
79    let mut receipt = session
80        .call::<_, Result<Vec<u8>, ContractError>>(
81            TRANSFER_CONTRACT,
82            "spend_and_execute",
83            tx.strip_off_bytecode().as_ref().unwrap_or(tx),
84            tx.gas_limit(),
85        )
86        .map_err(|e| {
87            clear_session(session, config);
88            e
89        })?;
90
91    // Deploy if this is a deployment transaction and spend part is successful.
92    contract_deploy(session, tx, config, &mut receipt);
93
94    // Ensure all gas is consumed if there's an error in the contract call
95    if receipt.data.is_err() {
96        receipt.gas_spent = receipt.gas_limit;
97    }
98
99    // Refund the appropriate amount to the transaction. This call is guaranteed
100    // to never error. If it does, then a programming error has occurred. As
101    // such, the call to `Result::expect` is warranted.
102    let refund_receipt = session
103        .call::<_, ()>(
104            TRANSFER_CONTRACT,
105            "refund",
106            &receipt.gas_spent,
107            u64::MAX,
108        )
109        .expect("Refunding must succeed");
110
111    receipt.events.extend(refund_receipt.events);
112
113    clear_session(session, config);
114
115    Ok(receipt)
116}
117
118fn clear_session(session: &mut Session, config: &Config) {
119    if config.with_public_sender {
120        let _ = session.remove_meta(Metadata::PUBLIC_SENDER);
121    }
122}
123
124fn deploy_check(tx: &Transaction, config: &Config) -> Result<(), Error> {
125    if tx.deploy().is_some() {
126        let gas_per_deploy_byte = config.gas_per_deploy_byte;
127        let min_deploy_gas_price = config.min_deploy_gas_price;
128        let deploy_charge =
129            tx.deploy_charge(gas_per_deploy_byte, min_deploy_gas_price);
130
131        if tx.gas_price() < min_deploy_gas_price {
132            return Err(Error::Panic("gas price too low to deploy".into()));
133        }
134        if tx.gas_limit() < deploy_charge {
135            return Err(Error::Panic("not enough gas to deploy".into()));
136        }
137    }
138
139    Ok(())
140}
141
142// Contract deployment will fail and charge full gas limit in the
143// following cases:
144// 1) Transaction gas limit is smaller than deploy charge plus gas used for
145//    spending funds.
146// 2) Transaction's bytecode's bytes are not consistent with bytecode's hash.
147// 3) Deployment fails for deploy-specific reasons like e.g.:
148//      - contract already deployed
149//      - corrupted bytecode
150//      - sufficient gas to spend funds yet insufficient for deployment
151fn contract_deploy(
152    session: &mut Session,
153    tx: &Transaction,
154    config: &Config,
155    receipt: &mut CallReceipt<Result<Vec<u8>, ContractError>>,
156) {
157    if let Some(deploy) = tx.deploy() {
158        let gas_per_deploy_byte = config.gas_per_deploy_byte;
159        let min_deploy_points = config.min_deploy_points;
160
161        let gas_left = tx.gas_limit() - receipt.gas_spent;
162        if receipt.data.is_ok() {
163            let deploy_charge =
164                tx.deploy_charge(gas_per_deploy_byte, min_deploy_points);
165            let min_gas_limit = receipt.gas_spent + deploy_charge;
166            if gas_left < min_gas_limit {
167                receipt.data = Err(ContractError::OutOfGas);
168            } else if !verify_bytecode_hash(&deploy.bytecode) {
169                receipt.data = Err(ContractError::Panic(
170                    "failed bytecode hash check".into(),
171                ))
172            } else {
173                let result = session.deploy_raw(
174                    Some(gen_contract_id(
175                        &deploy.bytecode.bytes,
176                        deploy.nonce,
177                        &deploy.owner,
178                    )),
179                    deploy.bytecode.bytes.as_slice(),
180                    deploy.init_args.clone(),
181                    deploy.owner.clone(),
182                    gas_left,
183                );
184                match result {
185                    // Should the gas spent by the INIT method charged too?
186                    Ok(_) => receipt.gas_spent += deploy_charge,
187                    Err(err) => {
188                        let msg = format!("failed deployment: {err:?}");
189                        receipt.data = Err(ContractError::Panic(msg))
190                    }
191                }
192            }
193        }
194    }
195}
196
197// Verifies that the stored contract bytecode hash is correct.
198fn verify_bytecode_hash(bytecode: &ContractBytecode) -> bool {
199    let computed: [u8; 32] = blake3::hash(bytecode.bytes.as_slice()).into();
200
201    bytecode.hash == computed
202}
203
204/// Generates a unique identifier for a smart contract.
205///
206/// # Arguments
207/// * 'bytes` - The contract bytecode.
208/// * `nonce` - A unique nonce.
209/// * `owner` - The contract-owner.
210///
211/// # Returns
212/// A unique [`ContractId`].
213///
214/// # Panics
215/// Panics if [blake2b-hasher] doesn't produce a [`CONTRACT_ID_BYTES`]
216/// bytes long hash.
217///
218/// [blake2b-hasher]: [`blake2b_simd::Params.finalize`]
219pub fn gen_contract_id(
220    bytes: impl AsRef<[u8]>,
221    nonce: u64,
222    owner: impl AsRef<[u8]>,
223) -> ContractId {
224    let mut hasher = Params::new().hash_length(CONTRACT_ID_BYTES).to_state();
225    hasher.update(bytes.as_ref());
226    hasher.update(&nonce.to_le_bytes()[..]);
227    hasher.update(owner.as_ref());
228    let hash_bytes: [u8; CONTRACT_ID_BYTES] = hasher
229        .finalize()
230        .as_bytes()
231        .try_into()
232        .expect("the hash result is exactly `CONTRACT_ID_BYTES` long");
233    ContractId::from_bytes(hash_bytes)
234}
235
236#[cfg(test)]
237mod tests {
238    use alloc::vec;
239
240    // the `unused_crate_dependencies` lint complains for dev-dependencies that
241    // are only used in integration tests, so adding this work-around here
242    use ff as _;
243    use once_cell as _;
244    use rand::rngs::StdRng;
245    use rand::{RngCore, SeedableRng};
246
247    use super::*;
248
249    #[test]
250    fn test_gen_contract_id() {
251        let mut rng = StdRng::seed_from_u64(42);
252
253        let mut bytes = vec![0; 1000];
254        rng.fill_bytes(&mut bytes);
255
256        let nonce = rng.next_u64();
257
258        let mut owner = vec![0, 100];
259        rng.fill_bytes(&mut owner);
260
261        let contract_id =
262            gen_contract_id(bytes.as_slice(), nonce, owner.as_slice());
263
264        assert_eq!(
265            contract_id.as_bytes(),
266            [
267                45, 168, 182, 39, 119, 137, 168, 140, 114, 21, 120, 158, 34,
268                126, 244, 221, 151, 72, 109, 178, 82, 229, 84, 128, 92, 123,
269                135, 74, 23, 224, 119, 133
270            ]
271        );
272    }
273}