Skip to content

Commit

Permalink
rusk: comments explaining the deployment code
Browse files Browse the repository at this point in the history
rusk: refactored deploy execution as method

rusk: improved comment for the execute method
  • Loading branch information
miloszm committed Jul 19, 2024
1 parent 7208f91 commit bd15f89
Showing 1 changed file with 77 additions and 30 deletions.
107 changes: 77 additions & 30 deletions rusk/src/lib/chain/rusk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use dusk_consensus::operations::{
CallParams, VerificationOutput, VoterWithCredits,
};
use execution_core::bytecode::Bytecode;
use execution_core::transfer::ContractDeploy;
use execution_core::{
stake::StakeData, transfer::Transaction as PhoenixTransaction, BlsScalar,
StakePublicKey,
Expand Down Expand Up @@ -505,22 +506,87 @@ fn bytecode_charge(
* gas_per_deploy_byte.unwrap_or(DEFAULT_GAS_PER_DEPLOY_BYTE)
}

// Contract deployment will fail and charge full gas limit in the
// following cases:
// 1) Transaction gas limit is smaller than deploy charge plus gas used for
// spending funds.
// 2) Transaction's bytecode's bytes are not consistent with bytecode's hash.
// 3) Deployment fails for deploy-specific reasons like e.g.:
// - contract already deployed
// - corrupted bytecode
// - sufficient gas to spend funds yet insufficient for deployment
fn contract_deploy(
session: &mut Session,
deploy: &ContractDeploy,
gas_limit: u64,
gas_per_deploy_byte: Option<u64>,
receipt: &mut CallReceipt<Result<Vec<u8>, ContractError>>,
) {
let deploy_charge = bytecode_charge(&deploy.bytecode, &gas_per_deploy_byte);
let min_gas_limit = receipt.gas_spent + deploy_charge;
let hash = blake3::hash(deploy.bytecode.bytes.as_slice());
if gas_limit < min_gas_limit {
receipt.data = Err(OutOfGas);
} else if hash != deploy.bytecode.hash {
receipt.data = Err(Panic("failed bytecode hash check".into()))
} else {
let result = session.deploy_raw(
None,
deploy.bytecode.bytes.as_slice(),
deploy.constructor_args.clone(),
deploy.owner.clone(),
gas_limit - receipt.gas_spent,
);
match result {
Ok(_) => receipt.gas_spent += deploy_charge,
Err(err) => {
info!("Tx caused deployment error {err:?}");
receipt.data = Err(Panic("failed deployment".into()))
}
}
}
}

/// Executes a transaction, returning the receipt of the call and the gas spent.
/// The following steps are performed:
///
/// 1. Call the "spend_and_execute" function on the transfer contract with
/// 1. Check if the transaction contains contract deployment data, and if so,
/// verifies if gas limit is enough for deployment. If gas limit is not
/// sufficient for deployment, transaction is discarded.
///
/// 2. Call the "spend_and_execute" function on the transfer contract with
/// unlimited gas. If this fails, an error is returned. If an error is
/// returned the transaction should be considered unspendable/invalid, but no
/// re-execution of previous transactions is required.
///
/// 2. Call the "refund" function on the transfer contract with unlimited gas.
/// 3. If the transaction contains contract deployment data, additional checks
/// are performed and if they pass, deployment is executed. The following
/// checks are performed:
/// - gas limit should be is smaller than deploy charge plus gas used for
/// spending funds
/// - transaction's bytecode's bytes are consistent with bytecode's hash
/// Deployment execution may fail for deployment-specific reasons, such as
/// for example:
/// - contract already deployed
/// - corrupted bytecode
/// If deployment execution fails, the entire gas limit is consumed and error
/// is returned.
///
/// 4. Call the "refund" function on the transfer contract with unlimited gas.
/// The amount charged depends on the gas spent by the transaction, and the
/// optional contract call in step 1.
/// optional contract call in steps 2 or 3.
///
/// Note that deployment transaction will never be re-executed for reasons
/// related to deployment, as it is either discarded or it charges the
/// full gas limit. It might be re-executed only if some other transaction
/// failed to fit the block.
fn execute(
session: &mut Session,
tx: &PhoenixTransaction,
gas_per_deploy_byte: Option<u64>,
) -> Result<CallReceipt<Result<Vec<u8>, ContractError>>, PiecrustError> {
// Transaction will be discarded if it is a deployment transaction
// with gas limit smaller than deploy charge.
if let Some(deploy) = tx.payload().contract_deploy() {
let deploy_charge =
bytecode_charge(&deploy.bytecode, &gas_per_deploy_byte);
Expand All @@ -541,35 +607,16 @@ fn execute(
tx.payload().fee.gas_limit,
)?;

// Deploy if this is a deployment transaction
// Deploy if this is a deployment transaction and spend part is successful.
if let Some(deploy) = tx.payload().contract_deploy() {
if receipt.data.is_ok() {
let deploy_charge =
bytecode_charge(&deploy.bytecode, &gas_per_deploy_byte);
let min_gas_limit = receipt.gas_spent + deploy_charge;
let hash = blake3::hash(deploy.bytecode.bytes.as_slice());
if tx.payload().fee.gas_limit < min_gas_limit {
receipt.data = Err(OutOfGas);
} else if hash != deploy.bytecode.hash {
receipt.data = Err(Panic("failed bytecode hash check".into()))
} else {
let result = session.deploy_raw(
None,
deploy.bytecode.bytes.as_slice(),
deploy.constructor_args.clone(),
deploy.owner.clone(),
tx.payload().fee.gas_limit - receipt.gas_spent,
);
match result {
Ok(_) => {
receipt.gas_spent += deploy_charge;
}
Err(err) => {
info!("Tx caused deployment error {err:?}");
receipt.data = Err(Panic("failed deployment".into()))
}
}
}
contract_deploy(
session,
deploy,
tx.payload().fee.gas_limit,
gas_per_deploy_byte,
&mut receipt,
);
}
};

Expand Down

0 comments on commit bd15f89

Please sign in to comment.