Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate reduced costs execution. #417

Merged
merged 7 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading