Skip to content

Commit

Permalink
Merge pull request #417 from Concordium/reduce-costs
Browse files Browse the repository at this point in the history
Integrate reduced costs execution.
  • Loading branch information
abizjak authored Apr 21, 2024
2 parents a932404 + dbda4f2 commit 03aa479
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 92 deletions.
2 changes: 1 addition & 1 deletion concordium-rust-sdk
4 changes: 4 additions & 0 deletions contract-testing/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased changes

- Integrate protocol version 7 cost semantics.
- The `ContractInvokeSuccess` and `ContractInvokeError` have additional fields
that record where parts of the energy was allocated during execution.

## 4.2.0

- Add support for querying the module reference and contract name of an instance.
Expand Down
164 changes: 104 additions & 60 deletions contract-testing/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ use concordium_rust_sdk::{
};
use num_bigint::BigUint;
use num_integer::Integer;
use sdk::types::smart_contracts::InvokeContractResult;
use sdk::{
smart_contracts::engine::wasm::CostConfigurationV1,
types::smart_contracts::InvokeContractResult,
};
use std::{
collections::{BTreeMap, BTreeSet},
future::Future,
Expand Down Expand Up @@ -582,8 +585,9 @@ impl Chain {
sender_account.balance.total -= transaction_fee;

// Construct the artifact.
let artifact = match wasm::utils::instantiate_with_metering::<v1::ProcessedImports, _>(
let artifact = match wasm::utils::instantiate_with_metering::<v1::ProcessedImports>(
ValidationConfig::V1,
CostConfigurationV1,
&v1::ConcordiumAllowedImports {
support_upgrade: true,
enable_debug,
Expand Down Expand Up @@ -918,7 +922,8 @@ impl Chain {
amount_reserved_for_energy: Amount,
payload: UpdateContractPayload,
remaining_energy: &mut Energy,
) -> Result<(InvokeResponse, ChangeSet, Vec<DebugTraceElement>), ContractInvokeError> {
) -> Result<(InvokeResponse, ChangeSet, Vec<DebugTraceElement>, Energy), ContractInvokeError>
{
// Check if the contract to invoke exists.
if !self.contract_exists(payload.address) {
return Err(self.convert_to_invoke_error(
Expand All @@ -929,6 +934,7 @@ impl Chain {
Vec::new(),
energy_reserved,
*remaining_energy,
0.into(),
));
}

Expand All @@ -939,6 +945,7 @@ impl Chain {
Vec::new(),
energy_reserved,
*remaining_energy,
0.into(),
));
}

Expand All @@ -956,6 +963,7 @@ impl Chain {
Vec::new(),
energy_reserved,
*remaining_energy,
0.into(),
));
}

Expand All @@ -969,29 +977,39 @@ impl Chain {
// Starts at 1 since 0 is the "initial state" of all contracts in the current
// transaction.
next_contract_modification_index: 1,
module_load_energy: 0.into(),
};

let module_load_energy = contract_invocation.module_load_energy;
let res = contract_invocation.invoke_entrypoint(invoker, sender, payload);
match res {
Ok((result, trace_elements)) => {
Ok((result, contract_invocation.changeset, trace_elements))
}
Ok((result, trace_elements)) => Ok((
result,
contract_invocation.changeset,
trace_elements,
contract_invocation.module_load_energy,
)),
Err(err) => Err(self.convert_to_invoke_error(
err.into(),
Vec::new(),
energy_reserved,
*remaining_energy,
module_load_energy,
)),
}
}

// Since this is an internal function it seems better to allow rather than
// introduce a new struct just to call this function.
#[allow(clippy::too_many_arguments)]
fn contract_invocation_process_response(
&self,
result: InvokeResponse,
trace_elements: Vec<DebugTraceElement>,
energy_reserved: Energy,
remaining_energy: Energy,
state_energy: Energy,
state_changed: bool,
module_load_energy: Energy,
) -> Result<ContractInvokeSuccess, ContractInvokeError> {
match result {
v1::InvokeResponse::Success {
Expand All @@ -1007,6 +1025,8 @@ impl Chain {
return_value: data.unwrap_or_default(),
state_changed,
new_balance,
storage_energy: state_energy,
module_load_energy,
})
}
v1::InvokeResponse::Failure {
Expand All @@ -1018,6 +1038,7 @@ impl Chain {
trace_elements,
energy_reserved,
remaining_energy,
module_load_energy,
)),
}
}
Expand Down Expand Up @@ -1052,10 +1073,11 @@ impl Chain {
// is verified upfront. So what we do here is custom behaviour, and we reject
// without consuming any energy.
return Err(ContractInvokeError {
energy_used: Energy::from(0),
transaction_fee: Amount::zero(),
trace_elements: Vec::new(),
kind: ContractInvokeErrorKind::SenderDoesNotExist(sender),
energy_used: Energy::from(0),
transaction_fee: Amount::zero(),
trace_elements: Vec::new(),
kind: ContractInvokeErrorKind::SenderDoesNotExist(sender),
module_load_energy: 0.into(),
});
}

Expand All @@ -1068,6 +1090,7 @@ impl Chain {
kind: ContractInvokeErrorKind::InvokerDoesNotExist(
AccountDoesNotExist { address: invoker },
),
module_load_energy: 0.into(),
});
};

