Skip to content

Commit

Permalink
WIP: transaction builder
Browse files Browse the repository at this point in the history
  • Loading branch information
willemneal authored and gitbutler-client committed Mar 20, 2024
1 parent 5f367ed commit 12010f7
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 1 deletion.
22 changes: 21 additions & 1 deletion cmd/soroban-cli/src/commands/contract/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::array::TryFromSliceError;
use std::fmt::Debug;
use std::num::ParseIntError;

use cargo_metadata::semver::Op;
use clap::{command, Parser};
use soroban_env_host::xdr::{
self, Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Memo, MuxedAccount,
Expand All @@ -11,9 +12,10 @@ use soroban_env_host::xdr::{

use super::restore;
use crate::commands::{global, NetworkRunnable};
use crate::key;
use crate::rpc::{self, Client};
use crate::txn::{InvokeHostFunctionOpBuilder, OperationBuilder, TransactionBuilder};
use crate::{commands::config, utils, wasm};
use crate::{key, txn};

const CONTRACT_META_SDK_KEY: &str = "rssdkver";
const PUBLIC_NETWORK_PASSPHRASE: &str = "Public Global Stellar Network ; September 2015";
Expand Down Expand Up @@ -45,6 +47,8 @@ pub enum Error {
#[error(transparent)]
Rpc(#[from] rpc::Error),
#[error(transparent)]
TxnBuilder(#[from] txn::Error),
#[error(transparent)]
Config(#[from] config::Error),
#[error(transparent)]
Wasm(#[from] wasm::Error),
Expand Down Expand Up @@ -190,6 +194,22 @@ pub(crate) fn build_install_contract_code_tx(
fee: u32,
key: &ed25519_dalek::SigningKey,
) -> Result<(Transaction, Hash), XdrError> {
let source_account = stellar_strkey::Strkey::PublicKeyEd25519(
stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes().try_into()?),
);
let mut txn = TransactionBuilder::new(source_account.clone())?;
let op = OperationBuilder::new()
.set_source_account(&source_account)
.set_host_function(HostFunction::UploadContractWasm(source_code.try_into()?))
.build();

let op = InvokeHostFunctionOpBuilder::upload(source_code)?.build()?
let op_body = OperationBuilder::new()
.set_source_account(&txn.txn.source_account)
.set_body(op)
.build();


let hash = utils::contract_hash(source_code)?;

let op = Operation {
Expand Down
1 change: 1 addition & 0 deletions cmd/soroban-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod fee;
pub mod key;
pub mod log;
pub mod toid;
pub mod txn;
pub mod utils;
pub mod wasm;

Expand Down
200 changes: 200 additions & 0 deletions cmd/soroban-cli/src/txn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
//! This module contains a transaction builder for Stellar.
//!
use soroban_env_host::xdr::{self, Operation, Transaction, Uint256, VecM};


#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("")]
InvokeHostFunctionOpMustBeOnlyOperation,
#[error(transparent)]
Xdr(#[from] xdr::Error),
#[error("invalid source account strkey type")]
InvalidSourceAccountStrkeyType,
}

fn to_muxed_account(source_account: stellar_strkey::Strkey) -> Result<xdr::MuxedAccount, Error> {
let raw_bytes = match source_account {
stellar_strkey::Strkey::PublicKeyEd25519(x) => x.0,
stellar_strkey::Strkey::MuxedAccountEd25519(x) => x.ed25519,
_ => return Err(Error::InvalidSourceAccountStrkeyType),
};
Ok(xdr::MuxedAccount::Ed25519(xdr::Uint256(raw_bytes)))
}

pub struct TransactionBuilder {
pub txn: Transaction,
}

impl TransactionBuilder {
pub fn new(source_account: stellar_strkey::Strkey) -> Result<Self, Error> {
let source_account = to_muxed_account(source_account)?;
Ok(Self {
txn: Transaction {
source_account,
fee: 100,
operations: VecM::default(),
seq_num: xdr::SequenceNumber(0),
cond: xdr::Preconditions::None,
memo: xdr::Memo::None,
ext: xdr::TransactionExt::V0,
},
})
}

pub fn set_source_account(&mut self, source_account: stellar_strkey::Strkey) -> Result<&mut Self, Error> {
self.txn.source_account = to_muxed_account(source_account)?;
Ok(self)
}

pub fn set_fee(&mut self, fee: u32) -> &mut Self {
self.txn.fee = fee;
self
}

pub fn set_sequence_number(&mut self, sequence_number: i64) -> &mut Self {
self.txn.seq_num = xdr::SequenceNumber(sequence_number);
self
}

pub fn add_operation(&mut self, operation: Operation) -> Result<&mut Self, Error> {
if !self.txn.operations.is_empty()
&& matches!(
operation,
Operation {
body: xdr::OperationBody::InvokeHostFunction(_),
..
}
)
{
return Err(Error::InvokeHostFunctionOpMustBeOnlyOperation);
}
self.txn.operations.push(operation);
Ok(self)
}

pub fn cond(&mut self, cond: xdr::Preconditions) -> &mut Self {
self.txn.cond = cond;
self
}

pub fn build(&self) -> Transaction {
self.txn.clone()
}
}

pub struct OperationBuilder {
op: Operation,
}

impl OperationBuilder {
pub fn new() -> Self {
Self {
op: Operation {
source_account: None,
body: xdr::OperationBody::Inflation,
},
}
}

pub fn set_source_account(&mut self, source_account: stellar_strkey::Strkey) -> Result<&mut Self, Error> {
self.op.source_account = Some(to_muxed_account(source_account)?);
Ok(self)
}

pub fn set_body(&mut self, body: xdr::OperationBody) -> &mut Self {
self.op.body = body;
self
}

pub fn set_host_function(&mut self, host_function: xdr::HostFunction) -> &mut Self {
if let xdr::OperationBody::InvokeHostFunction(ref mut op) = self.op.body {
op.host_function = host_function;
}
self
}

pub fn set_auth(&mut self, auth: VecM<u8>) -> &mut Self {
if let xdr::OperationBody::InvokeHostFunction(ref mut op) = self.op.body {
op.auth = auth;
}
self
}

pub fn build(&self) -> Operation {
self.op.clone()
}
}

pub struct OperationBodyBuilder {
body: xdr::OperationBody,
}

impl OperationBodyBuilder {
pub fn new() -> Self {
Self {
body: xdr::OperationBody::Inflation,
}
}

pub fn set_invoke_host_function(&mut self, invoke_host_function: xdr::InvokeHostFunctionOp) -> &mut Self {
self.body = xdr::OperationBody::InvokeHostFunction(invoke_host_function);
self
}

pub fn build(&self) -> xdr::OperationBody {
self.body.clone()
}
}

pub struct InvokeHostFunctionOpBuilder(xdr::HostFunction, Vec<xdr::SorobanAuthorizationEntry>);

impl InvokeHostFunctionOpBuilder {
fn new(host_function: xdr::HostFunction) -> Self {
Self(host_function, vec![])
}
pub fn upload(wasm: &[u8]) -> Result<Self, Error> {
Ok(Self::new(xdr::HostFunction::UploadContractWasm(
wasm.try_into()?,
)))
}

pub fn create_contract(
source_account: stellar_strkey::Strkey,
salt: [u8; 32],
wasm_hash: xdr::Hash,
) -> Result<Self, Error> {
let stellar_strkey::Strkey::PublicKeyEd25519(bytes) = source_account else {
panic!("Invalid public key");
};

let contract_id_preimage =
xdr::ContractIdPreimage::Address(xdr::ContractIdPreimageFromAddress {
address: xdr::ScAddress::Account(xdr::AccountId(
xdr::PublicKey::PublicKeyTypeEd25519(bytes.0.into()),
)),
salt: Uint256(salt),
});

Ok(Self::new(xdr::HostFunction::CreateContract(
xdr::CreateContractArgs {
contract_id_preimage,
executable: xdr::ContractExecutable::Wasm(wasm_hash),
},
)))
}

pub fn add_auth(&mut self, auth: xdr::SorobanAuthorizationEntry) -> &mut Self {
self.1.push(auth);
self
}

pub fn build(self) -> Result<xdr::OperationBody, Error> {
Ok(xdr::OperationBody::InvokeHostFunction(
xdr::InvokeHostFunctionOp {
host_function: self.0,
auth: self.1.try_into()?,
},
))
}
}

0 comments on commit 12010f7

Please sign in to comment.