Skip to content

Commit

Permalink
Add list_authorization_keys to contract API.
Browse files Browse the repository at this point in the history
  • Loading branch information
Michał Papierski committed Dec 14, 2021
1 parent 8f56ee5 commit 427abc2
Show file tree
Hide file tree
Showing 14 changed files with 361 additions and 3 deletions.
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions execution_engine/src/core/resolvers/v1_function_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub(crate) enum FunctionIndex {
DictionaryGetFuncIndex,
DictionaryPutFuncIndex,
LoadCallStack,
LoadAuthorizationKeys,
}

impl From<FunctionIndex> for usize {
Expand Down
4 changes: 4 additions & 0 deletions execution_engine/src/core/resolvers/v1_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ impl ModuleImportResolver for RuntimeModuleImportResolver {
Signature::new(&[ValueType::I32; 1][..], Some(ValueType::I32)),
FunctionIndex::NewDictionaryFuncIndex.into(),
),
"casper_load_authorization_keys" => FuncInstance::alloc_host(
Signature::new(&[ValueType::I32; 2][..], Some(ValueType::I32)),
FunctionIndex::LoadAuthorizationKeys.into(),
),
_ => {
return Err(InterpreterError::Function(format!(
"host module doesn't export function with name {}",
Expand Down
11 changes: 11 additions & 0 deletions execution_engine/src/core/runtime/externals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,17 @@ where
let ret = self.load_call_stack(call_stack_len_ptr, result_size_ptr)?;
Ok(Some(RuntimeValue::I32(api_error::i32_from(ret))))
}
FunctionIndex::LoadAuthorizationKeys => {
// args(0) (Output) Pointer to number of authorization keys.
// args(1) (Output) Pointer to size in bytes of the total bytes.
let (len_ptr, result_size_ptr) = Args::parse(args)?;
self.charge_host_function_call(
&HostFunction::fixed(10_000),
[len_ptr, result_size_ptr],
)?;
let ret = self.load_authorization_keys(len_ptr, result_size_ptr)?;
Ok(Some(RuntimeValue::I32(api_error::i32_from(ret))))
}
}
}
}
42 changes: 41 additions & 1 deletion execution_engine/src/core/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::{
cmp,
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
convert::TryFrom,
iter::IntoIterator,
iter::{FromIterator, IntoIterator},
};

use itertools::Itertools;
Expand Down Expand Up @@ -3650,6 +3650,46 @@ where
}
}
}

fn load_authorization_keys(
&mut self,
len_ptr: u32,
result_size_ptr: u32,
) -> Result<Result<(), ApiError>, Trap> {
if !self.can_write_to_host_buffer() {
// Exit early if the host buffer is already occupied
return Ok(Err(ApiError::HostBufferFull));
}

// A set of keys is converted into a vector so it can be written to a host buffer
let authorization_keys =
Vec::from_iter(self.context.authorization_keys().clone().into_iter());

let total_keys = authorization_keys.len() as u32;
let total_keys_bytes = total_keys.to_le_bytes();
if let Err(error) = self.memory.set(len_ptr, &total_keys_bytes) {
return Err(Error::Interpreter(error.into()).into());
}

if total_keys == 0 {
// No need to do anything else, we leave host buffer empty.
return Ok(Ok(()));
}

let named_keys = CLValue::from_t(authorization_keys).map_err(Error::CLValue)?;

let length = named_keys.inner_bytes().len() as u32;
if let Err(error) = self.write_host_buffer(named_keys) {
return Ok(Err(error));
}

let length_bytes = length.to_le_bytes();
if let Err(error) = self.memory.set(result_size_ptr, &length_bytes) {
return Err(Error::Interpreter(error.into()).into());
}

Ok(Ok(()))
}
}

#[cfg(test)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use casper_engine_test_support::{
DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNT_ADDR,
DEFAULT_PAYMENT, DEFAULT_RUN_GENESIS_REQUEST, MINIMUM_ACCOUNT_CREATION_BALANCE,
};
use casper_execution_engine::core::{engine_state::Error, execution};
use casper_types::{
account::{AccountHash, Weight},
runtime_args,
system::{mint, standard_payment::ARG_AMOUNT},
ApiError, PublicKey, RuntimeArgs, SecretKey, U512,
};
use once_cell::sync::Lazy;

const ARG_ACCOUNT: &str = "account";
const ARG_WEIGHT: &str = "weight";
const DEFAULT_WEIGHT: Weight = Weight::new(1);

const CONTRACT_ADD_ASSOCIATED_KEY: &str = "add_associated_key.wasm";

const CONTRACT_LIST_AUTHORIZATION_KEYS: &str = "list_authorization_keys.wasm";
const ARG_EXPECTED_AUTHORIZATION_KEYS: &str = "expected_authorized_keys";

static ACCOUNT_1_SECRET_KEY: Lazy<SecretKey> =
Lazy::new(|| SecretKey::secp256k1_from_bytes(&[234u8; 32]).unwrap());
static ACCOUNT_1_PUBLIC_KEY: Lazy<PublicKey> =
Lazy::new(|| PublicKey::from(&*ACCOUNT_1_SECRET_KEY));
static ACCOUNT_1_ADDR: Lazy<AccountHash> = Lazy::new(|| ACCOUNT_1_PUBLIC_KEY.to_account_hash());

static ACCOUNT_2_SECRET_KEY: Lazy<SecretKey> =
Lazy::new(|| SecretKey::secp256k1_from_bytes(&[243u8; 32]).unwrap());
static ACCOUNT_2_PUBLIC_KEY: Lazy<PublicKey> =
Lazy::new(|| PublicKey::from(&*ACCOUNT_2_SECRET_KEY));
static ACCOUNT_2_ADDR: Lazy<AccountHash> = Lazy::new(|| ACCOUNT_2_PUBLIC_KEY.to_account_hash());

const USER_ERROR_ASSERTION: u16 = 0;

#[ignore]
#[test]
fn should_list_authorization_keys() {
assert!(
test_match(
*DEFAULT_ACCOUNT_ADDR,
vec![*DEFAULT_ACCOUNT_ADDR],
vec![*DEFAULT_ACCOUNT_ADDR]
),
"one signature should match the expected authorization key"
);
assert!(
!test_match(
*DEFAULT_ACCOUNT_ADDR,
vec![*ACCOUNT_2_ADDR, *DEFAULT_ACCOUNT_ADDR],
vec![*DEFAULT_ACCOUNT_ADDR, *ACCOUNT_1_ADDR]
),
"two signatures are off by one"
);
assert!(
test_match(
*DEFAULT_ACCOUNT_ADDR,
vec![*ACCOUNT_2_ADDR, *DEFAULT_ACCOUNT_ADDR],
vec![*DEFAULT_ACCOUNT_ADDR, *ACCOUNT_2_ADDR]
),
"two signatures should match the expected list"
);
assert!(
test_match(
*ACCOUNT_1_ADDR,
vec![*ACCOUNT_1_ADDR],
vec![*ACCOUNT_1_ADDR]
),
"one signature should match the output for non-default account"
);

assert!(
test_match(
*DEFAULT_ACCOUNT_ADDR,
vec![*ACCOUNT_2_ADDR, *DEFAULT_ACCOUNT_ADDR, *ACCOUNT_1_ADDR],
vec![*ACCOUNT_1_ADDR, *ACCOUNT_2_ADDR, *DEFAULT_ACCOUNT_ADDR]
),
"multisig matches expected list"
);
assert!(
!test_match(
*DEFAULT_ACCOUNT_ADDR,
vec![*ACCOUNT_2_ADDR, *DEFAULT_ACCOUNT_ADDR, *ACCOUNT_1_ADDR],
vec![]
),
"multisig is not empty"
);
assert!(
!test_match(
*DEFAULT_ACCOUNT_ADDR,
vec![*ACCOUNT_2_ADDR, *DEFAULT_ACCOUNT_ADDR, *ACCOUNT_1_ADDR],
vec![*ACCOUNT_2_ADDR, *ACCOUNT_1_ADDR]
),
"multisig does not include caller account"
);
}

fn test_match(
caller: AccountHash,
signatures: Vec<AccountHash>,
expected_authorization_keys: Vec<AccountHash>,
) -> bool {
let mut builder = setup();
let exec_request = {
let session_args = runtime_args! {
ARG_EXPECTED_AUTHORIZATION_KEYS => expected_authorization_keys
};
let deploy_hash = [42; 32];

let deploy = DeployItemBuilder::new()
.with_address(caller)
.with_session_code(CONTRACT_LIST_AUTHORIZATION_KEYS, session_args)
.with_empty_payment_bytes(runtime_args! {
ARG_AMOUNT => *DEFAULT_PAYMENT
})
.with_authorization_keys(&signatures)
.with_deploy_hash(deploy_hash)
.build();
ExecuteRequestBuilder::new().push_deploy(deploy).build()
};
builder.exec(exec_request).commit();

match builder.get_error() {
Some(Error::Exec(execution::Error::Revert(ApiError::User(USER_ERROR_ASSERTION)))) => false,
Some(error) => panic!("Unexpected error {:?}", error),
None => {
// Success
true
}
}
}

fn setup() -> InMemoryWasmTestBuilder {
let mut builder = InMemoryWasmTestBuilder::default();
builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST);