Expand All @@ -1082,12 +1105,13 @@ impl Chain {
// Charge the header cost.
let mut remaining_energy =
energy_reserved.checked_sub(check_header_cost).ok_or(ContractInvokeError {
energy_used: Energy::from(0),
transaction_fee: Amount::zero(),
trace_elements: Vec::new(),
kind: ContractInvokeErrorKind::OutOfEnergy {
energy_used: Energy::from(0),
transaction_fee: Amount::zero(),
trace_elements: Vec::new(),
kind: ContractInvokeErrorKind::OutOfEnergy {
debug_trace: DebugTracker::empty_trace(), // we haven't done anything yet.
},
module_load_energy: 0.into(),
})?;

let invoker_amount_reserved_for_nrg =
Expand All @@ -1101,6 +1125,7 @@ impl Chain {
transaction_fee: self.parameters.calculate_energy_cost(energy_used),
trace_elements: Vec::new(),
kind: ContractInvokeErrorKind::InsufficientFunds,
module_load_energy: 0.into(),
});
}

Expand All @@ -1114,37 +1139,43 @@ impl Chain {
&mut remaining_energy,
);
let res = match res {
Ok((result, changeset, trace_elements)) => {
Ok((result, changeset, trace_elements, module_load_energy)) => {
// Charge energy for contract storage. Or return an error if out
// of energy.
let state_changed = if matches!(result, v1::InvokeResponse::Success { .. }) {
let res = changeset.persist(
&mut remaining_energy,
contract_address,
&mut self.accounts,
&mut self.contracts,
);
if let Ok(res) = res {
res
let (state_energy, state_changed) =
if matches!(result, v1::InvokeResponse::Success { .. }) {
let energy_before = remaining_energy;
let res = changeset.persist(
&mut remaining_energy,
contract_address,
&mut self.accounts,
&mut self.contracts,
);
let state_energy = energy_before.checked_sub(remaining_energy).unwrap();
if let Ok(res) = res {
(state_energy, res)
} else {
// the error happens when storing the state, so there are no trace
// elements associated with it. The trace is
// already in the "debug trace" vector.
return Err(self.invocation_out_of_energy_error(
energy_reserved,
DebugTracker::empty_trace(),
module_load_energy,
));
}
} else {
// the error happens when storing the state, so there are no trace elements
// associated with it. The trace is already in the
// "debug trace" vector.
return Err(self.invocation_out_of_energy_error(
energy_reserved,
DebugTracker::empty_trace(),
));
}
} else {
// An error occurred, so state hasn't changed.
false
};
// An error occurred, so state hasn't changed.
(0.into(), false)
};
self.contract_invocation_process_response(
result,
trace_elements,
energy_reserved,
remaining_energy,
state_energy,
state_changed,
module_load_energy,
)
}
Err(e) => Err(e),
Expand Down Expand Up @@ -1184,10 +1215,11 @@ impl Chain {
// Ensure the sender exists.
if !self.address_exists(sender) {
return Err(ContractInvokeError {
energy_used: Energy::from(0),
transaction_fee: Amount::zero(),
trace_elements: Vec::new(),
kind: ContractInvokeErrorKind::SenderDoesNotExist(sender),
energy_used: Energy::from(0),
transaction_fee: Amount::zero(),
trace_elements: Vec::new(),
kind: ContractInvokeErrorKind::SenderDoesNotExist(sender),
module_load_energy: 0.into(),
});
}

Expand All @@ -1199,6 +1231,7 @@ impl Chain {
kind: ContractInvokeErrorKind::InvokerDoesNotExist(
AccountDoesNotExist { address: invoker },
),
module_load_energy: 0.into(),
});
};

Expand All @@ -1212,6 +1245,7 @@ impl Chain {
transaction_fee: self.parameters.calculate_energy_cost(energy_used),
trace_elements: Vec::new(),
kind: ContractInvokeErrorKind::InsufficientFunds,
module_load_energy: 0.into(),
});
}

