Skip to content

Commit

Permalink
WIP -- updating for new xdr
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Bellamy committed Dec 1, 2022
1 parent e7abf3c commit 2df357a
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 149 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

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

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ serde = "1.0.82"
serde_derive = "1.0.82"
serde_json = "1.0.82"
hex = "0.4.3"
num-bigint = "0.4"
tokio = { version = "1", features = ["full"] }
warp = "0.3"
clap_complete = "3.2.3"
Expand Down
118 changes: 97 additions & 21 deletions cmd/soroban-cli/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ use rand::Rng;
use sha2::{Digest, Sha256};
use soroban_env_host::xdr::HashIdPreimageSourceAccountContractId;
use soroban_env_host::xdr::{
AccountId, Error as XdrError, Hash, HashIdPreimage, HostFunction, InvokeHostFunctionOp,
LedgerFootprint, LedgerKey::ContractData, LedgerKeyContractData, Memo, MuxedAccount, Operation,
OperationBody, Preconditions, PublicKey, ScObject, ScStatic::LedgerKeyContractCode, ScVal,
SequenceNumber, Transaction, TransactionEnvelope, TransactionExt, Uint256, VecM, WriteXdr,
AccountId, ContractId, CreateContractArgs, Error as XdrError, Hash, HashIdPreimage,
HostFunction, InstallContractCodeArgs, InvokeHostFunctionOp, LedgerFootprint,
LedgerKey::ContractCode, LedgerKey::ContractData, LedgerKeyContractCode, LedgerKeyContractData,
Memo, MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, ScContractCode,
ScStatic, ScVal, SequenceNumber, Transaction, TransactionEnvelope, TransactionExt, Uint256,
VecM, WriteXdr,
};
use soroban_env_host::HostError;

Expand Down Expand Up @@ -178,31 +180,81 @@ impl Cmd {
// TODO: create a cmdline parameter for the fee instead of simply using the minimum fee
let fee: u32 = 100;
let sequence = account_details.sequence.parse::<i64>()?;
let (tx, contract_id) = build_create_contract_tx(

let (tx, hash) = build_install_contract_code_tx(
contract,
sequence + 1,
fee,
self.network_passphrase.as_ref().unwrap(),
salt,
&key,
)?;
client.send_transaction(&tx).await?;

let (tx, contract_id) = build_create_contract_tx(
hash,
sequence + 2,
fee,
self.network_passphrase.as_ref().unwrap(),
salt,
&key,
)?;
client.send_transaction(&tx).await?;

Ok(hex::encode(contract_id.0))
}
}

fn build_create_contract_tx(
fn build_install_contract_code_tx(
contract: Vec<u8>,
sequence: i64,
fee: u32,
network_passphrase: &str,
salt: [u8; 32],
key: &ed25519_dalek::Keypair,
) -> Result<(TransactionEnvelope, Hash), Error> {
let hash = contract_hash(&contract)?;

let op = Operation {
source_account: None,
body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
function: HostFunction::InstallContractCode(InstallContractCodeArgs {
code: contract.try_into()?,
}),
footprint: LedgerFootprint {
read_only: VecM::default(),
read_write: vec![ContractCode(LedgerKeyContractCode { hash })].try_into()?,
},
}),
};

let tx = Transaction {
source_account: MuxedAccount::Ed25519(Uint256(key.public.to_bytes())),
fee,
seq_num: SequenceNumber(sequence),
cond: Preconditions::None,
memo: Memo::None,
operations: vec![op].try_into()?,
ext: TransactionExt::V0,
};

let envelope = utils::sign_transaction(key, &tx, network_passphrase)?;

Ok((envelope, hash))
}