for account in [*ACCOUNT_1_ADDR, *ACCOUNT_2_ADDR] {
let add_key_request = {
let session_args = runtime_args! {
ARG_ACCOUNT => account,
ARG_WEIGHT => DEFAULT_WEIGHT,
};
ExecuteRequestBuilder::standard(
*DEFAULT_ACCOUNT_ADDR,
CONTRACT_ADD_ASSOCIATED_KEY,
session_args,
)
.build()
};

let transfer_request = {
let transfer_args = runtime_args! {
mint::ARG_AMOUNT => U512::from(MINIMUM_ACCOUNT_CREATION_BALANCE),
mint::ARG_TARGET => account,
mint::ARG_ID => Option::<u64>::None,
};

ExecuteRequestBuilder::transfer(*DEFAULT_ACCOUNT_ADDR, transfer_args).build()
};

builder.exec(add_key_request).expect_success().commit();
builder.exec(transfer_request).expect_success().commit();
}

builder
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod get_blocktime;
mod get_call_stack;
mod get_caller;
mod get_phase;
mod list_authorization_keys;
mod list_named_keys;
mod main_purse;
mod mint_purse;
Expand Down
27 changes: 26 additions & 1 deletion smart_contracts/contract/src/contract_api/runtime.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Functions for interacting with the current runtime.

use alloc::{vec, vec::Vec};
use alloc::{collections::BTreeSet, vec, vec::Vec};
use core::mem::MaybeUninit;

use casper_types::{
Expand Down Expand Up @@ -274,6 +274,31 @@ pub fn remove_key(name: &str) {
unsafe { ext_ffi::casper_remove_key(name_ptr, name_size) }
}

/// Returns the set of [`AccountHash`] from the calling account's context `authorization_keys`.
pub fn list_authorization_keys() -> BTreeSet<AccountHash> {
let (total_authorization_keys, result_size) = {
let mut authorization_keys = MaybeUninit::uninit();
let mut result_size = MaybeUninit::uninit();
let ret = unsafe {
ext_ffi::casper_load_authorization_keys(
authorization_keys.as_mut_ptr(),
result_size.as_mut_ptr(),
)
};
api_error::result_from(ret).unwrap_or_revert();
let total_authorization_keys = unsafe { authorization_keys.assume_init() };
let result_size = unsafe { result_size.assume_init() };
(total_authorization_keys, result_size)
};

if total_authorization_keys == 0 {
return BTreeSet::new();
}

let bytes = read_host_buffer(result_size).unwrap_or_revert();
bytesrepr::deserialize(bytes).unwrap_or_revert()
}

/// Returns the named keys of the current context.
///
/// The current context is either the caller's account or a stored contract depending on whether the
Expand Down
9 changes: 9 additions & 0 deletions smart_contracts/contract/src/ext_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ extern "C" {
/// * `value_ptr` - pointer to bytes representing the value to write under the new `URef`
/// * `value_size` - size of the value (in bytes)
pub fn casper_new_uref(uref_ptr: *mut u8, value_ptr: *const u8, value_size: usize);
/// This function loads a set of authorized keys used to sign this deploy from the host.
/// The data will be available through the host buffer and can be copied to Wasm memory through
/// [`casper_read_host_buffer`].
///
/// # Arguments
///
/// * `total_keys`: number of authorization keys used to sign this deploy
/// * `result_size`: size of the data loaded in the host
pub fn casper_load_authorization_keys(total_keys: *mut usize, result_size: *mut usize) -> i32;
///
pub fn casper_load_named_keys(total_keys: *mut usize, result_size: *mut usize) -> i32;
/// This function causes a `Trap`, terminating the currently running module,
Expand Down
16 changes: 16 additions & 0 deletions smart_contracts/contracts/test/add-associated-key/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "add-associated-key"
version = "0.1.0"
authors = ["Michał Papierski <michal@casperlabs.io"]
edition = "2018"

[[bin]]
name = "add_associated_key"
path = "src/main.rs"
bench = false
doctest = false
test = false

[dependencies]
casper-contract = { path = "../../../contract" }
casper-types = { path = "../../../../types" }
18 changes: 18 additions & 0 deletions smart_contracts/contracts/test/add-associated-key/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![no_std]
#![no_main]

use casper_contract::{
contract_api::{account, runtime},
unwrap_or_revert::UnwrapOrRevert,
};
use casper_types::account::{AccountHash, Weight};

const ARG_ACCOUNT: &str = "account";
const ARG_WEIGHT: &str = "weight";

#[no_mangle]
pub extern "C" fn call() {
let account: AccountHash = runtime::get_named_arg(ARG_ACCOUNT);
let weight: Weight = runtime::get_named_arg(ARG_WEIGHT);
account::add_associated_key(account, weight).unwrap_or_revert();
}
Loading

0 comments on commit 427abc2

Please sign in to comment.