use crate::crypto::MuxedAccount;
use crate::error::{Error, Result};
use crate::operations::Operation;
use crate::xdr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvokeHostFunctionOperation {
source_account: Option<MuxedAccount>,
host_function: xdr::HostFunction,
auth: Vec<xdr::SorobanAuthorizationEntry>,
}
#[derive(Debug, Default)]
pub struct InvokeHostFunctionOperationBuilder {
source_account: Option<MuxedAccount>,
host_function: Option<xdr::HostFunction>,
auth: Vec<xdr::SorobanAuthorizationEntry>,
}
impl InvokeHostFunctionOperation {
pub fn source_account(&self) -> &Option<MuxedAccount> {
&self.source_account
}
pub fn source_account_mut(&mut self) -> &mut Option<MuxedAccount> {
&mut self.source_account
}
pub fn host_function(&self) -> &xdr::HostFunction {
&self.host_function
}
pub fn host_function_mut(&mut self) -> &mut xdr::HostFunction {
&mut self.host_function
}
pub fn auth(&self) -> &[xdr::SorobanAuthorizationEntry] {
&self.auth
}
pub fn auth_mut(&mut self) -> &mut Vec<xdr::SorobanAuthorizationEntry> {
&mut self.auth
}
pub fn to_xdr_operation_body(&self) -> Result<xdr::OperationBody> {
let auth_xdr = self.auth.clone().try_into().map_err(|_| Error::XdrError)?;
let inner = xdr::InvokeHostFunctionOp {
host_function: self.host_function.clone(),
auth: auth_xdr,
};
Ok(xdr::OperationBody::InvokeHostFunction(inner))
}
pub fn from_xdr_operation_body(
source_account: Option<MuxedAccount>,
x: &xdr::InvokeHostFunctionOp,
) -> Result<InvokeHostFunctionOperation> {
let auth: Vec<xdr::SorobanAuthorizationEntry> = x.auth.iter().cloned().collect();
Ok(InvokeHostFunctionOperation {
source_account,
host_function: x.host_function.clone(),
auth,
})
}
}
impl InvokeHostFunctionOperationBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn with_source_account<S>(mut self, source: S) -> Self
where
S: Into<MuxedAccount>,
{
self.source_account = Some(source.into());
self
}
pub fn with_host_function(mut self, hf: xdr::HostFunction) -> Self {
self.host_function = Some(hf);
self
}
pub fn with_auth(mut self, auth: Vec<xdr::SorobanAuthorizationEntry>) -> Self {
self.auth = auth;
self
}
pub fn add_auth_entry(mut self, entry: xdr::SorobanAuthorizationEntry) -> Self {
self.auth.push(entry);
self
}
pub fn with_invoke_contract(
mut self,
contract_address: xdr::ScAddress,
function_name: xdr::ScSymbol,
args: Vec<xdr::ScVal>,
) -> Self {
let args_xdr = args.try_into().unwrap_or_else(|_| xdr::VecM::default());
let invoke_args = xdr::InvokeContractArgs {
contract_address,
function_name,
args: args_xdr,
};
self.host_function = Some(xdr::HostFunction::InvokeContract(invoke_args));
self
}
pub fn with_upload_wasm(mut self, wasm: xdr::BytesM) -> Self {
self.host_function = Some(xdr::HostFunction::UploadContractWasm(wasm));
self
}
pub fn with_create_contract(
mut self,
contract_id_preimage: xdr::ContractIdPreimage,
executable: xdr::ContractExecutable,
) -> Self {
let args = xdr::CreateContractArgs {
contract_id_preimage,
executable,
};
self.host_function = Some(xdr::HostFunction::CreateContract(args));
self
}
pub fn with_create_contract_v2(
mut self,
contract_id_preimage: xdr::ContractIdPreimage,
executable: xdr::ContractExecutable,
constructor_args: Vec<xdr::ScVal>,
) -> Self {
let ctor_args_xdr = constructor_args
.try_into()
.unwrap_or_else(|_| xdr::VecM::default());
let args = xdr::CreateContractArgsV2 {
contract_id_preimage,
executable,
constructor_args: ctor_args_xdr,
};
self.host_function = Some(xdr::HostFunction::CreateContractV2(args));
self
}
pub fn build(self) -> Result<Operation> {
let host_function = self.host_function.ok_or_else(|| {
Error::InvalidOperation("missing host function for invoke host function".to_string())
})?;
if self.auth.len() > 10_000 {
return Err(Error::InvalidOperation(
"too many authorization entries for invoke host function".to_string(),
));
}
Ok(Operation::InvokeHostFunction(InvokeHostFunctionOperation {
source_account: self.source_account,
host_function,
auth: self.auth,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::operations::Operation;
use crate::xdr::{ContractId, Hash, ScAddress, ScSymbol, XDRDeserialize, XDRSerialize};
#[test]
fn test_invoke_host_function_invoke_contract_roundtrip() {
let contract_hash: [u8; 32] = [1u8; 32];
let addr = ScAddress::Contract(ContractId(Hash(contract_hash)));
let symbol: ScSymbol = "ping".to_string().try_into().unwrap();
let op = InvokeHostFunctionOperationBuilder::new()
.with_invoke_contract(addr, symbol, vec![])
.build()
.unwrap();
let encoded = op.xdr_base64().unwrap();
let decoded = Operation::from_xdr_base64(&encoded).unwrap();
assert_eq!(op, decoded);
if let Operation::InvokeHostFunction(inner) = decoded {
assert!(matches!(
inner.host_function(),
xdr::HostFunction::InvokeContract(_)
));
} else {
panic!("expected invoke host function operation");
}
}
}