fn build_create_contract_tx(
hash: Hash,
sequence: i64,
fee: u32,
network_passphrase: &str,
salt: [u8; 32],
key: &ed25519_dalek::Keypair,
) -> Result<(TransactionEnvelope, Hash), Error> {
let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into());
let preimage =
HashIdPreimage::ContractIdFromSourceAccount(HashIdPreimageSourceAccountContractId {
network_id,
source_account: AccountId(PublicKey::PublicKeyTypeEd25519(
key.public.to_bytes().into(),
)),
Expand All @@ -211,24 +263,20 @@ fn build_create_contract_tx(
let preimage_xdr = preimage.to_xdr()?;
let contract_id = Sha256::digest(preimage_xdr);

let contract_parameter = ScVal::Object(Some(ScObject::Bytes(contract.try_into()?)));
let salt_parameter = ScVal::Object(Some(ScObject::Bytes(salt.try_into()?)));

let lk = ContractData(LedgerKeyContractData {
contract_id: Hash(contract_id.into()),
key: ScVal::Static(LedgerKeyContractCode),
});

let parameters: VecM<ScVal, 256_000> = vec![contract_parameter, salt_parameter].try_into()?;

let op = Operation {
source_account: None,
body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
function: HostFunction::CreateContractWithSourceAccount,
parameters: parameters.into(),
function: HostFunction::CreateContract(CreateContractArgs {
contract_id: ContractId::SourceAccount(Uint256(salt)),
source: ScContractCode::WasmRef(hash),
}),
footprint: LedgerFootprint {
read_only: VecM::default(),
read_write: vec![lk].try_into()?,
read_only: vec![ContractCode(LedgerKeyContractCode { hash })].try_into()?,
read_write: vec![ContractData(LedgerKeyContractData {
contract_id: Hash(contract_id.into()),
key: ScVal::Static(ScStatic::LedgerKeyContractCode),
})]
.try_into()?,
},
}),
};
Expand All @@ -247,14 +295,42 @@ fn build_create_contract_tx(
Ok((envelope, Hash(contract_id.into())))
}

fn contract_hash(contract: &[u8]) -> Result<Hash, Error> {
let install_contract_code_args = InstallContractCodeArgs {
code: contract.try_into()?,
};
let mut buf: Vec<u8> = vec![];
install_contract_code_args.write_xdr(&mut buf)?;
Ok(Hash(Sha256::digest(buf.try_into()?).try_into()?))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_build_install_contract_code() {
let result = build_install_contract_code_tx(
b"foo".to_vec(),
300,
1,
"Public Global Stellar Network ; September 2015",
[0u8; 32],
&utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP")
.unwrap(),
);

assert!(result.is_ok());
}

#[test]
fn test_build_create_contract() {
let hash = hex::decode("0000000000000000000000000000000000000000000000000000000000000000")
.unwrap()
.try_into()
.unwrap();
let result = build_create_contract_tx(
b"foo".to_vec(),
Hash(hash),
300,
1,
"Public Global Stellar Network ; September 2015",
Expand Down
4 changes: 1 addition & 3 deletions cmd/soroban-cli/src/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ use soroban_spec::read::FromWasmError;
use stellar_strkey::StrkeyPublicKeyEd25519;

use crate::rpc::Client;
use crate::utils::{
contract_code_to_spec_entries, create_ledger_footprint, default_account_ledger_entry,
};
use crate::utils::{create_ledger_footprint, default_account_ledger_entry};
use crate::{
rpc, snapshot,
strval::{self, StrValError},
Expand Down
29 changes: 13 additions & 16 deletions cmd/soroban-cli/src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,31 +288,27 @@ fn parse_transaction(
};

// TODO: Support creating contracts and token wrappers here as well.
if body.function != HostFunction::InvokeContract {
let parameters: ScVec = if let HostFunction::InvokeContract(p) = body {
p
} else {
return Err(Error::UnsupportedTransaction {
message: "Function must be invokeContract".to_string(),
});
};

if body.parameters.len() < 2 {
if parameters.len() < 2 {
return Err(Error::UnsupportedTransaction {
message: "Function must have at least 2 parameters".to_string(),
});
};

let contract_xdr = body
.parameters
.get(0)
.ok_or(Error::UnsupportedTransaction {
message: "First parameter must be the contract id".to_string(),
})?;
let method_xdr = body
.parameters
.get(1)
.ok_or(Error::UnsupportedTransaction {
message: "Second parameter must be the contract method".to_string(),
})?;
let (_, params) = body.parameters.split_at(2);
let contract_xdr = parameters.get(0).ok_or(Error::UnsupportedTransaction {
message: "First parameter must be the contract id".to_string(),
})?;
let method_xdr = parameters.get(1).ok_or(Error::UnsupportedTransaction {
message: "Second parameter must be the contract method".to_string(),
})?;
let (_, params) = parameters.split_at(2);

let contract_id: [u8; 32] = if let ScVal::Object(Some(ScObject::Bytes(bytes))) = contract_xdr {
bytes
Expand Down Expand Up @@ -380,7 +376,8 @@ fn execute_transaction(

// TODO: Check the parameters match the contract spec, or return a helpful error message

let res = h.invoke_function(HostFunction::InvokeContract, args.try_into()?)?;
// TODO: Handle installing code and creating contracts here as well
let res = h.invoke_function(HostFunction::InvokeContract(args.try_into()?))?;

let (storage, budget, _) = h.try_finish().map_err(|_h| {
HostError::from(ScStatus::HostStorageError(
Expand Down
66 changes: 41 additions & 25 deletions cmd/soroban-cli/src/strval.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use serde_json::Value;
use std::{error::Error, fmt::Display, str::FromStr};

use num_bigint::{BigInt, Sign};
use soroban_env_host::xdr::{
AccountId, BytesM, Error as XdrError, PublicKey, ScBigInt, ScMap, ScMapEntry, ScObject,
AccountId, BytesM, Error as XdrError, Int128Parts, PublicKey, ScMap, ScMapEntry, ScObject,
ScSpecTypeDef, ScSpecTypeMap, ScSpecTypeOption, ScSpecTypeTuple, ScSpecTypeVec, ScStatic,
ScVal, ScVec, Uint256,
};
Expand Down Expand Up @@ -65,21 +64,26 @@ pub fn from_string(s: &str, t: &ScSpecTypeDef) -> Result<ScVal, StrValError> {
}
}

ScSpecTypeDef::U128 => {
if let Ok(Value::String(raw)) = serde_json::from_str(s) {
// First, see if it is a json string, strip the quotes and recurse
from_string(&raw, t)?
} else {
let val =
u128::from_str(s).map_err(|_| StrValError::InvalidValue(Some(t.clone())))?;
ScVal::Object(Some(ScObject::U128(val.into())))
}
}

// Might have wrapping quotes if it is negative. e.g. "-5"
ScSpecTypeDef::BigInt => {
ScSpecTypeDef::I128 => {
if let Ok(Value::String(raw)) = serde_json::from_str(s) {
// First, see if it is a json string, strip the quotes and recurse
from_string(&raw, t)?
} else {
let big =
BigInt::from_str(s).map_err(|_| StrValError::InvalidValue(Some(t.clone())))?;
let (sign, bytes) = big.to_bytes_be();
let b: BytesM<256_000_u32> = bytes.try_into().map_err(StrValError::Xdr)?;
ScVal::Object(Some(ScObject::BigInt(match sign {
Sign::NoSign => ScBigInt::Zero,
Sign::Minus => ScBigInt::Negative(b),
Sign::Plus => ScBigInt::Positive(b),
})))
let val =
i128::from_str(s).map_err(|_| StrValError::InvalidValue(Some(t.clone())))?;
ScVal::Object(Some(ScObject::I128(val.into())))
}
}

Expand Down Expand Up @@ -110,8 +114,22 @@ pub fn from_json(v: &Value, t: &ScSpecTypeDef) -> Result<ScVal, StrValError> {
}

// Number parsing
(ScSpecTypeDef::BigInt, Value::String(s)) => from_string(s, t)?,
(ScSpecTypeDef::BigInt, Value::Number(n)) => from_json(&Value::String(format!("{n}")), t)?,
(ScSpecTypeDef::U128, Value::String(s)) => from_string(s, t)?,
(ScSpecTypeDef::U128, Value::Number(n)) => ScVal::Object(Some(ScObject::U128(
// json numbers can only be u64 anyway, so...
n.as_u64()
.ok_or(StrValError::InvalidValue(Some(t.clone())))?
.try_into()
.map_err(|_| StrValError::InvalidValue(Some(t.clone())))?,
))),
(ScSpecTypeDef::I128, Value::String(s)) => from_string(s, t)?,
(ScSpecTypeDef::I128, Value::Number(n)) => ScVal::Object(Some(ScObject::I128(
// json numbers can only be i64 anyway, so...
n.as_i64()
.ok_or(StrValError::InvalidValue(Some(t.clone())))?
.try_into()
.map_err(|_| StrValError::InvalidValue(Some(t.clone())))?,
))),
(ScSpecTypeDef::I32, Value::Number(n)) => ScVal::I32(
n.as_i64()
.ok_or_else(|| StrValError::InvalidValue(Some(t.clone())))?
Expand Down Expand Up @@ -300,17 +318,15 @@ pub fn to_json(v: &ScVal) -> Result<Value, StrValError> {
Value::String(StrkeyPublicKeyEd25519(*k).to_string())
}
},
ScVal::Object(Some(ScObject::BigInt(n))) => {
// Always output bigints as strings
Value::String(match n {
ScBigInt::Zero => "0".to_string(),
ScBigInt::Negative(bytes) => {
BigInt::from_bytes_be(Sign::Minus, bytes.as_ref()).to_str_radix(10)
}
ScBigInt::Positive(bytes) => {
BigInt::from_bytes_be(Sign::Plus, bytes.as_ref()).to_str_radix(10)
}
})
ScVal::Object(Some(ScObject::U128(n))) => {
// Always output u128s as strings
let v: u128 = n.into();
Value::String(v.to_string())
}
ScVal::Object(Some(ScObject::I128(n))) => {
// Always output i128s as strings
let v: i128 = n.into();
Value::String(v.to_string())
}
// TODO: Implement these
ScVal::Object(Some(ScObject::ContractCode(_))) | ScVal::Bitset(_) | ScVal::Status(_) => {
Expand Down
Loading

0 comments on commit 2df357a

Please sign in to comment.