Expand All @@ -1228,33 +1262,39 @@ impl Chain {
&mut remaining_energy,
);
match res {
Ok((result, changeset, trace_elements)) => {
Ok((result, changeset, trace_elements, module_load_energy)) => {
// Charge energy for contract storage. Or return an error if out
// of energy.
let state_changed = if matches!(result, v1::InvokeResponse::Success { .. }) {
if let Ok(state_changed) =
changeset.collect_energy_for_state(&mut remaining_energy, contract_address)
{
state_changed
let (state_energy, state_changed) =
if matches!(result, v1::InvokeResponse::Success { .. }) {
let energy_before = remaining_energy;
if let Ok(state_changed) = changeset
.collect_energy_for_state(&mut remaining_energy, contract_address)
{
let state_energy = energy_before.checked_sub(remaining_energy).unwrap();
(state_energy, state_changed)
} else {
// the error happens when storing the state, so there are no trace
// elements associated with it. The trace is
// already in the "debug trace" vector.
return Err(self.invocation_out_of_energy_error(
energy_reserved,
DebugTracker::empty_trace(),
module_load_energy,
));
}
} else {
// the error happens when storing the state, so there are no trace elements
// associated with it. The trace is already in the
// "debug trace" vector.
return Err(self.invocation_out_of_energy_error(
energy_reserved,
DebugTracker::empty_trace(),
));
}
} else {
// An error occurred, so state hasn't changed.
false
};
// An error occurred, so state hasn't changed.
(0.into(), false)
};
self.contract_invocation_process_response(
result,
trace_elements,
energy_reserved,
remaining_energy,
state_energy,
state_changed,
module_load_energy,
)
}
Err(e) => Err(e),
Expand Down Expand Up @@ -1551,6 +1591,7 @@ impl Chain {
trace_elements: Vec<DebugTraceElement>,
energy_reserved: Energy,
remaining_energy: Energy,
module_load_energy: Energy,
) -> ContractInvokeError {
let remaining_energy = if matches!(kind, ContractInvokeErrorKind::OutOfEnergy { .. }) {
0.into()
Expand All @@ -1564,6 +1605,7 @@ impl Chain {
transaction_fee,
trace_elements,
kind,
module_load_energy,
}
}

Expand All @@ -1574,6 +1616,7 @@ impl Chain {
&self,
energy_reserved: Energy,
debug_trace: DebugTracker,
module_load_energy: Energy,
) -> ContractInvokeError {
self.convert_to_invoke_error(
ContractInvokeErrorKind::OutOfEnergy {
Expand All @@ -1582,6 +1625,7 @@ impl Chain {
Vec::new(),
energy_reserved,
Energy::from(0),
module_load_energy,
)
}

Expand Down Expand Up @@ -2114,7 +2158,7 @@ pub(crate) fn from_interpreter_energy(interpreter_energy: &InterpreterEnergy) ->
/// Calculate the energy for looking up a [`ContractModule`].
pub(crate) fn lookup_module_cost(module: &ContractModule) -> Energy {
// The ratio is from Concordium/Cost.hs::lookupModule
Energy::from(module.size / 50)
Energy::from(module.size / 500)
}

/// Calculate the microCCD(mCCD) cost of energy(NRG) using the two exchange
Expand Down
Loading

0 comments on commit 03aa479

Please sign in to comment.