use exonum::{
crypto::{KeyPair, PublicKey, SecretKey},
messages::Verified,
runtime::{
AnyTx, CallInfo, ExecutionContext, ExecutionContextUnstable, ExecutionError, InstanceId,
InstanceQuery, MethodId,
},
};
#[derive(Debug, Clone, Copy)]
pub struct MethodDescriptor<'a> {
pub interface_name: &'a str,
pub id: MethodId,
}
impl<'a> MethodDescriptor<'a> {
pub const fn new(interface_name: &'a str, id: MethodId) -> Self {
Self { interface_name, id }
}
pub const fn inherent(id: MethodId) -> Self {
Self::new("", id)
}
}
pub trait Interface<'a> {
const INTERFACE_NAME: &'static str;
fn dispatch(
&self,
context: ExecutionContext<'a>,
method: MethodId,
payload: &[u8],
) -> Result<(), ExecutionError>;
}
pub trait GenericCall<Ctx> {
type Output;
fn generic_call(
&self,
context: Ctx,
method: MethodDescriptor<'_>,
args: Vec<u8>,
) -> Self::Output;
}
pub trait GenericCallMut<Ctx> {
type Output;
fn generic_call_mut(
&mut self,
context: Ctx,
method: MethodDescriptor<'_>,
args: Vec<u8>,
) -> Self::Output;
}
#[derive(Debug, Clone, Copy)]
pub struct TxStub;
impl GenericCall<InstanceId> for TxStub {
type Output = AnyTx;
fn generic_call(
&self,
instance_id: InstanceId,
method: MethodDescriptor<'_>,
args: Vec<u8>,
) -> Self::Output {
if !method.interface_name.is_empty() {
panic!("Creating transactions with non-default interface is not yet supported");
}
let call_info = CallInfo::new(instance_id, method.id);
AnyTx::new(call_info, args)
}
}
impl GenericCall<InstanceId> for (PublicKey, SecretKey) {
type Output = Verified<AnyTx>;
fn generic_call(
&self,
instance_id: InstanceId,
method: MethodDescriptor<'_>,
args: Vec<u8>,
) -> Self::Output {
let tx = TxStub.generic_call(instance_id, method, args);
Verified::from_value(tx, self.0, &self.1)
}
}
impl GenericCall<InstanceId> for KeyPair {
type Output = Verified<AnyTx>;
fn generic_call(
&self,
instance_id: InstanceId,
method: MethodDescriptor<'_>,
args: Vec<u8>,
) -> Self::Output {
let tx = TxStub.generic_call(instance_id, method, args);
Verified::from_value(tx, self.public_key(), self.secret_key())
}
}
#[cfg(test)]
mod explanation {
use super::*;
use exonum::{crypto::KeyPair, merkledb::BinaryValue};
use pretty_assertions::assert_eq;
trait Token<Ctx> {
type Output;
fn create_wallet(&self, context: Ctx, wallet: CreateWallet) -> Self::Output;
fn transfer(&self, context: Ctx, transfer: Transfer) -> Self::Output;
}
type CreateWallet = String;
type Transfer = u64;
impl<T, Ctx> Token<Ctx> for T
where
T: GenericCall<Ctx>,
{
type Output = <Self as GenericCall<Ctx>>::Output;
fn create_wallet(&self, context: Ctx, wallet: CreateWallet) -> Self::Output {
const DESCRIPTOR: MethodDescriptor<'static> = MethodDescriptor {
interface_name: "",
id: 0,
};
self.generic_call(context, DESCRIPTOR, wallet.into_bytes())
}
fn transfer(&self, context: Ctx, transfer: Transfer) -> Self::Output {
const DESCRIPTOR: MethodDescriptor<'static> = MethodDescriptor {
interface_name: "",
id: 1,
};
self.generic_call(context, DESCRIPTOR, transfer.into_bytes())
}
}
#[test]
fn standard_stubs_work() {
const SERVICE_ID: InstanceId = 100;
let keypair = KeyPair::random();
let tx: Verified<AnyTx> = keypair.create_wallet(SERVICE_ID, CreateWallet::default());
assert_eq!(tx.payload().call_info.method_id, 0);
let other_tx = keypair.transfer(SERVICE_ID, Transfer::default());
assert_eq!(other_tx.payload().call_info.method_id, 1);
}
struct PayloadSize;
impl GenericCall<()> for PayloadSize {
type Output = usize;
fn generic_call(
&self,
_context: (),
_method: MethodDescriptor<'_>,
args: Vec<u8>,
) -> Self::Output {
args.len()
}
}
#[test]
fn custom_stub() {
let len = PayloadSize.create_wallet((), "Alice".into());
assert_eq!(len, 5);
let other_len = PayloadSize.transfer((), 42);
assert_eq!(other_len, 8);
}
}
impl<'a, I> GenericCallMut<I> for ExecutionContext<'a>
where
I: Into<InstanceQuery<'a>>,
{
type Output = Result<(), ExecutionError>;
fn generic_call_mut(
&mut self,
called_instance: I,
method: MethodDescriptor<'_>,
args: Vec<u8>,
) -> Self::Output {
self.make_child_call(
called_instance,
method.interface_name,
method.id,
args.as_ref(),
false,
)
}
}
#[derive(Debug)]
#[doc(hidden)] pub struct FallthroughAuth<'a>(pub ExecutionContext<'a>);
impl<'a, I> GenericCallMut<I> for FallthroughAuth<'a>
where
I: Into<InstanceQuery<'a>>,
{
type Output = Result<(), ExecutionError>;
fn generic_call_mut(
&mut self,
called_instance: I,
method: MethodDescriptor<'_>,
args: Vec<u8>,
) -> Self::Output {
self.0.make_child_call(
called_instance,
method.interface_name,
method.id,
args.as_ref(),
true,
)
}
}