Skip to content

Commit

Permalink
Merge pull request #136 from Concordium/extract-contract-updates
Browse files Browse the repository at this point in the history
Extract contract updates
  • Loading branch information
abizjak authored Nov 16, 2023
2 parents ccf3ce3 + f1006ff commit dea44da
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
## Unreleased changes

- The sdk now requires a `rustc` version at least 1.67 (Before it required version 1.66).
- Add a `contract_update` helper analogous to `contract_init` to extract an
execution tree from a smart contract update transaction.
- Add a `ccd_cost` helper to `ChainParameters` to convert NRG cost to CCD.

## 3.1.0

Expand Down
311 changes: 311 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ use concordium_base::{
AccountAddress, AccountCredentialWithoutProofs, AccountKeys, CredentialPublicKeys,
},
},
smart_contracts::{
ContractEvent, ModuleReference, OwnedParameter, OwnedReceiveName, WasmVersion,
},
transactions::{AccountAccessStructure, ExactSizeTransactionSigner, TransactionSigner},
};
use std::{
Expand Down Expand Up @@ -1099,6 +1102,22 @@ impl BlockItemSummary {
}
}

/// If the block item is a smart contract update transaction then return the
/// execution tree.
pub fn contract_update(self) -> Option<ExecutionTree> {
if let BlockItemSummaryDetails::AccountTransaction(at) = self.details {
match at.effects {
AccountTransactionEffects::ContractInitialized { .. } => None,
AccountTransactionEffects::ContractUpdateIssued { effects } => {
execution_tree(effects)
}
_ => None,
}
} else {
None
}
}

/// If the block item is a smart contract init transaction then
/// return the initialization data.
pub fn contract_init(&self) -> Option<&ContractInitializedEvent> {
Expand Down Expand Up @@ -1219,6 +1238,298 @@ impl BlockItemSummary {
}
}

#[derive(Debug, PartialEq)]
/// A result of updating a smart contract instance.
pub enum ExecutionTree {
/// The top-level call was a V0 contract instance update.
V0(ExecutionTreeV0),
/// The top-level call was a V1 contract instance update.
V1(ExecutionTreeV1),
}

