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

Access to callstack via uplink API #406

Open
wants to merge 4 commits into
base: full-delta-index
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"box",
"c-example",
"callcenter",
"callstack",
"initializer",
"counter",
"counter_float",
Expand Down
27 changes: 27 additions & 0 deletions contracts/callcenter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ impl Callcenter {
uplink::caller()
}

/// Return the entire call stack of this contract
pub fn return_callstack(&self) -> Vec<ContractId> {
uplink::callstack()
}

/// Make sure that the caller of this contract is the contract itself
pub fn call_self(&self) -> Result<bool, ContractError> {
let self_id = uplink::self_id();
Expand All @@ -90,6 +95,16 @@ impl Callcenter {
}
}

/// Return a call stack after calling itself n times
pub fn call_self_n_times(&self, n: u32) -> Vec<ContractId> {
let self_id = uplink::self_id();
match n {
0 => uplink::callstack(),
_ => uplink::call(self_id, "call_self_n_times", &(n - 1))
.expect("calling self should succeed")
}
}

/// Calls the `spend` function of the `contract` with no arguments, and the
/// given `gas_limit`, assuming the called function returns `()`. It will
/// then return the call's result itself.
Expand Down Expand Up @@ -133,6 +148,12 @@ unsafe fn call_self(arg_len: u32) -> u32 {
wrap_call(arg_len, |_: ()| STATE.call_self())
}

/// Expose `Callcenter::call_self_n_times()` to the host
#[no_mangle]
unsafe fn call_self_n_times(arg_len: u32) -> u32 {
wrap_call(arg_len, |n: u32| STATE.call_self_n_times(n))
}