/// Convert the trace elements into an [`ExecutionTree`].
/// This will fail if the list was not generated correctly, but if the list of
/// trace elements is coming from the node it will always be in the correct
/// format.
pub fn execution_tree(elements: Vec<ContractTraceElement>) -> Option<ExecutionTree> {
#[derive(Debug)]
struct PartialTree {
address: ContractAddress,
/// Whether the matching resume was seen for the interrupt.
resumed: bool,
events: Vec<TraceV1>,
}

#[derive(Debug)]
enum Worker {
V0(ExecutionTreeV0),
Partial(PartialTree),
}

// The current stack of calls. Stack is pushed on new interrupts (interrupts
// that introduce new nested calls) and on calls to V0 contracts.
let mut stack: Vec<Worker> = Vec::new();
let mut elements = elements.into_iter();
while let Some(element) = elements.next() {
match element {
ContractTraceElement::Updated {
data:
InstanceUpdatedEvent {
contract_version,
address,
instigator,
amount,
message,
receive_name,
events,
},
} => {
if let Some(end) = stack.pop() {
let tree = match contract_version {
WasmVersion::V0 => ExecutionTree::V0(ExecutionTreeV0 {
top_level: UpdateV0 {
address,
instigator,
amount,
message,
receive_name,
events,
},
rest: Vec::new(),
}),
WasmVersion::V1 => ExecutionTree::V1(ExecutionTreeV1 {
address,
instigator,
amount,
message,
receive_name,
events: vec![TraceV1::Events { events }],
}),
};
match end {
Worker::V0(mut v0) => {
v0.rest.push(TraceV0::Call(tree));
stack.push(Worker::V0(v0));
}
Worker::Partial(mut partial) => {
if partial.resumed {
// terminate it.
let ExecutionTree::V1(mut tree) = tree else {
return None;
};
std::mem::swap(&mut tree.events, &mut partial.events);
tree.events.append(&mut partial.events);
if let Some(last) = stack.last_mut() {
match last {
Worker::V0(v0) => {
v0.rest.push(TraceV0::Call(ExecutionTree::V1(tree)));
}
Worker::Partial(v0) => {
v0.events.push(TraceV1::Call {
call: ExecutionTree::V1(tree),
});
}
}
} else {
// and return it.
if elements.next().is_none() {
return Some(ExecutionTree::V1(tree));
} else {
return None;
}
}
} else {
partial.events.push(TraceV1::Call { call: tree });
stack.push(Worker::Partial(partial));
}
}
}
} else {
// no stack yet
match contract_version {
WasmVersion::V0 => stack.push(Worker::V0(ExecutionTreeV0 {
top_level: UpdateV0 {
address,
instigator,
amount,
message,
receive_name,
events,
},
rest: Vec::new(),
})),
WasmVersion::V1 => {
let tree = ExecutionTreeV1 {
address,
instigator,
amount,
message,
receive_name,
events: vec![TraceV1::Events { events }],
};
// and return it.
if elements.next().is_none() {
return Some(ExecutionTree::V1(tree));
} else {
return None;
}
}
}
}
}
ContractTraceElement::Transferred { from, amount, to } => {
let last = stack.last_mut()?;
match last {
Worker::V0(v0) => v0.rest.push(TraceV0::Transfer { from, amount, to }),
Worker::Partial(partial) => {
partial.events.push(TraceV1::Transfer { from, amount, to });
}
}
}
ContractTraceElement::Interrupted { address, events } => match stack.last_mut() {
Some(Worker::Partial(partial)) if partial.resumed => {
partial.resumed = false;
partial.events.push(TraceV1::Events { events })
}
_ => {
stack.push(Worker::Partial(PartialTree {
address,
resumed: false,
events: vec![TraceV1::Events { events }],
}));
}
},
ContractTraceElement::Resumed {
address,
success: _,
} => {
match stack.pop()? {
Worker::V0(v0) => {
let Worker::Partial(partial) = stack.last_mut()? else {
return None;
};
partial.events.push(TraceV1::Call {
call: ExecutionTree::V0(v0),
});
partial.resumed = true;
}
Worker::Partial(mut partial) => {
if address != partial.address {
return None;
}
partial.resumed = true;
stack.push(Worker::Partial(partial));
}
};
}
ContractTraceElement::Upgraded { address, from, to } => {
let Worker::Partial(partial) = stack.last_mut()? else {
return None;
};
if address != partial.address {
return None;
}
// Put an upgrade event to the list, and continue.
partial.events.push(TraceV1::Upgrade { from, to });
}
}
}
let Worker::V0(v0) = stack.pop()? else {
return None;
};
if stack.is_empty() {
Some(ExecutionTree::V0(v0))
} else {
None
}
}

#[derive(Debug, PartialEq)]
pub struct UpdateV0 {
/// Address of the affected instance.
pub address: ContractAddress,
/// The origin of the message to the smart contract. This can be either
/// an account or a smart contract.
pub instigator: Address,
/// The amount the method was invoked with.
pub amount: Amount,
/// The message passed to method.
pub message: OwnedParameter,
/// The name of the method that was executed.
pub receive_name: OwnedReceiveName,
/// Events emitted by the contract call.
pub events: Vec<ContractEvent>,
}

#[derive(Debug, PartialEq)]
/// An update of a V0 contract with all of its subsequent trace elements in the
/// order they were executed. Note that some of those events might have been
/// generated by subsequent calls, not directly by the top-level call.
pub struct ExecutionTreeV0 {
top_level: UpdateV0,
rest: Vec<TraceV0>,
}

#[derive(Debug, PartialEq)]
/// An action generated by a V0 contract.
enum TraceV0 {
/// A contract call, either V0 or V1 contract with all its nested calls.
Call(ExecutionTree),
/// A transfer of CCD from the V0 contract to an account.
Transfer {
/// Sender contract.
from: ContractAddress,
/// Amount transferred.
amount: Amount,
/// Receiver account.
to: AccountAddress,
},
}

#[derive(Debug, PartialEq)]
/// An update of a V1 contract with all of its nested calls.
pub struct ExecutionTreeV1 {
/// Address of the affected instance.
pub address: ContractAddress,
/// The origin of the message to the smart contract. This can be either
/// an account or a smart contract.
pub instigator: Address,
/// The amount the method was invoked with.
pub amount: Amount,
/// The message passed to method.
pub message: OwnedParameter,
/// The name of the method that was executed.
pub receive_name: OwnedReceiveName,
/// A sequence of calls, transfers, etc. performed by the contract, in the
/// order that they took effect.
pub events: Vec<TraceV1>,
}

#[derive(Debug, PartialEq)]
/// An operation performed directly by a V1 contract.
pub enum TraceV1 {
/// New events emitted.
Events { events: Vec<ContractEvent> },
/// A successful call to another contract, either V0 or V1.
Call { call: ExecutionTree },
/// A transfer of CCD from the contract to the account.
Transfer {
/// Sender contract.
from: ContractAddress,
/// Amount transferred.
amount: Amount,
/// Receiver account.
to: AccountAddress,
},
/// An upgrade of the contract instance.
Upgrade {
/// The existing module reference that is in effect before the upgrade.
from: ModuleReference,
/// The new module reference that is in effect after the upgrade.
to: ModuleReference,
},
}

#[derive(Debug, Clone)]
/// Details of a block item summary, split by the kind of block item it is for.
pub enum BlockItemSummaryDetails {
Expand Down
20 changes: 19 additions & 1 deletion src/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use concordium_base::{
pub use endpoints::{QueryError, QueryResult, RPCError, RPCResult};
use futures::{Stream, StreamExt};
pub use http::uri::Scheme;
use num::{BigUint, ToPrimitive};
use std::{collections::HashMap, num::ParseIntError};
use tonic::IntoRequest;
pub use tonic::{
Expand Down Expand Up @@ -467,6 +468,23 @@ impl ChainParameters {
num::rational::Ratio::new(num, denom)
}

/// Get the CCD cost of the given amount of energy for the current chain
/// parameters.
pub fn ccd_cost(&self, nrg: Energy) -> Amount {
let ratio = self.micro_ccd_per_energy();
let numer = BigUint::from(*ratio.numer()) * nrg.energy;
let denomer = BigUint::from(*ratio.denom());
let cost = num::rational::Ratio::new(numer, denomer);
let i = cost.ceil().to_integer();
// The next line should be a no-op when the values are coming from the chain
// since the amount should always fit a u64. With a 3_000_000 maximum
// NRG limit the exchange rate would have to be more than 6148914691236
// microCCD per NRG (6148914 CCD per NRG) for this to occur. But even in that
// case this behaviour here matches the node behaviour.
let micro = i % u64::MAX;
Amount::from_micro_ccd(micro.to_u64().expect("Value is known to be under u64::MAX"))
}

/// The foundation account that gets the foundation tax.
pub fn foundation_account(&self) -> AccountAddress {
match self {
Expand Down Expand Up @@ -2623,7 +2641,7 @@ impl Client {
}

/// Get all bakers in the reward period of a block.
/// This endpoint is only supported for protocol version 6 and onwards.
/// This endpoint is only supported for protocol version 4 and onwards.
/// If the protocol does not support the endpoint then an
/// [`IllegalArgument`](tonic::Code::InvalidArgument) is returned.
pub async fn get_bakers_reward_period(
Expand Down

0 comments on commit dea44da

Please sign in to comment.