/// Expose `Callcenter::call_spend_with_limit` to the host
#[no_mangle]
unsafe fn call_spend_with_limit(arg_len: u32) -> u32 {
Expand All @@ -153,6 +174,12 @@ unsafe fn return_caller(arg_len: u32) -> u32 {
wrap_call(arg_len, |_: ()| STATE.return_caller())
}

/// Expose `Callcenter::return_callstack()` to the host
#[no_mangle]
unsafe fn return_callstack(arg_len: u32) -> u32 {
wrap_call(arg_len, |_: ()| STATE.return_callstack())
}

/// Expose `Callcenter::delegate_query()` to the host
#[no_mangle]
unsafe fn delegate_query(arg_len: u32) -> u32 {
Expand Down
15 changes: 15 additions & 0 deletions contracts/callstack/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "callstack"
version = "0.1.0"
authors = [
"Milosz Muszynski <[email protected]>",
]
edition = "2021"

license = "MPL-2.0"

[dependencies]
piecrust-uplink = { path = "../../piecrust-uplink", features = ["abi", "dlmalloc"] }

[lib]
crate-type = ["cdylib", "rlib"]
34 changes: 34 additions & 0 deletions contracts/callstack/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) DUSK NETWORK. All rights reserved.

//! Contract which exposes the call stack

#![no_std]

extern crate alloc;

use piecrust_uplink as uplink;
use alloc::vec::Vec;
use uplink::ContractId;

/// Struct that describes the state of the contract
pub struct CallStack;

/// State of the Counter contract
static mut STATE: CallStack = CallStack;

impl CallStack {
/// Return the call stack
pub fn return_callstack(&self) -> Vec<ContractId> {
uplink::callstack()
}
}

/// Expose `CallStack::read_callstack()` to the host
#[no_mangle]
unsafe fn return_callstack(arg_len: u32) -> u32 {
uplink::wrap_call_unchecked(arg_len, |_: ()| STATE.return_callstack())
}
17 changes: 17 additions & 0 deletions piecrust-uplink/src/abi/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ mod ext {
pub fn feed(arg_len: u32);

pub fn caller() -> i32;
pub fn callstack() -> i32;
pub fn limit() -> u64;
pub fn spent() -> u64;
pub fn owner(contract_id: *const u8) -> i32;
Expand Down Expand Up @@ -289,6 +290,22 @@ pub fn caller() -> Option<ContractId> {
}
}

/// Returns IDs of all calling contracts present in the calling stack
pub fn callstack() -> Vec<ContractId> {
let n = unsafe { ext::callstack() };
with_arg_buf(|buf| {
let mut v = Vec::new();
for i in 0..n as usize {
let mut bytes = [0; CONTRACT_ID_BYTES];
bytes.copy_from_slice(
&buf[i * CONTRACT_ID_BYTES..(i + 1) * CONTRACT_ID_BYTES],
);
v.push(ContractId::from_bytes(bytes));
}
v
})
}

/// Returns the gas limit with which the contact was called.
pub fn limit() -> u64 {
unsafe { ext::limit() }
Expand Down
14 changes: 14 additions & 0 deletions piecrust/src/call_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ impl CallTree {
current.map(|inner| unsafe { (*inner).elem })
}

/// Returns all call ids.
pub(crate) fn call_ids(&self) -> Vec<&ContractId> {
let mut v = Vec::new();
let mut current = self.0;

while current.is_some() {
let p = *current.as_ref().unwrap();
v.push(unsafe { &(*p).elem.contract_id });
current = current.and_then(|inner| unsafe { (*inner).parent });
}

v
}

/// Clears the call tree of all elements.
pub(crate) fn clear(&mut self) {
unsafe {
Expand Down
16 changes: 16 additions & 0 deletions piecrust/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ impl Imports {
fn import(store: &mut Store<Env>, name: &str, is_64: bool) -> Option<Func> {
Some(match name {
"caller" => Func::wrap(store, caller),
"callstack" => Func::wrap(store, callstack),
"c" => match is_64 {
false => Func::wrap(store, wasm32::c),
true => Func::wrap(store, wasm64::c),
Expand Down Expand Up @@ -386,6 +387,21 @@ fn caller(env: Caller<Env>) -> i32 {
}
}

fn callstack(env: Caller<Env>) -> i32 {
let env = env.data();
let instance = env.self_instance();

let mut i = 0usize;
for contract_id in env.call_ids() {
instance.with_arg_buf_mut(|buf| {
buf[i * CONTRACT_ID_BYTES..(i + 1) * CONTRACT_ID_BYTES]
.copy_from_slice(contract_id.as_bytes());
});
i += 1;
}
i as i32
}

fn feed(mut fenv: Caller<Env>, arg_len: u32) -> WasmtimeResult<()> {
let env = fenv.data_mut();
let instance = env.self_instance();
Expand Down
4 changes: 4 additions & 0 deletions piecrust/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,10 @@ impl Session {
self.inner.call_tree.nth_parent(n)
}

pub(crate) fn call_ids(&self) -> Vec<&ContractId> {
self.inner.call_tree.call_ids()
}

/// Creates a new instance of the given contract, returning its memory
/// length.
fn create_instance(
Expand Down
60 changes: 60 additions & 0 deletions piecrust/tests/callcenter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,66 @@ pub fn cc_caller_uninit() -> Result<(), Error> {
Ok(())
}

#[test]
pub fn cc_callstack() -> Result<(), Error> {
let vm = VM::ephemeral()?;

let mut session = vm.session(SessionData::builder())?;

let center_id = session.deploy(
contract_bytecode!("callcenter"),
ContractData::builder().owner(OWNER),
LIMIT,
)?;

let callstack_id = session.deploy(
contract_bytecode!("callstack"),
ContractData::builder().owner(OWNER),
LIMIT,
)?;

let callstack: Vec<ContractId> = session
.call(center_id, "return_callstack", &(), LIMIT)?
.data;
assert_eq!(callstack.len(), 1);

let self_id: ContractId =
session.call(center_id, "return_self_id", &(), LIMIT)?.data;
assert_eq!(callstack[0], self_id);

const N: u32 = 5;
let callstack: Vec<ContractId> = session
.call(center_id, "call_self_n_times", &N, LIMIT)?
.data;
assert_eq!(callstack.len(), N as usize + 1);
for i in 1..=N as usize {
assert_eq!(callstack[0], callstack[i]);
}

let res = session
.call::<_, Result<Vec<u8>, ContractError>>(
center_id,
"delegate_query",
&(
callstack_id,
String::from("return_callstack"),
Vec::<u8>::new(),
),
LIMIT,
)?
.data
.expect("ICC should succeed");

let callstack: Vec<ContractId> =
rkyv::from_bytes(&res).expect("Deserialization to succeed");

assert_eq!(callstack.len(), 2);
assert_eq!(callstack[0], callstack_id);
assert_eq!(callstack[1], center_id);

Ok(())
}

#[test]
pub fn cc_self_id() -> Result<(), Error> {
let vm = VM::ephemeral()?;
Expand Down